Hoạt hình Worklet của Houdini

Tăng hiệu quả cho ảnh động trong ứng dụng web

Tóm tắt: Worklet ảnh động cho phép bạn viết các ảnh động bắt buộc sẽ chạy ở tốc độ khung hình gốc của thiết bị để có được độ mượt mà không bị giật. giúp ảnh động của bạn chống lại hiện tượng giật luồng chính và có thể liên kết để cuộn thay vì thời gian. Worklet ảnh động nằm trong Chrome Canary (phía sau "Các tính năng của Nền tảng web thử nghiệm" cờ) và chúng tôi đang lên kế hoạch Dùng thử theo nguyên gốc cho Chrome 71. Bạn có thể bắt đầu sử dụng tài khoản này làm một cải tiến tăng dần hôm nay.

Một API Ảnh động khác?

Thực ra là không, đó là phần mở rộng của những gì chúng tôi đã có và vì lý do chính đáng! Hãy bắt đầu từ đầu. Nếu bạn muốn tạo ảnh động cho bất kỳ phần tử DOM nào trên web hôm nay, bạn có 2 1⁄2 lựa chọn: Chuyển đổi CSS cho hiệu ứng chuyển tiếp A sang B đơn giản, Ảnh động CSS cho các ảnh động dựa trên thời gian có khả năng theo chu kỳ, phức tạp hơn và API Ảnh động trên web (WAAPI) cho các ảnh động phức tạp gần như tuỳ ý. Ma trận hỗ trợ của WAAPI trông khá buồn tẻ, nhưng video đang được chuyển đến. Cho đến lúc đó, có một polyfill.

Điểm chung của tất cả những phương thức này là chúng không có trạng thái và dựa trên thời gian. Tuy nhiên, một số hiệu ứng mà nhà phát triển đang thử không phải là theo thời gian hoặc phi trạng thái. Ví dụ: ứng dụng cuộn thị sai khét tiếng là, vì ngụ ý tên, theo hướng cuộn. Hiện nay, việc triển khai trình cuộn thị sai hiệu suất trên web thật khó khăn một cách đáng ngạc nhiên.

Còn tính không có trạng thái thì sao? Hãy nghĩ về thanh địa chỉ của Chrome trên Android, cho ví dụ: Nếu bạn di chuyển xuống, thao tác này sẽ cuộn ra khỏi chế độ xem. Tuy nhiên, khi bạn cuộn lên, nút sẽ xuất hiện trở lại ngay cả khi bạn đã đi được nửa đường xuống trang đó. Ảnh động không chỉ phụ thuộc vào vị trí cuộn mà còn phụ thuộc vào hướng cuộn trước đó của bạn. Đó là có trạng thái.

Một vấn đề khác là tạo kiểu cho thanh cuộn. Chúng nổi tiếng là không cách điệu — hoặc là không đủ kiểu cách. Nếu tôi muốn một chú mèo nyan làm thanh cuộn của mình thì sao? Cho dù bạn chọn kỹ thuật nào, việc tạo thanh cuộn tuỳ chỉnh cũng không hiệu suất cao hay dễ dàng.

Vấn đề là tất cả những điều này đều ngớ ngẩn và khó thực hiện được triển khai hiệu quả. Hầu hết các công cụ này đều dựa vào sự kiện và/hoặc requestAnimationFrame, có thể giữ cho bạn ở tốc độ 60 khung hình/giây, ngay cả khi màn hình đang có khả năng chạy ở tốc độ 90 khung hình/giây, 120 khung hình/giây hoặc cao hơn và sử dụng một phần nhỏ ngân sách khung cho luồng chính quý giá.

Worklet ảnh động mở rộng chức năng của ngăn xếp ảnh động trên web để giúp các loại hiệu ứng này dễ dàng hơn. Trước khi tìm hiểu, hãy chắc chắn rằng chúng ta cập nhật thông tin cơ bản về ảnh động.

Sơ lược về ảnh động và dòng thời gian

WAAPI và Worklet ảnh động sử dụng rộng rãi dòng thời gian để cho phép bạn sắp xếp các hoạt ảnh và hiệu ứng theo cách mà bạn muốn. Phần này là một cập nhật nhanh hoặc giới thiệu về dòng thời gian cũng như cách thức hoạt động với ảnh động.

Mỗi tài liệu có document.timeline. Nó bắt đầu từ 0 khi tài liệu là được tạo và tính số mili giây kể từ khi tài liệu bắt đầu hiện có. Toàn bộ hoạt ảnh của tài liệu hoạt động tương ứng với dòng thời gian này.

Để làm cho mọi thứ cụ thể hơn, hãy xem đoạn mã WAAPI này

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();

