Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

React styles update only after wheel event fulfilled

What I want to achieve is smoothly scaled div container while scrolling (using mouse wheel to be strict) so user can zoom in and out.

However, my styles are "applied" by the browser only either when I scroll really slow or scroll normally and then wait about 0.2 seconds (after that time the changes are "bunched up"). I would like for the changes to be visible even during "fast" scrolling, not at the end.

The element with listener:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

<div onWheel={(event) => {
         console.log("wheeling"); // this console log fires frequently, 
                                  // and I want to update styles at the same rate
         changeZoom(event);
     }}
>
    <div ref={scaledItem}> // content div that will be scaled according to event.deltaY
        ... // contents
    </div>
</div>

My React code:

const changeZoom = useCallback((event: React.WheelEvent<HTMLDivElement>) => {
    if (!scaledItem.current) return;
    const newZoom = parseFloat(scaledItem.current.style.scale) + event.deltaY * 0.001;
    console.log(newZoom); // logs as frequently as "wheeling" above
    setCurrentZoom(newZoom);
}, []);


useEffect(() => {
    if (!scaledItem.current) return;
    scaledItem.current.style.scale = currentZoom.toString();
}, [currentZoom]);

useEffect(() => {        // this is just for reproduction, needs to set initial scale to 1
    if (!scaledItem.current) return;
    scaledItem.current.style.scale = "1";
}, [])

What I have tried first was to omit all the React states, and edit scaledItem.current.style.scale directly from useCallback, but the changes took place in a bunch, after the wheeling events stopped coming. Then I moved zoom amount to currentZoom useState hook, but rerenders don’t help either.

Edit:
I have also tried adding EventListener inside useEffect directly to the DOM Node:

useEffect(() => {
    if (!scaledItemWrapper.current) return; // ref for wrapper of my scaled content
    const container = scaledItemWrapper.current;
    container.addEventListener("wheel", changeZoom);
    return () => {
        container.removeEventListener("wheel", changeZoom);
    };
}, [changeZoom]);

>Solution :

Instead of setting up multiple states and observing can you try using a single state below is a working example. Try this if this works

https://codesandbox.io/s/wonderful-cerf-69doe?file=/src/App.js:0-727

export default () => {
  const [pos, setPos] = useState({ x: 0, y: 0, scale: 1 });

  const changeZoom = (e) => {
    e.preventDefault();
    const delta = e.deltaY * -0.01;
    const newScale = pos.scale + delta;

    const ratio = 1 - newScale / pos.scale;

    setPos({
      scale: newScale,
      x: pos.x + (e.clientX - pos.x) * ratio,
      y: pos.y + (e.clientY - pos.y) * ratio
    });
  };

  return (
    <div onWheelCapture={changeZoom}>
      <img
        src="https://source.unsplash.com/random/300x300?sky"
        style={{
          transformOrigin: "0 0",
          transform: `translate(${pos.x}px, ${pos.y}px) scale(${pos.scale})`
        }}
      />
    </div>
  );
};
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading