좋아하든 싫어하든 시차 효과는 계속될 것입니다. 신중하게 사용하면 웹 앱에 깊이와 미묘함을 더할 수 있습니다. 하지만 성능이 좋은 방식으로 패럴랙스를 구현하는 것은 어려울 수 있습니다. 이 도움말에서는 성능이 우수하고 브라우저 간에 작동하는 솔루션을 설명합니다.

요약
- 스크롤 이벤트나
background-position
를 사용하여 시차 애니메이션을 만들지 마세요. - CSS 3D 변환을 사용하여 더 정확한 시차 효과를 만드세요.
- 모바일 Safari의 경우
position: sticky
를 사용하여 시차 효과가 전파되도록 합니다.
드롭인 솔루션을 사용하려면 UI 요소 샘플 GitHub 저장소로 이동하여 시차 도우미 JS를 가져오세요. GitHub 저장소에서 시차 스크롤러의 라이브 데모를 확인할 수 있습니다.
문제 패럴랙스
먼저 시차 효과를 구현하는 두 가지 일반적인 방법을 살펴보고 특히 이러한 방법이 우리의 목적에 적합하지 않은 이유를 알아보겠습니다.
나쁨: 스크롤 이벤트 사용
시차의 핵심 요구사항은 스크롤과 결합되어야 한다는 것입니다. 페이지의 스크롤 위치가 변경될 때마다 시차 요소의 위치가 업데이트되어야 합니다. 간단해 보이지만 최신 브라우저의 중요한 메커니즘은 비동기식으로 작동하는 기능입니다. 이 경우 스크롤 이벤트에 적용됩니다. 대부분의 브라우저에서 스크롤 이벤트는 '최선의 방법'으로 제공되며 스크롤 애니메이션의 모든 프레임에서 제공된다고 보장할 수 없습니다.
이 중요한 정보는 스크롤 이벤트를 기반으로 요소를 이동하는 JavaScript 기반 솔루션을 피해야 하는 이유를 알려줍니다. JavaScript는 시차가 페이지의 스크롤 위치와 동기화되도록 보장하지 않습니다. 이전 버전의 모바일 Safari에서는 스크롤 이벤트가 실제로 스크롤이 끝날 때 전달되어 JavaScript 기반 스크롤 효과를 만들 수 없었습니다. 최신 버전에서는 애니메이션 중에 스크롤 이벤트를 전송합니다. 하지만 Chrome과 마찬가지로 '최대한' 전송합니다. 기본 스레드가 다른 작업으로 인해 사용 중인 경우 스크롤 이벤트가 즉시 전달되지 않으므로 시차 효과가 손실됩니다.
잘못된 예: background-position
업데이트
또 다른 피해야 할 상황은 모든 프레임에서 페인팅하는 것입니다. 많은 솔루션에서 background-position
를 변경하여 시차 효과를 제공하려고 시도합니다. 이로 인해 브라우저가 스크롤 시 페이지의 영향을 받는 부분을 다시 칠하게 되며 애니메이션이 크게 끊길 수 있습니다.
시차 모션의 약속을 지키려면 가속 속성으로 적용할 수 있고 (현재는 변환 및 불투명도 유지) 스크롤 이벤트를 사용하지 않는 것이 필요합니다.
3D의 CSS
Scott Kellum과 Keith Clark 모두 CSS 3D를 사용하여 시차 모션을 구현하는 분야에서 상당한 작업을 수행했으며, 이들이 사용하는 기법은 다음과 같습니다.
overflow-y: scroll
(및 아마도overflow-x: hidden
)로 스크롤할 포함 요소를 설정합니다.- 동일한 요소에
perspective
값을 적용하고perspective-origin
을top left
또는0 0
로 설정합니다. - 해당 요소의 하위 요소에 Z축 변환을 적용하고 다시 확대하여 화면의 크기에 영향을 주지 않고 시차 모션을 제공합니다.
이 접근 방식의 CSS는 다음과 같습니다.
.container {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
perspective: 1px;
perspective-origin: 0 0;
}
.parallax-child {
transform-origin: 0 0;
transform: translateZ(-2px) scale(3);
}
다음과 같은 HTML 스니펫을 가정합니다.
<div class="container">
<div class="parallax-child"></div>
</div>
원근감의 크기 조정
하위 요소를 뒤로 밀면 원근법 값에 비례하여 작아집니다. (원근법 - 거리) / 원근법이라는 수식을 사용하여 확대해야 하는 정도를 계산할 수 있습니다. 시차 효과 요소가 시차 효과를 적용하면서도 작성한 크기로 표시되기를 원할 가능성이 높으므로 그대로 두는 대신 이와 같이 크기를 조정해야 합니다.
위 코드의 경우 원근법은 1px이고 parallax-child
의 Z 거리는 -2px입니다. 즉, 요소가 3배로 확대되어야 합니다. 이는 코드에 삽입된 값인 scale(3)
에서 확인할 수 있습니다.
translateZ
값이 적용되지 않은 콘텐츠의 경우 0 값을 대체할 수 있습니다. 즉, 스케일은 (원근법 - 0) / 원근법이며, 이는 1의 값을 나타내므로 위아래로 스케일링되지 않았음을 의미합니다. 정말 편리합니다.
이 접근 방식의 작동 방식
이러한 방식이 작동하는 이유를 명확히 이해하는 것이 중요합니다. 곧 이 지식을 사용할 예정이기 때문입니다. 스크롤은 사실상 변환이므로 가속화할 수 있습니다. 스크롤은 주로 GPU를 사용하여 레이어를 이동하는 것과 관련이 있습니다. 원근감이 없는 일반적인 스크롤에서는 스크롤 요소와 그 하위 요소를 비교할 때 스크롤이 1:1 방식으로 발생합니다.
요소를 300px
만큼 아래로 스크롤하면 하위 요소가 동일한 양인 300px
만큼 위로 변환됩니다.
하지만 스크롤 요소에 원근법 값을 적용하면 이 프로세스가 엉망이 됩니다. 스크롤 변환을 뒷받침하는 행렬이 변경되기 때문입니다.
이제 300px 스크롤은 선택한 perspective
및 translateZ
값에 따라 자녀를 150px만 이동할 수 있습니다. 요소의 translateZ
값이 0이면 이전과 같이 1:1로 스크롤되지만, 원근법 원점에서 Z 방향으로 밀려난 하위 요소는 다른 비율로 스크롤됩니다. 최종 결과: 시차 모션 또한 매우 중요한 점은 브라우저의 내부 스크롤 메커니즘의 일부로 자동으로 처리되므로 scroll
이벤트를 수신 대기하거나 background-position
를 변경할 필요가 없습니다.
문제: 모바일 Safari
모든 효과에는 주의사항이 있으며, 변환의 경우 하위 요소에 대한 3D 효과 보존에 관한 중요한 주의사항이 있습니다. 원근법이 있는 요소와 시차를 적용하는 하위 요소 사이에 계층 구조의 요소가 있으면 3D 원근법이 '평탄화'되어 효과가 사라집니다.
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
위 HTML에서 .parallax-container
는 새로 추가된 것으로, perspective
값을 효과적으로 평면화하여 시차 효과가 사라집니다. 대부분의 경우 솔루션은 매우 간단합니다. 요소에 transform-style: preserve-3d
를 추가하면 트리에서 더 위쪽에 적용된 3D 효과 (예: 원근감 값)가 전파됩니다.
.parallax-container {
transform-style: preserve-3d;
}
하지만 모바일 Safari의 경우 상황이 조금 더 복잡합니다.
컨테이너 요소에 overflow-y: scroll
를 적용하는 것은 기술적으로 작동하지만 스크롤 요소를 빠르게 스크롤할 수 없다는 단점이 있습니다. 해결책은 -webkit-overflow-scrolling: touch
를 추가하는 것이지만 perspective
도 평면화되므로 시차를 얻을 수 없습니다.
점진적 개선 관점에서 보면 큰 문제는 아닐 수 있습니다. 모든 상황에서 시차를 적용할 수 없는 경우에도 앱은 계속 작동하지만 해결 방법을 찾는 것이 좋습니다.
position: sticky
가 도와드립니다.
실제로 스크롤 중에 요소가 뷰포트 또는 지정된 상위 요소의 상단에 '고정'되도록 하는 position: sticky
의 형태로 도움이 있습니다. 대부분의 사양과 마찬가지로 이 사양도 상당히 방대하지만 유용한 작은 정보가 포함되어 있습니다.
처음에는 큰 의미가 없는 것처럼 보일 수 있지만 이 문장의 핵심은 요소의 고정성이 정확히 어떻게 계산되는지를 언급하는 부분입니다. '오프셋은 스크롤 상자가 있는 가장 가까운 상위 요소를 참조하여 계산됩니다.' 즉, 스티키 요소를 이동하는 거리(다른 요소나 뷰포트에 연결된 것처럼 표시되도록)는 다른 변환이 적용된 후가 아닌 전에 계산됩니다. 즉, 앞서 나온 스크롤 예시와 마찬가지로 오프셋이 300px로 계산된 경우 관점 (또는 다른 변환)을 사용하여 고정 요소에 적용되기 전에 300px 오프셋 값을 조작할 수 있습니다.
시차 요소에 position: -webkit-sticky
를 적용하면 -webkit-overflow-scrolling:
touch
의 평탄화 효과를 효과적으로 '반전'할 수 있습니다. 이렇게 하면 시차 요소가 스크롤 상자가 있는 가장 가까운 상위 요소를 참조합니다. 이 경우 .container
입니다. 그런 다음 이전과 마찬가지로 .parallax-container
가 perspective
값을 적용하여 계산된 스크롤 오프셋을 변경하고 시차 효과를 만듭니다.
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
.container {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.parallax-container {
perspective: 1px;
}
.parallax-child {
position: -webkit-sticky;
top: 0px;
transform: translate(-2px) scale(3);
}
이렇게 하면 모바일 Safari의 시차 효과가 복원되므로 전반적으로 좋은 소식입니다.
고정 위치 주의사항
하지만 여기에는 차이점이 있습니다. position: sticky
는 시차 메커니즘을 변경합니다. 고정 위치는 요소를 스크롤 컨테이너에 고정하려고 시도하는 반면, 고정되지 않은 버전은 그렇지 않습니다. 즉, 스티키가 있는 시차는 스티키가 없는 시차의 반대가 됩니다.
position: sticky
를 사용하면 요소가 z=0에 가까울수록 움직임이 적어집니다.position: sticky
이 없으면 요소가 z=0에 가까울수록 더 많이 이동합니다.
이 모든 것이 다소 추상적으로 느껴진다면 Robert Flack의 이 데모를 살펴보세요. 스티키 위치 지정이 있는 경우와 없는 경우 요소가 다르게 작동하는 방식을 보여줍니다. 차이점을 확인하려면 Chrome Canary (작성 시점 버전 56) 또는 Safari가 필요합니다.

position: sticky
가 시차 스크롤에 미치는 영향을 보여주는 로버트 플랙의 데모
다양한 버그 및 해결 방법
하지만 다른 모든 것과 마찬가지로 아직 해결해야 할 문제가 있습니다.
- 스티커 지원이 일관되지 않습니다. Chrome에서는 아직 지원이 구현되고 있으며, Edge는 완전히 지원되지 않고, Firefox에는 sticky가 원근법 변환과 결합될 때 페인팅 버그가 있습니다. 이러한 경우 모바일 Safari에만 필요한
position: sticky
(-webkit-
접두사가 붙은 버전)를 필요한 경우에만 추가하는 코드를 추가하는 것이 좋습니다. - Edge에서는 효과가 '그냥 작동'하지 않습니다. Edge는 OS 수준에서 스크롤을 처리하려고 합니다. 이는 일반적으로 좋은 일이지만 이 경우 스크롤 중에 원근법 변경을 감지하지 못합니다. 이 문제를 해결하려면 고정 위치 요소를 추가하세요. 이렇게 하면 Edge가 비OS 스크롤 방법으로 전환되고 원근법 변경이 고려됩니다.
- '페이지 콘텐츠가 엄청나게 많아졌어요!' 많은 브라우저에서 페이지 콘텐츠의 크기를 결정할 때 크기를 고려하지만, 안타깝게도 Chrome과 Safari에서는 원근법을 고려하지 않습니다. 따라서 요소에 3배의 크기가 적용된 경우
perspective
이 적용된 후 요소가 1배가 되더라도 스크롤 막대 등이 표시될 수 있습니다.transform-origin: bottom right
를 사용하여 오른쪽 하단에서 요소를 확장하면 이 문제를 해결할 수 있습니다. 이렇게 하면 크기가 너무 큰 요소가 스크롤 가능한 영역의 '음수 영역'(일반적으로 왼쪽 상단)으로 확장되기 때문입니다. 스크롤 가능한 영역에서는 음수 영역의 콘텐츠를 보거나 스크롤할 수 없습니다.
결론
시차 효과는 신중하게 사용하면 재미있는 효과를 낼 수 있습니다. 성능이 우수하고 스크롤 결합이 가능하며 크로스 브라우저인 방식으로 구현할 수 있습니다. 원하는 효과를 얻으려면 약간의 수학적 조작과 소량의 상용구가 필요하므로 작은 도우미 라이브러리와 샘플을 래핑했습니다. 이는 UI 요소 샘플 GitHub 저장소에서 확인할 수 있습니다.
직접 사용해 보고 어땠는지 알려주세요.