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에서 inViewVideoControl
을 inView
값으로 업데이트 합니다.
또한 inView
가 true일 때 inViewControl
을 true로 업데이트합니다.
우선 inView
를inViewVideoControl
,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
라는 상태가 들어있습니다. videoFlag
는 changeArrayIndex
함수의 실행을 통해 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으로 되돌렸습니다.
그 이후에는 배열의 맨 앞부분을 떼서 마지막에 넣어 순서를 조작하였습니다.