How to scroll to an element using react-spring?
Asked Answered
I

3

5

I have read through the entire react-spring docs and there doesn't seem to be a clear way to do this.

My attempt:

import React, { useRef, useState } from "react"
import { animated, useSpring } from "react-spring"

const App = () => {
    const scrollDestinationRef = useRef()

    const [elementScroll, setElementScroll] = useState(false)

    const buttonClickHandler = () => setElementScroll(prevState => !prevState)

    const scrollAnimation = useSpring({
        scroll: elementScroll
            ? scrollDestinationRef.current.getBoundingClientRect().top
            : 0
    })

    return (
        <main>
            {/* Click to scroll to destination */}
            <animated.button
                onClick={buttonClickHandler}
                scrollTop={scrollAnimation.scroll}
                style={{
                    height: "1000px",
                    width: "100%",
                    backgroundColor: "tomato"
                }}
            >
                Scroll to destination
            </animated.button>

            {/* Scroll destination */}
            <div
                ref={scrollDestinationRef}
                style={{
                    height: "200px",
                    width: "200px",
                    backgroundColor: "green"
                }}
            ></div>
        </main>
    )
}

export default App

I'm using a ref and hooks for my attempt.

The useRef is attached the scroll destination in-order to find its offset top from the website's ceiling.

I use useState to toggle between the state on click to trigger the scroll.

I use useSpring to trigger an animation that goes from 0 to the scroll destination's scroll top a.k.a. getBoundingClientRect().top.

Can anyone assist in solving this?

There doesn't to be much explanation online, thanks!

Immaterialism answered 27/8, 2019 at 10:1 Comment(0)
L
5

useSpring returns a function to set/update animated values. You can use that function to assign a new value to your animated variable. Then, you can use the onFrame property to update the scroll position.

Define your spring like this:

const [y, setY] = useSpring(() => ({
    immediate: false,
    y: 0,
    onFrame: props => {
      window.scroll(0, props.y);
    },
    config: config.slow,
  }));

Then use setY function to start the spring, like this:

 <button
        onClick={() => {
          setY({ y: scrollDestinationRef.current.getBoundingClientRect().top });
        }}
      >
        Click to scroll
 </button>

When you click the button it will assign a new value to y variable in your spring, and onFrame function will be called upon every update.

Note that we call window.scroll function from onFrame property in useSpring.

See working demo here.

Lozier answered 27/8, 2019 at 14:7 Comment(1)
Thank you very much. When you scroll up and down after pressing the button once upon load, it no longer works. It only works the first time. Can you amend this please?Immaterialism
E
2

Finally after getting through https://github.com/pmndrs/react-spring/issues/544, answer from Yannick Schuchmann here worked for me, I just had to change onFrame to onChange

I made a custom hook for the solution: https://github.com/TomasSestak/react-spring-scroll-to-hook

react-sring version: 9.0.0-rc.3

Erasmoerasmus answered 1/3, 2021 at 14:4 Comment(2)
Updated for react-spring 9+ on the same linkCoray
Thank you, for now I'm using your project!Rancidity
C
1
const targetElement = useRef(null)
const [, setY] = useSpring(() => ({ y: 0 }))

let isStopped = false
const onWheel = () => {
  isStopped = true
  window.removeEventListener('wheel', onWheel)
}

const scrollToTarget = () => {
  const element = targetElement.current
  const value = window.scrollY + element.getBoundingClientRect().top

  window.addEventListener('wheel', onWheel)

  setY({
    y: value,
    reset: true,
    from: { y: window.scrollY },
    onRest: () => {
      isStopped = false
      window.removeEventListener('wheel', onWheel)
    },
    onFrame: props => {
      if (!isStopped) {
        window.scroll(0, props.y)
      }
    }
  })
}

This version also allows the user to break out of the forced scrolling by using wheel event. (I personally hate forced scrolling :D)

useSpring(fn) does return a stop method besides the set. But I couldn't make it work with that. I posted it to a related github issue. If you read this, maybe there's a good answer for that already :)

For now it uses a little workaround with isStopped flag.


UPDATE:

Seems like there is actually something fishy with stop fn which should be addressed in v9 canary. Haven't tested since v9 is not stable yet.

Candis answered 10/10, 2019 at 16:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.