소개
최근 시차 효과 사이트가 인기를 끌고 있습니다. 다음을 살펴보세요.
스크롤할 때 페이지의 시각적 구조가 변경되는 사이트를 말합니다. 일반적으로 페이지 내의 요소는 페이지의 스크롤 위치에 비례하여 크기를 조절하거나 회전하거나 이동합니다.
패럴랙스 사이트를 좋아하든 싫어하든 성능의 블랙홀이라는 점은 확실히 말할 수 있습니다. 이는 브라우저가 스크롤할 때 화면 상단 또는 하단에 새 콘텐츠가 표시되는 경우에 최적화되는 경향이 있고, 일반적으로 브라우저는 스크롤 중에 시각적으로 거의 변경되지 않을 때 가장 잘 작동하기 때문입니다. 그러나 시차 사이트의 경우 페이지 전체의 대형 시각적 요소가 자주 변경되어 브라우저에서 전체 페이지를 다시 칠하기 때문에 드물게 발생합니다.
다음과 같이 시차 사이트를 일반화하는 것이 좋습니다.
- 위아래로 스크롤할 때 위치, 회전, 크기가 변경되는 배경 요소입니다.
- 텍스트나 작은 이미지와 같이 일반적으로 위에서 아래로 스크롤되는 페이지 콘텐츠
이전에 스크롤 성능과 앱의 응답성을 개선하는 방법을 다루었으며, 이 도움말은 그 기반을 바탕으로 작성되었으므로 아직 읽지 않은 경우 읽어 보시기 바랍니다.
따라서 질문은 패럴랙스 스크롤 사이트를 빌드할 때 비용이 많이 드는 다시 칠하기를 사용해야 하는지 아니면 성능을 극대화하기 위해 취할 수 있는 다른 접근 방식이 있는지입니다. 옵션을 살펴보겠습니다.
옵션 1: DOM 요소 및 절대 위치 사용
이는 대부분의 사용자가 취하는 기본적인 접근 방식인 것으로 보입니다. 페이지 내에 여러 요소가 있으며 스크롤 이벤트가 실행될 때마다 여러 시각적 업데이트가 실행되어 요소를 변환합니다.
프레임 모드에서 DevTools 타임라인을 시작하고 스크롤하면 비용이 많이 드는 전체 화면 페인트 작업이 있음을 알 수 있으며, 스크롤을 많이 하면 단일 프레임 내에 여러 스크롤 이벤트가 표시될 수 있으며, 각각의 스크롤 이벤트는 레이아웃 작업을 트리거합니다.
중요한 점은 60fps (일반적인 모니터 새로고침 빈도인 60Hz에 맞춤)를 달성하려면 모든 작업을 완료하는 데 16ms가 조금 넘게 걸린다는 것입니다. 이 첫 번째 버전에서는 스크롤 이벤트가 발생할 때마다 시각적 업데이트를 실행하지만, requestAnimationFrame을 사용한 더 간결하고 강력한 애니메이션 및 스크롤 성능에 관한 이전 도움말에서 설명한 것처럼 브라우저의 업데이트 일정과 일치하지 않으므로 프레임을 놓치거나 각 프레임 내에서 너무 많은 작업을 실행하게 됩니다. 이로 인해 사이트가 불안정하고 부자연스러워져 사용자가 실망하고 고양이가 불만족하게 될 수 있습니다.
업데이트 코드를 스크롤 이벤트에서 requestAnimationFrame
콜백으로 이동하고 스크롤 이벤트의 콜백에서 스크롤 값을 캡처해 보겠습니다.
스크롤 테스트를 반복하면 약간의 개선이 있을 수 있습니다. 스크롤하여 트리거하는 레이아웃 작업은 그다지 비용이 많이 들지 않지만 다른 사용 사례에서는 비용이 많이 들 수 있기 때문입니다. 이제 각 프레임에서 하나의 레이아웃 작업만 실행합니다.
이제 프레임당 1~100개의 스크롤 이벤트를 처리할 수 있지만 중요한 점은 requestAnimationFrame
콜백이 실행되고 시각적 업데이트를 실행할 때마다 사용할 가장 최근 값만 저장한다는 것입니다. 요점은 스크롤 이벤트를 수신할 때마다 시각적 업데이트를 강제하려고 시도하는 대신 브라우저가 업데이트를 실행할 적절한 창을 제공하도록 요청한다는 것입니다. 얼마나 다정한가요?
이 접근 방식의 주된 문제는 requestAnimationFrame
이든 아니든 간에 페이지 전체에 하나의 레이어가 있고 이러한 시각적 요소를 움직이면 대규모의 비용이 많이 드는 다시 페인트가 필요하다는 것입니다. 일반적으로 페인팅은 차단 작업입니다 (변경됨). 즉, 브라우저는 다른 작업을 실행할 수 없으며 프레임의 예산인 16ms를 훨씬 초과하여 실행되는 경우가 많고 문제가 계속 발생합니다.
옵션 2: DOM 요소 및 3D 변환 사용
절대 위치를 사용하는 대신 요소에 3D 변환을 적용하는 방법을 사용할 수도 있습니다. 이 경우 3D 변환이 적용된 요소에 요소당 새 레이어가 할당되며 WebKit 브라우저에서는 하드웨어 컴포저로 전환되는 경우도 많습니다. 반면 옵션 1에서는 페이지에 하나의 큰 레이어가 있었으며, 레이어는 변경사항이 있을 때마다 다시 페인트되어야 했고 모든 페인팅과 합성은 CPU에서 처리되었습니다.
즉, 이 옵션을 사용하면 상황이 달라집니다. 3D 변환을 적용하는 모든 요소에 하나의 레이어가 있을 수 있습니다. 이 시점부터 요소에 더 많은 변환만 수행하면 레이어를 다시 칠할 필요가 없으며 GPU가 요소를 이동하고 최종 페이지를 함께 합성할 수 있습니다.
많은 경우 -webkit-transform: translateZ(0);
해킹을 사용하면 마술처럼 성능이 개선되는 것을 확인할 수 있습니다. 현재는 이 방법이 작동하지만 다음과 같은 문제가 있습니다.
- 교차 브라우저 호환이 되지 않습니다.
- 변환된 각 요소에 새 레이어를 만들어 브라우저를 강제합니다. 레이어가 많으면 다른 성능 병목 현상이 발생할 수 있으므로 가급적 사용하지 마세요.
- 일부 WebKit 포트에서 사용 중지되었습니다 (아래에서 네 번째 글머리기호).
3D 변환 경로를 선택하는 경우 주의하세요. 이는 문제의 임시 해결 방법일 뿐입니다. 이상적으로는 3D에서와 마찬가지로 2D 변환에서 유사한 렌더링 특성을 확인할 수 있습니다. 브라우저가 놀라운 속도로 발전하고 있으므로 그 전에 이러한 기능이 제공되기를 바랍니다.
마지막으로 가급적 페인트를 피하고 페이지에서 기존 요소를 옮기는 것이 좋습니다. 예를 들어 시차 사이트에서는 고정된 높이의 div를 사용하고 배경 위치를 변경하여 효과를 제공하는 것이 일반적입니다. 하지만 이렇게 하면 매 패스마다 요소를 다시 칠해야 하므로 성능이 저하될 수 있습니다. 대신 가능하면 요소를 만들고 (필요한 경우 overflow: hidden
를 사용하여 div 내에 래핑) 대신 단순히 번역해야 합니다.
옵션 3: 고정된 위치 캔버스 또는 WebGL 사용
마지막으로 고려할 옵션은 변환된 이미지를 그릴 페이지 뒤쪽에 고정된 위치 캔버스를 사용하는 것입니다. 언뜻 보면 가장 성능이 우수한 솔루션처럼 보이지 않을 수 있지만, 이 접근 방식에는 몇 가지 이점이 있습니다.
- 요소가 하나인 캔버스만 있으므로 더 이상 컴포지터 작업이 많이 필요하지 않습니다.
- 단일 하드웨어 가속 비트맵을 효과적으로 처리합니다.
- Canvas2D API는 실행하려는 변환 유형에 매우 적합하므로 개발 및 유지관리가 더 쉬워집니다.
캔버스 요소를 사용하면 새 레이어가 생성되지만 하나의 레이어만 생성됩니다. 반면 옵션 2에서는 3D 변환이 적용된 모든 요소에 새 레이어가 실제로 생성되므로 이러한 모든 레이어를 함께 컴포지팅하는 워크로드가 증가합니다. 또한 변환의 브라우저 간 구현이 다르다는 점을 고려할 때 현재 가장 호환성이 뛰어난 솔루션입니다.
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page's scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
이 접근 방식은 대용량 이미지 (또는 캔버스에 쉽게 쓸 수 있는 다른 요소)를 처리하는 경우에 적합하며, 대용량 텍스트 블록을 처리하는 것은 더 어려울 수 있지만 사이트에 따라 가장 적절한 해결책일 수 있습니다. 캔버스의 텍스트를 처리해야 하는 경우 fillText
API 메서드를 사용해야 하지만 접근성이 저하됩니다 (텍스트를 비트맵으로 래스터화한 것임). 이제 줄 바꿈과 기타 여러 문제를 처리해야 합니다. 피할 수 있다면 피하는 것이 좋으며 위의 변환 접근 방식을 사용하는 것이 더 나을 수 있습니다.
최대한 멀리 이동할 수 있으므로, 시차 작업이 캔버스 요소 내에서 이루어져야 한다고 가정할 이유가 없습니다. 브라우저에서 지원하는 경우 WebGL을 사용할 수 있습니다. 여기서 중요한 점은 WebGL이 그래픽 카드에 연결되는 모든 API 중 가장 직접적인 경로를 가지고 있으므로, 특히 사이트의 효과가 복잡한 경우 60fps를 달성할 가능성이 가장 높다는 것입니다.
WebGL이 과도하거나 지원 범위가 광범위하지 않다고 생각할 수 있지만 Three.js와 같은 도구를 사용하면 언제든지 캔버스 요소를 사용하도록 대체할 수 있으며 코드는 일관되고 친근한 방식으로 추상화됩니다. Modernizr를 사용하여 적절한 API 지원을 확인하기만 하면 됩니다.
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
이 접근 방식에 관한 마지막 생각으로, 페이지에 요소를 추가하는 것을 좋아하지 않는다면 언제든지 Firefox와 WebKit 기반 브라우저에서 캔버스를 배경 요소로 사용할 수 있습니다. 물론 이러한 상황이 항상 발생하는 것은 아니므로 평소와 같이 주의해서 처리해야 합니다.
원하는 대로 설정 가능
개발자가 다른 옵션이 아닌 절대적으로 배치된 요소를 기본값으로 사용하는 주된 이유는 지원의 보편성 때문일 수 있습니다. 이는 타겟팅되는 이전 브라우저가 매우 열악한 렌더링 환경을 제공할 가능성이 높기 때문에 어느 정도는 착시일 수 있습니다. 오늘날의 최신 브라우저에서도 절대 위치 지정된 요소를 사용한다고 해서 반드시 성능이 향상되는 것은 아닙니다.
특히 3D 변환은 DOM 요소와 직접 작업하고 안정적인 프레임 속도를 달성할 수 있는 기능을 제공합니다. 여기서 중요한 점은 가능한 한 페인팅을 피하고 요소를 움직여 보는 것입니다. WebKit 브라우저가 레이어를 만드는 방식이 다른 브라우저 엔진과 반드시 상관관계가 있는 것은 아니므로 이 솔루션을 사용하기 전에 테스트해야 합니다.
최상위 브라우저만 타겟팅하고 캔버스를 사용하여 사이트를 렌더링할 수 있다면 이 방법이 가장 적합할 수 있습니다. 물론 Three.js를 사용하는 경우 필요한 지원에 따라 렌더러 간에 매우 쉽게 전환하고 변경할 수 있습니다.
결론
절대 위치 요소에서 고정된 위치 캔버스 사용에 이르기까지 시차 사이트를 처리하는 몇 가지 접근 방식을 평가했습니다. 구현 방법은 물론 달성하려는 목표와 사용 중인 구체적인 디자인에 따라 달라지지만, 항상 다양한 옵션이 있다는 것을 알고 있으면 좋습니다.
어떤 접근 방식을 사용하든 항상 추측하지 말고 테스트하세요.