성능 저하를 최소화하면서 box-shadow에 애니메이션 추가하기

이 포스트는 이곳에 게시되어 있는 내용을 바탕으로 작성했습니다. 더 알아보고자 하실 경우 해당 웹페이지에서 자세한 내용을 읽어보실 수 있습니다.

CSS에서 transition 속성을 통해 해당 객체의 많은 속성들을 자연스럽게 애니메이션화할 수 있습니다. 그 중에는 해당 객체에 그림자를 그리는 box-shadow 속성도 포함되어 있습니다. 하지만 바로 간편하게 할 수 있는 방법이 있다 해도 악효과가 같이 나타나면 우회 방법이더라도 되도록 문제가 발생하지 않는 방향으로 제작하는게 맞겠지요. box-shadow에 직접 transition을 걸면 애니메이팅되는 동안 프레임이 떨어지는 등 성능에 문제가 생길 수 있습니다.

http://tobiasahlin.com/blog/how-to-animate-box-shadow에 따르면, box-shadow에 transition과 같은 전환 애니메이션 효과를 주고자 할 경우 직접 box-shadow에 대하여 설정하지 말고 ::after::before와 같은 Pseudo 객체에 box-shadow를 지정하고, transition을 할 때 그 객체의 투명도(opacity)를 0과 1을 오가는 방법을 사용하라고 권장하고 있습니다. box-shadow에 전환 효과가 직접 부여되면 그렇지 않았을 때보다 성능 이슈가 생겨서인데, 위 페이지에 포함되어 있는 데모 페이지에서 직접 체험해보실 수 있습니다. 그래도 실제로 그런지 링크의 설명대로 아래와 같은 간단한 페이지를 만들고 세 개의 브라우저로 직접 테스트해봤습니다.

HTML
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <link rel="stylesheet" type="text/css" href="boxShadow_PerfTest.css">
        <title>CSS box-shadow Transition PerfTest</title>
    </head>
    <body>
    <div class="bShadow-A">
        <div></div>
        <div></div>
        <div></div>
    </div>
    <div class="bShadow-B">
        <div></div>
        <div></div>
        <div></div>
    </div>
    </body>
</html>
CSS (boxShadow_PerfTest.css)
body {
    background-color: #F7F7F7;
    padding: 48px;
}
.bShadow-A, .bShadow-B {
    padding: 24px;
    box-sizing: border-box;
}
.bShadow-A > div, .bShadow-B > div {
    display: inline-block;
    position: relative;
    width: 200px;
    height: 200px;
    margin: 20px;
    border-radius: 4px;
}

.bShadow-A > div:hover, .bShadow-B > div:hover {
    transform: scale(1.1);
}

.bShadow-A > div {
    background-color: #E74856;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
    transition: box-shadow 300ms ease-in-out, transform 300ms ease-in-out;
}
.bShadow-A > div:hover {
    box-shadow: 0px 10px 48px -4px rgba(0,0,0,0.75);
}

.bShadow-B > div {
    background-color: #10893E;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
    transition: transform 300ms ease-in-out;
}
.bShadow-B > div::after {
    position: absolute;
    width: 100%;
    height: 100%;
    z-index: -1;
    top: 0;
    left: 0;
    content: "";
    border-radius: 4px;
    box-shadow: 0px 10px 48px -4px rgba(0,0,0,0.75);
    opacity: 0;
    transition: opacity 300ms ease-in-out;
}
.bShadow-B > div:hover::after {
    opacity: 1;
}

아래와 같은 페이지가 만들어지며, 브라우저 별로 성능 테스트를 진행하기 위해 객체에 커서를 올릴 때(:hover), 아래와 같은 순서로 움직였습니다.

.bShadow-A가 box-shadow가 직접 transition되는 붉은 색의 사각형들이며, .bShadow-B가 Pseudo 객체에 box-shadow가 설정되고 opacity가 transition되는 초록색의 사각형들입니다.

