CSS 애니메이션 퍼포먼스 향상 전략

문제. CSS 애니메이션을 사용할 때 퍼포먼스 향상을 위해 고려해야할 점

엘리먼트에 어떠한 변경을 할 것인지를 미리 브라우저에 알려주는 것이 will-change 속성의 역할이다. 이것을 사용하면 그 변경이 시작되기 전에 적절히 최적화할 수 있다. 즉, 페이지 출력에 악영향을 줄 수 있는 처리 비용을 줄일 수 있다는 것이다. 그로인해 효율적으로 엘리먼트의 변경 또는 렌더링을 처리할 수 있고 페이지는 순식간에 갱신돼 부드러운 화면 처리가 가능하게 된다.

CSS 3D Transforms를 예로 들어보겠다. 「CPU, GPU, 하드웨어 가속」절에서 말했듯이 이 속성을 어느 특정 엘리먼트에 사용하면 그 엘리먼트와 컨텐츠는 레이어로 관리되고 나중에 다시 합쳐진다. 새로운 레이어로 엘리먼트를 분리하는 것은 비교적 비용이 필요한 작업이다. 따라서 애니메이션에 몇 분의 1초 단위의 눈에 띄는 지연 현상이 발생한다. 이것이 화면에서 깜빡이는 현상으로 이어지는 것이다.

이 지연 현상을 회피하려면 엘리먼트의 변경이 실제로 발생하기 전에 그 변경에 관해 브라우저에 알려주면 된다. 그러면 브라우저는 여유를 가지고 그 변경에 대비할 수 있게된다. 변경이 실제로 일어나게 되면 엘리먼트의 레이어가 준비되고 애니메이션 및 엘리먼트의 렌더링은 적절히 처리되며 페이지는 신속히 갱신된다.

will-change 속성을 사용해 앞으로 일어날 변경에 관해 브라우저에게 알려주고자 할때는 대상이 되는 엘리먼트에 아래와 같이 CSS 코드를 작성하면 된다.

변형 처리 외에도 스크롤 위치(표시되고 있는 윈도우 내의 엘리먼트 위치, 화면 내에 엘리먼트가 얼마나 보이고 있는지), 컨텐츠 등 기타 복수의 CSS 속성을 변경할 때에는 그 대상이 되는 속성 이름을 지정하여 브라우저에 변경 의사를 전달할 수 있다. 1개의 엘리먼트에 여러개의 값을 변경할 생각이라면 콤마(,)로 구분하여 기술할 수 있다. 예를들어 한 엘리먼트를 애니메이션 시키는 동시에 위치를 변경하고자하는 경우는 다음과 같이 선언하면 된다.

무엇을 변경하고 싶은지를 정확하게 기술하면 그 변경에 대비한 최적화를 브라우저가 시행한다. 핵을 사용해 불필요한 비용을 발생시키는 레이어를 브라우저에게 억지로 생성시키는 방법보다 이 방법이 명확히 고속화에 도움이된다.

will-change는 엘리먼트의 변경을 브라우저에 알려주는 이외에 엘리먼트 자체에 영향을 끼치는가?

이 질문에 관해서는 “그럴 수도 있고 아닐 수도 있다.”라는 답변을 줄 수 있다. 변경할 때 사용하는 속성의 종류에 따라 상황이 달라진다. 엘리먼트에 스택 컨텍스트(참고)를 생성하는 초기화하지 않은 속성이 있다면 그것을 will-change에 지정하는 것으로 엘리먼트에 스택 컨텍스트가 생성될 수 있다.

예를 들어 clip-path와 opacity 속성은 모두 기본 값 이외의 값을 지정할 때 대상이 되는 엘리먼트에 스택 컨텍스트를 생성한다. 즉, will-change의 값으로 두 속성(혹은 모두)을 지정하면 엘리먼트에 관한 실제 변경이 발생하기 전(즉, opacity 기본 값을 변경하기 전)에 그 엘리먼트에 스택 컨텍스트가 만들어지는 것이다. 이와 같은 원리는 엘리먼트에 스택 컨텍스트를 생성하는 또다른 속성 역시 동일하게 적용된다.

또, 일부 속성은 위치가 고정된 엘리먼트(fixed-position elements)에서 컨테이닝 블럭(containing block) 생성을 일으킨다. 예를 들면 transform을 적용한 엘리먼트는 위치가 고정(position: fixed)돼 있더라도 모든 자식 엘리먼트에 대해 컨테이닝 블럭을 생성한다. 즉, 컨테이닝 블럭의 생성을 발생시키는 속성을 will-change의 값으로 지정한 경우 위치가 고정된 엘리먼트에 컨테이닝 블럭이 만들어진다는 뜻이된다.

