블러 애니메이션

흐리게 처리는 사용자의 포커스를 리디렉션하는 데 좋은 방법입니다. 일부 시각적 요소는 흐리게 보이도록 하면서 다른 요소에는 포커스를 두면 자연스럽게 사용자의 집중을 유도할 수 있습니다. 사용자는 블러 처리된 콘텐츠를 무시하고 읽을 수 있는 콘텐츠에 집중합니다. 예를 들어 개별 항목에 대한 세부정보를 표시하는 아이콘 목록을 들 수 있습니다. 그동안 나머지 선택 항목은 흐리게 처리되어 사용자를 새로 표시된 정보로 리디렉션할 수 있습니다.

요약

블러 애니메이션은 매우 느리기 때문에 실제로 선택할 수 있는 것은 아닙니다. 대신 점점 더 블러가 심해지는 일련의 버전을 미리 계산하고 버전 간에 크로스 페이드합니다. 제 동료인 Yi Gu가 모든 것을 해결해 드리기 위해 라이브러리를 작성했습니다. 데모를 확인하세요.

그러나 이 기법은 과도기적 기간 없이 적용하면 꽤 지저분해질 수 있습니다. 블러를 애니메이션 처리(흐리게 처리되지 않음에서 블러 처리로 전환)하는 것은 합리적인 선택인 것 같습니다. 하지만 웹에서 이 작업을 시도해 본 적이 있다면 애니메이션이 매끄럽지 않다는 것을 알 수 있을 것입니다. 강력한 머신이 없는 경우 이 데모를 통해 알 수 있습니다. 개선의 여지가 있나요?

문제

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

현재는 블러 애니메이션 작업을 효율적으로 처리할 수 없습니다. 그러나 충분히 좋아 보이지만 기술적으로는 애니메이션 블러가 아닌 해결 방법을 찾을 수 있습니다. 시작하려면 먼저 애니메이션 블러가 느린 이유를 알아보겠습니다. 웹에서 요소를 블러 처리하는 데는 CSS filter 속성과 SVG 필터라는 두 가지 기법이 있습니다. 지원이 확대되고 사용 편의성이 높아서 일반적으로 CSS 필터가 사용됩니다. 하지만 Internet Explorer를 지원해야 하는 경우 IE 10 및 11은 CSS 필터는 지원하지만 SVG 필터는 지원하지 않으므로 SVG 필터를 사용할 수밖에 없습니다. 좋은 소식은 블러를 애니메이션 처리하는 Google의 해결 방법이 두 기술 모두에서 작동한다는 것입니다. DevTools를 살펴보면서 병목 현상을 찾아보겠습니다

DevTools에서 'Paint Flashing'을 사용 설정하면 플래시가 전혀 표시되지 않습니다. 다시 페인트가 발생하지 않는 것 같습니다. '다시 페인트'란 CPU에서 승격된 요소의 텍스처를 다시 페인트해야 한다는 의미이므로 기술적으로 정확합니다. 요소가 승격 블러 처리될 때마다 GPU는 셰이더를 사용하여 블러를 적용합니다.

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

여기서 문제가 있는 것입니다. 프레임마다 다소 비싼 GPU 작업을 실행하여 프레임 예산이 16ms를 초과하므로 60fps 미만이 됩니다.

토끼 굴 아래로

그렇다면 이를 원활하게 진행하려면 어떻게 해야 할까요? 슬픔을 해낼 수 있어! 실제 블러 값 (블러의 반경)을 애니메이션 처리하는 대신 블러 값이 기하급수적으로 증가하는 두 개의 블러 처리된 복사본을 미리 계산한 후 opacity를 사용하여 크로스 페이드합니다.

크로스 페이드는 겹쳐진 일련의 불투명도 페이드 인 및 페이드 아웃입니다. 예를 들어 블러 단계가 4개 있는 경우 첫 번째 스테이지는 페이드 아웃되고 동시에 두 번째 스테이지도 페이드 인됩니다. 두 번째 단계가 100% 불투명도에 도달하고 첫 번째 단계가 0%에 도달하면 두 번째 단계가 페이드 아웃되고 세 번째 단계에서 페이드 인됩니다. 이 작업이 완료되면 마지막으로 세 번째 스테이지를 페이드 아웃하고 네 번째이자 최종 버전을 페이드 인합니다. 이 시나리오에서 각 단계에는 원하는 총 기간의 1⁄4이 사용됩니다. 시각적으로는 실제 애니메이션 블러와 매우 유사합니다.

이 실험에서는 단계별로 블러 반경을 기하급수적으로 늘리면 최상의 시각적 결과를 얻었습니다. 예: 블러 단계가 4개 있으면 각 단계(0: 1px, 1단계: 2px, 2단계: 4px, 3단계: 8px)에 filter: blur(2^n)을 적용합니다. 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 DOM맞춤 요소에 '내부' 요소를 연결하는 방법으로 생각하지만, 이는 격리 및 성능 기본 요소이기도 합니다. JavaScript 및 CSS는 Shadow DOM 경계를 관통할 수 없으므로 개발자의 스타일이나 애플리케이션 로직을 방해하지 않고 콘텐츠를 복제할 수 있습니다. 각 복사본에 래스터화할 <div> 요소가 이미 있으며 이제 이러한 <div>를 섀도우 호스트로 사용합니다. attachShadow({mode: 'closed'})를 사용하여 ShadowRoot를 만들고 콘텐츠 사본을 <div> 자체 대신 ShadowRoot에 연결합니다. 또한 원본과 동일한 방식으로 사본의 스타일이 지정되도록 모든 스타일시트도 ShadowRoot에 복사해야 합니다.

일부 브라우저는 Shadow DOM v1을 지원하지 않습니다. 이러한 경우 콘텐츠를 복제하고 아무것도 중단되지 않는 최상의 결과가 있기를 바랍니다. Shadow DOM polyfillShadyCSS와 함께 사용할 수 있지만 라이브러리에는 구현하지 않았습니다.

좋습니다. Chrome의 렌더링 파이프라인을 진행한 후 여러 브라우저에서 블러에 효율적으로 애니메이션을 적용할 수 있는 방법을 알아봤습니다.

결론

이러한 효과는 가볍게 사용해서는 안 됩니다. DOM 요소를 복사하여 자체 레이어로 강제한다는 사실 때문에 저사양 기기의 한계를 뛰어넘을 수 있습니다. 모든 스타일시트를 각 ShadowRoot에 복사하면 성능상의 위험도 발생할 수 있으므로, LightDOM의 사본에 영향을 받지 않도록 로직과 스타일을 조정할지 아니면 Google의 ShadowDOM 기법을 사용할지 결정해야 합니다. 그러나 때때로 우리의 기법은 가치 있는 투자가 될 수 있습니다. GitHub 저장소의 코드와 데모를 살펴보고, 궁금한 점이 있으면 트위터를 통해 문의해 주세요.