Luôn là bạn, Canvas2D

Aaron Krajeski
Aaron Krajeski

Trong thế giới bóng đổ, lưới và bộ lọc, Canvas2D có thể không làm bạn hào hứng. Nhưng bạn nên làm vậy! 30–40% các trang web có phần tử <canvas> và 98% trong số tất cả ảnh in trên vải canvas sử dụng ngữ cảnh kết xuất Canvas2D. Có Canvas2D trong ô tô, trên tủ lạnh và trong không gian (thực sự là).

Phải thừa nhận rằng API này hơi chậm so với thời gian nói đến bản vẽ 2D hiện đại. May mắn là chúng tôi đã nỗ lực triển khai các tính năng mới trong Canvas2D để bắt kịp với CSS, tinh giản tính công thái học và cải thiện hiệu suất.

Phần 1: Bắt kịp CSS

CSS có một số lệnh vẽ bị thiếu trong Canvas2D. Với API mới, chúng tôi đã thêm một số tính năng được yêu cầu nhiều nhất:

Hình chữ nhật bo cạnh

Hình chữ nhật bo tròn: nền tảng của Internet, của máy tính, thậm chí, của nền văn minh.

Nhìn chung, hình chữ nhật bo tròn cực kỳ hữu ích, chẳng hạn như bong bóng trò chuyện, hình thu nhỏ, bong bóng lời thoại và nhiều cách khác. Bạn có thể tạo một hình chữ nhật bo tròn trong Canvas2D bất cứ lúc nào. Điều này hơi lộn xộn một chút:

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'magenta';

const top = 10;
const left = 10;
const width = 200;
const height = 100;
const radius = 20;

ctx.beginPath();
ctx.moveTo(left + radius, top);
ctx.lineTo(left + width - radius, top);
ctx.arcTo(left + width, top, left + width, top + radius, radius);
ctx.lineTo(left + width, top + height - radius);
ctx.arcTo(left + width, top + height, left + width - radius, top + height, radius);
ctx.lineTo(left + radius, top + height);
ctx.arcTo(left, top + height, left, top + height - radius, radius);
ctx.lineTo(left, top + radius);
ctx.arcTo(left, top, left + radius, top, radius);
ctx.stroke();

Tất cả những điều này là cần thiết cho một hình chữ nhật bo tròn đơn giản và khiêm tốn:

Một hình chữ nhật góc tròn.

Với API mới, có một phương thức roundRect().

ctx.roundRect(upper, left, width, height, borderRadius);

Vì vậy, nội dung trên có thể được thay thế hoàn toàn bằng:

ctx.roundRect(10, 10, 200, 100, 20);

Phương thức ctx.roundRect() cũng lấy một mảng cho đối số borderRadius gồm tối đa 4 số. Các bán kính này kiểm soát 4 góc của hình chữ nhật bo tròn theo cách giống đối với CSS. Ví dụ:

ctx.roundRect(10, 10, 200, 100, [15, 50, 30]);

Hãy xem bản minh hoạ để khám phá!

Chuyển màu conic

Bạn đã thấy màu chuyển tiếp tuyến tính:

const gradient = ctx.createLinearGradient(0, 0, 200, 100);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(0.5, 'magenta');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);

Độ dốc theo tuyến tính.

Chuyển màu dạng hình tròn:

const radialGradient = ctx.createRadialGradient(150, 75, 10, 150, 75, 70);
radialGradient.addColorStop(0, 'white');
radialGradient.addColorStop(0.5, 'magenta');
radialGradient.addColorStop(1, 'lightblue');

ctx.fillStyle = radialGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);

Độ dốc theo bán kính.

Nhưng thế còn hiệu ứng chuyển màu hình nón đẹp thì sao?

const grad = ctx.createConicGradient(0, 100, 100);

grad.addColorStop(0, 'red');
grad.addColorStop(0.25, 'orange');
grad.addColorStop(0.5, 'yellow');
grad.addColorStop(0.75, 'green');
grad.addColorStop(1, 'blue');

ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 200);

Một dải chuyển màu hình nón.

Đối tượng sửa đổi văn bản

Khả năng hiển thị văn bản của Canvas2D đã bị chậm trễ. Chrome đã thêm một số thuộc tính mới vào tính năng kết xuất văn bản Canvas2D:

Tất cả các thuộc tính này đều khớp với các thuộc tính CSS có cùng tên.

Phần 2: Điều chỉnh công thái học

Trước đây, bạn có thể triển khai một số tính năng với Canvas2D, nhưng việc triển khai không cần thiết. Dưới đây là một số điểm cải thiện về chất lượng cuộc sống dành cho các nhà phát triển JavaScript muốn sử dụng Canvas2D:

Đã đặt lại ngữ cảnh

Để giải thích cách xoá canvas, tôi đã viết một hàm nhỏ để vẽ một mô hình cổ điển:

draw90sPattern();

Một hoa văn cổ điển gồm các hình tam giác và hình vuông.