Khi chúng ta gọi animation.play(), ảnh động sẽ sử dụng currentTime của dòng thời gian làm thời gian bắt đầu. Ảnh động của chúng ta có độ trễ 3000 mili giây, tức là ảnh động sẽ bắt đầu (hoặc trở thành "đang hoạt động") khi dòng thời gian đến "startTime"

  • 3000. After that time, the animation engine will animate the given element from the first keyframe (dịchX(0)), through all intermediate keyframes (dịchX(500px)) all the way to the last keyframe (dịchY(500px)) in exactly 2000ms, as prescribed by thethời lượngoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'shiện tạiisthời gian bắt đầu+3000+1000and the last keyframe atthời gian bắt đầu+3000+2000. Vấn đề là dòng thời gian kiểm soát vị trí của chúng ta trong ảnh động!

Sau khi đạt đến khung hình chính cuối cùng, ảnh động sẽ quay lại khung hình đầu tiên khung hình chính và bắt đầu vòng lặp tiếp theo của ảnh động. Quá trình này lặp lại một tổng cộng 3 lần kể từ khi đặt iterations: 3. Nếu chúng tôi muốn hoạt ảnh không bao giờ dừng lại, chúng ta sẽ viết iterations: Number.POSITIVE_INFINITY. Sau đây là kết quả của mã ở trên.

WAAPI vô cùng mạnh mẽ và có nhiều tính năng khác trong API này như tốc độ, độ lệch bắt đầu, trọng số khung hình chính và hành vi tô màu sẽ thổi của bài viết này. Nếu muốn biết thêm, bạn nên đọc bài viết này về thủ thuật CSS về Ảnh động CSS.

Viết Worklet ảnh động

Giờ đây, khi đã nắm được khái niệm dòng thời gian, chúng ta có thể bắt đầu xem xét Worklet ảnh động và cách công cụ này cho phép bạn làm rối dòng thời gian! Ảnh động Worklet API không chỉ dựa trên WAAPI, mà còn – theo nghĩa là web có thể mở rộng – là một dữ liệu nguyên gốc cấp thấp hơn giải thích cách hoạt động của WAAPI. Xét về cú pháp, chúng rất giống nhau:

Worklet ảnh động Hoạt động trên web và ứng dụng
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();
        

Sự khác biệt nằm ở tham số đầu tiên, tức là tên của worklet giúp thúc đẩy hoạt ảnh này.

Phát hiện tính năng

Chrome là trình duyệt đầu tiên cung cấp tính năng này nên bạn cần đảm bảo mã không chỉ mong đợi AnimationWorklet có mặt. Vì vậy, trước khi tải worklet, chúng ta sẽ phát hiện xem trình duyệt của người dùng có hỗ trợ AnimationWorklet bằng một bước kiểm tra đơn giản:

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

Đang tải một worklet

Worklet là một khái niệm mới được nhóm đặc nhiệm Houdini đưa ra nhằm khiến nhiều các API mới dễ dàng hơn trong việc xây dựng và mở rộng quy mô. Chúng ta sẽ đề cập đến chi tiết về các Worklet muộn hơn một chút, nhưng để đơn giản, bạn có thể coi chúng là quảng cáo rẻ và hiện tại, luồng nhẹ (như worker).

Chúng ta cần đảm bảo đã tải một worklet có tên là "passpass" (thông qua), trước khi khai báo ảnh động:

// 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;
    }
  }
);

Điều gì đang xảy ra ở đây? Chúng ta đang đăng ký một lớp làm hoạ sĩ ảnh động bằng cách sử dụng Lệnh gọi registerAnimator() của AnimationWorklet, đặt tên là "thông qua". Tên này giống với tên mà chúng ta đã sử dụng trong hàm khởi tạo WorkletAnimation() ở trên. Khi đăng ký đã hoàn tất, lời hứa mà addModule() trả về sẽ giải quyết và chúng ta có thể bắt đầu tạo ảnh động bằng cách dùng worklet đó.

Phương thức animate() của thực thể sẽ được gọi cho mọi khung trình duyệt muốn kết xuất, truyền currentTime của dòng thời gian của ảnh động cũng như tác động hiện đang được xử lý. Chúng ta chỉ có một thì KeyframeEffect và chúng ta đang sử dụng currentTime để đặt giá trị localTime. Do đó, hoạ sĩ hoạt hình này được gọi là "thông qua". Với mã này cho worklet, WAAPI và AnimationWorklet ở trên hoạt động chính xác như bạn có thể thấy trong bản minh hoạ.

Thời gian

