분류 Reactjs

React로 멋진 가로 스크롤 상호 작용 만들기

컨텐츠 정보

  • 조회 1,179 (작성일 )

본문

이 자습서에서는 스크롤 방향으로 항목이 "플립"되는 재미있는 스크롤 애니메이션을 만듭니다. 

애니메이션을 스크롤 이벤트에 연결하기 위해 애니메이션 및 React-use-gesturereact-spring을 사용할 것입니다. 

네이티브 onScroll 이벤트 핸들러는 네이티브 onScroll 핸들러가 제공하지 않는 스크롤에 대한 추가 정보 (픽셀 단위의 스크롤 델타)와 스크롤이 진행 중인지 여부에 대한 추가 정보가 필요하기 때문에 수행하지 않습니다.


https://medium.com/dailyjs/horizontal-scroll-animation-fc39ae43cbe5 


이것이 우리가 만들 것입니다 :


0*8mI9R9aO4CF7mAe1.gif 


기본 설정 


아래에서 볼 수 있는 기본 React 구성 요소부터 시작하겠습니다. 이 구성 요소는 공용 폴더에서 이미지 목록을 렌더링하고 div 요소의 배경으로 설정합니다.


const movies = [
"/breaking-bad.webp",
"/the-leftovers.jpg",
"/game-of-thrones.jpg",
"/true-detective.jpg",
"/walking-dead.jpg"
];
const App = () => {
return (
<>
<div className="container">
{movies.map(src => (
<div
key={src}
className="card"
style={{
backgroundImage: `url(${src})`
}}
/>
))}
</div>
</>
);
};


다음으로 스타일을 적용하겠습니다. 컨테이너가 너비의 100%를 차지하고 하위 항목이 넘치도록 해야 합니다.


::-webkit-scrollbar {
width: 0px;
}
.container {
display: flex;
overflow-x: scroll;
width: 100%;
}
.card {
flex-shrink: 0;
width: 300px;
height: 200px;
border-radius: 10px;
margin-left: 10px;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}


기본 스타일을 사용하면 구성 요소가 다음과 같이 됩니다.

0*-NgMxCOvEfQ2gLVV.gif 


애니메이션 추가 


회전 애니메이션을 추가하여 시작하겠습니다. 먼저 div 요소를 animated.div로 바꿉니다. animated는 기본 요소를 확장하여 애니메이션 값을 수신하는 데코레이터입니다. 모든 HTML 및 SVG 요소에는 애니메이션을 적용하려는 애니메이션 요소가 있습니다.


다음으로 반응 스프링 패키지의 useSpring 후크를 사용하여 구성 요소가 마운트 될 때 실행될 기본 애니메이션을 작성합니다. 결국 애니메이션을 스크롤 이벤트에 바인딩 하지만 당분간 애니메이션이 마운트에서 실행되는 경우 변경 한 결과를보다 쉽게 ​​확인할 수 있습니다.


useSpring 후크는 애니메이션 되어야 하는 CSS 속성이 있는 객체를 사용합니다. 이러한 속성은 애니메이션의 끝 값으로 설정해야하므로 div를 0에서 25 도로 회전하려면 변환 값을 rotateY (25deg)로 설정하십시오. 초기 값을 설정하기 위해 CSS 속성을 가진 객체를 취하는 속성을 사용합니다.


useSpring 후크는 대상 구성 요소에 설정해야 하는 스타일 객체를 반환합니다. 업데이트 된 코드와 결과는 다음과 같습니다.


import { animated, useSpring } from "react-spring";
const App = () => {
const style = useSpring({
from: {
transform: "rotateY(0deg)"
},
transform: "rotateY(25deg)"
});
return (
<>
<div className="container">
{movies.map(src => (
<animated.div
key={src}
className="card"
style={{
...style,
backgroundImage: `url(${src})`
}}
/>
))}
</div>
</>
);
};


0*Jq4t23uC0XrY_YSF.gif 


이 애니메이션은 기본적으로 회전이 2 차원이기 때문에 평평해 보입니다. 애니메이션을 관찰하는 사용자와 회전 평면 사이에 거리가 없는 것처럼 렌더링 됩니다. 원근 변환을 사용하면 관측점을 회전 평면에서 멀리 이동할 수 있으므로 2차원 애니메이션이 3차원으로 보입니다.


const style = useSpring({
from: {
transform: "perspective(500px) rotateY(0deg)"
},
transform: "perspective(500px) rotateY(25deg)"
});


0*Yt8XOAesRzYJ2Pf2.gif 


마지막으로 하위 요소가 잘리지 않도록 컨테이너 div에 세로 패딩을 추가해야 합니다.


.container {
display: flex;
overflow-x: scroll;
width: 100%;
padding: 20px 0;
}


0*x08P_PKpi1vaU_4N.gif 


