Search code examples
javascripthtmlreactjshtml5-videogiphy

React autoplay <video /> (mp4) only when visible


I am writing a component that pulls gifs from Giphy and renders them inside a React Infinite Scroll Component. The infinite scroll is worked as expected, but then I ran into a problem. I want the gifs to auto play, so I added the autoplay attribute. I then realised that with all of the gifs playing at once the performance just dies.
I have tried a number of different solutions, including attaching a ref to each video component and using react-visibility-sensor to try to play when visible, but I have not been able to work out a way that works.
This is the component as it currently stands with each mp4 autoplaying. I have removed the VisibilitySensor because I could not get it working.

  const videoRefs = useRef(
    [...new Array(gifArray.length)].map(() => createRef()),
  );

  return (
    <div>
      {gifArray && (
        <InfiniteScroll
          dataLength={gifArray}
          next={getMore}
          hasMore={true}
          loader={<div>Loading...</div>}
        >
          {gifArray.map((gif, i) => {
            return (
              <video
                width='320'
                height='240'
                loop
                autoPlay
                key={gif.id}
                ref={videoRefs.current[i]}
              >
                <source src={gif.images.downsized_small.mp4} type='video/mp4' />
              </video>
            );
          })}
        </InfiniteScroll>
      )}
    </div>
  );
};

The data is retrieved in a seperate component and passed as props.

export const HomePage = () => {
  const [gifObject, setGifObject] = useState([]);
  const [searchValue, setSearchValue] = useState('');
  const [pageNumber, setPageNumber] = useState(0);

  const handleSearch = (value) => {
    setSearchValue(value);
    fetch(
      `http://api.giphy.com/v1/gifs/search?q=${value}&api_key=${API_KEY}&limit=20`,
    )
      .then((data) => data.json())
      .then((result) => {
        setGifObject(result);
      });
  };
  return (
    <div>
      <SearchForm handleSearchValue={handleSearch} />
      {gifObject?.data?.length > 1 ? (
        <GifDisplay gifArray={gifObject.data} getMore={getData} />
      ) : null}
    </div>
  );
}

Solution

  • I ended up solving this one. I created a component for each gif and used a package called react-visibility-sensor to check if it was visible or not.
    The GifDisplay component remained pretty close to the same:

    // GifDisplay.tsx
    export const GifDisplay = ({ gifArray, getMore, handleSearch }) => {
      return (
        <GifDisplayWrapper>
          {gifArray && (
            <InfiniteScroll
              dataLength={gifArray}
              next={getMore}
              hasMore={true}
              loader={<div>Loading...</div>}
            >
              <ArrayWrapper>
                {gifArray.map((gif) => {
                  return (
                    <div key={gif.id}>
                      <SingleGif gif={gif} />
                    </div>
                  );
                })}
              </ArrayWrapper>
            </InfiniteScroll>
          )}
        </GifDisplayWrapper>
      );
    };
    

    The SingleGif component used react-visibility-sensor and updated the state of each component.

    // SingleGif.tsx
    import VisibilitySensor from 'react-visibility-sensor';
    
    export const SingleGif = ({ gif }) => {
      const videoRef = useRef(null);
      const [isVisible, setIsVisible] = useState(false);
    
      useEffect(() => {
        if (isVisible) {
          videoRef.current.play();
        } else {
          if (videoRef.current.play) {
            videoRef.current.pause();
          }
        }
      }, [isVisible]);
    
      return (
        <VisibilitySensor onChange={(isVisible) => setIsVisible(isVisible)}>
          <video width='320' height='240' loop ref={videoRef}>
            <source src={gif.images.downsized_small.mp4} type='video/mp4' />
          </video>
        </VisibilitySensor>
      );
    };