Vậy thì tuyệt quá! Bây giờ, tôi đã hoàn tất với mẫu đó, tôi muốn xoá canvas và vẽ hình khác. Chờ đã, làm cách nào để xoá lại ảnh in trên vải canvas? Thật tuyệt! ctx.clearRect(), tất nhiên rồi.

ctx.clearRect(0, 0, canvas.width, canvas.height);

Hừm... cách đó không hiệu quả. Thật tuyệt! Trước tiên, tôi phải đặt lại sự biến đổi:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Một canvas trống.

Tuyệt lắm! Một bức canvas trống đẹp. Bây giờ, hãy bắt đầu vẽ một đường ngang đẹp mắt:

ctx.moveTo(10, 10);
ctx.lineTo(canvas.width, 10);
ctx.stroke();

Một đường chéo và một đường chéo.

Grrrrừ! Chưa chính xác! 😡 Dòng bổ sung đó có chức năng gì ở đây? Ngoài ra, tại sao lại có màu hồng? Hãy cùng kiểm tra StackOverflow.

canvas.width = canvas.width;

Tại sao câu này lại ngớ ngẩn? Tại sao việc này khó?

Giờ đây, không còn là lần nào nữa. Với API mới, chúng tôi có phiên bản đột phá đơn giản, thanh lịch và đẹp mắt:

ctx.reset();

Rất tiếc vì việc này mất nhiều thời gian.

Bộ lọc

Bộ lọc SVG là một thế giới của chính chúng. Nếu mới sử dụng các bộ lọc này, bạn nên đọc bài viết Khám phá nghệ thuật của các bộ lọc SVG và lý do khiến nó tuyệt vời. Phần này cho thấy một số tiềm năng tuyệt vời của những bộ lọc này.

Đã có bộ lọc kiểu SVG cho Canvas2D! Bạn chỉ cần sẵn sàng chuyển bộ lọc dưới dạng URL trỏ đến một phần tử bộ lọc SVG khác trên trang:

<svg>
  <defs>
    <filter id="svgFilter">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
      <feConvolveMatrix kernelMatrix="-3 0 0 0 0.5 0 0 0 3" />
      <feColorMatrix type="hueRotate" values="90" />
    </filter>
  </defs>
</svg>
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 400;
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

ctx.filter = "url('#svgFilter')";
draw90sPattern(ctx);

Điều này làm hỏng mẫu của chúng ta khá tốt:

Hoa văn cổ điển có áp dụng hiệu ứng được làm mờ.

Nhưng, điều gì sẽ xảy ra nếu bạn muốn làm như trên nhưng vẫn ở trong JavaScript và không nhầm lẫn với các chuỗi? Điều này hoàn toàn có thể xảy ra với API mới.

ctx.filter = new CanvasFilter([
  { filter: 'gaussianBlur', stdDeviation: 5 },
  {
    filter: 'convolveMatrix',
    kernelMatrix: [
      [-3, 0, 0],
      [0, 0.5, 0],
      [0, 0, 3],
    ],
  },
  { filter: 'colorMatrix', type: 'hueRotate', values: 90 },
]);

Thật dễ dàng! Hãy dùng thử và dùng các tham số trong bản minh hoạ tại đây.

Phần 3: cải thiện hiệu suất

Với API Canvas2D mới, chúng tôi cũng muốn cải thiện hiệu suất khi có thể. Chúng tôi đã thêm một số tính năng để giúp nhà phát triển kiểm soát chi tiết hơn các trang web của họ và cho phép tốc độ khung hình mượt mà nhất có thể:

Sẽ đọc thường xuyên

Sử dụng getImageData() để đọc lại dữ liệu pixel trên canvas. Tốc độ có thể rất chậm. API mới cung cấp cho bạn cách đánh dấu rõ ràng một canvas để đọc lại (ví dụ: đối với các hiệu ứng tạo sinh). Điều này cho phép bạn tối ưu hoá mọi thứ và đảm bảo canvas luôn hoạt động nhanh cho nhiều trường hợp sử dụng hơn. Tính năng này đã có trên Firefox một thời gian và cuối cùng chúng tôi cũng đưa tính năng này vào quy cách canvas.

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });

Mất ngữ cảnh

Hãy cùng tạo thẻ buồn vui vẻ trở lại! Trong trường hợp ứng dụng hết bộ nhớ GPU hoặc một số thảm hoạ khác xảy ra với canvas của bạn, bạn hiện có thể nhận lệnh gọi lại và vẽ lại nếu cần:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

canvas.addEventListener('contextlost', onContextLost);
canvas.addEventListener('contextrestored', redraw);

Nếu bạn muốn đọc thêm về bối cảnh và sự mất mát của ảnh in trên vải canvas, WhatWG có thể xem phần giải thích rõ trên wiki của họ.

Kết luận

Dù bạn mới sử dụng Canvas2D, đã sử dụng hay đã tránh sử dụng Canvas2D trong nhiều năm qua, thì tôi vẫn muốn giới thiệu với bạn một cái nhìn khác về canvas này. Đó là cửa hàng API đã tồn tại từ trước đến nay.

Xác nhận

Hình ảnh chính của Sandie Clarke trên Unsplash.