앞서 설명한 스택 컨텍스트와 컨테이닝 블럭, 두 가지 예외를 제외하면 will-change 속성은 대상 엘리먼트에 직접적인 영향을 끼치지 않는다. will-change는 브라우저에 앞으로 일어난 변화를 알려줘 최적화를 실시할 뿐이다.

will-change 사용법:”할 것”과 “하지 말아야할 것”

will-change의 역할을 이해하고 나면 “브라우저에 모든 것을 최적화하면 좋지 않을까”라고 생각할 수도 있을 것이다. 누구라도 모든 변경에 관한 최적화가 한번에 됐으면 좋겠다고 생각할 수 있다.

확실히 will-change는 파워풀하고 훌륭한 도구지만, 또다른 훌륭한 도구들과 마찬가지로 위력이 있는 만큼 책임을 가지고 다뤄야한다. will-change는 무분별하게 사용하면 성능저하가 발생하고 결과적으로 페이지의 작동이 중단될 것이다.

will-change는 성능저하뿐 아니라 바로 감지하기 어려운 사이드 이펙트(원래 will-change는 보이지 않는 곳에서 브라우저에 명령하는 방법이므로 감지하지 못하는 건 당연하다)를 발생시키기 때문에 사용하기 까다로운 속성이다. 이 속성을 사용해 최대한 효과를 발휘하고 발생할 수 있는 문제를 피하기 위해서는 아래 몇 가지 규칙을 이해해야한다.

너무 많은 속성이나 엘리먼트에 will-change를 사용하지 않는다

이전 절에서도 말했듯이 모든 속성, 모든 엘리먼트에 대해 일어날 변경을 전부 브라우저에 최적화 처리를 하려한다면 CSS로 다음과 같이 작성할 수 있다. 언뜻 보기엔 그렇게 틀려보이지 않을 수 있다.

이 코드는 동작할 것으로 보이지만(나도 처음엔 이치에 맞고, 잘 될 것 같다고 생각했음) 실제론 상당히 해롭고 무엇보다 전혀 효과가 없다. 모든 키워드가 will-change 대해 무효한 값이 될 뿐 아니라(유효한 값과 무효한 값은 뒤에서 소개하겠다) 위 코드와 같이 모든것에 적용되도록 하는 규칙(룰)은 유용하지 않다. 이유는 브라우저는 이미 가능한한 최적화를 시행하고 있으므로(opacity나 3D transform의 예를 기억해보자) 최적화를 명확히 지시하더라도 아무것도 바뀌지 않고 결국 아무 것도 행하지 않기 때문이다. 사실, 이 설정을 하므로써 더 많은 폐단이 생길 수 있다. 왜냐하면 will-change에 연결된 강력한 최적화가 머신의 자원을 대량으로 소비하는 결과를 낳고, 결국 페이지의 속도 지연이나 때때로 크래쉬까지 일으키는 원인이 되기 때문이다.

즉, 일어날지 일어나지 않을지 모르는 변경에 대비해 브라우저를 최적화 시키는 것은 현명하지 못하고 효과가 없을 뿐더러 문제를 발생시키니 하지 말자.

브라우저에 충분한 시간을 준다

will-change 속성의 명칭의 유래에는 명확한 이유가 있다. will-change가 브라우저에게 알려주는 것은 현재 일어날 변화가 아닌 미래에 일어날 변화다. will-change를 사용하여 브라우저에게 앞으로 일어날 변경에 관해 최적화를 시키는 것이기 때문에 이를 위해 브라우저도 최적화를 위한 시간이 필요하다. 이는 실제로 변화가 생길 때 지연 없이 최적화를 적용할 수 있도록 하기 위함이다.

변화가 생길 직전에 엘리먼트에 관해 will-change를 설정해도 거의 효과는 없다(오히려 더 나쁠지도 모른다. 이전 절의 예는 애니메이션이 필요하지 않는 엘리먼트까지 새로운 층을 만들 가능성이 있었다). 예를들어 호버로 변경이 생기는 경우를 살펴보자.

