고성능 시차

Robert Flack
Robert Flack

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

시차 그림

요약

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

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

문제 Parallaxers

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

나쁨: 스크롤 이벤트 사용

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

이 중요한 정보는 스크롤 이벤트를 기반으로 요소를 이동하는 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입니다. 즉, 요소를 3배 확대해야 합니다. 이는 코드에 삽입된 값인 scale(3)에서 확인할 수 있습니다.

translateZ 값이 적용되지 않은 콘텐츠의 경우 0 값을 대체할 수 있습니다. 즉, 크기는 (perspective - 0) / perspective이며, 이는 1이라는 값으로 정리됩니다. 즉, 크기가 확대 또는 축소되지 않았음을 의미합니다. 정말 유용합니다.

이 접근 방식의 작동 방식

이 지식을 곧 사용할 예정이므로 이 방법이 작동하는 이유를 명확하게 이해하는 것이 중요합니다. 스크롤은 사실상 변환이므로 가속할 수 있습니다. 대부분 GPU를 사용하여 레이어를 이동하는 작업이 포함됩니다. 원근 개념이 없는 일반적인 스크롤에서는 스크롤 요소와 하위 요소를 비교할 때 1:1 방식으로 스크롤이 발생합니다. 요소를 300px만큼 아래로 스크롤하면 하위 요소가 동일한 크기(300px)만큼 위로 변환됩니다.

그러나 스크롤 요소에 관점 값을 적용하면 이 프로세스가 엉망이 되어 스크롤 변환을 뒷받침하는 행렬이 변경됩니다. 이제 300px의 스크롤은 선택한 perspectivetranslateZ 값에 따라 하위 요소를 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 오프셋 값을 조작할 수 있는 새로운 기회가 있습니다.

Parallaxing 요소에 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);
}

이렇게 하면 Mobile Safari의 시차 효과가 복원되므로 좋은 소식입니다.

고정된 게재위치 관련 주의사항

하지만 차이점이 있습니다. position: sticky는 시차 메커니즘을 변경합니다. 고정된 위치 지정은 요소를 스크롤 컨테이너에 고정하려고 시도하는 반면 고정되지 않은 버전은 그렇지 않습니다. 즉, 고정된 시차는 고정되지 않은 시차의 역수가 됩니다.

  • position: sticky사용하면 요소가 이동하는 더 적은 z=0에 가까워집니다.
  • position: sticky없으면 요소가 z=0에 가까울수록 더 많이 이동합니다.

다소 추상적으로 느껴진다면 로버트 플랙의 이 데모를 살펴보세요. 이 데모에서는 고정된 위치 지정 유무에 따라 요소가 다르게 작동하는 방식을 보여줍니다. 차이점을 확인하려면 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 저장소에서 확인할 수 있습니다.

사용해 보고 의견을 알려주세요.