Last active
August 16, 2023 04:59
-
-
Save benknight/3bbf8dbcbb0dfef9adc611be74538f67 to your computer and use it in GitHub Desktop.
Revisions
-
benknight revised this gist
Nov 20, 2020 . 1 changed file with 13 additions and 10 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,13 +1,13 @@ import { useRef, useState, useCallback, useEffect } from 'react'; export default function useCarousel() { const scrollArea = useRef(); const [isTouchDevice, setIsTouchDevice] = useState(null); const [scrollBy, setScrollBy] = useState(null); const [scrollPosition, setScrollPosition] = useState(null); const [showNav, setShowNav] = useState(null); const navigate = useCallback( delta => { const { scrollLeft } = scrollArea.current; scrollArea.current.scroll({ @@ -18,15 +18,18 @@ export default function useCarousel() { [scrollBy], ); useEffect(() => { const scrollAreaNode = scrollArea.current; const calculateScrollPosition = () => { if (!scrollAreaNode) return; const { width } = scrollAreaNode.getBoundingClientRect(); if (scrollAreaNode.scrollLeft === 0) { setScrollPosition('start'); } else if ( scrollAreaNode.scrollLeft + width === scrollAreaNode.scrollWidth ) { setScrollPosition('end'); } else { setScrollPosition('between'); @@ -71,7 +74,7 @@ export default function useCarousel() { return detachListeners; }, [isTouchDevice, navigate]); useEffect(() => { const mql = window.matchMedia('(pointer: fine)'); const handleMql = ({ matches }) => { setIsTouchDevice(!matches); -
benknight revised this gist
Nov 9, 2020 . 2 changed files with 9 additions and 31 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,11 +16,17 @@ const { } = useCarousel(); ``` Use CSS to avoid unwanted scrollbars using the following technique: ```css parent { overflow: hidden; } child { overflow-x: auto; margin-bottom: -16px; padding-bottom: 16px; } ``` This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,12 +1,10 @@ import React from 'react'; export default function useCarousel() { const scrollArea = React.useRef(); const [isTouchDevice, setIsTouchDevice] = React.useState(null); const [scrollBy, setScrollBy] = React.useState(null); const [scrollPosition, setScrollPosition] = React.useState(null); const [showNav, setShowNav] = React.useState(null); const navigate = React.useCallback( @@ -46,34 +44,17 @@ export default function useCarousel() { setScrollBy(childWidth * Math.floor(containerWidth / childWidth)); }; const observer = new MutationObserver(calculateScrollBy); const attachListeners = () => { if (scrollAreaNode) observer.observe(scrollAreaNode, { childList: true }); scrollAreaNode.addEventListener('scroll', calculateScrollPosition); window.addEventListener('resize', calculateScrollBy); }; const detachListeners = () => { observer.disconnect(); scrollAreaNode.removeEventListener('scroll', calculateScrollPosition); window.removeEventListener('resize', calculateScrollBy); }; @@ -94,7 +75,6 @@ export default function useCarousel() { const mql = window.matchMedia('(pointer: fine)'); const handleMql = ({ matches }) => { setIsTouchDevice(!matches); }; handleMql(mql); mql.addEventListener('change', handleMql); @@ -103,14 +83,6 @@ export default function useCarousel() { }; }, []); return { getLeftNavProps: () => ({ onClick: () => navigate(-1), -
benknight revised this gist
Oct 28, 2020 . 1 changed file with 2 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -13,8 +13,8 @@ export default function useCarousel() { delta => { const { scrollLeft } = scrollArea.current; scrollArea.current.scroll({ behavior: 'smooth', left: scrollLeft + scrollBy * delta, }); }, [scrollBy], @@ -24,9 +24,7 @@ export default function useCarousel() { const scrollAreaNode = scrollArea.current; const calculateScrollPosition = () => { if (!scrollAreaNode) return; const { width } = scrollAreaNode.getBoundingClientRect(); if (scrollAreaNode.scrollLeft === 0) { setScrollPosition('start'); -
benknight revised this gist
Oct 28, 2020 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,7 @@ # [use-carousel] Headless UI React hook for building a scroll-based carousel BYO-UI. No CSS necessary. Inspired by react-table. ## Usage: ```js -
benknight revised this gist
Oct 28, 2020 . 1 changed file with 13 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -20,4 +20,16 @@ I recommend adding the following style to avoid weird elastic pull effect: body { overscroll-behavior-x: none; } ``` ## Instance properties | Property | Type | Description | | --------- | ---- | ----------- | | getLeftNavProps | `function` | Returns props for left arrow button | | getRightNavProps | `function` | Returns props for right arrow button | | isTouchDevice | `Boolean` | Whether the user is using a touch device or not, useful for hiding the navigate for touch users who can just swipe | | navigate | `function(delta: Int)` | Navigates by a specified delta e.g. 1 or -1 | | scrollAreaRef | `ref` | Reference to be assigned to the scroll parent | | scrollPosition | `string` | Describes current scroll position as "start", "end", or "between" | | showNav | `Boolean` | `false` if there aren't enough items to scroll | -
benknight revised this gist
Oct 28, 2020 . 2 changed files with 23 additions and 17 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,23 @@ # [use-carousel] Headless UI React hook for building a scroll-based carousel ## Usage: ```js const { getLeftNavProps, getRightNavProps, isTouchDevice, navigate, scrollAreaRef, scrollPosition, showNav, } = useCarousel(); ``` I recommend adding the following style to avoid weird elastic pull effect: ```css body { overscroll-behavior-x: none; } ``` This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,20 +1,3 @@ import _debounce from 'lodash/debounce'; import React from 'react'; -
benknight revised this gist
Oct 28, 2020 . 1 changed file with 39 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,62 +1,76 @@ /* Source: https://gist.github.com/benknight/3bbf8dbcbb0dfef9adc611be74538f67 Usage: const { getLeftNavProps, getRightNavProps, isTouchDevice, navigate, scrollAreaRef, scrollPosition, showNav, } = useCarousel(); */ import _debounce from 'lodash/debounce'; import React from 'react'; export default function useCarousel() { const scrollArea = React.useRef(); const [isTouchDevice, setIsTouchDevice] = React.useState(null); const [scrollBy, setScrollBy] = React.useState(null); const [scrollPosition, setScrollPosition] = React.useState(null); const [swipeDirection, setSwipeDirection] = React.useState(null); const [showNav, setShowNav] = React.useState(null); const navigate = React.useCallback( delta => { const { scrollLeft } = scrollArea.current; scrollArea.current.scroll({ left: scrollLeft + scrollBy * delta, behavior: 'smooth', }); }, [scrollBy], ); React.useEffect(() => { const scrollAreaNode = scrollArea.current; const calculateScrollPosition = () => { if (!scrollAreaNode) { return; } const { width } = scrollAreaNode.getBoundingClientRect(); if (scrollAreaNode.scrollLeft === 0) { setScrollPosition('start'); } else if (scrollAreaNode.scrollLeft + width === scrollAreaNode.scrollWidth) { setScrollPosition('end'); } else { setScrollPosition('between'); } }; // Calculate scrollBy offset const calculateScrollBy = () => { if (!scrollAreaNode) return; const { width: containerWidth } = scrollAreaNode.getBoundingClientRect(); setShowNav(scrollAreaNode.scrollWidth > containerWidth); const childNode = scrollAreaNode.querySelector(':scope > *'); if (!childNode) return; const { width: childWidth } = childNode.getBoundingClientRect(); setScrollBy(childWidth * Math.floor(containerWidth / childWidth)); }; // Swipe behavior for non-touch devices const resetSwipeDirection = _debounce(() => setSwipeDirection(null), 50, { leading: true, trailing: false, }); const onWheel = event => { if (event.deltaX > 20) { setSwipeDirection('right'); @@ -66,13 +80,17 @@ export default function useCarousel() { resetSwipeDirection(); }; const observer = new MutationObserver(calculateScrollBy); const attachListeners = () => { if (scrollAreaNode) observer.observe(scrollAreaNode, { childList: true }); scrollAreaNode.addEventListener('scroll', calculateScrollPosition); scrollAreaNode.addEventListener('wheel', onWheel); window.addEventListener('resize', calculateScrollBy); }; const detachListeners = () => { observer.disconnect(); scrollAreaNode.removeEventListener('scroll', calculateScrollPosition); scrollAreaNode.removeEventListener('wheel', onWheel); window.removeEventListener('resize', calculateScrollBy); @@ -123,5 +141,6 @@ export default function useCarousel() { navigate, scrollAreaRef: scrollArea, scrollPosition, showNav, }; } -
benknight revised this gist
Oct 24, 2020 . 1 changed file with 24 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -15,6 +15,7 @@ export default function useCarousel() { const [isTouchDevice, setIsTouchDevice] = React.useState(null); const [scrollBy, setScrollBy] = React.useState(null); const [swipeDirection, setSwipeDirection] = React.useState(null); const [scrollPosition, setScrollPosition] = React.useState(null); const navigate = React.useCallback( delta => { @@ -27,6 +28,23 @@ export default function useCarousel() { ); React.useEffect(() => { const calculateScrollPosition = () => { if (!scrollArea.current) { return; } const { width } = scrollArea.current.getBoundingClientRect(); if (scrollArea.current.scrollLeft === 0) { setScrollPosition('start'); } else if ( scrollArea.current.scrollLeft + width === scrollArea.current.scrollWidth ) { setScrollPosition('end'); } else { setScrollPosition('between'); } }; // Calculate scrollBy offset const scrollAreaNode = scrollArea.current; const calculateScrollBy = () => { @@ -49,11 +67,13 @@ export default function useCarousel() { }; const attachListeners = () => { scrollAreaNode.addEventListener('scroll', calculateScrollPosition); scrollAreaNode.addEventListener('wheel', onWheel); window.addEventListener('resize', calculateScrollBy); }; const detachListeners = () => { scrollAreaNode.removeEventListener('scroll', calculateScrollPosition); scrollAreaNode.removeEventListener('wheel', onWheel); window.removeEventListener('resize', calculateScrollBy); }; @@ -63,8 +83,9 @@ export default function useCarousel() { } if (isTouchDevice === false) { attachListeners(); calculateScrollBy(); calculateScrollPosition(); } return detachListeners; @@ -101,5 +122,6 @@ export default function useCarousel() { isTouchDevice, navigate, scrollAreaRef: scrollArea, scrollPosition, }; } -
benknight revised this gist
Oct 24, 2020 . 1 changed file with 9 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,12 @@ /* I recommend adding the following style to avoid weird elastic pull effect: body { overscroll-behavior-x: none; } */ import _debounce from 'lodash/debounce'; import React from 'react'; -
benknight created this gist
Oct 24, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,96 @@ import _debounce from 'lodash/debounce'; import React from 'react'; export default function useCarousel() { const scrollArea = React.useRef(); const [isTouchDevice, setIsTouchDevice] = React.useState(null); const [scrollBy, setScrollBy] = React.useState(null); const [swipeDirection, setSwipeDirection] = React.useState(null); const navigate = React.useCallback( delta => { scrollArea.current.scroll({ left: scrollArea.current.scrollLeft + scrollBy * delta, behavior: 'smooth', }); }, [scrollBy], ); React.useEffect(() => { // Calculate scrollBy offset const scrollAreaNode = scrollArea.current; const calculateScrollBy = () => { const { width: containerWidth } = scrollAreaNode.getBoundingClientRect(); const { width: itemWidth } = scrollAreaNode .querySelector(':scope > *') .getBoundingClientRect(); setScrollBy(itemWidth * Math.floor(containerWidth / itemWidth)); }; // Swipe behavior for non-touch devices const resetSwipeDirection = _debounce(() => setSwipeDirection(null), 40); const onWheel = event => { if (event.deltaX > 20) { setSwipeDirection('right'); } else if (event.deltaX < -20) { setSwipeDirection('left'); } resetSwipeDirection(); }; const attachListeners = () => { window.addEventListener('resize', calculateScrollBy); scrollAreaNode.addEventListener('wheel', onWheel); }; const detachListeners = () => { scrollAreaNode.removeEventListener('wheel', onWheel); window.removeEventListener('resize', calculateScrollBy); }; if (isTouchDevice === true) { detachListeners(); } if (isTouchDevice === false) { calculateScrollBy(); attachListeners(); } return detachListeners; }, [isTouchDevice, navigate]); React.useEffect(() => { const mql = window.matchMedia('(pointer: fine)'); const handleMql = ({ matches }) => { setIsTouchDevice(!matches); scrollArea.current.style.overflow = matches ? 'hidden' : 'auto'; }; handleMql(mql); mql.addEventListener('change', handleMql); return () => { mql.removeEventListener('change', handleMql); }; }, []); React.useEffect(() => { if (swipeDirection === 'right') { navigate(1); } else if (swipeDirection === 'left') { navigate(-1); } }, [navigate, swipeDirection]); return { getLeftNavProps: () => ({ onClick: () => navigate(-1), }), getRightNavProps: () => ({ onClick: () => navigate(1), }), isTouchDevice, navigate, scrollAreaRef: scrollArea, }; }