Trong thế giới của chương trình đổ 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% trang web có phần tử <canvas>
và 98% tất cả các 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ự).
Phải thừa nhận rằng API này hơi lỗi thời khi nói đến bản vẽ 2D hiện đại. May mắn thay, chúng tôi đã nỗ lực triển khai các tính năng mới trong Canvas2D để bắt kịp CSS, đơn giản hoá tính năng công thái học và cải thiện hiệu suất.
Phần 1: Cập nhật kiến thức về 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 tròn
Hình chữ nhật bo tròn: nền tảng của Internet, của điện toán, gần như của nền văn minh.
Nghiêm túc mà nói, hình chữ nhật bo tròn cực kỳ hữu ích: dưới dạng nút, bong bóng trò chuyện, hình thu nhỏ, bong bóng lời nói, v.v. Bạn luôn có thể tạo một hình chữ nhật bo tròn trong Canvas2D, chỉ là hình ảnh có hơi lộn xộn:
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, khiêm tố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 nhận một mảng cho đối số borderRadius
có tối đa bốn số. Các bán kính này kiểm soát bốn góc của hình chữ nhật bo tròn theo cách tương tự như đối với CSS. Ví dụ:
ctx.roundRect(10, 10, 200, 100, [15, 50, 30]);
Hãy xem bản minh hoạ để tìm hiểu thêm!
Chuyển màu hình nón
Bạn đã thấy độ dốc theo đường thẳng:
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);
Chuyển màu xuyên tâm:
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);
Nhưng 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);
Đối tượng sửa đổi văn bản
Khả năng kết xuất văn bản của Canvas2Ds đã bị tụt hậu đáng kể. Chrome đã thêm một số thuộc tính mới vào quá trình kết xuất văn bản Canvas2D:
- ctx.letterSpacing
- ctx.wordSpacing
- ctx.fontVariant
- ctx.fontKerning
- ctx.fontStretch
- ctx.textDecoration
- ctx.textUnderlinePosition
- ctx.textRendering
Tất cả các thuộc tính này đều khớp với các thuộc tính CSS tương ứng có cùng tên.
Phần 2: tinh chỉnh về công thái học
Trước đây, một số việc có thể thực hiện được với Canvas2D nhưng quá phức tạp để triển khai. Sau đây là một số điểm cải tiế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ỏ ngớ ngẩn để vẽ một mẫu retro:
draw90sPattern();
Tuyệt vời! Giờ đây, khi đã hoàn tất mẫu đó, tôi muốn xoá canvas và vẽ một hình khác.
Khoan đã, làm cách nào để xoá canvas? Ồ, vâng! Tất nhiên là ctx.clearRect()
.
ctx.clearRect(0, 0, canvas.width, canvas.height);
Ồ… cách đó không hiệu quả. Ồ, vâng! Trước tiên, tôi phải đặt lại phép biến đổi:
ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Tuyệt lắm! Một canvas trống đẹp mắt. 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();
Grrrrừ! Chưa chính xác! 😡 Dòng bổ sung đó có tác dụng gì ở đây? Ngoài ra, tại sao nó lại có màu hồng? Được rồi, hãy kiểm tra StackOverflow.
canvas.width = canvas.width;
Tại sao điều này lại ngớ ngẩn? Tại sao việc này lại khó khăn?
Không còn nữa. Với API mới, chúng tôi đã có một đột phá đơn giản, tinh tế và đẹp mắt:
ctx.reset();
Xin lỗi vì đã để bạn phải chờ lâu.
Bộ lọc
Bộ lọc SVG là một thế giới riêng. Nếu bạn mới sử dụng bộ lọc này, thì bạn nên đọc bài viết Nghệ thuật về bộ lọc SVG và tại sao lại tuyệt vời. Tài liệu này sẽ cho thấy một số tiềm năng tuyệt vời của các bộ lọc này.
Bộ lọc kiểu SVG đã có cho Canvas2D! Bạn chỉ cần sẵn sàng truyền bộ lọc dưới dạng một 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 rối loạn mẫu của chúng ta:
Nhưng nếu bạn muốn làm như trên nhưng vẫn sử dụng JavaScript và không làm rối mã nguồn bằng các chuỗi thì sao? Với API mới, bạn hoàn toàn có thể làm được điều này.
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 },
]);
Dễ như ăn bánh! Hãy thử và chơi với 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 nếu 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 trang web của họ một cách chi tiết hơn 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 từ canvas. Quá trình này có thể rất chậm. API mới cung cấp cho bạn một cách để đánh dấu rõ ràng canvas để đọc lại (ví dụ: đối với hiệu ứng tạo sinh).
Nhờ đó, bạn có thể tối ưu hoá mọi thứ và đảm bảo canvas nhanh chóng cho nhiều trường hợp sử dụng hơn. Tính năng này đã có trong Firefox được 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 thông số kỹ thuật của canvas.
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
Mất ngữ cảnh
Hãy cùng làm cho các thẻ buồn trở lại vui vẻ! Trong trường hợp ứng dụng hết bộ nhớ GPU hoặc một số sự cố khác xảy ra với canvas, giờ đây, bạ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à tổn thất của canvas, WHATWG có nội dung giải thích hay trên trang wiki của họ.
Kết luận
Dù bạn là người mới sử dụng Canvas2D, đã sử dụng Canvas2D trong nhiều năm hay đã tránh sử dụng Canvas2D trong nhiều năm, tôi vẫn muốn bạn xem xét lại Canvas2D. Cửa sổ API liền kề luôn có mặt ở đó.