Web Animations API로 복잡성 조정
게시 됨: 2022-03-10단순한 전환과 복잡한 애니메이션 사이에는 중간 지점이 없습니다. CSS 전환 및 애니메이션이 제공하는 기능에 문제가 없거나 갑자기 얻을 수 있는 모든 기능이 필요합니다. Web Animations API는 애니메이션 작업을 위한 많은 도구를 제공합니다. 그러나 당신은 그들을 처리하는 방법을 알아야합니다. 이 기사에서는 유연성을 유지하면서 복잡한 애니메이션을 처리하는 데 도움이 될 수 있는 주요 요점과 기술 을 안내합니다.
이 기사를 살펴보기 전에 Web Animations API 및 JavaScript의 기본 사항을 숙지하는 것이 중요합니다. 명확하게 하고 당면한 문제로부터 주의를 산만하게 하지 않도록 제공된 코드 예제는 단순합니다. 함수와 객체보다 더 복잡한 것은 없을 것입니다. 애니메이션 자체에 대한 좋은 진입점으로 MDN을 일반 참조로, Daniel C. Wilson의 우수한 시리즈 및 Ollie Williams의 CSS Animations vs Web Animations API를 제안합니다. 효과를 정의하고 원하는 결과를 얻을 수 있도록 조정하는 방법은 다루지 않습니다. 이 문서에서는 애니메이션이 정의되어 있고 이를 처리하기 위한 아이디어와 기술이 필요하다고 가정합니다.
인터페이스의 개요와 인터페이스의 용도부터 시작합니다. 그런 다음 무엇을, 언제, 얼마 동안 정의하기 위해 타이밍과 제어 수준을 살펴보겠습니다. 그런 다음 여러 애니메이션을 개체로 감싸서 하나로 처리하는 방법을 배웁니다. Web Animations API를 사용하기 위한 좋은 출발점이 될 것입니다.
인터페이스
Web Animations API는 새로운 차원의 제어를 제공합니다. 그 전에는 CSS 전환 및 애니메이션이 효과를 정의하는 강력한 방법을 제공하면서 여전히 단일 작동 지점이 있었습니다. 전등 스위치처럼 켜져 있거나 꺼져 있습니다. 지연 및 완화 기능을 사용하여 매우 복잡한 효과를 만들 수 있습니다. 그래도 어느 순간부터는 번거롭고 힘들어요.
Web Animations API는 이 단일 작동 지점을 재생에 대한 완전한 제어 로 전환합니다. 조명 스위치는 슬라이더가 있는 조광 스위치로 바뀝니다. 원한다면 재생 컨트롤에 추가하여 이제 런타임에 효과를 정의하고 변경할 수 있기 때문에 전체 스마트 홈으로 전환할 수 있습니다. 이제 컨텍스트에 맞게 효과를 적용하거나 실시간 미리 보기로 애니메이션 편집기를 구현할 수 있습니다.
애니메이션 인터페이스부터 시작합니다. 애니메이션 개체를 가져오기 위해 Element.animate
메서드를 사용할 수 있습니다. 키프레임과 옵션을 지정하면 애니메이션이 즉시 재생됩니다. 또한 Animation
개체 인스턴스를 반환합니다. 그 목적은 재생을 제어하는 것입니다.
이것을 기억한다면 카세트 플레이어 로 생각하십시오. 어떤 독자들은 그것이 무엇인지 잘 알지 못할 수도 있다는 것을 알고 있습니다. 추상적인 컴퓨터 사물을 설명하기 위해 실제 개념을 적용하려는 시도가 빠르게 무너지는 것은 불가피합니다. 그러나 연필로 테이프를 되감는 즐거움을 모르는 독자는 카세트 플레이어가 무엇인지 알고 있는 사람들은 이 기사가 끝날 때쯤이면 훨씬 더 혼란스러울 것이라는 점을 안심시키십시오.
상자를 상상해보십시오. 카세트가 들어가는 슬롯이 있고 재생, 중지 및 되감기 버튼이 있습니다. 이것이 바로 Animation 인터페이스 인스턴스입니다. 정의된 애니메이션을 보유하고 재생과 상호 작용하는 방법을 제공하는 상자입니다 . 당신은 그것에 뭔가를 제공하고 그것은 당신에게 다시 제어를 제공합니다.
얻는 컨트롤은 오디오 및 비디오 요소에서 얻는 컨트롤과 편리하게 유사합니다. 재생 및 일시 중지 메서드와 현재 시간 속성입니다. 이 세 가지 컨트롤을 사용하면 재생과 관련하여 무엇이든 만들 수 있습니다.
카세트 자체는 애니메이션되는 요소 에 대한 참조, 효과의 정의 및 무엇보다도 타이밍을 포함하는 옵션을 포함하는 패키지입니다 . 그리고 그것이 바로 KeyframeEffect
가 무엇인지입니다. 우리의 카세트 테이프는 모든 녹음과 녹음 길이에 대한 정보를 담고 있는 것입니다. 이러한 모든 속성을 실제 카세트의 구성 요소와 일치시키는 것은 나이든 청중의 상상에 맡기겠습니다. 내가 보여줄 것은 코드에서 어떻게 보이는지입니다.
Element.animate
를 통해 애니메이션을 만들 때 세 가지 작업을 수행하는 바로 가기를 사용하게 됩니다. KeyframeEffect
인스턴스를 생성합니다. 새 Animation
인스턴스에 넣습니다. 그것은 즉시 재생을 시작합니다.
const animation = element.animate(keyframes, options);
그것을 분해하고 같은 일을 하는 동등한 코드를 봅시다.
const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)
카세트(1)를 가져와 플레이어(2)에 넣은 다음 재생 버튼(3)을 누르십시오.
뒤에서 어떻게 작동하는지 아는 것의 핵심은 키프레임의 정의를 분리하고 재생 시점을 결정할 수 있다는 것입니다. 조정할 애니메이션이 많은 경우 먼저 애니메이션을 모두 수집하여 재생할 준비가 되었음을 알리는 것이 도움이 될 수 있습니다. 즉석에서 생성하고 적절한 순간에 재생을 시작하기를 바라는 것은 원하는 것이 아닙니다. 몇 프레임 드래그로 원하는 효과를 깨는 것은 너무 쉽습니다. 긴 시퀀스의 경우 드래그가 누적되어 전혀 설득력이 없는 경험이 됩니다.
타이밍
코미디에서와 마찬가지로 애니메이션에서는 타이밍이 모든 것입니다. 효과가 작동하도록 하려면 특정 느낌을 얻으려면 속성이 변경되는 방식을 미세 조정할 수 있어야 합니다. Web Animations API에서 제어할 수 있는 두 가지 수준의 타이밍 이 있습니다.
개별 속성 수준에서 offset
이 있습니다. 오프셋을 사용하면 단일 속성 타이밍을 제어 할 수 있습니다. 0에서 1 사이의 값을 지정하면 각 효과가 시작되는 시점을 정의할 수 있습니다. 생략하면 0과 같습니다.
CSS의 @keyframes
에서 from
/ to
대신 백분율을 사용하는 방법을 기억할 수 있습니다. offset
은 100으로 나눈 값입니다. offset
값은 단일 반복 기간의 일부입니다 .
offset
을 사용하면 KeyframeEffect
내에서 키프레임을 정렬할 수 있습니다. 상대적인 숫자 오프셋은 재생 시간이나 재생 속도에 관계없이 모든 키프레임이 서로 상대적으로 동일한 순간에 시작되도록 합니다.
이전에 언급했듯이 offset
은 duration 의 일부입니다 . 이제 나는 당신이 이것에 대한 나의 실수와 시간 낭비를 피하길 바랍니다. 애니메이션의 지속 시간은 애니메이션의 전체 지속 시간과 동일하지 않다는 것을 이해하는 것이 중요합니다. 일반적으로 그것들은 동일하며 그것이 당신을 혼란스럽게 할 수 있고 나를 혼란스럽게 만든 것입니다.
Duration 은 한 번의 반복이 완료되는 데 걸리는 시간(밀리초) 입니다. 기본적으로 전체 기간과 동일합니다. 애니메이션 지속 시간에서 지연을 추가하거나 반복 횟수를 늘리면 더 이상 알고 싶은 숫자가 표시되지 않습니다. 이점을 이해하는 것이 중요합니다.
미디어 재생과 같이 더 큰 컨텍스트 내에서 키프레임 재생을 조정해야 하는 경우 타이밍 옵션을 사용해야 합니다. 다음 방정식에서 시작부터 "완료" 이벤트까지의 전체 애니메이션 지속 시간:
delay + (iterations × duration) + end delay
다음 데모에서 작동하는 것을 볼 수 있습니다.
이를 통해 고정 길이 미디어 컨텍스트 내에서 여러 애니메이션을 정렬 할 수 있습니다. 애니메이션의 원하는 지속 시간을 그대로 유지하면 더 긴 지속 시간을 가진 컨텍스트에 포함시키기 위해 시작 시 delay
으로 애니메이션을 "패드"하고 끝에서 delayEnd
할 수 있습니다. 이런 의미에서 delay
은 키프레임에서 오프셋과 같은 역할을 합니다. 지연은 밀리초 단위로 설정되므로 상대 값으로 변환하는 것이 좋습니다.
애니메이션을 정렬하는 데 도움이 되는 또 다른 타이밍 옵션은 iterationStart
입니다. 반복의 시작 위치를 설정합니다. 당구 공 데모를 가져 가라. iterationStart
슬라이더를 조정하여 공의 시작 위치와 회전을 설정할 수 있습니다. 예를 들어 화면 중앙에서 점프를 시작하도록 설정하고 마지막 프레임에서 숫자가 카메라에서 직선이 되도록 설정할 수 있습니다.
여러 개를 하나로 제어
프레젠테이션 앱용 애니메이션 편집기로 작업할 때 타임라인의 단일 요소에 대해 여러 애니메이션을 정렬해야 했습니다. 첫 번째 시도는 offset
을 사용하여 타임라인의 올바른 시작 지점에 애니메이션을 배치하는 것이었습니다.
이는 offset
을 사용하는 잘못된 방법으로 빠르게 증명되었습니다. 이 특정 UI 측면에서 타임라인에서 움직이는 애니메이션은 애니메이션의 지속 시간을 변경하지 않고 시작 위치를 이동하는 것을 의미했습니다. offset
을 사용하면 여러 가지를 변경해야 했고 offset
자체도 변경해야 했으며 지속 시간이 변경되지 않도록 닫는 속성의 offset
도 변경해야 했습니다. 그 해법은 이해하기에는 너무 복잡한 것으로 판명되었습니다.
두 번째 문제는 transform
속성과 관련이 있습니다. 요소에 대한 몇 가지 특성 변경 을 나타낼 수 있기 때문에 원하는 대로 수행하기가 까다로울 수 있습니다. 이러한 속성을 서로 독립적으로 변경하려는 경우 훨씬 더 어려울 수 있습니다. 스케일 기능의 변경은 이후의 모든 기능에 영향을 미칩니다. 그 이유는 다음과 같습니다.
Transform 속성은 시퀀스의 여러 기능을 값으로 사용할 수 있습니다 . 함수의 순서에 따라 결과가 바뀝니다. scale
를 가지고 translate
하십시오. 때로는 요소의 크기에 상대적인 것을 의미하는 백분율로 translate
을 정의하는 것이 편리합니다. 공이 정확히 자신의 지름 3배 높이로 점프하기를 원한다고 가정해 보겠습니다. 이제 scale 함수를 배치하는 위치에 따라( translate
전후) 결과는 원래 크기 또는 크기 조정된 높이의 세 가지 높이에서 변경됩니다.
transform
속성의 중요한 특성입니다. 상당히 복잡한 변환을 수행하려면 필요합니다. 그러나 이러한 변환이 요소의 다른 변환과 구별되고 독립적이어야 하는 경우 방해가 됩니다.
하나의 transform
속성에 모든 효과를 넣을 수 없는 경우가 있습니다. 너무 빨리 너무 많이 얻을 수 있습니다. 특히 키프레임이 다른 위치에서 온 경우 변환된 문자열을 매우 복잡하게 병합 해야 합니다. 논리가 간단하지 않기 때문에 자동 메커니즘에 거의 의존할 수 없습니다. 또한 무엇을 기대해야 하는지 이해하기 어려울 수 있습니다. 이를 단순화하고 유연성을 유지하려면 이들을 서로 다른 채널로 분리해야 합니다.
한 가지 솔루션은 요소를 각각 별도로 애니메이션할 수 있는 div
로 래핑하는 것 입니다. 예를 들어 캔버스에 위치 지정을 위한 div, 크기 조정을 위한 다른 것, 회전을 위한 세 번째 것입니다. 그렇게 하면 애니메이션 정의를 크게 단순화할 수 있을 뿐만 아니라 적용 가능한 경우 다른 변환 원점을 정의할 수 있는 가능성도 열립니다.
그 속임수로 상황이 통제 불능인 것처럼 보일 수도 있습니다. 우리가 이전에 겪었던 문제의 수를 곱하고 있다는 것입니다. 사실 이 트릭을 처음 발견했을 때 너무 과하다고 해서 버렸습니다. 내 transform
속성이 모든 조각에서 올바른 순서로 한 조각으로 컴파일 되는지 확인할 수 있다고 생각했습니다. 관리하기 너무 복잡하고 특정 작업을 수행할 수 없도록 만들기 위해 transform
기능이 하나 더 필요했습니다. 내 transform
속성 문자열 컴파일러가 올바르게 작동하는 데 점점 더 많은 시간이 걸리기 시작하여 포기했습니다.
여러 애니메이션의 재생을 제어하는 것이 처음에 보이는 것처럼 그렇게 어렵지 않다는 것이 밝혀졌습니다. 처음부터 카세트 테이프 플레이어의 비유를 기억하십니까? 카세트의 수에 관계없이 자신의 플레이어를 사용할 수 있다면 어떨까요? 그 이상으로 해당 플레이어에 원하는 만큼 버튼을 추가할 수 있습니다.
단일 애니메이션에서 play
를 호출하는 것과 애니메이션 배열에서 호출하는 것의 유일한 차이점은 반복해야 한다는 것입니다. 다음은 Animation
인스턴스의 모든 메서드에 사용할 수 있는 코드입니다.
// To play just call play on all of them animations.forEach((animation) => animation.play());
우리는 이것을 사용하여 플레이어를 위한 모든 종류의 기능을 만들 것입니다.
애니메이션을 보관하고 재생할 상자를 만들어 보겠습니다. 적절한 방식으로 이러한 상자를 만들 수 있습니다. 명확하게 하기 위해 함수와 개체를 사용하여 수행하는 예를 보여 드리겠습니다. createPlayer
함수는 동기화되어 재생될 애니메이션 배열을 사용합니다. 단일 play
방법으로 개체를 반환합니다.
function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }
기능 확장을 시작하려면 이것으로 충분합니다. pause 및 currentTime
메서드를 추가해 보겠습니다.
function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }
이 세 가지 방법을 사용하는 createPlayer
는 애니메이션 수를 조정하기에 충분한 제어를 제공합니다. 그러나 조금 더 밀어붙여 보자. 우리 플레이어가 카세트를 얼마든지 받을 수 있을 뿐만 아니라 다른 플레이어도 사용할 수 있도록 합시다.
앞에서 보았듯이 Animation
인터페이스는 미디어 인터페이스와 유사합니다. 그 유사성을 사용하여 플레이어에 모든 종류의 것을 넣을 수 있습니다. 이를 수용하기 위해 currentTime
메서드를 조정하여 애니메이션 개체와 createPlayer
에서 가져온 개체 모두에서 작동하도록 합시다.
function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }
우리가 방금 만든 플레이어를 사용하면 단일 요소 애니메이션 채널에 대한 여러 div
의 복잡성을 숨길 수 있습니다. 이러한 요소는 장면에서 그룹화될 수 있습니다. 그리고 각 장면은 더 큰 것의 일부가 될 수 있습니다. 이 기술로 할 수 있는 모든 것.
타이밍 데모를 보여주기 위해 모든 애니메이션을 3명의 플레이어로 나눴습니다. 첫 번째는 오른쪽의 미리보기 재생을 제어하는 것입니다. 두 번째 것은 왼쪽에 있는 모든 공의 윤곽과 미리보기에 있는 윤곽의 점프 애니메이션을 결합합니다.
마지막으로 세 번째는 왼쪽 컨테이너에 있는 공의 위치 애니메이션을 결합한 플레이어입니다. 그 플레이어는 초당 약 60프레임의 슬라이스로 애니메이션의 연속 데모에서 공이 퍼지도록 합니다.
결론
Web Animations API와 같은 웹 인터페이스는 브라우저가 그동안 수행했던 특정 작업을 제공합니다. 브라우저는 GPU에 작업을 전달하여 빠르게 렌더링하는 방법을 알고 있습니다. Web Animations API를 사용하면 이를 제어할 수 있습니다. 해당 컨트롤이 다소 생소하거나 혼란스러워 보일 수 있지만 사용하는 것도 혼란스러워야 한다는 의미는 아닙니다. 타이밍 및 재생 제어를 이해하면 해당 API를 필요에 맞게 길들일 수 있는 도구가 있습니다. 얼마나 복잡해야 하는지 정의할 수 있어야 합니다.
추가 읽기
- "애니메이션 디자인에 대한 실용적인 기법", Sarah Drasner
- "동작 감도를 위한 감소된 동작으로 설계", Val Head
- Ottomatias Peura, "음성 비서에 대한 대체 음성 UI"
- "모바일 사용자 인터페이스를 위한 더 나은 도구 설명 설계", Eric Olive