OffscreenCanvas – tăng tốc các hoạt động canvas của bạn bằng một trình thực thi web

Tim Dresser

Canvas là một cách phổ biến để vẽ mọi loại đồ hoạ trên màn hình và là điểm truy cập vào thế giới WebGL. Bạn có thể dùng GPU để vẽ hình dạng, hình ảnh, chạy ảnh động hoặc thậm chí hiển thị và xử lý nội dung video. Công nghệ này thường được dùng để tạo trải nghiệm người dùng đẹp mắt trong các ứng dụng web giàu nội dung nghe nhìn và trò chơi trực tuyến.

Canvas có thể lập trình, tức là nội dung được vẽ trên canvas có thể được tạo bằng cách lập trình, chẳng hạn như trong JavaScript. Điều này giúp canvas trở nên linh hoạt hơn.

Đồng thời, trong các trang web hiện đại, việc thực thi tập lệnh là một trong những nguồn gây ra vấn đề về khả năng phản hồi của người dùng thường gặp nhất. Vì logic và kết xuất canvas diễn ra trên cùng một luồng với hoạt động tương tác của người dùng, nên các phép tính (đôi khi nặng) liên quan đến ảnh động có thể gây hại cho hiệu suất thực và hiệu suất cảm nhận được của ứng dụng.

Rất may, OffscreenCanvas là một giải pháp cho mối đe doạ đó.

Hỗ trợ trình duyệt

  • Chrome: 69.
  • Edge: 79.
  • Firefox: 105.
  • Safari: 16.4.

Nguồn

Trước đây, các tính năng vẽ trên canvas được liên kết với phần tử <canvas>, nghĩa là nó trực tiếp phụ thuộc vào DOM. Như tên gọi, OffscreenCanvas tách DOM và Canvas API bằng cách di chuyển API này ra khỏi màn hình.

Nhờ việc tách biệt này, quá trình kết xuất OffscreenCanvas được tách hoàn toàn khỏi DOM và do đó mang lại một số điểm cải tiến về tốc độ so với canvas thông thường vì không có quá trình đồng bộ hoá giữa hai loại này.

Tuy nhiên, bạn cũng có thể sử dụng tính năng này trong một Worker trên web, mặc dù không có DOM. Điều này cho phép tất cả các loại trường hợp sử dụng thú vị.

Sử dụng OffscreenCanvas trong worker

Worker (Trình chạy) là phiên bản luồng của web – cho phép bạn chạy các tác vụ ở chế độ nền.

Việc di chuyển một số tập lệnh sang worker sẽ giúp ứng dụng của bạn có thêm không gian để thực hiện các tác vụ quan trọng đối với người dùng trên luồng chính. Nếu không có OffscreenCanvas, bạn sẽ không thể sử dụng API Canvas trong worker vì không có DOM.

OffscreenCanvas không phụ thuộc vào DOM nên có thể được sử dụng. Ví dụ sau đây sử dụng OffscreenCanvas để tính màu chuyển màu trong một worker:

// file: worker.js
function getGradientColor(percent) {
 
const canvas = new OffscreenCanvas(100, 1);
 
const ctx = canvas.getContext('2d');
 
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
  gradient
.addColorStop(0, 'red');
  gradient
.addColorStop(1, 'blue');
  ctx
.fillStyle = gradient;
  ctx
.fillRect(0, 0, ctx.canvas.width, 1);
 
const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
 
const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
 
return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}

getGradientColor
(40);  // rgba(152, 0, 104, 255 )

Bỏ chặn luồng chính

Việc chuyển phép tính nặng sang một worker cho phép bạn giải phóng đáng kể tài nguyên trên luồng chính. Sử dụng phương thức transferControlToOffscreen để phản chiếu canvas thông thường vào một thực thể OffscreenCanvas. Các thao tác áp dụng cho OffscreenCanvas sẽ tự động hiển thị trên canvas nguồn.

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker
.postMessage({canvas: offscreen}, [offscreen]);

Trong ví dụ sau, quá trình tính toán nặng xảy ra khi giao diện màu thay đổi. Quá trình này sẽ mất vài mili giây ngay cả trên máy tính nhanh. Bạn có thể chọn chạy ảnh động trên luồng chính hoặc trong worker. Trong trường hợp luồng chính, bạn không thể tương tác với nút trong khi tác vụ nặng đang chạy – luồng bị chặn. Trong trường hợp của worker, điều này không ảnh hưởng đến khả năng phản hồi của giao diện người dùng.

Bản minh hoạ

Điều này cũng hoạt động theo cách khác: luồng chính đang bận không ảnh hưởng đến ảnh động đang chạy trên worker. Bạn có thể sử dụng tính năng này để tránh hiện tượng giật hình và đảm bảo ảnh động mượt mà mặc dù có lưu lượng truy cập luồng chính, như trong bản minh hoạ sau.

Bản minh hoạ

Trong trường hợp canvas thông thường, ảnh động sẽ dừng khi luồng chính bị quá tải một cách nhân tạo, trong khi OffscreenCanvas dựa trên worker sẽ phát mượt mà.

Vì OffscreenCanvas API thường tương thích với Phần tử Canvas thông thường, nên bạn có thể sử dụng API này làm tính năng nâng cao dần, cũng như với một số thư viện đồ hoạ hàng đầu trên thị trường.

Ví dụ: bạn có thể phát hiện tính năng này và sử dụng tính năng này với Three.js (nếu có) bằng cách chỉ định tuỳ chọn canvas trong hàm khởi tạo trình kết xuất:

const canvasEl = document.querySelector('canvas');
const canvas =
 
'OffscreenCanvas' in window
   
? canvasEl.transferControlToOffscreen()
   
: canvasEl;
canvas
.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});

Một điểm cần lưu ý ở đây là Three.js dự kiến canvas sẽ có thuộc tính style.widthstyle.height. OffscreenCanvas, vì được tách hoàn toàn khỏi DOM, nên không có thuộc tính này. Vì vậy, bạn cần tự cung cấp thuộc tính này, bằng cách tạo bản nháp hoặc cung cấp logic liên kết các giá trị này với kích thước canvas ban đầu.

Phần sau đây cho biết cách chạy ảnh động Three.js cơ bản trong một worker:

Bản minh hoạ

Xin lưu ý rằng một số API liên quan đến DOM không có sẵn trong worker, vì vậy, nếu muốn sử dụng các tính năng Three.js nâng cao hơn như hoạ tiết, bạn có thể cần thêm giải pháp. Để biết một số ý tưởng về cách bắt đầu thử nghiệm với các tính năng này, hãy xem video từ Google I/O 2017.

Nếu bạn đang sử dụng nhiều tính năng đồ hoạ của canvas, thì OffscreenCanvas có thể ảnh hưởng tích cực đến hiệu suất của ứng dụng. Việc cung cấp ngữ cảnh kết xuất canvas cho worker sẽ tăng tính song song trong các ứng dụng web và sử dụng hệ thống đa lõi hiệu quả hơn.

Tài nguyên khác