고성능 시차

Robert Flack
Robert Flack

좋든 싫든 시차는 이제 생겨날 겁니다. 적절히 사용하면 웹 앱에 깊이와 섬세함을 더할 수 있습니다. 그러나 문제는 시차를 성능 기준에 맞는 방식으로 구현하는 것이 어려울 수 있다는 것입니다. 이 문서에서는 성능이 우수하고 무엇보다도 브라우저 간에 작동하는 솔루션에 대해 설명합니다.

시차 삽화

요약

  • 시차 애니메이션을 만드는 데 스크롤 이벤트 또는 background-position를 사용하지 마세요.
  • CSS 3D 변환을 사용하여 더 정확한 시차 효과를 만들 수 있습니다.
  • 모바일 Safari의 경우 position: sticky를 사용하여 시차 효과가 전파되도록 합니다.

삽입형 솔루션이 필요하면 UI 요소 샘플 GitHub 저장소로 이동하여 Parallax 도우미 JS를 가져옵니다. GitHub 저장소에서 시차 스크롤러의 라이브 데모를 확인할 수 있습니다.

문제 시차

먼저 시차 효과를 얻는 두 가지 일반적인 방법과 특히 이 방법이 목적에 적합하지 않은 이유를 살펴보겠습니다.

나쁨: 스크롤 이벤트 사용

시차의 주요 요구사항은 스크롤이 결합되어야 한다는 것입니다. 페이지의 스크롤 위치가 변경될 때마다 시차 요소의 위치가 업데이트되어야 합니다. 간단해 보이지만 최신 브라우저의 중요한 메커니즘은 비동기식으로 작동하는 기능입니다. 이는 특히 스크롤 이벤트에 적용됩니다. 대부분의 브라우저에서 스크롤 이벤트는 '최선'으로 전달되며 스크롤 애니메이션의 모든 프레임에서 전달되지 않을 수 있습니다.

이 중요한 정보를 통해 스크롤 이벤트를 기반으로 요소를 이동하는 JavaScript 기반 솔루션을 피해야 하는 이유를 알 수 있습니다. JavaScript는 시차가 페이지의 스크롤 위치와 일치하도록 보장하지 않습니다. 이전 버전의 모바일 Safari에서는 스크롤 이벤트가 실제로 스크롤이 끝날 때 전달되어 JavaScript 기반 스크롤 효과를 만들 수 없었습니다. 최신 버전에서는 애니메이션 중에 스크롤 이벤트를 제공하지만 Chrome과 마찬가지로 '최선의 방식'으로 작동합니다. 기본 스레드가 다른 작업으로 사용 중인 경우 스크롤 이벤트가 즉시 전달되지 않으므로 시차 효과가 손실됩니다.

불량: background-position 업데이트 중

피해야 할 또 다른 상황은 모든 프레임에 그림을 그리는 것입니다. 많은 솔루션에서 시차 효과를 제공하기 위해 background-position를 변경하려고 합니다. 그러면 브라우저가 스크롤 시 페이지의 영향을 받는 부분을 다시 페인트하며 애니메이션에 상당한 버벅거림을 유발할 정도로 비용이 많이 들 수 있습니다.

시차 모션의 가능성을 실현하려면 가속 속성 (현재는 변환 및 불투명도를 고수한다는 의미)으로 적용할 수 있고 스크롤 이벤트에 의존하지 않는 속성을 원합니다.

3D CSS

스콧 켈럼키스 클라크는 모두 CSS 3D를 사용하여 시차 모션을 달성하는 영역에서 상당한 작업을 수행했으며, 이들이 사용하는 기법은 실질적으로 다음과 같습니다.

  • overflow-y: scroll (overflow-x: hidden 가능)로 스크롤할 포함 요소를 설정합니다.
  • 동일한 요소에 perspective 값을 적용하고 top left 또는 0 0로 설정된 perspective-origin을 적용합니다.
  • 이 요소의 하위 요소에 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입니다. 즉, 요소를 3x로 확장해야 합니다. scale(3) 코드에 연결된 값을 확인할 수 있습니다.

translateZ 값이 적용되지 않은 콘텐츠는 값을 0으로 대체할 수 있습니다. 즉, 척도가 (perspective - 0) / perspective 값이며 값이 1로 확대되어 축소되지 않았습니다. 정말 편리합니다.

이 접근 방식의 작동 방식

