skip to Main Content

I want to implement slow scrolling. I decided to create a container 10 times larger than its content. The content is then moved via JavaScript, based on scroll events.

The example is in React, but straightforward. I marked this question with the JavaScript tag because the problem I have relates to JavaScript itself, not to React/TypeScript/etc.

'use client'

import { useEffect, useRef, useState } from 'react'

const targetHeight = 100 // container's height
const rootHeight = 10000 // 10x times larger container

export default function FooBar() {
  const ref = useRef<HTMLDivElement | null>(null)

  useEffect(() => {
    function onScroll() {
      const scrollTop =
        document.documentElement.scrollTop || document.body.scrollTop
      const scrollHeight =
        document.documentElement.scrollHeight || document.body.scrollHeight
      const clientHeight =
        document.documentElement.clientHeight || window.innerHeight
      const scrollProgress = scrollTop / (scrollHeight - clientHeight) // from 0 to 1
      const result = Math.floor((rootHeight - targetHeight) * scrollProgress)
      setTop(result)
    }

    window.addEventListener('scroll', onScroll, { passive: true })
    return () => {
      window.removeEventListener('scroll', onScroll)
    }
  }, [])

  const [top, setTop] = useState(0)

  return (
    <div
      ref={ref}
      style={{
        height: rootHeight,
      }}
    >
      <div
        style={{
          willChange: 'transform',
          transform: `translate3d(0, ${top}px, 0)`,
          transition: 'width 0s',
        }}
      >
        <div style={{ width: 100, height: 100, background: 'white' }} />
      </div>
    </div>
  )
}

Everything is not complicated. However, when scrolling with a mouse, the animation is jumpy. See the following reproduction:
https://stackblitz.com/edit/stackblitz-starters-fqrzww?file=app%2Fpage.tsx

Try to scroll the example. I expect the white rectangle to move smoothly. However, it’s so jumpy.

I really have no idea what’s the reason of this behavior. The computed values are not "jumpy", they are sequential.

2

Answers


  1. In fact, it used the "transition" property css to be smooth, but put the calculation function outside of "useEffect" and try it.

    Login or Signup to reply.
  2. In fact, you wanted to move the element by subtracting the amount of page scrolling from the amount of scrolling of the entire page, but I manipulated your code a bit and this time I obtained the percentage of the scrolling by calculating the amount of scrolling and by changing the element’s state to floating state, it is good.

    export default function FooBar() {
      const ref = useRef<HTMLDivElement | null>(null);
    
      useEffect(() => {
        function onScroll() {
          const scrollTop =
            window.scrollY;
          const computingElement = (scrollTop * 100) / rootHeight;
          setTop(computingElement);
        }
    
        window.addEventListener('scroll', onScroll);
        return () => {
          window.removeEventListener('scroll', onScroll);
        };
      }, []);
    
      const [top, setTop] = useState(0);
    
      return (
        <div
          ref={ref}
          style={{
            position: "relative",
            height: rootHeight,
          }}
        >
          <div
            style={{
              position: "sticky",
              top: `${top}%`,
              transition: 'width 0s',
            }}
          >
            <div style={{ width: 100, height: 100, background: 'white', color: "black" }}>{top}</div>
          </div>
        </div>
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search