스크롤 이벤트 작업을 시작하기 전에 useSpring 후크 사용 방식을 약간 변경해야 합니다. 명심해야 할 두 가지가 있습니다.

  • 애니메이션을 수동으로 트리거할 수 있어야 합니다.
  • 더 이상 마운트시 애니메이션을 실행할 필요가 없습니다.

이러한 두 가지 문제를 해결하기 위해 CSS 속성이 있는 객체를 전달하는 대신 다른 useSpring 서명을 사용합니다. CSS 속성이 있는 객체를 반환하는 함수를 전달합니다. 이전에는 useSpring 후크가 스타일 객체를 반환했습니다. 새로운 시그니처를 사용하면 첫 번째 인수는 스타일 객체이고 두 번째 인수는 애니메이션을 트리거 하기 위해 언제든지 호출 할 수 있는 설정 함수 인 튜플을 반환합니다.


이 값은 div의 현재 회전을 기반으로 결정되므로 속성에서 삭제할 수도 있습니다.


const [style, set] = useSpring(() => ({
transform: "perspective(500px) rotateY(0deg)"
}));


이제 react-use-gesture 패키지에서 useScroll 후크를 가져 와서 컨테이너 div에 바인딩 할 수 있습니다. 스크롤 이벤트를 처리하는 논리는 매우 간단합니다. 사용자가 스크롤 하는 경우 (event.scrolling === true), Y 축의 스크롤 델타와 동일한 각도로 카드를 회전하려고 합니다 (event.delta [0] ); 스크롤이 멈 추면 회전 각도를 0으로 재설정 합니다.


import { useScroll } from "react-use-gesture";
const App = () => {
const [style, set] = useSpring(() => ({
transform: "perspective(500px) rotateY(0deg)"
}));
const bind = useScroll(event => {
set({
transform: `perspective(500px) rotateY(${
event.scrolling ? event.delta[0] : 0
}deg)`
});
});
return (
<>
<div className="container" {...bind()}>
{movies.map(src => (
<animated.div
key={src}
className="card"
style={{
...style,
backgroundImage: `url(${src})`
}}
/>
))}
</div>
</>
);
};


0*Q0feaT4GNFXZkbXN.gif 


애니메이션은 효과가 있지만 바람직하지 않은 부작용이 있습니다. 급하게 스크롤하면 Y 델타가 상당히 커져 카드가 90도 이상 뒤집힐 수 있습니다. 다른 값을 테스트 한 결과 카드가 30도 이상 넘지 않으면 애니메이션이 가장 좋습니다. 델타 값을 고정하는 도우미 함수를 작성하여 30을 초과하지 않고 -30보다 작게 만들 수 없습니다.


const clamp = (value: number, clampAt: number = 30) => {
if (value > 0) {
return value > clampAt ? clampAt : value;
} else {
return value < -clampAt ? -clampAt : value;
}
};


이제 이 도우미 함수를 사용하여 useScroll 후크 내부에서 Y 델타를 고정하고 최종 결과를 얻을 수 있습니다.


const bind = useScroll(event => {
set({
transform: `perspective(500px) rotateY(${
event.scrolling ? clamp(event.delta[0]) : 0
}deg)`
});
});


0*a25LaxSyRcTH-_8S.gif 


이 상호 작용에 대한 완전한 데모를 여기서 찾을 수 있습니다.


추신 : 나는 또한 framer-motion을 사용하여 동일한 상호 작용을 했습니다. 실무 데모는 여기에 있습니다.


마지막 생각들 


이 튜토리얼의 장막 뒤에 남아 있지만 이 특정 애니메이션을 만들기 전에 내려진 두 가지 결정에 대해 언급하고 싶습니다.


첫 번째 결정은 성능과 관련이 있습니다. 플립 애니메이션을 만들기 위해 GPU에 의해 가속되고 메인 스레드에서 시간을 소비하지 않는 두 가지 속성 중 하나 인 변환 속성 만 애니메이션 했습니다 (다른 속성은 불투명도). 변환과 불투명도 만 애니메이션으로 만들면 얻을 수 있는 것이 많으며 가능하면 다른 CSS 속성에 애니메이션을 적용하지 않아야 합니다.


둘째, 응답 성을 고려해야 합니다. 우리가 구현 한 가로 스크롤은 휴대폰과 태블릿에서 잘 작동하지만 더 큰 데스크톱 화면에서는 더 일반적인 그리드 레이아웃을 사용하고 싶을 수 있습니다. 작은 CSS 변경 및 미디어 쿼리를 사용하면 플렉스에서 그리드 레이아웃으로 전환 할 수 있으며 애니메이션을 전혀 변경할 필요가 없습니다. 플렉스 레이아웃을 사용하는 작은 화면에서는 계속 작동하며 큰 화면에서는 무시됩니다. 그리드 레이아웃을 사용하면 가로 스크롤이 없습니다.