동영상을 사용하는 컴포넌트 상태관리

잔잔바리한접시·2022년 10월 6일
2

react

목록 보기
8/12

설명

  1. 가운데에 있는 동영상이 재생됩니다.
  2. 동영상이 끝나면 재생중인 동영상이 왼쪽 혹은 오른쪽으로 넘어갑니다.
  3. 왼쪽, 혹은 오른쪽에 있는 영상이 가운데로 와서 영상이 재생됩니다.

명세

  1. 컴포넌트가 뷰포트 안에 들어오게 되면 동영상이 재생됩니다.
  2. 컴포넌트가 뷰포트 밖으로 나가게 되면 순서를 그대로 유지하며 영상이 일시정지 됩니다
  3. 재생이 완료된 영상은 시작점(0프레임)으로 돌아가서 차례를 기다립니다.

구현

react-intersection-observer 라이브러리를 이용하여 ref 객체가 뷰포트 안에 들어왔는지를 검사합니다.

import React, { useState, useEffect } from "react";
import { useInView } from 'react-intersection-observer';
...

function(){
	const [ref, inView] = useInView();
  	const [inViewControl, setInViewControl] = useState(false);
  
    useEffect(() => {
      if (inView) {
        setInViewControl(true);
      }
      setInViewVideoControl(inView);
    }, [inView]);

	return(
    	...
      	<MainContainer ref={ref}>
        	{inViewControll && (
        		<MainContainerContainer>
            		...
        )
    )
}

ref 객체인 MainContainer가 뷰포트 안에 있는지 검사하여 뷰포트 안에 있다면 boolean 값인 inView가 true로 바뀌게 됩니다.

inView 값이 바뀌게 되면 inView 값을 주시하고 있는 effect에서 inViewVideoControlinView 값으로 업데이트 합니다.

또한 inView가 true일 때 inViewControl을 true로 업데이트합니다.

우선 inViewinViewVideoControl,inViewControl 여러 상태로 나누어 관리하는 이유는 애니메이션의 트리거가 서로를 혼동하지 않게 하기 위함이었습니다. inViewVideoControl은 비디오의 재생,일시정지를 담당하는 상태고,inViewControl은 ref 객체가 화면에 들어올 때 나오는 등장 애니메이션을 담당합니다.

컴포넌트가 뷰포트에 등장하고, 사라짐을 반복할 때 inView 값은 계속 true, false를 오가게되고 inViewVideoControl값 또한 이와 동일합니다. 하지만 inViewControl 값은 true로 고정됩니다.

이로써, inViewControl은 생명주기당 1번, inViewVideoControl은 ref 객체의 등장, 퇴장 횟수만큼 바뀌게 됩니다.

useEffect(() => {
  let currentPlay = document.getElementById("current-play");
  if (currentPlay !== null && inViewVideoControl) {
    currentPlay.play();
  } else {
    currentPlay.pause();
  }
}, [videoFlag, inViewVideoControl]);

...

{videoArray?.map((video, index) => {
  return (
    <Video
      id={index === 1 ? "current-play" : undefined}
      key={video.key}
      onEnded={changeArrayIndex}
      whileHover={{
        boxShadow: `rgba(0, 0, 0, 0.35) 0px 5px 15px`,
      }}
      initial={{
        x: (index - 1) * 240,
          width: index === 1 ? "288px" : "240px",
            height: index === 1 ? "540px" : "450px",
              y: 30,
      }}
      animate={{
        x: (index - 1) * 240,
          y: 0,
            zIndex: index === 1 ? 999 : 100 - index,
              width: index === 1 ? "288px" : "240px",
                height: index === 1 ? "540px" : "450px",
                  transition: { duration: 1 },
      }}
      muted
      >
      <source src={video.video} type="video/mp4" />
    </Video>
  );
})}

위에서 inViewVideoControl은 비디오의 재생과 정지를 담당한다고 했습니다.

그렇다면 currentPlay는 무엇일까요?

currentPlay는 map으로 뿌려준 3개의 영상 중 가운데에 오는 영상을 뜻하고, current-play라는 id를 가진 영상을 뜻합니다. 즉, map으로 뿌린 3개의 영상 중 index가 1인, 가운데에 있는 영상에 current-play id를 부여하였습니다.

위의 effect deps array 안에 videoFlag라는 상태가 들어있습니다. videoFlagchangeArrayIndex함수의 실행을 통해 videoArray가 변경되었음을 알려주는 상태입니다. 이 videoFlag상태는 아래에 나올 함수의 실행을 통한 상태변화를 알려주는 역할을 하게 됩니다.

이렇게 inViewVideoControl, videoFlag 두 상태를 주시하는 effect는 현재 current-play id를 가진 영상의 변화, ref 객체가 뷰포트 안에 존재하는지에 대한 상태 변화를 통해 비디오의 재생과 일시정지를 관리합니다.

이제 마지막입니다. 영상이 재생을 마친다면 index가 바뀌어야 합니다. index를 바꾸기 위해서는 배열을 조작하여야 합니다.

const changeArrayIndex = async () => {
  let currentPlay = document.getElementById("current-play");
  if (currentPlay !== null) {
    currentPlay.pause();
    currentPlay.currentTime = 0;
  }

  const tmpVideo = videoArray[0];
  await setVideoArray([
    ...videoArray.filter((_, index) => index !== 0),
    tmpVideo,
  ]);
  setVideoFlag(!videoFlag);
};

위의 명세를 보면 재생을 마친 영상을 다시 0프레임으로 돌려놓아야 합니다. 그렇기 때문에 changeArrayIndex함수를 onEnded 속성에 연결하였습니다. 영상이 끝나면 changeArrayIndex 함수가 실행되고 currentPlay를 불러와 정지시키고 currentTime을 0으로 되돌렸습니다.

그 이후에는 배열의 맨 앞부분을 떼서 마지막에 넣어 순서를 조작하였습니다.

0개의 댓글