CSS transitions

이재윤·2021년 11월 19일
1

HTML/CSS

목록 보기
2/4
post-thumbnail

본 글은 An Interactive Guide to CSS Transitions를 번역한 글 입니다. 오역이 있을 수 있습니다.

💻 기초

애니메이션을 만들기 위해 필요한 것은 변하는 CSS 입니다.

아래는 hover시에 애니메이션없이 움직이는 버튼을 구현한 예시입니다.

위 코드는 이용자의 마우스가 버튼위에 올라갔을때의 행동을 정의하기 위해 가상 클래스 :hover를 사용했습니다. 이는 자바스크립트 이벤트 onMouseEnter와 유사합니다.

요소를 위로 움직이기 위해서 transform: translateY(-10px)를 사용했습니다. margin-top을 사용할 수 있지만 transform: translateY(-10px)이 이런 작업에 더 적합합니다. 왜 그런지는 이후에 살펴 보겠습니다.

기본적으로 CSS의 변경은 순식간에 일어납니다. 눈 깜짝할 사이에, 버튼은 새로운 위치로 이동합니다. 이는 어떤 사건이 점진적으로 일어나는 실제 세계와 어울리지 않은것 같습니다.

transition 속성을 사용하면 브라우저에게 한 상태에서 다른 상태로 점진적 변화가 가능합니다.

transition은 여러개의 값을 가질수 있지만, 두 가지 값은 필수값 입니다.

  1. 애니메이션 효과를 주고 싶은 속성명
  2. 애니메이션 효과의 지속 시간

만약 여러개의 속성에 효과를 주고 싶다면, 쉼표(,)로 구분된 배열을 전달해 주면 됩니다.

.btn {
 transition: transform 250ms, opacity 400ms;
}

.btn:hover {
 transform: translateY(-10px);
 opacity: 0;
}

💻 Timing functions

요소를 한 위치에서 다른 위치로 전이(transition) 시킬때, 브라우져는 중간단계의 프레임들이 어떻게 보이는지 알 필요가 있습니다.

예를 들어, 요소를 왼쪽에서 오른쪽으로 1초 동안 움직인다고 가정해 보겠습니다. 부드럽게 애니메이션 효과를 주기위해서는 60fps로 움직여야 합니다. 이는 시작점부터 끝점까지 60개의 위치를 찾아야 한다는 의미입니다.

위 그림에서 각각의 흐린 원들은 특정 시점에서의 순간을 의미합니다. 원이 왼쪽에서 오른쪽으로 움직임에 따라, 사용자에게 보이는 프레임들인 것입니다.

이 애니메이션에서는, linear timing function을 사용했습니다. 이것은 요소가 상수 속도로 움직이는 것을 의미합니다. 위 그림에서 원들은 각각의 프레임마다 동일한 양만큼 이동했습니다.

transition-timing-function속성으로 timing function을 선언할 수 있습니다.

.btn {
 transtion: transform 250ms;
 transition-timing-function: linear;
}

또는 transition속성에 축약형으로 선언할 수도 있습니다.

.btn {
 transition: transform 250ms linear;
}

linear는 좋은 선택은 아닙니다. 현실 세계에서 실제로 linear하게 움직이는 물체는 거의 없습니다. 좋은 애니메이션 효과는 현실 세계를 모방한 움직입니다. 좀더 유기적인 효과 몇개를 살펴 보겠습니다.

✔ ease-out

ease-out은 맹렬이 달려들다, 에너지가 다 떨어진 황소와 같이 움직입니다. 마지막에 졸린 거북이처럼 움직입니다.
위 그림에서 처음 몇개의 프레임들은 빠르게 움직이다 끝에서는 느려지는 것을 알 수 있습니다.
만약 시간에 따른 요소의 변위를 그래프로 표현한다면 아래와 같이 표현할 수 있습니다.

❗ 언제 ease-out을 사용하면 될까요?

보통 모달창과 같이 화면 밖에서 화면 안으로 들어올때 사용합니다. 이 효과는 멀리서 부터 빠르게 오다 사용자 앞에서 멈추는 효과를 줍니다.

✔ ease-in

ease-inease-out의 반대입니다. 처음에는 느리게 가다 속도가 증가합니다.

앞에서 살펴보았듯이, ease-out은 화면 밖에서 화면 안으로 들어오는 요소에 적합합니다. ease-in은 반대의 경우에 유용합니다.

이 조합(ease-out, ease-in)은 모달처럼 화면 안과 밖으로 움직이는 요소에 적합합니다.

✔ ease-in-out

ease-in-out은 이전 두개의 timing function의 조합입니다.

이 timing function은 대칭적 입니다. 감속과 가속의 정도가 동일합니다.

이 함수는 반복해서 움직이는 물체(사라졌다, 나타났다를 반복하는 물체)를 표현하는데 유용합니다.

✔ ease

ease-in-out과 달리 ease는 대칭적이지 않습니다. 이 함수는 감속의 비율이 더 높습니다.