이 방법이 작동하는 이유를 명확히 하는 것이 중요합니다. 이 지식을 곧 사용할 것입니다. 스크롤은 사실상 변환이므로 가속화할 수 있습니다. 스크롤은 주로 GPU와 함께 레이어를 이동하는 것입니다. 원근감이 없는 일반적인 스크롤에서는 스크롤 요소와 그 하위 요소를 비교할 때 1:1 방식으로 스크롤이 발생합니다. 요소를 300px까지 아래로 스크롤하면 하위 요소가 동일한 양(300px)만큼 위로 변환됩니다.

그러나 스크롤 요소에 관점 값을 적용하면 이 프로세스가 엉망이 되어 스크롤 변환을 뒷받침하는 행렬이 변경됩니다. 이제 300px의 스크롤은 선택한 perspectivetranslateZ 값에 따라 하위 요소를 150px씩만 이동할 수 있습니다. 요소의 translateZ 값이 0이면 이전처럼 1:1로 스크롤되지만 원점 원점에서 Z 멀리 밀린 하위 요소는 다른 속도로 스크롤됩니다. 실제 결과: 시차 운동 또한 매우 중요한 점은 이 작업이 브라우저 내부 스크롤 시스템의 일부로 자동 처리되므로 scroll 이벤트를 수신 대기하거나 background-position를 변경할 필요가 없다는 것입니다.

연고를 타는 파리: 모바일 사파리

모든 효과에는 주의사항이 있으며 변환에서 중요한 한 가지는 하위 요소에 대한 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-containerperspective 값을 적용하여 계산된 스크롤 오프셋을 변경하고 시차 효과를 생성합니다.

<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에 가깝고 더 많이 이동합니다.

이 모든 것이 추상적이라고 생각되면 로버트 플랙의 이 데모를 살펴보세요. 고정 위치 지정을 사용할 때와 사용하지 않을 때 요소가 어떻게 다르게 작동하는지를 보여줍니다. 차이점을 확인하려면 Chrome Canary (이 문서 작성 당시 버전 56) 또는 Safari가 필요합니다.

패럴랙스 원근 스크린샷

position: sticky가 시차 스크롤에 미치는 영향을 보여주는 로버트 플랙의 데모

다양한 버그 및 해결 방법

하지만 다른 모든 것과 마찬가지로 부드럽게 다듬어야 하는 덩어리와 돌출부가 여전히 있습니다.

  • 고정 지원이 일관되지 않습니다. Chrome에서 지원이 아직 구현되고 있으며 Edge는 완전히 지원되지 않으며 Firefox는 스티키가 원근 변환과 결합될 때 페인팅 버그가 있습니다. 이 경우 필요할 때(모바일 Safari 전용) position: sticky(-webkit- 접두사 버전)만 추가하도록 작은 코드를 추가하는 것이 좋습니다.
  • Edge에서 효과가 '제대로 작동하지' 않습니다. Edge는 OS 수준에서 스크롤을 처리하려고 시도하지만, 이 경우 일반적으로 바람직하지만, 이 경우에는 스크롤 중에 시점 변경을 감지하지 못합니다. 이 문제를 해결하려면 고정 위치 요소를 추가하면 됩니다. 이는 Edge를 비 OS 스크롤 방법으로 전환하는 것처럼 보이기 때문에 원근 변경사항을 고려하도록 할 수 있습니다.
  • '페이지 콘텐츠가 엄청나네요!' 많은 브라우저에서 페이지 콘텐츠의 크기를 결정할 때 크기를 고려하지만 Chrome과 Safari에서는 원시를 고려하지 않습니다. 따라서 요소에 3배 배율이 적용된 경우 perspective이 적용된 후 요소가 1배에 있더라도 스크롤바 등이 잘 표시될 수 있습니다. 오른쪽 하단 모서리에서 요소의 크기를 조정 (transform-origin: bottom right 사용)하여 이 문제를 해결할 수 있습니다. 이렇게 하면 크기가 큰 요소가 스크롤 가능한 영역의 '음수 영역' (일반적으로 왼쪽 상단)으로 커지기 때문입니다. 스크롤 가능한 영역에서는 제외 영역의 콘텐츠를 보거나 스크롤할 수 없습니다.

결론

시차는 신중하게 사용하면 재미있는 효과입니다. 보시다시피, 성능 기준에 맞고 스크롤이 결합되고 브라우저 간을 가로지르는 방식으로 구현할 수 있습니다. 원하는 효과를 얻으려면 약간의 수학적 꿈과 약간의 상용구가 필요하므로 UI 요소 샘플 GitHub 저장소에서 찾을 수 있는 작은 도우미 라이브러리와 샘플을 만들었습니다.

플레이해 보시고 어떻게 지내셨는지 알려주세요.