블러 애니메이션

흐리게 처리는 사용자의 시선을 리디렉션하는 좋은 방법입니다. 일부 시각적 요소를 흐리게 표시하고 다른 요소를 선명하게 유지하면 사용자의 시선이 자연스럽게 유도됩니다. 사용자는 흐리게 처리된 콘텐츠를 무시하고 대신 읽을 수 있는 콘텐츠에 집중합니다. 한 가지 예는 마우스를 가져가면 개별 항목에 관한 세부정보를 표시하는 아이콘 목록입니다. 이때 나머지 선택사항은 사용자를 새로 표시된 정보로 리디렉션하기 위해 흐리게 처리될 수 있습니다.

요약

흐림 효과를 애니메이션으로 만드는 것은 매우 느리므로 적합하지 않습니다. 대신 점점 더 흐려지는 일련의 버전을 미리 계산하고 버전 간에 크로스페이드합니다. 동료인 Yi Gu가 모든 것을 처리하는 라이브러리를 작성했습니다. 데모를 살펴보세요.

하지만 전환 기간 없이 적용하면 이 기술이 다소 갑작스러울 수 있습니다. 흐림 효과를 애니메이션으로 처리하는 것(흐림 효과가 없는 상태에서 흐림 효과가 있는 상태로 전환)은 합리적인 선택인 것 같지만 웹에서 이를 시도해 본 적이 있다면 강력한 머신이 없는 경우 이 데모에서 볼 수 있듯이 애니메이션이 부드럽지 않다는 것을 알 수 있습니다. 더 개선할 수 있을까요?

문제

마크업은 CPU에 의해 텍스처로 변환됩니다. 텍스처가 GPU에 업로드됩니다. GPU는 셰이더를 사용하여 이러한 텍스처를 프레임 버퍼에 그립니다. 흐리게 처리는 셰이더에서 발생합니다.

현재로서는 흐림 효과 애니메이션을 효율적으로 작동하도록 할 수 없습니다. 하지만 기술적으로는 애니메이션 흐림이 아니지만 충분히 괜찮아 보이는 해결 방법을 찾을 수 있습니다. 시작하려면 먼저 애니메이션 흐림 효과가 느린 이유를 이해해야 합니다. 웹에서 요소를 흐리게 처리하는 방법에는 CSS filter 속성과 SVG 필터의 두 가지가 있습니다. 지원 증가와 사용 편의성 덕분에 CSS 필터가 일반적으로 사용됩니다. 안타깝게도 Internet Explorer를 지원해야 하는 경우 IE 10과 11에서는 CSS 필터가 아닌 SVG 필터를 지원하므로 SVG 필터를 사용할 수밖에 없습니다. 다행히 흐림 효과를 애니메이션으로 만드는 해결 방법은 두 기법 모두에서 작동합니다. DevTools를 살펴 병목 현상을 찾아보겠습니다.

DevTools에서 '페인트 깜박임'을 사용 설정하면 깜박임이 전혀 표시되지 않습니다. 리페인트가 발생하지 않는 것 같습니다. '다시 그리기'는 승격된 요소의 텍스처를 CPU가 다시 그려야 함을 의미하므로 이는 기술적으로 올바릅니다. 요소가 홍보되고 흐리게 처리될 때마다 셰이더를 사용하여 GPU에서 흐리게 처리됩니다.

SVG 필터와 CSS 필터는 모두 컨볼루션 필터를 사용하여 흐림 효과를 적용합니다. 컨볼루션 필터는 모든 출력 픽셀에 대해 여러 입력 픽셀을 고려해야 하므로 비용이 상당히 많이 듭니다. 이미지가 클수록 또는 흐림 반경이 클수록 효과의 비용이 더 많이 듭니다.

여기에 문제가 있습니다. 프레임마다 다소 비용이 많이 드는 GPU 작업을 실행하여 16ms 프레임 예산을 초과하므로 60fps를 훨씬 밑돌게 됩니다.

이상한 나라로

이러한 문제를 방지하려면 어떻게 해야 할까요? 마술을 사용할 수 있습니다. 실제 흐림 값 (흐림의 반지름)을 애니메이션으로 처리하는 대신 흐림 값이 지수적으로 증가하는 흐린 복사본을 몇 개 미리 계산한 다음 opacity를 사용하여 복사본 간에 크로스페이드합니다.

크로스페이드는 불투명도 페이드 인과 페이드 아웃이 겹치는 일련의 효과입니다. 예를 들어 흐림 처리 단계가 4개인 경우 첫 번째 단계를 페이드 아웃하는 동시에 두 번째 단계를 페이드 인합니다. 두 번째 단계가 불투명도 100% 에 도달하고 첫 번째 단계가 0%에 도달하면 세 번째 단계를 페이드인하는 동안 두 번째 단계를 페이드아웃합니다. 이 작업이 완료되면 마지막으로 세 번째 단계를 페이드 아웃하고 네 번째이자 마지막 버전을 페이드 인합니다. 이 시나리오에서는 각 단계가 원하는 총 시간의 1/4을 차지합니다. 시각적으로는 실제 애니메이션 흐림 효과와 매우 유사합니다.