Tham số currentTime của phương thức animate()currentTime của dòng thời gian mà chúng tôi đã truyền đến hàm khởi tạo WorkletAnimation(). Trong phần trước ví dụ: chúng tôi vừa chuyển thời gian đó cho hiệu ứng. Nhưng vì đây là bằng đoạn mã JavaScript và chúng ta có thể xáo trộn thời gian 💫

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)
      );
    }
  }
);

Chúng ta sẽ lấy Math.sin() của currentTime và gán lại giá trị đó thành khoảng [0; 2000], đó là phạm vi thời gian mà hiệu ứng của chúng ta được xác định. Bây giờ ảnh động trông rất khác mà không cần đã thay đổi khung hình chính hoặc tuỳ chọn của ảnh động. Mã worklet có thể là phức tạp một cách tuỳ ý và cho phép bạn xác định bằng cách lập trình những hiệu ứng nào được phát theo thứ tự và phạm vi phát triển.

Lựa chọn thay vì Tuỳ chọn

Bạn có thể muốn sử dụng lại một worklet và thay đổi số của Worklet đó. Vì lý do này, Hàm khởi tạo WorkletAnimation cho phép bạn truyền một đối tượng tuỳ chọn đến 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();

Trong ví dụ này, cả hai ảnh động đều được điều khiển bằng cùng một mã, nhưng với các tuỳ chọn khác nhau.

Cung cấp thông tin về tiểu bang địa phương của bạn!

Như tôi đã gợi ý trước đó, một trong những vấn đề chính mà worklet hoạt ảnh cần giải quyết là ảnh động có trạng thái. Worklet ảnh động được phép duy trì trạng thái. Tuy nhiên, một đặc điểm cốt lõi của Worklet là chúng có thể được di chuyển sang luồng hoặc thậm chí bị huỷ để tiết kiệm tài nguyên, điều này cũng sẽ phá huỷ trạng thái. Để ngăn chặn tình trạng mất trạng thái, worklet ảnh động sẽ cung cấp một nội dung móc được gọi trước khi một worklet huỷ mà bạn có thể dùng để trả về một trạng thái . Đối tượng đó sẽ được truyền đến hàm khởi tạo khi worklet được tạo lại. Khi tạo ban đầu, tham số đó sẽ là 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,
      };
    }
  }
);

Mỗi lần làm mới bản minh hoạ này, bạn sẽ có tỷ lệ 50/50 khả năng hình vuông sẽ quay theo hướng nào. Nếu trình duyệt bị chia nhỏ worklet và di chuyển nó sang một luồng khác, thì sẽ có một luồng khác Math.random() lệnh gọi tạo. Việc này có thể gây ra sự thay đổi đột ngột chỉ đường. Để đảm bảo điều đó không xảy ra, chúng ta trả về ảnh động hướng được chọn ngẫu nhiên làm trạng thái và sử dụng hướng đó trong hàm khởi tạo nếu được cung cấp.

Đưa vào không gian không gian-thời gian: ScrollDòng thời gian

Như phần trước cho thấy, AnimationWorklet cho phép chúng ta bằng cách lập trình để xác định mức độ ảnh hưởng của việc tiến triển tiến trình đến các ảnh hưởng của ảnh động. Nhưng cho đến nay, dòng thời gian của chúng tôi vẫn luôn là document.timeline, điều đó theo dõi thời gian.

ScrollTimeline mở ra các khả năng mới và cho phép bạn tạo ảnh động bằng cách cuộn thay vì dành cho thời gian. Chúng tôi sẽ sử dụng lại "thông qua" worklet thực hiện việc này bản minh hoạ:

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();

Thay vì truyền document.timeline, chúng ta sẽ tạo một ScrollTimeline mới. Có thể bạn đã đoán ra, ScrollTimeline không sử dụng thời gian nhưng Vị trí cuộn của scrollSource để đặt currentTime trong worklet. Hiện hữu cuộn hết cỡ lên trên cùng (hoặc sang trái) có nghĩa là currentTime = 0, trong khi được cuộn đến tận cùng dưới cùng (hoặc sang phải) sẽ đặt currentTime thành timeRange. Nếu bạn cuộn ô trong này bản minh hoạ, bạn có thể kiểm soát vị trí của hộp màu đỏ.

Nếu bạn tạo ScrollTimeline có một phần tử không cuộn, thì currentTime của dòng thời gian sẽ là NaN. Đặc biệt là với thiết kế thích ứng trong xin lưu ý rằng bạn phải luôn chuẩn bị sẵn sàng cho NaNcurrentTime của bạn. Thông thường hợp lý để đặt mặc định thành giá trị 0.

