후디니의 애니메이션 Worklet

웹 앱의 애니메이션 기능 강화

요약: 애니메이션 Worklet을 사용하면 실행되는 명령형 애니메이션을 작성할 수 있습니다. 버터 같은 버터 같은 버터 같은 현상이 없는 부드러움TM을 위해서는 기기의 기본 프레임 속도로 기본 스레드 버벅거림에 대한 애니메이션의 복원력을 높이고 연결 가능합니다. 스크롤하는 것이 좋습니다. Animation Worklet은 Chrome Canary의 '실험용 웹 플랫폼 기능' Chrome 71용 오리진 트라이얼을 계획하고 있습니다. Cloud Shell에서 현재 점진적으로 개선되고 있습니다.

또 다른 Animation API는 무엇인가요?

아닙니다. 그것은 이미 우리가 가지고 있는 것의 확장이며 타당한 이유가 있습니다. 처음부터 시작해 봅시다. 웹의 DOM 요소에 애니메이션을 적용하고 싶은 경우 CSS 전환 옵션 중 2개는 간단한 A-B 전환, CSS 애니메이션 좀 더 복잡한 시간 기반 애니메이션 및 Web Animations API (WAAPI)를 지원합니다. WAAPI의 지원 매트릭스는 꽤 암울해 보이지만 진행 중입니다. 그때까지는 polyfill.

이러한 모든 메서드의 공통점은 스테이트리스(Stateless)이고 시간 기반입니다. 하지만 개발자들이 추구하는 효과 중 일부는 현재도 스테이트리스(Stateless) 환경입니다 예를 들어 악명 높은 패럴랙스 스크롤러는 스크롤 기반이라는 의미입니다. 오늘날 웹에서 성능 기준에 맞는 패럴랙스 스크롤러를 구현하는 것은 의외로 어렵습니다.

스테이트리스(Stateless)는 어떨까요? Android의 Chrome 주소 표시줄을 떠올려 보세요. 예로 들 수 있습니다 아래로 스크롤하면 뷰가 보이지 않습니다. 하지만 화면을 절반 정도 위를 넘더라도 위로 스크롤하면 표시됩니다. 애니메이션은 스크롤 위치뿐만 아니라 변경할 수 있습니다. 스테이트풀(Stateful)입니다.

또 다른 문제는 스크롤바의 스타일 지정입니다. 또한 디자인이 불안정하거나 적어도 스타일 지정이 안 되기는 하죠 스크롤바로 냥 고양이를 사용하려면 어떻게 해야 하나요? 어떤 기법을 선택하든 맞춤 스크롤바를 빌드하는 것은 쉬울 수도 있습니다.

요점은 이 모든 것이 어색하고 불가능하기 어렵다는 것입니다. 효율적으로 구현할 수 있습니다 대부분은 이벤트 및/또는 requestAnimationFrame: 화면이 꺼져 있을 때도 60fps로 유지될 수 있습니다. 최대 90fps, 120fps 이상에서 실행할 수 있으며, 초당 요청 수의 기본 스레드 프레임 예산이 너무 많습니다.

Animation Worklet은 웹 애니메이션 스택의 기능을 확장하여 더 쉽게 만들 수 있습니다. 본격적으로 시작하기 전에 배웠습니다.

애니메이션 및 타임라인에 대한 입문서

WAAPI 및 Animation Worklet은 타임라인을 광범위하게 사용하여 원하는 방식으로 애니메이션과 효과를 조정할 수 있습니다. 이 섹션에서는 타임라인 및 타임라인이 애니메이션과 함께 작동하는 방법에 대한 간단한 복습 또는 소개.

각 문서에는 document.timeline가 있습니다. 문서가 문서가 만들어지고 기존 문서가 시작된 이후의 밀리초를 계산합니다. 전체 이 타임라인을 기준으로 문서의 애니메이션이 작동합니다.

