Skip to content
This repository has been archived by the owner on Dec 22, 2023. It is now read-only.

Removed getBoundingClientRect + Controlled Scroll support #110

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15,348 changes: 15,348 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -42,9 +42,9 @@
"build:lib": "rollup -c",
"build:ts": "tsc -p tsconfig.build.json",
"build:copy": "node scripts/build-copy.js",
"dev": "concurrently -k -r 'jest --watch' 'yarn run storybook'",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using yarn for the project, so i would prefer if it wasn't changed to npm

"dev": "concurrently -k -r 'jest --watch' 'npm run storybook'",
"lint": "eslint . --ext js,ts,tsx",
"preversion": "yarn build",
"preversion": "npm build",
"pretty": "prettier '**/*.{js,ts,tsx,md,,yml,html}' --write",
"storybook": "start-storybook -p 9000",
"storybook:build": "build-storybook --output-dir example",
Expand Down
27 changes: 24 additions & 3 deletions src/ScrollPercentage.tsx
Expand Up @@ -40,6 +40,8 @@ export class ScrollPercentage extends React.Component<
> {
static displayName = 'ScrollPercentage'
static defaultProps = {
controlledScroll: false,
controlledScrollY: 0,
threshold: 0,
}

Expand All @@ -64,6 +66,9 @@ export class ScrollPercentage extends React.Component<
this.props.onChange(this.state.percentage, this.state.entry)
}

if (this.props.controlledScroll)
return this.handleControlledScroll(prevProps.controlledScrollY)

if (prevProps.root !== this.props.root) {
if (this.monitoring) {
this.monitorScroll(false, prevProps.root)
Expand All @@ -77,6 +82,7 @@ export class ScrollPercentage extends React.Component<
}

componentWillUnmount(): void {
if (this.props.controlledScroll) return
this.monitorScroll(false)
}

Expand All @@ -97,17 +103,32 @@ export class ScrollPercentage extends React.Component<
}
}

handleControlledScroll(prevControlledScrollY?: Number | boolean): void {
if (!this.node || prevControlledScrollY === this.props.controlledScrollY)
return

const percentage = calculateVerticalPercentage(
this.node,
this.props.threshold,
this.props.root,
this.props.controlledScrollY,
)

if (percentage === this.state.percentage) return

this.setState({ percentage })
}

handleScroll = () => {
if (!this.node) return
const bounds = this.node.getBoundingClientRect()
const percentage = this.props.horizontal
? calculateHorizontalPercentage(
bounds,
this.node,
this.props.threshold,
this.props.root,
)
: calculateVerticalPercentage(
bounds,
this.node,
this.props.threshold,
this.props.root,
)
Expand Down
4 changes: 4 additions & 0 deletions src/index.tsx
Expand Up @@ -14,6 +14,10 @@ interface RenderProps {
}

export interface ScrollPercentageOptions extends IntersectionObserverInit {
/** Boolean to enable controlled scrolling mode */
controlledScroll?: boolean
/** Number to pass the current scrollY in controlled scrolling mode */
controlledScrollY?: number
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering */
threshold?: number
/** Horizontal scroll mode (true/false) */
Expand Down
33 changes: 29 additions & 4 deletions src/useScrollPercentage.tsx
Expand Up @@ -19,17 +19,36 @@ export function useScrollPercentage(

const handleScroll = useCallback(() => {
if (!target) return
const bounds = target.getBoundingClientRect()
const percentage = options.horizontal
? calculateHorizontalPercentage(bounds, options.threshold, options.root)
: calculateVerticalPercentage(bounds, options.threshold, options.root)
? calculateHorizontalPercentage(target, options.threshold, options.root)
: calculateVerticalPercentage(target, options.threshold, options.root)

setPercentage(percentage)
}, [target, options.threshold, options.root, options.horizontal])

const handleControlledScroll = useCallback(() => {
if (!target) return

const percentage = calculateVerticalPercentage(
target,
options.threshold,
options.root,
options.controlledScrollY,
)

setPercentage(percentage)

return
}, [target, options.threshold, options.root, options.controlledScrollY])

useEffect(() => {
if (inView) {
const root = options.root || window

if (options.controlledScroll) {
return handleControlledScroll()
}

root.addEventListener('scroll', handleScroll, { passive: true })
root.addEventListener('resize', handleScroll)

Expand All @@ -42,7 +61,13 @@ export function useScrollPercentage(
}
}
return
}, [inView, options.root, handleScroll])
}, [
inView,
options.root,
handleScroll,
options.controlledScroll,
handleControlledScroll,
])

return [ref, percentage, entry]
}
60 changes: 51 additions & 9 deletions src/utils.ts
@@ -1,27 +1,69 @@
/*
* Helpers
*/

