Skip to content

Commit

Permalink
refactor: scroller
Browse files Browse the repository at this point in the history
  • Loading branch information
silverlila committed Sep 26, 2023
1 parent 77e946d commit b218d87
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 249 deletions.
147 changes: 73 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
## use-scroller
# use-scroller

![npm](https://img.shields.io/npm/v/scroll-js)
![node](https://img.shields.io/node/v/scroll-js)
![license](https://img.shields.io/npm/l/scroll-js)
A lightweight library that enables smooth scrolling for HTML elements using simple React hooks provided by the package. This library offers additional scrolling features and provides several callback functions for customizable scroll operations.

A light-weight library that will allow you to scroll any html element using simple react hooks provided by the package.
In addition to providing extra scrolling features, this library also return a number of callback that can triger scroll operation depending on the user needs.

<p align="middle">
<div align="center">
<img src="https://s9.gifyu.com/images/ezgif.com-gif-maker3092e8916a41884d.gif" width="400"/>
<img src="https://s3.gifyu.com/images/ezgif.com-gif-maker-1c4ee7c66dcd4dd01.gif" width="400"/>
</p>
</div>

## Installation

### React

```javascript
```bash
#YARN
yarn add use-scroller

Expand All @@ -32,120 +25,126 @@ pnpm install use-scroller

### Scroll state

```javascript
```typescript
import { useScroll } from 'use-scroller'

export default function Carousel() {
export default function ScrollStateExample() {
const { ref, state } = useScroll<HTMLDivElement>()

return (
<>
{JSON.stringify(state)}
<Box ref={ref} />
<p>Scroll State:</p>
<pre>{JSON.stringify(state, null, 2)}</pre>
<div ref={ref} style={{ height: '200px', overflowY: 'scroll' }}>
{/* Your content */}
</div>
</>
)
}
```

### Carousel

```javascript
```typescript
import { useScroll } from 'use-scroller'

export default function Carousel() {
export default function CarouselExample() {
const { ref, scrollLeft, scrollRight } = useScroll<HTMLDivElement>()

return (
<>
<button onClick={() => scrollLeft()} >
Top
</button>
<button onClick={() => scrollRight()}>
Next
</button>

<Box ref={ref} />
<button onClick={() => scrollLeft()}>Scroll Left</button>
<button onClick={() => scrollRight()}>Scroll Right</button>

<div ref={ref} style={{ width: '400px', overflowX: 'scroll' }}>
{/* Your carousel content */}
</div>
</>
)
}
```

### Box

```javascript
```typescript
import { useScroll } from 'use-scroller'

export default function ScrollBox() {
const { ref, scrollLeft, scrollRight, scrollTop, scrollBotom, scrollCenter } = useScroll<HTMLDivElement>()
export default function BoxExample() {
const { ref, scrollLeft, scrollRight, scrollTop, scrollBottom, scrollToCenter } =
useScroll<HTMLDivElement>()

return (
<>
<button onClick={() => scrollCenter()} >
Center
</button>
<button onClick={() => scrollLeft()} >
Left
</button>
<button onClick={() => scrollRight()}>
Right
</button>
<button onClick={() => scrollTop()}>
Top
</button>
<button onClick={() => scrollBotom()}>
Bottom
</button>
# Box component
<Box ref={ref} />
<button onClick={() => scrollToCenter()}>Scroll to Center</button>
<button onClick={() => scrollLeft()}>Scroll Left</button>
<button onClick={() => scrollRight()}>Scroll Right</button>
<button onClick={() => scrollTop()}>Scroll to Top</button>
<button onClick={() => scrollBottom()}>Scroll to Bottom</button>
<div ref={ref} style={{ width: '300px', height: '300px', overflow: 'scroll' }}>
{/* Your content */}
</div>
</>
)
}
```

### Scroll to target

```javascript
import { useScroll } from 'use-window-scroll'
```typescript
import { useWindowScroll } from 'use-scroller'

export default function App() {
export default function ScrollToTargetExample() {
const { scrollToTarget } = useWindowScroll()
const ref1 = useRef(null)
const ref2 = useRef(null)
const ref3 = useRef(null)

return (
<>
<Navigation>
<Item onClick={() => scrollToTarget(ref1)}>Target 1</Item>
<Item onClick={() => scrollToTarget(ref2)}>Target 2</Item>
<Item onClick={() => scrollToTarget(ref3)}>Target 3</Item>
</Navigation>

<FirstTarget ref={ref1} />
<SecondTarget ref={ref2} />
<ThirdTarget ref={ref3} />
<button onClick={() => scrollToTarget(ref1)}>Scroll to Target 1</button>
<button onClick={() => scrollToTarget(ref2)}>Scroll to Target 2</button>
<button onClick={() => scrollToTarget(ref3)}>Scroll to Target 3</button>

<div>
<div ref={ref1} style={{ height: '400px', backgroundColor: 'lightcoral' }}>
Target 1
</div>
<div ref={ref2} style={{ height: '400px', backgroundColor: 'lightseagreen' }}>
Target 2
</div>
<div ref={ref3} style={{ height: '400px', backgroundColor: 'lightsalmon' }}>
Target 3
</div>
</div>
</>
)
}
```

## API Documentation

#### Available hooks
### Available Hooks

#### `useScroll`

This hook is designed to handle element scroll events, enabling smooth scrolling for specified HTML elements.

##### Options

- `direction` (Type: `horizontal` or `vertical`, Default: `vertical`): Specifies the desired scroll direction. You can set it to `horizontal` or `vertical` based on your needs.

- `duration` (Type: `number`, Default: `300`): Sets the animation duration for scrolling. Adjust this value to control the speed of the scroll animation.

- `easingOption` (Type: `EasingOptions`, Default: `ease-in-out`): Determines the type of animation to use for scrolling. You can choose from various easing options such as `ease-in`, `ease-out`, `ease-in-out`, etc., to customize the scroll animation's behavior.

#### `useWindowScroll`

| Hook | Description |
| ----------------- | ----------------------------- |
| `useScroll` | Handles element scroll events |
| `useWindowScroll` | Handles window scroll events |
This hook is tailored to handle window scroll events, making it easy to manage scrolling operations within the entire window.

#### useScroll
##### Options

| Option | Type | Default | Description |
| -------------- | ------------------------ | ----------- | ------------------------------------ |
| `direction` | `horizontal or vertical` | vertical | Set the desired scroll direction |
| `duration` | `number` | 300 | Set the animation duration to scroll |
| `easingOption` | `EasingOptions` | ease-in-out | Set the type of animation to scroll |
- `duration` (Type: `number`, Default: `300`): Specifies the animation duration for scrolling operations. Adjust this value to control the speed of the scroll animation.

#### useWindowScroll
- `easingOption` (Type: `EasingOptions`, Default: `ease-in-out`): Defines the type of animation to use for scrolling within the window. You can choose from various easing options to customize the scroll animation's behavior.

| Option | Type | Default | Description |
| -------------- | --------------- | ----------- | ------------------------------------ |
| `duration` | `number` | 300 | Set the animation duration to scroll |
| `easingOption` | `EasingOptions` | ease-in-out | Set the type of animation to scroll |
Feel free to update and expand this documentation further based on your library's features and usage patterns.
105 changes: 47 additions & 58 deletions src/hooks/use-scroll.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,66 @@
import { useCallback, useRef, useState } from 'react'
import { scroller } from '../scroller'
import { useRef, useState } from 'react'
import { createScroller } from '../scroller'
import { ScrollOptions } from '../types'
import { defaultScrollOptions, getElement } from '../utils'
import { useIsoMorphicEffect } from './use-iso-morphic-effect'

const initialState = {
left: 0, // Current scroll position on the X-axis
top: 0, // Current scroll position on the Y-axis
isScrollable: false, // Determines if the container has content that can be scrolled
isScrolledLeft: true, // Determines if the container is at its leftmost position
isScrolledRight: false, // Determines if the container is at its rightmost position
isScrolledTop: true, // Determines if the container is at its topmost position
isScrolledBottom: false, // Determines if the container is at its bottommost position
maxScrollLeft: 0, // Maximum possible scroll on the X-axis
maxScrollTop: 0, // Maximum possible scroll on the Y-axis
scrollPercentageX: 0, // Percentage of horizontal scroll covered
scrollPercentageY: 0, // Percentage of vertical scroll covered
}

export function useScroll<T extends HTMLElement>(props?: Partial<ScrollOptions>) {
const ref = useRef<T>(null)
const [state, setState] = useState(() => ({
left: 0,
top: 0,
isScrollable: false,
isScrolledLeft: false,
isScrolledRight: false,
isScrolledTop: false,
isScrolledBottom: false,
}))
const scrollOpt = { ...defaultScrollOptions, ...props }

const scrollTo = useCallback((position: number) => {
const container = getElement(ref)
const scrollContainer = scroller({ container, options: scrollOpt })
scrollContainer.scrollTo(position)
}, [])

const scrollCenter = useCallback(() => {
const container = getElement(ref)
const scrollContainer = scroller({ container, options: scrollOpt })
scrollContainer.scrollToCenter()
}, [])

const scrollTop = useCallback((offset?: number) => {
const container = getElement(ref)
const scrollContainer = scroller({ container, options: scrollOpt })
scrollContainer.scrollToTop(offset)
}, [])

const scrollBottom = useCallback((offset?: number) => {
const container = getElement(ref)
const scrollContainer = scroller({ container, options: scrollOpt })
scrollContainer.scrollToBottom(offset)
}, [])

const scrollRight = useCallback((offset?: number) => {
const container = getElement(ref)
const scrollContainer = scroller({ container, options: scrollOpt })
scrollContainer.scrollToRight(offset)
}, [])

const scrollLeft = useCallback((offset?: number) => {
const container = getElement(ref)
const scrollContainer = scroller({ container, options: scrollOpt })
scrollContainer.scrollToLeft(offset)
}, [])
const scrollerRef = useRef<ReturnType<typeof createScroller> | null>(null)
const [state, setState] = useState(() => initialState)

useIsoMorphicEffect(() => {
const scrollContainer = ref.current
if (!scrollContainer) return

// Create the scroller instance only once inside the effect
if (!scrollerRef.current) {
const container = getElement(ref)
scrollerRef.current = createScroller(container, scrollOpt)
}

const { scrollWidth, clientWidth, clientHeight, scrollHeight } = scrollContainer
const isScrollable = clientWidth !== scrollWidth || clientHeight !== scrollHeight
setState((prev) => ({ ...prev, isScrollable }))

const handleScroll = () => {
if (scrollContainer) {
const { scrollLeft, scrollTop } = scrollContainer
const isAtLeftBoundary = scrollLeft === 0
const isAtRightBoundary = scrollLeft >= scrollWidth - clientWidth
const isAtTopBoundary = scrollTop === 0
const isAtBottomBoundary = scrollTop >= scrollHeight - clientHeight
const scrollState = {
...state,
isScrolledLeft: scrollLeft === 0,
isScrolledRight: scrollLeft >= scrollWidth - clientWidth,
isScrolledTop: scrollTop === 0,
isScrolledBottom: scrollTop >= scrollHeight - clientHeight,
isScrolledLeft: isAtLeftBoundary,
isScrolledRight: isAtRightBoundary,
isScrolledTop: isAtTopBoundary,
isScrolledBottom: isAtBottomBoundary,
left: scrollLeft,
top: scrollTop,
maxScrollLeft: scrollWidth - clientWidth,
maxScrollTop: scrollHeight - clientHeight,
scrollPercentageX: isAtLeftBoundary
? 0
: (scrollLeft / (scrollWidth - clientWidth)) * 100,
scrollPercentageY: isAtTopBoundary
? 0
: (scrollTop / (scrollHeight - clientHeight)) * 100,
}
setState(scrollState)
}
Expand All @@ -80,16 +69,16 @@ export function useScroll<T extends HTMLElement>(props?: Partial<ScrollOptions>)
handleScroll()
scrollContainer?.addEventListener('scroll', handleScroll)
return () => scrollContainer?.removeEventListener('scroll', handleScroll)
}, [])
}, [state])

return {
ref,
state,
scrollLeft,
scrollRight,
scrollBottom,
scrollTop,
scrollCenter,
scrollTo,
scrollLeft: scrollerRef.current?.scrollToLeft,
scrollRight: scrollerRef.current?.scrollToRight,
scrollBottom: scrollerRef.current?.scrollToBottom,
scrollTop: scrollerRef.current?.scrollToTop,
scrollCenter: scrollerRef.current?.scrollToCenter,
scrollTo: scrollerRef.current?.scrollTo,
}
}
Loading

0 comments on commit b218d87

Please sign in to comment.