react-split-text icon indicating copy to clipboard operation
react-split-text copied to clipboard

Repeated rerenders on iOS Safari scroll

Open Cerom opened this issue 3 years ago • 7 comments

On iOS Safari with URL bar at the bottom of the screen (>15.0), the window resizes every time the scroll direction changes, as the URL bar is being minimized or expanded. This triggers a re render of the SplitText component and the animation is triggered every single time. Is there a way to prevent those undesired rerenders by filtering vertical resize listening events on iOS devices maybe?

Cerom avatar Sep 19 '22 13:09 Cerom

Any updates on this? @Cerom have you managed to find a workaround?

pchurro avatar Nov 07 '23 11:11 pchurro

I'm clone this repo, and edit SplitText.ts

useDebounce hook ` import { useState, useEffect } from 'react';

export function useDebounce<T = any>(value: T, delay: number) { const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(
    () => {
        const handler = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);

        return () => {
            clearTimeout(handler);
        };
    },
    [value, delay],
);

return debouncedValue;

} `

viewport.ts

` const isServer = typeof window === 'undefined';

export const viewport = { width: isServer ? 0 : window.innerWidth, height: isServer ? 0 : window.innerHeight, };

if (!isServer) { window.addEventListener('resize', () => { viewport.width = window.innerWidth; viewport.height = window.innerHeight; }); }

`

and SplitText.ts

` export const SplitText: FC<SplitTextProps> = forwardRef(function SplitText({ children, ...props }, ref) { const [key, setKey] = useState(0); const [lastWidth, setLastWidth] = useState();

const onResize = useDebounce(() => {
    if (viewport.width !== lastWidth) {
        setLastWidth(viewport.width);
        setKey((v) => v + 1);
    }
}, 300);

useLayoutEffect(() => {
    setLastWidth(viewport.width);
}, []);

useIsomorphicLayoutEffect(() => {
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
}, []);

return (
    <SplitTextInner key={key} {...props} ref={ref}>
        {children}
    </SplitTextInner>
);

}); `

also I'm use our project dependencies and add some types, anyone can use it, or I try to find time to fork it

BaranovRoman avatar Dec 18 '23 08:12 BaranovRoman

Also facing this issue... any plans to fix?

hyperflux avatar Aug 20 '24 22:08 hyperflux

I ended up splitting the text myself

Cerom avatar Aug 21 '24 13:08 Cerom

I ended up splitting the text myself

Damn. Should have tested on safari myself. Now I have to unwind a bunch of work

sbuys avatar Aug 23 '24 19:08 sbuys

Hey people! I was able to solve the issue without having to split the text myself and I suggest you give it a try...

I just forked the repo and added it directly to my project. Then I simply removed the resize event handler in SplitText.tsx... and instead added a resize handler in my component that re-renders the text only if the window width has changed (instead of on width OR height change) - the re-render happens because I'm storing the window width as a state variable. I hope this helps someone!

image

My component:

  const [currentWidth, setCurrentWidth] = useState(
    typeof window !== 'undefined' ? window.outerWidth : 0, // next.js builds fail without this window undefined check
  );

  const onResize = (e: any) => {
    const newWidth = e.target.outerWidth;
    if (newWidth !== currentWidth) {
      setCurrentWidth(newWidth);
    }
  };

  useEffect(() => {
    if (typeof window !== 'undefined') {
      setCurrentWidth(window.outerWidth);
      window.addEventListener('resize', onResize);
      return () => window.removeEventListener('resize', onResize);
    }
  }, []);

hyperflux avatar Aug 23 '24 20:08 hyperflux

Cool thanks for sharing! Should we open a PR for this?

Cerom avatar Aug 28 '24 21:08 Cerom