실험 결과 단계별로 흐림 반경을 지수적으로 늘리면 시각적 결과가 가장 좋은 것으로 나타났습니다. 예: 흐림 처리 단계가 4개인 경우 각 단계에 filter: blur(2^n)를 적용합니다. 즉, 0단계: 1px, 1단계: 2px, 2단계: 4px, 3단계: 8px입니다. will-change: transform를 사용하여 흐리게 처리된 각 사본을 자체 레이어('승격'이라고 함)로 강제하면 이러한 요소의 불투명도를 매우 빠르게 변경할 수 있습니다. 이론적으로는 이를 통해 흐리게 처리하는 데 드는 비용이 많이 드는 작업을 미리 로드할 수 있습니다. 하지만 이 논리에는 결함이 있습니다. 이 데모를 실행하면 프레임 속도가 여전히 60fps 미만이고 흐림 현상이 이전보다 심해짐을 알 수 있습니다.

GPU의 사용 시간이 긴 트레이스를 보여주는 DevTools

DevTools를 살펴보면 GPU가 여전히 매우 바쁘고 각 프레임을 ~90ms까지 늘리는 것을 알 수 있습니다. 이유가 무엇인가요? 더 이상 흐림 값을 변경하지 않고 불투명도만 변경하고 있는데 어떻게 된 일인가요? 문제는 다시 한번 흐림 효과의 특성에 있습니다. 앞에서 설명한 것처럼 요소가 승격되고 흐려지면 효과가 GPU에 의해 적용됩니다. 따라서 더 이상 흐림 값을 애니메이션으로 만들지 않더라도 텍스처 자체는 여전히 흐려지지 않으며 GPU에서 프레임마다 다시 흐려야 합니다. 프레임 속도가 이전보다 더 나빠진 이유는 순진한 구현과 비교할 때 GPU가 실제로 이전보다 더 많은 작업을 하기 때문입니다. 대부분의 경우 독립적으로 흐리게 처리해야 하는 텍스처가 두 개 표시되기 때문입니다.

결과가 예쁘지는 않지만 애니메이션이 매우 빨라집니다. 흐리게 처리할 요소를 홍보하지 않고 대신 상위 래퍼를 홍보합니다. 요소가 흐리게 처리되고 강조 표시된 경우 효과는 GPU에 의해 적용됩니다. 이로 인해 데모가 느려졌습니다. 요소가 흐리지만 승격되지 않은 경우 흐림은 가장 가까운 상위 텍스처로 래스터화됩니다. 이 경우 프로모션된 상위 래퍼 요소입니다. 이제 흐리게 처리된 이미지가 상위 요소의 텍스처가 되며 향후 모든 프레임에 다시 사용할 수 있습니다. 흐리게 처리된 요소가 애니메이션으로 처리되지 않고 캐싱하는 것이 실제로 유용하다는 것을 알고 있기 때문에만 작동합니다. 이 기법을 구현하는 데모를 확인하세요. Moto G4는 이 접근 방식에 대해 어떻게 생각할까요? 스포일러 주의: 대답은 다음과 같습니다.

GPU에 유휴 시간이 많은 트레이스를 보여주는 DevTools

이제 GPU에 여유 공간이 많아지고 부드러운 60fps가 제공됩니다. 우리가 해냈어!

프로덕션화

데모에서는 서로 다른 강도로 흐리게 처리할 콘텐츠의 사본을 만들기 위해 DOM 구조를 여러 번 복제했습니다. 프로덕션 환경에서 이 기능이 어떻게 작동하는지 궁금할 수 있습니다. 작성자의 CSS 스타일이나 JavaScript에 의도치 않은 부작용이 있을 수 있기 때문입니다. 맞습니다. Shadow DOM을 소개합니다.

대부분의 사용자는 Shadow DOMCustom Elements에 '내부' 요소를 연결하는 방법으로 생각하지만 격리 및 성능 기본 요소이기도 합니다. JavaScript와 CSS는 Shadow DOM 경계를 뚫을 수 없으므로 개발자의 스타일이나 애플리케이션 로직을 방해하지 않고 콘텐츠를 복제할 수 있습니다. 각 사본을 래스터화할 <div> 요소가 이미 있으며 이제 이러한 <div>를 그림자 호스트로 사용합니다. attachShadow({mode: 'closed'})를 사용하여 ShadowRoot를 만들고 콘텐츠 사본을 <div> 자체가 아닌 ShadowRoot에 첨부합니다. 모든 스타일시트를 ShadowRoot에도 복사하여 복사본의 스타일이 원본과 동일하도록 해야 합니다.

일부 브라우저는 Shadow DOM v1을 지원하지 않으며, 이러한 브라우저의 경우 콘텐츠를 복제하고 아무것도 깨지지 않기를 바라는 방식으로 대체됩니다. ShadyCSS와 함께 Shadow DOM 폴리필을 사용할 수 있지만 라이브러리에서는 이를 구현하지 않았습니다.

이제 Chrome의 렌더링 파이프라인을 살펴본 후 브라우저 전반에서 흐림 효과를 효율적으로 애니메이션 처리하는 방법을 알아냈습니다.

결론

이러한 종류의 효과는 가볍게 사용해서는 안 됩니다. DOM 요소를 복사하고 자체 레이어로 강제 적용하므로 하위 기기의 한계를 푸시할 수 있습니다. 모든 스타일시트를 각 ShadowRoot에 복사하는 것도 잠재적인 성능 위험이 있으므로 LightDOM의 사본에 영향을 받지 않도록 로직과 스타일을 조정할지 아니면 ShadowDOM 기법을 사용할지 결정해야 합니다. 하지만 때로는 Google의 기술이 가치 있는 투자가 될 수도 있습니다. GitHub 저장소의 코드와 데모를 살펴보고 궁금한 점이 있으면 Twitter로 문의하세요.