위의 코드가 브라우저에 알려주고 있는 것은 이미 일어난 변화에 관한 최적화다. 효과가 없을 뿐 아니라 will-change의 포괄적인 개념을 부정하는 것이다. 더 나은 방법은 적어도 변화가 생길 아주 조금 전에 그 변화를 알려줄 방법을 생각해 will-change를 설정하는 것이다.

예를 들어 엘리먼트를 클릭한는 순간 변화한다면 마우스 커서를 올릴 때(hover) will-change를 설정하면 브라우저가 변경에 관해 최적화하는 시간을 벌 수 있다. 사용자가 엘리먼트에 마우스 커서를 올린 후 실제 클릭할 때까지의 시간 차는 브라우저가 충분히 최적화를 실시할 수 있는 시간이기 때문이다. 인간의 반응은 비교적 시간이 걸리기 때문에 실제 변경이 생기기 전 까지 약 200밀리세컨드의 시간이 브라우저에게 주어진다. 브라우저가 최적화를 하는데는 그만큼 시간이면 충분하다.

그러면, 선택이 아니라 마우스 커서를 엘리먼트에 올렸을 때 변경이 필요한 경우는 어떻게 처리할까? 아까도 말했듯이 처음 선언한 코드는 소용이 없다. 그러나 이 경우에도 변경이 발생하기 전에 예측할 수 있는 방법을 찾아낼 수 있다. 예를 들어, 변경하는 엘리먼트의 조상에 :hover로 will-change를 선언하면 최적화에 필요한 시간을 벌 수 있다.

하지만, 조상 엘리먼트에 마우스 커서를 둔다고 해서 대상의 엘리먼트에 반드시 상호 작용이 발생한다고 할 수는 없다. 그러므로 애플리케이션에서 뷰가 활성화 될 때나 엘리먼트가 뷰포트에 보이는 위치에 있을 때 will-change를 설정하면 엘리먼트가 상호 작용할 가능성을 높일 수 있다.

변경이 종료되면 will-change를 삭제한다

브라우저가 앞으로 발생할 변화에 관해 최적화를 하면 일반적으로 비용이 든다. 「너무 많은 속성이나 엘리먼트에 will-change를 사용하지 않는다」 절에서 잠깐 이야기했듯이 머신의 자원을 대량으로 소비하는 일이 있기 때문이다. 브라우저는 보통, 적용한 최적화를 삭제하고 가능한한 빨리 평소의 행동(nomal behavior)으로 돌아온다. 하지만 will-change는 이 행위를 무시하고 본래 브라우저가 하는 것보다 훨씬 길게 최적화를 유지해버린다.

그러니, 엘리먼트에 변경이 종료되면 반드시 will-change를 삭제하도록 하자. 그렇게하면 브라우저는 최적화때문에 사용하고 있던 자원을 회수할 수 있다.

스타일시트에 선언한 will-change는 삭제할 수 없다. 따라서 대부분 자바스크립트를 사용해 설정 및 삭제하는 것을 권장한다. 스크립트에서 브라우저에 변경을 선언하고 그 변경이 종료될 때 즈음에 이벤트를 등록하면 변경이 종료된 후 will-change를 삭제할 수 있다. 예를 들어 앞 절에서 소개한 스타일 규칙과 동일하게 엘리먼트(또는 그 조상)에 mouseenter 이벤트를 이용해 마우스가 호버될 때를 리슨하여 will-change를 설정할 수 있다. 엘리먼트에 애니메이션을 적용하는 경우에는 DOM 이벤트의 animationEnd 이벤트를 사용해 애니메이션이 종료하여 animationEnd가 발생하면 will-change를 삭제한다.

크레이그 버클러(Craig Buckler)가 자바스크립트로 CSS의 애니메이션을 캡쳐하는 방법에 관해 글(How to Capture CSS3 Animation Events in JavaScript)을 작성했다. 스크립트를 이용한 조작 방식에 익숙치 않은 분은 참고하길 바란다. 또 CSS-TRICKS의 CSS animtaion과 transition을 조작하는 방법에 관한 글(Controlling CSS Animations and Transitions with JavaScript)도 참고가 될 것이다.

스타일시트에서는 will-change를 소극적으로 사용한다

전 절에서 살펴본 것 처럼 will-change는 엘리먼트에 변경이 생기기 몇 밀리세컨트 전에 브라우저에게 그 일을 알리고자 할 때 사용할 수 있다. 이번 절에서는 스타일시트에서 will-change를 선언하는 좋은 사례를 소개한다. will-change의 설정 및 삭제엔 자바스크립트를 이용하는 것을 추천하지만 몇몇은 will-change를 스타일시트에서 설정(또는 유지)하는게 적절한 경우도 있다.

