pmndrs / drei

๐Ÿฅ‰ useful helpers for react-three-fiber
https://docs.pmnd.rs/drei
MIT License
8.42k stars 707 forks source link

Add ScrollSnap to <ScrollControls /> #1146

Open AidanNelson opened 2 years ago

AidanNelson commented 2 years ago

Describe the feature you'd like:

I'd love to see scroll snapping added to ScrollControls to enable more control over views. In this case, you could set the number of pages and have the scrolling snap to X number of different views across those pages (i.e. full height views).

Suggested implementation:

I've started implementing this by setting scroll-snap-type on the parent element and replacing the fill container with multiple child elements with scroll-snap-align set.

That said, the behavior is quite odd at this point, and doesn't really feel 'snappy' as expected. Perhaps damping is interfering? Does anyone have a different approach to suggest here?

drcmda commented 1 year ago

i tried and i couldn't make it work. even just the plain vanilla snap thing feels weird to me from all the sandboxes i'm trying. but working it into scrollcontrols, i failed completely. i think this needs to be done by someone that knows the spec better than i do.

AidanNelson commented 1 year ago

Thanks for trying @drcmda ๐Ÿ™

I found it easier to program the 'snappiness' into my r3f/threejs camera control code directly, rather than trying to enable scroll-snap. I did this by creating a number of curves to represent different parts of a camera movement, then sampling along those curves with some easing added to smooth things out. This way, I was able to program in a 'detent' at each of the stop-points (where I would have used scroll snap).

I will leave this open in case anyone has a better approach using css scroll snap. Feel free to close if you like!

rogersanick commented 1 year ago

I'm also having this issue! I may have to try your solution with curves / camera movement.

rogersanick commented 1 year ago

@AidanNelson - let me know if you are comfortable, but I'd love to see how you've handled this. Scroll snapping is sorrily missed in my implementation!

TurbatBay commented 1 year ago

Thanks for trying @drcmda ๐Ÿ™

I found it easier to program the 'snappiness' into my r3f/threejs camera control code directly, rather than trying to enable scroll-snap. I did this by creating a number of curves to represent different parts of a camera movement, then sampling along those curves with some easing added to smooth things out. This way, I was able to program in a 'detent' at each of the stop-points (where I would have used scroll snap).

I will leave this open in case anyone has a better approach using css scroll snap. Feel free to close if you like!

Hi can you show us ur code ? i'm trying to accomplish same thing and if possible i want to see it : )

shunmian commented 7 months ago

Is it possible to have scroll by page feature like this?

saori-eth commented 6 months ago

bump

nmyatt commented 2 months ago

I came here to see if anyone had solved it. I feel i'm getting close, but it's a PIA. A couple of flies in the ointment are:

    useEffect(() => {
        // start the scrollcheck loop
        checkIfScrolling()
    }, [])

    const checkIfScrolling = () => {
        // This is a workaround because some browsers (Safari) don't have a scrollEnd event
        // It may seem expensive to check every frame, but it's not - it's just setting a boolean
        if (scroll.offset !== previousScrollOffset) {            
            isScrolling = true            

        } else {            
            isScrolling = false            
        }        

        requestAnimationFrame(checkIfScrolling)
    }
 const handleScroll = () => {
        // inertia scrolling on e.g. touch devices means that scroll.offset is still changing after the user has stopped scrolling.
        // Using .toFixed() desensitises the scroll handler
        previousScrollOffset = scroll.offset.toFixed(SCROLL_PRECISION)
        currentSectionOffset = mapRange(previousScrollOffset, 0, 1, 1, scroll.pages)
        currentSectionIndex = Math.floor(currentSectionOffset)

        const sectionDelta = currentSectionOffset - Math.floor(currentSectionOffset)

        if (isScrolling) {            
            return null
        }

        scrollTimeout = setTimeout(() => {
            //setSection(currentSectionIndex)                        

            if (Math.abs(sectionDelta) < SCROLL_SNAP_AMOUNT) {
                // snap to the next section
                snapToNext()
            } else if (Math.abs(sectionDelta) > 1 - SCROLL_SNAP_AMOUNT) {
                // snap to the current section
                snapToPrevious()
            }
            clearTimeout(scrollTimeout)
            // calculate the delta between the current section and the current scroll offset

        }, SCROLL_SNAP_DELAY)

    }

I still have a bunch of issues to work through, but I need to work on something else so I thought i'd throw this code up here in case it helps anyone. If I get it working perfectly, i'll update here.

github-actions[bot] commented 3 days ago

Thank you for contributing! Weโ€™re marking this issue as stale as a gentle reminder to revisit it and give it the attention it needs to move forward.

Any activity, like adding an update or comment, will automatically remove the stale label so it stays on our radar.

Feel free to reach out on Discord if you need support or feedback from the community. This issue will close automatically soon if thereโ€™s no further activity. Thank you for understanding and for being part of the project!