커서가 해당 객체 위로 올라갈 때 그림자가 표시되고 transform: scael(1.1);1.1배 확대가 적용되며, 커서가 객체 위에서 빠져나갈 때 표시되던 그림자가 사라지고 확대 효과도 해제되는 시나리오입니다.

Untitled_GIF_05-13-18.gif
 

Microsoft Edge 42.17134.1.0 - EdgeHTML 17.17134

bShadow-A에 커서가 올라가고 내려가면서 전환 애니메이션이 그려질 때, bShadow-B에서 전환 애니메이션이 표시될 때보다 프레임 드랍이 순간적으로 더 크게 떨어지는 경향이 보였습니다. 제대로 된 화면 녹화 장비가 없어 담아내지는 못했지만, 크롬과 파이어폭스와는 다르게 bShadow-A에 전환 애니메이션이 그려질 때 래스터화를 하지 않는지, bShadow-B처럼 부드럽게 애니메이션이 나오지 않고 안티 앨리어싱이 안 된 것마냥 딱딱 끊기는 모습으로 표시되었습니다. 이 부분은 위에 링크한 데모 사이트에서도 재현되니 직접 Edge 브라우저로 확인해보시는 것을 추천드립니다.

 

Firefox Dev 60.0b15

수치상으로나 실제 표시되는 모습이나 눈에 띄는 문제는 없었습니다. 아마 랜더 쪽에서 타 브라우저 대비 최적화가 더 잘 되어 있는게 아닐까 싶네요.

 

Chrome 66.0.3359.170

bShadow-A에 전환 애니메이션이 표시될 때 bShadow-B보다 더 많은 빈도로 프레임이 드랍되고, 커서가 올라가고 내려갈 때마다 래스터 스레드에 부하가 걸리는 모습입니다.

 

왜 차이가 날까요?

bShadow-A에는, 대상 객체에 그림자가 바로 부여되고, 이 그림자를 transition에서 전환 애니메이션을 조정합니다. 따라서 그림자가 생기거나 없어질 때 브라우저가 그림자 그래픽을 일일히 그리고 없애는 과정을 매번 거칩니다. 그림자가 표시되고 해제될 때마다 그래픽 리소스를 잡아먹는 건 당연한 일입니다.

반면 bShadow-B에는, 대상 객체 아래에 속하는 Pseudo 객체에 그림자가 부여되고, Pseudo 객체에 대한 투명도를 transition에서 전환 애니메이션을 조정합니다. 웹페이지가 처음 로드될 때 그림자가 그려지고 표시할 조건이 될 때까지(위 예시에서는 커서가 올라갈 때) 보이지 않게만 처리하는 것입니다. 그림자를 추가할 때, 투명하게 되어 있던 Pseudo 객체를 불투명하게 만들어 주기만 하면 되므로 그래픽 리소스에 부담이 분산됩니다.

PC에서 크롬이나 파이어폭스 브라우저로 위 예제를 실행했을 때에는 스로틀링이 걸려있다든지와 같은 특수한 상황이 아니면 대게 프레임 드랍 없이 소화가 가능합니다. 하지만 모바일과 같은 저성능 기기에서는 같은 효과도 성능 저하가 일어나 보기 좋지 않을 수 있습니다. 성능 저하 이슈가 눈에 보이지 않더라도 같은 기능을 달성하면서 리소스를 적게 활용하는 것은 언제나 옳은 일이므로 특수한 경우가 아니면 위와 같은 방법을 활용하시는 것을 추천합니다.

이 방식은 기존 객체와 완전히 동일한 크기의 Pseudo 객체에 z-index: -1; 속성을 부여해 기존 객체 바로 아래에 위치하게 하고, Pseudo 객체에 그림자를 부여하는 방식이므로 설령 Pseudo 객체를 활용하셔야 하는 경우이더라도 객체를 겹치면 되므로 쓸 수 없는 상황이 있을 것 같진 않네요.