고성능 시차

폴 루이스
로버트 플랙
로버트 플랙

좋아하든 싫어하든, 시차는 계속 사용할 수 있습니다. 현명하게 사용하면 웹 앱에 깊이와 미묘함을 더할 수 있습니다. 그러나 문제는 성능 기준에 맞는 시차를 구현하기가 어려울 수 있다는 것입니다. 이 문서에서는 성능이 우수하고 무엇보다도 여러 브라우저에서 작동하는 솔루션을 설명합니다.

시차 삽화

요약

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

삽입형 솔루션이 필요하면 UI 요소 샘플 GitHub 저장소로 이동하여 시차 도우미 JS를 사용하세요. GitHub 저장소에서 시차 스크롤러의 실시간 데모를 볼 수 있습니다.

문제 시차

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

나쁜 예: 스크롤 이벤트 사용

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

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

좋지 않음: background-position 업데이트 중

피해야 할 또 다른 상황은 모든 프레임에 칠하는 것입니다. 많은 솔루션에서 시차 모양을 제공하기 위해 background-position를 변경하려고 합니다. 이렇게 하면 브라우저에서 스크롤 시 페이지의 영향을 받은 부분을 다시 그려야 하며, 이로 인해 애니메이션 버벅거림이 상당히 발생할 수 있습니다.

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

3D CSS

Scott KellumKeith Clark는 모두 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-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에 가까울수록 더 많이 이동합니다.

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

시차 원근 스크린샷

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

여러 가지 버그 및 해결 방법

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

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

결론

시차는 신중하게 사용할 때 재미있는 효과입니다. 보시다시피 성능이 뛰어나고, 스크롤과 결합되며, 교차 브라우저인 방식으로 구현할 수 있습니다. 원하는 효과를 얻으려면 수학적 디글링이 약간 필요하고 상용구는 약간 필요하므로 UI 요소 샘플 GitHub 저장소에서 찾을 수 있는 작은 도우미 라이브러리와 샘플을 래핑했습니다.

플레이를 즐기시고 어떻게 발전했는지 알려주세요.