ease는 기본값 입니다. 만약 timing function을 선언하지 않는다면 ease가 선언이 됩니다. 사실 이는 옳은 판단인거 같습니다. ease는 대부분의 경우에서 최선의 선택입니다. 만약 움직이는 요소가 화면의 밖으로 나가거나, 안으로 들어오지 않는다면 ease는 좋은 대안입니다.

✔ Custom curves

만약 내장된 함수가 사용목적에 맞지 않는다면, cubic bezier timing function을 이용해 custom curve를 정의할 수 있습니다.

.btn {
 transition: transform 250ms cubic-bezier(0.1, 0.2, 0.3, 0.4);
}

지금까지 위에서 살펴봤던 함수들은 cubic-bezier 함수에 미리 값을 설정해 놓은 것들 입니다. cubic-bezier는 두개의 기준점을 나타내는 4개의 숫자들을 인자로 가집니다.

wonderful helper from Lea Verou에서 Bezier timing function을 만들어 볼 수 있습니다. 또한 extended set of timing functions에서 함수를 선택할 수도 있습니다. 한 가지 유의할 점은 몇몇 특이한 함수들은 CSS에서 동작하지 않을 수 있습니다.

💻 Animation performance

초반에, 60fps로 움직이는 애니메이션에 대해 언급했습니다. 계산을 해보면, 브라우저가 각각의 프레임들을 그리는데 16.6ms 밖에 걸리지 않는다는 것을 알 수 있습니다. 이는 정말로 짧은 시간입니다. 참고로 우리가 눈을 깜빡일때 걸리는 시간이 100ms~300ms 입니다.

만약 애니메이션의 고비용의 작업이면, 버벅거릴 것이고, 프레임들도 누락될 것입니다.

애니메이션 퍼포먼스는 깊고 흥미로운 분야이지만, 꼭 필요하고 필수적인것만 살펴보겠습니다.

  1. 몇몇 CSS 속성들은 다른 속성들 보다 비용이 비쌉니다. 예를들어 height는 layout에 영향을 미치기 때문에 고비용의 연산입니다. 요소의 높이를 줄이면, 연쇄작용으로 다른 자식요소들도 빈 공간을 채우기 위해 움직여야 합니다.

  2. background-color와 같은 속성들은 애니메이션 효과를 주기에 비용이 비쌉니다. layout에 영향을 미치지는 않지만 모든 프레임마다 새롭게 색을 칠해야 하므로 고비용의 작업입니다.

  3. transformopacity는 애니메이션 효과를 입히기 좋은 속성입니다. 만약 현재 애니메이션 효과가 widthleft속성을 변경하고 있다면, transform을 사용하는 것이 성능을 높일 수 있습니다. (하지만 항상 같은 효과를 표현하는것이 불가능 할 수 있습니다.)

  4. 애니메이션 효과를 가장 성능이 낮은 기기로 테스트 해보세요. 개발 장비는 그 기기보다 빠르게 동작합니다.

✔ Hardware Acceleration

브라우저나 OS에 따라, 앞선 예제에서 조금 이상한 점을 발견 했을 수 있습니다.

글자에 주목해 보세요. transition의 시작과 끝에서 약간 밀리는 것을 알 수 있습니다.

이는 컴퓨터의 CPU와 GPU 사이의 hand-off 때문입니다.

transformopacity를 이용해 요소에 애니메이션 효과를 주게되면, 브라우저는 이 효과를 최적화 하려고 합니다. 모든 프레임에서 픽셀들을 래스터화 하는 것 대신에, 브라우저는 텍스처로 GPU에게 전달합니다. GPU는 텍스처기반의 전환작업에 매우 적합하며, 그 결과로 매끄럽고 성능좋은 애니메이션 효과를 경험할 수 있습니다. 이것이 'hardware acceleration' 입니다.

하지만 문제가 한가지 있습니다. GPU와 CPU는 요소를 렌더링하는 방식이 조금 다릅니다. CPU가 GPU에 요소를 전달하거나 그 반대의 경우, 조금씩 움직입니다.

이 문제를 아래의 CSS 속성을 사용하여 해결할 수 있습니다.

.btn {
 will-change: transform;
}

will-change는 선택한 요소에게 애니메이션 효과를 줄 것이며, 이 경우 최적화 해야함을 브라우저에게 알리는 속성입니다.

실제로 이것은 브라우저가 GPU가 항상 이 요소를 처리하도록 한다는 것을 의미합니다. 따라서 CPU와 GPU간의 hand-off도 없으며, 그로 인해 발생하는 움직임도 없어집니다.

will-change는 어떤 요소들을 하드웨어 가속을 사용할 것인지 의도를 가지고 선택할 수 있게 합니다.

하드웨어 가속의 또다른 이점은 '서브 픽셀 렌더링'을 사용할 수 있다는 점 입니다.

아래의 두 박스 요소들을 확인해 보세요. 호버시에 아래로 움직이는 효과를 적용했습니다. 이 중 하나는 하드웨어 가속이 적용되었고, 나머지 하나는 적용되지 않았습니다.

