import * as React from 'react'; import Head from 'next/head'; interface Props { mode?: 'outer' | 'inner'; amount?: 'small' | 'normal'; direction?: 'left' | 'right' | 'top' | 'bottom'; } const Parallax: React.FC = ({ mode = 'outer', amount = 'normal', direction = 'bottom', children, }) => { const inner = mode === 'inner'; const small = amount === 'small'; const ref = React.useRef() as React.MutableRefObject; const [aboveFold, setAboveFold] = React.useState(false); const checkAboveFold = () => { const scrollY = window.scrollY; const windowBottom = scrollY + window.innerHeight; const elementBounds = ref.current.getBoundingClientRect(); if (elementBounds.top <= windowBottom) { setAboveFold(true); return; } window.addEventListener('scroll', handleOuter); window.addEventListener('resize', handleOuter); handleOuter(); setTimeout(handleOuter, 100); return () => { window.removeEventListener('scroll', handleOuter); window.removeEventListener('resize', handleOuter); }; }; React.useEffect(() => { if (inner) { window.addEventListener('scroll', handleInner); window.addEventListener('resize', handleInner); handleInner(); setTimeout(handleInner, 100); return () => { window.removeEventListener('scroll', handleInner); window.removeEventListener('resize', handleInner); }; } checkAboveFold(); }, []); const handleInner = () => { if (!ref || !ref.current) { return; } requestAnimationFrame(() => { const windowBottom = window.scrollY + window.innerHeight; const elementBounds = ref.current.getBoundingClientRect(); const maxTransform = small ? 10 : 15; const height = elementBounds.height >= window.innerHeight ? window.innerHeight : elementBounds.height; const top = elementBounds.top + scrollY; const bottom = top + height; ref.current.style.willChange = ''; if (windowBottom >= top && scrollY <= bottom) { let perc = scrollY / bottom; if (perc > 1) { perc = 1; } const transform = maxTransform * perc; ref.current.style.willChange = 'transform'; switch (direction) { case 'left': ref.current.style.transform = `translate3d(${transform}%, 0, 0)`; break; case 'right': ref.current.style.transform = `translate3d(-${transform}%, 0, 0)`; break; case 'top': ref.current.style.transform = `translate3d(0, ${transform}%, 0)`; break; case 'bottom': ref.current.style.transform = `translate3d(0, -${transform}%, 0)`; break; } } }); }; const handleOuter = () => { if (!ref || !ref.current) { return; } requestAnimationFrame(() => { const scrollY = window.scrollY; const windowBottom = scrollY + window.innerHeight; const elementBounds = ref.current.getBoundingClientRect(); let maxTransform = 25; const maxOpacity = 1; if (elementBounds.height >= window.innerHeight) { maxTransform = 10; } const height = elementBounds.height >= window.innerHeight ? window.innerHeight : elementBounds.height; const top = elementBounds.top + scrollY; const bottom = top + height; ref.current.style.willChange = ''; if (windowBottom >= top && windowBottom <= bottom) { const perc = 1 - (windowBottom - top) / (bottom - top); const opacity = 1 - maxOpacity * perc; const transform = maxTransform * perc; ref.current.style.opacity = String(opacity); ref.current.style.willChange = 'opacity, transform'; switch (direction) { case 'left': ref.current.style.transform = `translate3d(-${transform}%, 0, 0)`; break; case 'right': ref.current.style.transform = `translate3d(${transform}%, 0, 0)`; break; case 'top': ref.current.style.transform = `translate3d(0, -${transform}%, 0)`; break; case 'bottom': ref.current.style.transform = `translate3d(0, ${transform}%, 0)`; break; } } if (windowBottom >= bottom) { ref.current.style.opacity = '1'; ref.current.style.transform = 'translate3d(0, 0, 0)'; } }); }; return ( <>