Việc liên kết ảnh động với vị trí cuộn là điều mà từ lâu, bạn đã tìm kiếm nhưng chưa bao giờ thực sự đạt được độ chân thực này (ngoài độ tin cậy) với CSS3D). Worklet ảnh động cho phép các hiệu ứng này được triển khai theo cách đơn giản mà vẫn mang lại hiệu suất cao. Ví dụ: hiệu ứng cuộn thị sai như thế này bản minh hoạ cho thấy hiện chỉ cần một vài dòng để xác định ảnh động di chuyển.

Tìm hiểu sâu

Worklet

Worklet là các ngữ cảnh JavaScript có phạm vi riêng biệt và một API rất nhỏ nền tảng. Nền tảng API nhỏ cho phép tối ưu hoá linh hoạt hơn từ trình duyệt, đặc biệt là trên các thiết bị cấp thấp. Ngoài ra, Worklet không bị ràng buộc với một vòng lặp sự kiện cụ thể, nhưng có thể di chuyển giữa các chuỗi khi cần. Đây là đặc biệt quan trọng đối với AnimationWorklet.

NSync của bộ tổng hợp

Bạn có thể biết rằng một số thuộc tính CSS nhất định có tốc độ tạo ảnh động nhanh, trong khi các thuộc tính khác thì thì không. Một số thuộc tính chỉ cần một số tác vụ trên GPU để tạo ảnh động, trong khi các thuộc tính khác buộc trình duyệt bố cục lại toàn bộ tài liệu.

Trong Chrome (như trong nhiều trình duyệt khác), chúng tôi có một quy trình gọi là trình tổng hợp, công việc của ai là — và tôi đơn giản hoá rất nhiều ở đây — sắp xếp các lớp và rồi sử dụng GPU để cập nhật màn hình thường xuyên nhất có thể, lý tưởng là tốc độ nhanh nhất mà màn hình có thể cập nhật (thường là 60 Hz). Tùy thuộc vào Các thuộc tính CSS đang được tạo ảnh động, trình duyệt có thể chỉ cần có các trình tổng hợp sẽ thực hiện công việc này, trong khi các thuộc tính khác cần chạy bố cục, vốn là mà chỉ luồng chính có thể thực hiện. Tuỳ thuộc vào tài sản mà bạn đang định tạo ảnh động, worklet hoạt ảnh của bạn sẽ được liên kết với luồng hoặc chạy trong một luồng riêng biệt đồng bộ hoá với trình tổng hợp.

Vỗ cổ tay

Thường chỉ có một quy trình tổng hợp có thể được chia sẻ trên nhiều thẻ, vì GPU là một tài nguyên được cạnh tranh cao. Nếu trình tổng hợp nhận được bị chặn bằng cách nào đó, toàn bộ trình duyệt ngừng hoạt động và không còn phản hồi với hoạt động đầu vào của người dùng. Bạn cần tránh sử dụng lựa chọn này bằng mọi giá. Vậy điều gì sẽ xảy ra nếu worklet không thể phân phối dữ liệu mà trình tổng hợp cần kịp thời để khung hình đã hiển thị không?

Nếu điều này xảy ra, worklet sẽ được phép "t Trượt" theo thông số kỹ thuật. Bị tụt lại phía sau trình tổng hợp và trình tổng hợp được phép sử dụng lại dữ liệu của khung hình gần đây nhất để duy trì tốc độ khung hình. Về mặt trực quan, hiệu ứng này trông giống như giật, nhưng phần lớn sự khác biệt là trình duyệt vẫn phản hồi với hoạt động đầu vào của người dùng.

Kết luận

Có nhiều khía cạnh của AnimationWorklet và những lợi ích mà tính năng này mang lại cho web. Lợi ích rõ ràng là có nhiều quyền kiểm soát hơn đối với ảnh động và các cách thức mới để lái để mang lại độ trung thực trực quan mới cho web. Tuy nhiên, các API cũng cho phép bạn làm cho ứng dụng có khả năng chống giật tốt hơn trong khi quyền truy cập vào tất cả những lợi ích mới cùng một lúc.

Worklet ảnh động có trong Canary và chúng tôi đang hướng đến việc dùng thử theo nguyên gốc Chrome 71. Chúng tôi rất mong chờ những trải nghiệm tuyệt vời trên web và lắng nghe ý kiến của bạn về những điều chúng tôi có thể cải thiện. Ngoài ra còn có polyfill cung cấp cho bạn cùng một API nhưng không tách biệt hiệu suất.

Lưu ý rằng hiệu ứng chuyển đổi CSS và ảnh động CSS vẫn hợp lệ và có thể đơn giản hơn nhiều đối với ảnh động cơ bản. Nhưng nếu bạn cần đi yêu thích, AnimationWorklet sẽ hỗ trợ bạn!