좀 더 구체적으로 만들기 위해 이 WAAPI 스니펫을 살펴보겠습니다.

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

animation.play()를 호출하면 애니메이션에서 타임라인의 currentTime를 사용합니다. 시작 시간으로 설정합니다. 애니메이션의 지연 시간은 3000ms입니다. 타임라인이 'startTime'에 도달하면 애니메이션이 시작 (또는 '활성')됩니다.

  • 3000. After that time, the animation engine will animate the given element from the first keyframe (translateX(0)), through all intermediate keyframes (translateX(500px)) all the way to the last keyframe (translateY(500px)) in exactly 2000ms, as prescribed by thedurationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3000 + 1000and the last keyframe atstartTime + 3000 + 2000`. 요점은, 타임라인은 애니메이션의 위치를 제어합니다.

애니메이션이 마지막 키프레임에 도달하면 첫 번째 키프레임으로 다시 이동합니다. 애니메이션의 다음 반복을 시작합니다. 이 프로세스는 iterations: 3를 설정한 이후 총 3회입니다. 애니메이션을 좀 더 쉽게 멈추지 않으면 iterations: Number.POSITIVE_INFINITY라고 씁니다. 사용 가능한 코드 결과 참조하세요.

WAAPI는 놀라울 정도로 강력하며 이 API에는 이징, 시작 오프셋, 키프레임 가중치 및 채우기 동작 등 이 도움말의 범위를 벗어납니다. 자세한 내용은 CSS 도움말의 CSS 애니메이션에 대한 도움말을 참조하세요.

애니메이션 Worklet 작성

타임라인이라는 개념을 이해했으니 이제 Animation Worklet과 이를 통해 타임라인을 뒤처지게 하는 방법을 알아보세요. 애니메이션 Worklet API는 WAAPI를 기반으로 할 뿐만 아니라 확장 가능한 웹이라는 측면에서 볼 때 WAAPI의 작동 방식을 설명합니다. 구문 측면에서 매우 유사합니다.

애니메이션 Worklet WAAPI
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

차이점은 worklet의 이름인 첫 번째 매개변수에 있습니다. 이 애니메이션을 구동합니다.

특성 감지

Chrome은 이 기능을 제공하는 첫 번째 브라우저이므로 코드는 AnimationWorklet가 있을 것으로만 예상하지 않습니다. 따라서 포드를 로드하기 전에 사용자의 브라우저가 간단한 확인으로 AnimationWorklet:

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

Worklet 로드

Worklet은 다수의 애플리케이션을 개발하기 위해 Houdini 태스크 포스에서 도입한 새로운 개념입니다. 새로운 API를 더 쉽게 빌드하고 확장할 수 있습니다. 워크렛의 세부 사항을 다룰 것입니다. 나중에 설명하겠습니다. 하지만 단순성을 위해 이러한 방법이 비용이 저렴하고 지금은 간단한 스레드 (예: worker)를 제공합니다.

이름이 'passthrough'인 Worklet을 로드했는지 확인해야 합니다. 를 호출합니다.

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

문제가 무엇인가요? 클래스를 애니메이터로 등록하고 AnimationWorklet의 registerAnimator() 호출에 '패스 스루'라는 이름을 지정합니다. 위의 WorkletAnimation() 생성자에서 사용한 이름과 동일합니다. 일단 등록이 완료되면 addModule()에서 반환한 프로미스가 확인되고 해당 Worklet을 사용하여 애니메이션 생성을 시작할 수 있습니다.

인스턴스의 animate() 메서드는 모든 프레임 브라우저가 렌더링을 원하며 애니메이션 타임라인의 currentTime를 전달합니다. 현재 처리 중인 효과도 포함됩니다. Google에서는 KeyframeEffect, currentTime를 사용하여 효과의 localTime이므로 이 애니메이터가 '패스 스루'라고 하는 것입니다. 이 코드를 사용하면 Worklet, WAAPI 및 AnimationWorklet은 모두 여기 보이는 것처럼 demo

시간

animate() 메서드의 currentTime 매개변수는currentTime WorkletAnimation() 생성자에 전달한 타임라인입니다. 이전 방금 이 시간을 효과에 넘겼습니다. 하지만 이것이 JavaScript 코드를 사용하면 시간을 왜곡할 수 있습니다 💫

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

currentTimeMath.sin()를 가져와 값을 범위는 [0; 2000]은 효과가 정의된 시간 범위입니다. 진행 중 애니메이션이 아주 다르게 변경할 수 있습니다. Worklet 코드는 임의로 복잡하며 사용자가 원하는 효과를 프로그램 방식으로 정의할 수 어느 정도까지 연주할 수 있어야 합니다.

옵션 대신 옵션

Worklet을 재사용하여 숫자를 변경할 수도 있습니다. 따라서 WorkletAnimation 생성자를 사용하면 옵션 객체를 worklet에 전달할 수 있습니다.

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

에서는 두 애니메이션 모두 동일한 코드로 구동되지만 옵션은 서로 다릅니다.

거주 지역의 주를 표시하세요.

앞서 언급했듯이 애니메이션 Worklet이 해결하고자 하는 주요 문제 중 하나는 스테이트풀(Stateful) 애니메이션입니다. 애니메이션 워크렛은 상태를 보유할 수 있습니다. 그러나 워크릿의 핵심 기능 중 하나는 다른 워크로드로 마이그레이션할 수 있다는 것입니다. 심지어 리소스를 저장하기 위해 폐기될 수도 있습니다. 그러면 있습니다. 상태 손실을 방지하기 위해 애니메이션 Worklet은 상태를 반환하는 데 사용할 수 있는 Worklet이 소멸되기 전에 호출됩니다. 객체를 지정합니다. Worklet이 호출될 때 이 객체가 생성자에 전달됩니다. 다시 만들었죠. 처음 만들 때 이 매개변수는 undefined입니다.

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

이 데모를 새로고침할 때마다 정사각형이 회전하는 방향 확률입니다. 브라우저가 다른 스레드로 마이그레이션하는 경우 생성 시 Math.random() 호출을 수행하여 방향을 설정할 수 있습니다. 이런 일이 발생하지 않도록 애니메이션을 반환합니다. 무작위로 선택된 방향을 state로 만들고, 제공된 경우 생성자에서 사용합니다.

시공간 연속체 활용하기: ScrollTimeline

이전 섹션에서 설명한 것처럼 AnimationWorklet을 사용하면 타임라인 진행이 효과에 미치는 영향을 프로그래밍 방식으로 정의 애니메이션을 적용할 수 있습니다. 하지만 지금까지의 타임라인은 항상 document.timeline였으며 시간도 추적할 수 있습니다.

ScrollTimeline은(는) 새로운 가능성을 열어주고 애니메이션을 재생할 수 있게 해 줍니다 스크롤하는 것이 좋습니다. 이전에 생성한 코드를 '패스 스루' 이 작업을 위한 demo:

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

document.timeline를 전달하는 대신 새 ScrollTimeline를 만듭니다. 짐작하셨겠지만 ScrollTimeline는 시간을 사용하지 않지만 scrollSource의 스크롤 위치로, worklet에서 currentTime를 설정합니다. 활동 중 상단 (또는 왼쪽)까지 스크롤하면 currentTime = 0를 의미하고 하단 (또는 오른쪽)까지 스크롤하면 currentTimetimeRange입니다. 이 페이지의 상자를 스크롤하면 데모를 사용하는 경우 빨간색 상자의 위치를 조정합니다.

스크롤하지 않는 요소로 ScrollTimeline를 만드는 경우 타임라인의 currentTimeNaN이 됩니다. 특히 모바일 앱의 반응형 디자인을 항상 NaNcurrentTime로 준비해야 합니다. 종종 0으로 기본 설정 가능합니다.

애니메이션을 스크롤 위치와 연결하는 것은 오랫동안 추구해 온 일입니다. 그러나 실제로 이 정도의 충실도는 달성할 수 없었습니다 해결 방법). 애니메이션 Worklet을 사용하면 매우 높은 성능을 발휘하면서 간단하게 구현됩니다. 예를 들면 다음과 같습니다. 시차 스크롤 효과를 볼 수 있습니다. demo가 이제 몇 줄만 사용하여 스크롤 기반 애니메이션을 정의합니다.

자세히 들여다보기

Worklet

Worklet은 격리된 범위와 매우 작은 API를 사용하는 JavaScript 컨텍스트입니다. 표시 영역입니다. API 노출 영역이 작기 때문에 특히 저사양 기기에서 더욱 그렇습니다. 또한 워크렛은 특정 이벤트 루프이지만 필요에 따라 스레드 간에 이동할 수 있습니다. 이것은 특히 AnimationWorklet에 중요합니다.

컴포지터 NSync

어떤 CSS 속성은 빠르게 애니메이션 처리되지만 아닙니다. 일부 속성의 경우 애니메이션을 적용하려면 GPU에서 일부 작업만 필요합니다. 브라우저가 전체 문서의 레이아웃을 강제로 재배치하도록 합니다.

다른 많은 브라우저에서처럼 Chrome에는 컴포지터라는 프로세스가 있습니다. 여기서는 레이어와 레이어 정렬을 매우 단순화합니다. GPU를 활용하여 화면을 가능한 한 정기적으로 업데이트하고, 가급적 빠르게 화면이 업데이트될 수 있는 속도 (일반적으로 60Hz)를 지정합니다. 사용 중인 CSS 속성에 애니메이션이 적용되어 있으므로 브라우저에 컴포지터가 작동하지만 다른 속성은 레이아웃을 실행해야 합니다. 할 수 있습니다. 어떤 속성을 사용하느냐에 따라 애니메이션을 계획 중인 경우 애니메이션 Worklet은 기본 컴포지터와 동기화된 별도의 스레드에서 실행됩니다.

손목을 때리는 타격

일반적으로 하나의 컴포지터 프로세스만 공유될 수 있습니다. 탭을 여러 개 만들 수 있습니다. 컴포지터가 차단되면 전체 브라우저가 중단되어 사용자 입력입니다. 어떤 경우에도 이를 피해야 합니다. 만약 Worklet은 컴포지터가 프레임이 이루는 데 필요한 데이터를 제시간에 제공할 수 없습니다. 무엇인가요?

이 경우 Worklet은 사양에 따라 "slip"이 허용됩니다. 뒤처짐 컴포지터는 마지막 프레임의 데이터를 재사용하여 프레임 속도를 계속 높여야 합니다. 시각적으로는 버벅거림처럼 보이지만 차이점은 브라우저가 여전히 사용자 입력에 반응한다는 것입니다.

결론

AnimationWorklet에는 많은 패싯과 AnimationWorklet이 웹에 제공하는 이점이 있습니다. 명확한 이점은 애니메이션을 더 세밀하게 제어할 수 있게 해주며 웹 애니메이션에 새로운 차원의 시각적 충실도를 제공합니다. 하지만 API는 설계를 통해 앱이 버벅거림에 대한 복원력을 높이고 모든 새로운 기능에 동시에 접근할 수 있게 됩니다.

Animation Worklet은 카나리아 버전이므로 Chrome 71 Google은 여러분이 새롭고 멋진 웹 환경을 경험하고 의견을 보내주세요. 또한 폴리필 같은 API를 제공하지만 성능 격리를 제공하지는 않습니다.

CSS 전환 및 CSS 애니메이션은 여전히 유효합니다. 기본 애니메이션의 경우 훨씬 간단할 수 있습니다. 하지만 멋지군요. AnimationWorklet이 도움이 될 수 있습니다.