function getScrollPercentage(
upper: number,
inner: number,
threshold: number = 0,
scrollY: number = 0,
frame: number = 0,
) {
const thresholdOffset = threshold * inner

const top = upper - frame + thresholdOffset
const bottom = upper + inner - thresholdOffset
const percentage = (scrollY - top) / (bottom - top)

return Math.max(0, Math.min(1, percentage))
}

/*
* Export functions
*/

export function calculateVerticalPercentage(
bounds: ClientRect,
node: HTMLDivElement | Element | any,
threshold: number = 0,
root: Window | Element | null | undefined = window,
scrollY?: number | boolean | null | undefined,
) {
if (!root) return 0

const vh =
(root instanceof Element ? root.clientHeight : root.innerHeight) || 0
const offset = threshold * bounds.height
const percentage =
(bounds.bottom - offset) / (vh + bounds.height - offset * 2)

return 1 - Math.max(0, Math.min(1, percentage))
if (typeof scrollY !== 'number') {
scrollY = (root instanceof Element ? root.scrollTop : root.scrollY) || 0
}

return getScrollPercentage(
node.offsetTop,
node.offsetHeight,
threshold,
scrollY,
vh,
)
}

export function calculateHorizontalPercentage(
bounds: ClientRect,
node: HTMLDivElement | Element | any,
threshold: number = 0,
root: Window | Element | null | undefined = window,
scrollX?: number | boolean | null | undefined,
) {
if (!root) return 0
const vw = (root instanceof Element ? root.clientWidth : root.innerWidth) || 0
const offset = threshold * bounds.width
const percentage = (bounds.right - offset) / (vw + bounds.width - offset * 2)

return 1 - Math.max(0, Math.min(1, percentage))
if (typeof scrollX !== 'number') {
scrollX = (root instanceof Element ? root.scrollLeft : root.scrollX) || 0
}

return getScrollPercentage(
node.offsetLeft,
node.offsetWidth,
threshold,
scrollX,
vw,
)
}
10 changes: 10 additions & 0 deletions stories/Hooks.story.tsx
Expand Up @@ -5,6 +5,7 @@ import styled from 'styled-components'
import { ScrollPercentageOptions, useScrollPercentage } from '../src'
import { number, withKnobs } from '@storybook/addon-knobs'
import ScrollWrapper from './ScrollWrapper'
import VirtualScrollWrapper from './VirtualScrollWrapper'

type Props = {
options?: ScrollPercentageOptions
Expand Down Expand Up @@ -68,3 +69,12 @@ storiesOf('useScrollPercentage', module)
<HookComponent options={{ horizontal: true }} />
</ScrollWrapper>
))
.add('Example virtual', () => (
<VirtualScrollWrapper>
{({ scrollY = 0 }) => (
<HookComponent
options={{ controlledScroll: true, controlledScrollY: scrollY }}
/>
)}
</VirtualScrollWrapper>
))
20 changes: 20 additions & 0 deletions stories/ScrollPercentage.story.tsx
Expand Up @@ -3,6 +3,7 @@ import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { ScrollPercentage } from '../src'
import ScrollWrapper from './ScrollWrapper'
import VirtualScrollWrapper from './VirtualScrollWrapper'
import Status from './Status'

const calcPercentage = (percentage: number) => Math.floor(percentage * 100)
Expand Down Expand Up @@ -124,3 +125,22 @@ storiesOf('Scroll Percentage', module)
</ScrollPercentage>
</ScrollWrapper>
))
.add('Virtual scroll', () => (
<VirtualScrollWrapper>
{({ scrollY }) => (
<ScrollPercentage controlledScroll={true} controlledScrollY={scrollY}>
{({ percentage, ref }) => (
<Header ref={ref}>
<Status
percentage={percentage}
style={{
transform: `translate3d( 0, ${scrollY}px, 0)`,
}}
/>
{`Percentage scrolled: ${calcPercentage(percentage)}%.`}
</Header>
)}
</ScrollPercentage>
)}
</VirtualScrollWrapper>
))
6 changes: 3 additions & 3 deletions stories/Status/index.tsx
@@ -1,6 +1,6 @@
import { CSSProperties, default as React } from 'react'

type Props = { percentage: number }
type Props = { percentage: number; style?: object }

const calcPercentage = (percentage: number) => Math.floor(percentage * 100)

Expand All @@ -23,9 +23,9 @@ const percentageStyle: CSSProperties = {
fontSize: '1.25rem',
}

function Status({ percentage }: Props) {
function Status({ percentage, style = {} }: Props) {
return (
<div style={statusElement}>
<div style={{ ...statusElement, ...style }}>
<span style={percentageStyle}>{calcPercentage(percentage)}%</span>
</div>
)
Expand Down