하나의 예로 사용자가 지속해서 상호 작용하는 것이 전제되고 빠른 응답을 요구하는 소수의 엘리먼트에 will-change를 설정하는 경우를 들 수 있다. 한정된 엘리먼트에 설정한다면 너무 과한 최적화로 연결되지 않아 그에 따른 폐해도 적다. 예를 들어 유저의 요청에 따라 사이드바를 슬라이드하는 경우라면 아래 규칙이 적절할 수 있다.

또다른 예로 거의 항상 변경되는 엘리먼트에 관해 will-change를 이용하는 경우를 들 수 있다. 예를 들면, 유저의 마우스 이벤트에 반응해 마우스의 움직에 맞춰 엘리먼트를 이동시키는 경우가 있다고 하자. 이 경우엔 스타일시트로 will-change를 선언해도 문제 없다. 왜냐하면 엘리먼트가 항상 혹은 정기적으로 변화하기 때문에 최적화도 유지될 필요성이 있기 때문이다.

will-change 속성의 값

will-change 속성은 4개의 설정 가능한 값(auto, scroll-position, contents,)을 제공한다.

<custom-ident>의 값으로는 변경하고 싶은 1개 이상의 속성 이름을 지정한다. 여러 개의 속성을 작성하는 경우에는 쉼표(,)로 구분한다. 아래 코드는 속성 이름으로 지정한 유효한 will-change 선언이다.

<custrom-ident>의 값은 보통 <custom-ident>에서 제외되는 키워드와 함께 will-change, none, all, auto, scroll-position, contents의 키워드도 제외된다(identifier-value). 이 글의 처음에도 언급했듯이 will-change: all은 무효이기 때문에 브라우저가 무시한다.

auto라는 값은 특별한 변경 의지를 보이지 않기 때문에 브라우저는 평소에 하는 최적화 이외에 또 다른 최적화를 실시하지 않는다.

scroll-position 값은, 이름 그대로 후에 엘리먼트의 스크롤 위치를 변경할 것을 나타낸다. 이 값을 설정하면 브라우저는 스크롤 가능한 엘리먼트를 포함해 윈도우에 보이는 컨텐츠를 위해 미리 최적화하여 준비한 후 렌더링하기 때문에 유용하다. 브라우저는 스크롤 윈도우에 보이는 컨텐츠만을 렌더링하는 경우가 많지만, 그 중에는 윈도우에서 벗어난 컨텐츠도 있다. 렌더링을 생략하는 것으로 메모리 및 시간 절약과 미려한 스크롤링 그리고 밸런스를 맞춘다. will-change: scroll-position을 사용하면 한층 더 렌더링의 최적화가 이뤄지기 때문에, 한번에 많은 양을 스크롤 하거나 빠른 스크롤이 필요한 경우에도 매끄러운 화면을 선보일 수 있다.

contents의 값은, 엘리먼트의 컨텐츠의 변화를 나타낸다. 브라우저는 보통 엘리먼트의 렌더링 결과를 오랫동안 캐쉬한다. 왜냐하면 대부분의 엘리먼트는 변화가 잦지 않으며, 변화하더라도 위치를 바꾸는 정도이기 때문이다. 브라우저가 이 값을 통해 전달받는 신호는 엘리먼트의 캐시를 덜하거나 아예 하지 않아야 한다는 것이다. 그 이유는 엘리먼트의 컨텐츠가 정기적으로 변화하는 경우 그 엘리먼트를 캐쉬하더라도 도움이 되지 않고, 시간만 낭비하기 때문이다. 따라서 브라우저는 단순하게 캐쉬를 그만두고 컨텐츠가 변화할 때마다 처음부터 렌더링하게 된다.

앞에서 말했듯이 will-change를 지정해도 아무 영향을 받지 않는 속성도 있다. 왜냐하면 브라우저는 대부분의 속성 변화에 관해 어떤 특별한 최적화를 행하지 않기 때문이다. 그러한 속성을 will-change에 지정해도 안전하지만 별다른 영향도 주지 않는다. 기타 속성은 스택 컨텍스트(opacity, clip-path 등)나 컨테이닝 블럭 또는 양쪽 모두 생길 수 있다.

 

https://dev.opera.com/articles/ko/css-will-change-property/

Check CSS Animation Performance with the Browser’s Dev Tools

https://csstriggers.com

You may also like...

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.