기기나 화면에 따라 다를 수 있지만, 하나의 박스 요소가 다른 것 보다 더 부드럽게 움직입니다.

margin-top속성은 '서브 픽셀 렌더'를 하지 못합니다. 이는 주변의 픽셀들의 값을 반올림해야 함을 의미하며, 그 결과로 자연스럽지 못한 효과가 적용됩니다. 반면에 transform속성은 GPU의 안티 엘리어싱에 의해 픽셀간의 이동이 부드럽습니다.

❗ Tradeoffs
세상에 공짜는 없고, 이는 하드웨어 가속도 마찬가지 입니다.
요소의 렌더링을 GPU에게 위임하면 한정된 비디오 메모리를 소비하게 됩니다. 이는 성능이 낮은 모바일기기에서 문제가 될 수 있습니다.

💻 UX touches

✔ Action-driven motion

위 그림에서 전환 효과는 마우스가 들어갈때와 나올때가 동일한 대칭입니다.

  • 마우스가 요소에 안으로 들어갈 때, 250ms 동안 10px 위로 움직입니다.
  • 마우스가 요소 밖으로 나갈때, 250ms 동안 10px 아래로 움직입니다.

각 액션에 대한 전환효과를 다르게 지정해 디테일을 줄 수 있습니다. 포인터가 안으로 들어갈때 빠른 효과를 밖으로 나갈때 느린 전환 효과를 지정해 보겠습니다.

또다른 예시로 모달 창을 들 수 있습니다. 모달이 화면에 나타날때 ease-out효과를, 화면에서 사라질때 ease-in효과를 줄 수 있습니다.

이런 디테일은 사소하지만, 많은 것을 얻을 수 있습니다.

대부분의 개발자들은 '상태'의 관점에서 생각합니다. 예를 들어, 위 예시의 경우 'hover'된 상태와 기본 상태 두 가지로 나눌 수 있습니다. '상태'의 관점에서 생각하는것 대신 '행동'의 관점에서 생각해 보는 것은 어떨까요? '상태'가 아니라 사용자의 행동에 근거해 애니메이션 효과를 주는 것 입니다. 위 경우에 적용해 보면, 포인터가 요소 안으로 들어갈때 효과와, 요소 밖으로 나갈때 효과를 줄 수 있습니다.

의미있는 애니메이션을 알고 싶다면 MeaningFun Motion with Action-Driven Animation을 참고해 보세요.

✔ Delay

전환 효과의 지연에 대해 살펴보겠습니다.

많은 사람들이 아래와 같은 불편한 경험을 겪었을 것입니다.

이런 일이 발생하는 이유는 드롭다운은 hover 될 때에만 활성화 되기 때문입니다. 자식 요소를 선택하기 위해 커서를 대각선으로 움직이게 되면, 범위를 벗어나 메뉴가 사라지게 됩니다.

이 문제는 transition-delay를 사용하면 JS를 이용하지 않고도 해결할 수 있습니다.

.dropdown {
 opacity: 0;
 transition: opacity 400ms;
 transition-delay: 300ms;
}

.dropdown-wrapper:hover .dropdown {
 opacity: 1;
 transtion: opacity 100ms;
 transition-delay: 0ms;
}

transition-delay 속성은 현재의 상태를 일정 시간 유지하게 해줍니다. 이 경우는, 커서가 .dropdown-wrapper 밖으로 나갈때 300ms 동안 아무일도 일어나지 않습니다. 만약 커서가 300ms 이내에 다시 들어갈 경우, 전환효과는 발생하지 않습니다.

300ms가 지나면 효과는 정상적으로 진행되며, 드롭다운은 400ms동안 사라 집니다.

❗ 축약형을 사용하지 않는 이유
지금까지 transition과 관련된 모든 값들을 축약형으로 표현했습니다. transition-delay 역시 축약형으로 사용할 수 있습니다.

.dropdown {
 opacity: 0;
 transition: opacity 250ms 300ms;
}

이 글의 필자는 transition-delay를 명시하는 것을 선호합니다. 시간에 관련된 값이 두개이면 어떤 값이 무엇을 의미하는지 모호하기 때문입니다.

✔ Doom flicker

지금까지의 데모에서 위 그림과 같은 경우를 겪었을 수도 있습니다.

이 문제는 마우스를 요소의 경계에 가져갔을때 발생합니다. 호버효과는 아래의 커서를 요소 바깥으로 벗어나게 하고 이는 요소를 아래로 이동 시킵니다. 이때 커서는 요소 안으로 들어가게 되고 다시 호버 효과를 발생시킵니다. 이런식으로 수차례 반복이 됩니다.

이 문제는 triggereffect와 분리함으로써 해결할 수 있습니다. 아래의 예시를 보겠습니다.

button 요소에 새로운 자식요소 .background가 생겼습니다. 이 span 요소에 모든 스타일들이 지정되어 있습니다.

우리가 버튼에 커서를 가져가면, 자식 요소는 위로 움직이지만 버튼은 움직이지 않습니다.

0개의 댓글