흐리게 처리는 사용자의 관심을 다른 곳으로 유도하는 좋은 방법입니다. 일부 시각적 요소를 흐리게 처리하고 다른 요소는 선명하게 유지하면 자연스럽게 사용자의 포커스가 유도됩니다. 사용자는 흐리게 처리된 콘텐츠를 무시하고 대신 읽을 수 있는 콘텐츠에 집중합니다. 마우스를 가져갈 때 개별 항목에 관한 세부정보를 표시하는 아이콘 목록이 한 가지 예입니다. 이 기간 동안 나머지 선택사항은 흐리게 처리하여 사용자를 새로 표시된 정보로 리디렉션할 수 있습니다.
요약
흐리게 처리하는 애니메이션은 속도가 매우 느리므로 적합하지 않습니다. 대신 점점 더 흐리게 처리된 일련의 버전을 미리 계산하고 그 사이를 크로스페이드합니다. 동료인 이구님이 모든 작업을 처리하는 라이브러리를 작성했습니다. 데모를 살펴보세요.
그러나 이 기술은 전환 기간 없이 적용하면 상당히 불편할 수 있습니다. 흐리게 처리되지 않은 상태에서 흐리게 처리된 상태로 전환하는 흐리게 처리 애니메이션은 합리적인 선택처럼 보이지만 웹에서 이 작업을 시도해 본 적이 있다면 컴퓨터 성능이 좋지 않은 경우 애니메이션이 원활하지 않다는 것을 알 수 있습니다(이 데모 참고). 더 나은 방법이 있을까요?
문제
현재로서는 흐리게 처리 애니메이션을 효율적으로 작동하도록 할 수 없습니다. 하지만 충분히 괜찮아 보이면서 기술적으로 애니메이션 흐리게 처리가 아닌 해결 방법을 찾을 수 있습니다. 시작하려면 먼저 애니메이션 흐리게 처리가 느린 이유를 알아보겠습니다. 웹에서 요소를 흐리게 처리하는 방법에는 CSS filter
속성과 SVG 필터라는 두 가지 기법이 있습니다. 지원과 사용 편의성이 향상되어 일반적으로 CSS 필터가 사용됩니다. 안타깝게도 Internet Explorer를 지원해야 하는 경우 SVG 필터를 사용해야 합니다. IE 10 및 11에서는 CSS 필터가 아닌 SVG 필터를 지원하기 때문입니다. 좋은 소식은 흐리게 처리 애니메이션을 위한 해결 방법이 두 기술 모두에 적용된다는 것입니다. DevTools를 살펴보고 병목 현상을 찾아보겠습니다.
DevTools에서 'Paint Flashing'을 사용 설정하면 플래시가 전혀 표시되지 않습니다. 다시 칠해지지 않는 것 같습니다. 이는 기술적으로 정확합니다. '재페인팅'은 CPU가 승격된 요소의 텍스처를 다시 페인트해야 함을 의미하기 때문입니다. 요소가 승격 및 흐리게 처리될 때마다 GPU에서 셰이더를 사용하여 흐리게 처리를 적용합니다.
SVG 필터와 CSS 필터 모두 컨볼루션 필터를 사용하여 흐리게 처리합니다. 컨볼루션 필터는 출력 픽셀마다 여러 입력 픽셀을 고려해야 하므로 상당히 비용이 많이 듭니다. 이미지가 크거나 흐리게 처리하는 반경이 클수록 효과에 드는 비용이 더 큽니다.
여기에서 문제가 발생합니다. 프레임마다 상당히 비용이 많이 드는 GPU 작업을 실행하여 프레임 예산 16ms를 초과하여 60fps 미만으로 떨어집니다.
래빗홀로 들어가기
원활하게 진행되도록 하려면 어떻게 해야 하나요? 손재주를 사용할 수 있습니다. 실제 흐리게 처리 값 (흐리게 처리의 반지름)을 애니메이션하는 대신 흐리게 처리 값이 지수적으로 증가하는 흐리게 처리된 사본을 미리 계산한 다음 opacity
를 사용하여 사본 간에 크로스페이드합니다.
크로스페이드는 일련의 중첩된 불투명도 페이드 인 및 페이드 아웃입니다. 예를 들어 흐리게 처리 단계가 4개 있는 경우 첫 번째 단계를 페이드 아웃하는 동시에 두 번째 단계를 페이드 인합니다. 두 번째 단계가 100% 불투명도에 도달하고 첫 번째 단계가 0%에 도달하면 두 번째 단계를 페이드 아웃하는 동시에 세 번째 단계를 페이드 인합니다. 이 작업이 완료되면 마지막으로 세 번째 단계를 페이드 아웃하고 네 번째 버전인 최종 버전을 페이드 인합니다. 이 시나리오에서 각 단계는 총 원하는 기간의 1/4이 소요됩니다. 시각적으로는 실제 애니메이션 처리된 흐리게 처리와 매우 유사합니다.
실험 결과, 단계별로 흐리게 처리 반지름을 기하급수적으로 늘리면 가장 좋은 시각적 결과를 얻을 수 있었습니다. 예: 흐리게 처리 단계가 4개 있는 경우 각 단계(예: 0단계: 1픽셀, 1단계: 2픽셀, 2단계: 4픽셀, 3단계: 8픽셀)에 filter: blur(2^n)
를 적용합니다. will-change: transform
를 사용하여 이러한 흐리게 처리된 사본을 각각 자체 레이어('승격'이라고 함)로 강제하면 이러한 요소의 불투명도를 매우 빠르게 변경할 수 있습니다. 이론적으로 이렇게 하면 비용이 많이 드는 흐리게 처리 작업을 미리 로드할 수 있습니다. 알고 보니 로직에 결함이 있었습니다. 이 데모를 실행하면 프레임 속도가 여전히 60fps 미만이고 흐리게 처리되는 정도가 이전보다 더 나빠집니다.
DevTools를 빠르게 살펴보면 GPU가 여전히 매우 바쁘고 각 프레임을 약 90ms로 늘린다는 것을 알 수 있습니다. 이유가 무엇인가요? 더 이상 흐리게 처리 값을 변경하지 않고 불투명도만 변경하는데 무슨 일이 일어나고 있는 것일까요? 문제는 다시 한번 흐리게 처리 효과의 특성에 있습니다. 앞서 설명한 대로 요소가 승격되고 흐리게 처리되면 효과가 GPU에 의해 적용됩니다. 따라서 더 이상 흐리게 처리 값을 애니메이션 처리하지 않더라도 텍스처 자체는 여전히 흐리게 처리되지 않으며 GPU에서 프레임마다 다시 흐리게 처리해야 합니다. 프레임 속도가 이전보다 더 나쁜 이유는 대부분의 경우 독립적으로 흐리게 처리해야 하는 두 개의 텍스처가 표시되므로 순진한 구현에 비해 GPU에 이전보다 더 많은 작업이 실제로 발생하기 때문입니다.
결과물은 깔끔하지 않지만 애니메이션이 매우 빠릅니다. 흐리게 처리할 요소를 승격하지 않고 대신 상위 래퍼를 승격합니다. 요소가 흐리게 처리되고 승격된 경우 효과가 GPU에 의해 적용됩니다. 이로 인해 데모가 느려졌습니다. 요소가 흐리게 처리되었지만 승격되지 않은 경우 흐리게 처리가 대신 가장 가까운 상위 텍스처로 래스터화됩니다. 여기서는 승격된 상위 래퍼 요소입니다. 이제 흐리게 처리된 이미지가 상위 요소의 텍스처가 되며 향후 모든 프레임에 재사용할 수 있습니다. 흐리게 처리된 요소는 애니메이션이 적용되지 않으며 캐시하면 실제로 유용하다는 것을 알고 있기 때문에 이 방법이 작동합니다. 다음은 이 기법을 구현하는 데모입니다. Moto G4는 이 접근 방식에 대해 어떻게 생각할까요? 스포일러 주의: 굉장히 멋진 앱이라고 생각합니다.
이제 GPU에 여유가 생겼고 부드럽게 60fps를 실행할 수 있습니다. 해냈습니다.
프로덕션화
이 데모에서는 DOM 구조를 여러 번 복제하여 콘텐츠 사본을 여러 강도로 흐리게 처리했습니다. 작성자의 CSS 스타일이나 JavaScript에 의도치 않은 부작용이 있을 수 있으므로 프로덕션 환경에서 어떻게 작동하는지 궁금할 수 있습니다. 맞습니다. Shadow DOM을 시작하세요.
대부분의 사람들은 Shadow DOM을 Custom 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로 문의해 주세요.