CyriacBr / react-split-text

Wrap your letters, words and lines with custom React elements
https://cyriacbr.github.io/react-split-text/
MIT License
50 stars 18 forks source link

Repeated rerenders on iOS Safari scroll #17

Open Cerom opened 2 years ago

Cerom commented 2 years ago

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?

pchurro commented 1 year ago

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

BaranovRoman commented 11 months ago

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(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 = 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

hyperflux commented 2 months ago

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

Cerom commented 2 months ago

I ended up splitting the text myself

sbuys commented 2 months ago

I ended up splitting the text myself

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

hyperflux commented 2 months ago

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);
    }
  }, []);
Cerom commented 2 months ago

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