Các khả năng mới trong Chrome 65
CSS Paint API (còn gọi là "CSS Custom Paint" hoặc "worklet vẽ của Houdini") được bật theo mặc định kể từ Chrome 65. Giải pháp này là gì? Bạn có thể làm gì với ứng dụng này? Cách thức hoạt động như thế nào? Hãy đọc tiếp nhé…
CSS Paint API cho phép bạn tạo hình ảnh theo phương thức lập trình bất cứ khi nào một thuộc tính CSS cần hình ảnh. Các thuộc tính như background-image
hoặc border-image
thường được dùng với url()
để tải tệp hình ảnh hoặc với các hàm tích hợp sẵn CSS như linear-gradient()
. Thay vì sử dụng các lớp đó, giờ đây, bạn có thể sử dụng paint(myPainter)
để tham chiếu đến worklet vẽ.
Viết một worklet sơn
Để xác định một worklet vẽ có tên là myPainter
, chúng ta cần tải tệp worklet vẽ CSS bằng CSS.paintWorklet.addModule('my-paint-worklet.js')
. Trong tệp đó, chúng ta có thể sử dụng hàm registerPaint
để đăng ký một lớp công cụ vẽ:
class MyPainter {
paint(ctx, geometry, properties) {
// ...
}
}
registerPaint('myPainter', MyPainter);
Bên trong lệnh gọi lại paint()
, chúng ta có thể sử dụng ctx
giống như cách chúng ta sử dụng CanvasRenderingContext2D
mà chúng ta biết từ <canvas>
. Nếu biết cách vẽ trong <canvas>
, bạn có thể vẽ trong một công cụ vẽ! geometry
cho chúng ta biết chiều rộng và chiều cao của canvas mà chúng ta có thể sử dụng. properties
Tôi sẽ giải thích ở phần sau của bài viết này.
Để làm ví dụ giới thiệu, hãy viết một worklet vẽ ô bàn cờ và sử dụng worklet đó làm hình nền của <textarea>
. (Tôi đang sử dụng textarea vì theo mặc định, textarea có thể đổi kích thước.):
<!-- index.html -->
<!doctype html>
<style>
textarea {
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
paint(ctx, geom, properties) {
// Use `ctx` as if it was a normal canvas
const colors = ['red', 'green', 'blue'];
const size = 32;
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
const color = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.fillStyle = color;
ctx.rect(x * size, y * size, size, size);
ctx.fill();
}
}
}
}
// Register our class under a specific name
registerPaint('checkerboard', CheckerboardPainter);
Nếu bạn đã từng sử dụng <canvas>
, thì mã này sẽ trông quen thuộc. Xem bản minh hoạ trực tiếp tại đây.
Điểm khác biệt so với việc sử dụng hình nền thông thường ở đây là mẫu sẽ được vẽ lại theo yêu cầu, bất cứ khi nào người dùng đổi kích thước textarea. Điều này có nghĩa là hình nền luôn có kích thước chính xác như cần thiết, bao gồm cả việc bù cho màn hình có mật độ điểm ảnh cao.
Điều đó khá thú vị, nhưng cũng khá tĩnh. Chúng ta có muốn viết một worklet mới mỗi khi muốn có cùng một mẫu nhưng với các hình vuông có kích thước khác nhau không? Câu trả lời là không!
Tham số hoá worklet
May mắn thay, công cụ vẽ có thể truy cập vào các thuộc tính CSS khác, đây là nơi tham số bổ sung properties
phát huy tác dụng. Bằng cách cung cấp cho lớp một thuộc tính inputProperties
tĩnh, bạn có thể đăng ký nhận các thay đổi đối với bất kỳ thuộc tính CSS nào, bao gồm cả các thuộc tính tuỳ chỉnh. Các giá trị sẽ được cung cấp cho bạn thông qua tham số properties
.
<!-- index.html -->
<!doctype html>
<style>
textarea {
/* The paint worklet subscribes to changes of these custom properties. */
--checkerboard-spacing: 10;
--checkerboard-size: 32;
background-image: paint(checkerboard);
}
</style>
<textarea></textarea>
<script>
CSS.paintWorklet.addModule('checkerboard.js');
</script>
// checkerboard.js
class CheckerboardPainter {
// inputProperties returns a list of CSS properties that this paint function gets access to
static get inputProperties() { return ['--checkerboard-spacing', '--checkerboard-size']; }
paint(ctx, geom, properties) {
// Paint worklet uses CSS Typed OM to model the input values.
// As of now, they are mostly wrappers around strings,
// but will be augmented to hold more accessible data over time.
const size = parseInt(properties.get('--checkerboard-size').toString());
const spacing = parseInt(properties.get('--checkerboard-spacing').toString());
const colors = ['red', 'green', 'blue'];
for(let y = 0; y < geom.height/size; y++) {
for(let x = 0; x < geom.width/size; x++) {
ctx.fillStyle = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.rect(x*(size + spacing), y*(size + spacing), size, size);
ctx.fill();
}
}
}
}
registerPaint('checkerboard', CheckerboardPainter);
Giờ đây, chúng ta có thể sử dụng cùng một mã cho tất cả các loại bàn cờ. Nhưng tốt hơn nữa, giờ đây, chúng ta có thể chuyển sang DevTools và tìm hiểu các giá trị cho đến khi tìm thấy giao diện phù hợp.
Các trình duyệt không hỗ trợ công cụ vẽ
Tại thời điểm viết bài, chỉ Chrome mới triển khai được công cụ paint worklet. Mặc dù có các tín hiệu tích cực từ tất cả các nhà cung cấp trình duyệt khác, nhưng tiến trình vẫn chưa có nhiều thay đổi. Để nắm bắt thông tin mới nhất, hãy thường xuyên kiểm tra bài viết Houdini đã sẵn sàng chưa? Trong thời gian chờ đợi, hãy nhớ sử dụng tính năng cải tiến tiến bộ để duy trì hoạt động của mã ngay cả khi không có tính năng hỗ trợ cho công cụ làm việc vẽ. Để đảm bảo mọi thứ hoạt động như mong đợi, bạn phải điều chỉnh mã của mình ở hai vị trí: CSS và JS.
Bạn có thể phát hiện tính năng hỗ trợ cho công cụ vẽ trong JS bằng cách kiểm tra đối tượng CSS
:
js
if ('paintWorklet' in CSS) {
CSS.paintWorklet.addModule('mystuff.js');
}
Đối với phía CSS, bạn có hai lựa chọn. Bạn có thể sử dụng @supports
:
@supports (background: paint(id)) {
/* ... */
}
Một mẹo nhỏ gọn hơn là sử dụng thực tế là CSS sẽ vô hiệu hoá và sau đó bỏ qua toàn bộ nội dung khai báo thuộc tính nếu có một hàm không xác định trong đó. Nếu chỉ định một thuộc tính hai lần – lần đầu tiên không có công việc vẽ, sau đó có công việc vẽ – bạn sẽ nhận được tính năng cải tiến dần:
textarea {
background-image: linear-gradient(0, red, blue);
background-image: paint(myGradient, red, blue);
}
Trong các trình duyệt có hỗ trợ cho công cụ vẽ, phần khai báo thứ hai của background-image
sẽ ghi đè phần khai báo đầu tiên. Trong các trình duyệt không hỗ trợ
worklet vẽ, nội dung khai báo thứ hai sẽ không hợp lệ và sẽ bị loại bỏ,
chỉ để lại nội dung khai báo đầu tiên có hiệu lực.
CSS Paint Polyfill
Đối với nhiều trường hợp sử dụng, bạn cũng có thể sử dụng CSS Paint Polyfill. Tính năng này sẽ thêm tính năng hỗ trợ CSS Custom Paint và Paint Worklets vào các trình duyệt hiện đại.
Trường hợp sử dụng
Có nhiều trường hợp sử dụng cho các công cụ vẽ, một số trường hợp rõ ràng hơn các trường hợp khác. Một trong những cách rõ ràng hơn là sử dụng công cụ vẽ để giảm kích thước DOM. Thông thường, các phần tử được thêm vào chỉ để tạo điểm nhấn bằng CSS. Ví dụ: trong Material Design Lite, nút có hiệu ứng gợn sóng chứa thêm 2 phần tử <span>
để triển khai hiệu ứng gợn sóng. Nếu bạn có nhiều nút, thì điều này có thể làm tăng đáng kể số lượng phần tử DOM và có thể làm giảm hiệu suất trên thiết bị di động. Nếu triển khai hiệu ứng gợn sóng bằng cách sử dụng công cụ vẽ, bạn sẽ có 0 phần tử bổ sung và chỉ có một công cụ vẽ.
Ngoài ra, bạn có một thứ dễ tuỳ chỉnh và tham số hoá hơn nhiều.
Một ưu điểm khác của việc sử dụng công cụ vẽ là – trong hầu hết các trường hợp – giải pháp sử dụng công cụ vẽ có kích thước nhỏ tính theo byte. Tất nhiên, có một sự đánh đổi: mã vẽ của bạn sẽ chạy bất cứ khi nào kích thước của canvas hoặc bất kỳ tham số nào thay đổi. Vì vậy, nếu mã của bạn phức tạp và mất nhiều thời gian, thì mã đó có thể gây ra hiện tượng giật. Chrome đang nỗ lực di chuyển các công việc vẽ ra khỏi luồng chính để ngay cả các công việc vẽ chạy trong thời gian dài cũng không ảnh hưởng đến khả năng phản hồi của luồng chính.
Đối với tôi, triển vọng thú vị nhất là paint worklet cho phép polyfill hiệu quả các tính năng CSS mà trình duyệt chưa có. Ví dụ: bạn có thể polyfill hiệu ứng chuyển màu hình nón cho đến khi hiệu ứng này xuất hiện trong Chrome. Một ví dụ khác: trong một cuộc họp CSS, chúng tôi đã quyết định rằng giờ đây, bạn có thể có nhiều màu đường viền. Trong khi cuộc họp này vẫn đang diễn ra, đồng nghiệp của tôi Ian Kilpatrick đã viết một polyfill cho hành vi CSS mới này bằng cách sử dụng công cụ paint worklet.
Tư duy vượt ra khỏi "hộp"
Hầu hết mọi người bắt đầu nghĩ đến hình nền và hình ảnh đường viền khi tìm hiểu về công cụ vẽ. Một trường hợp sử dụng ít trực quan hơn cho công cụ vẽ là mask-image
để tạo các phần tử DOM có hình dạng tuỳ ý. Ví dụ: kim cương:
mask-image
lấy một hình ảnh có kích thước bằng phần tử. Các vùng mà hình ảnh mặt nạ trong suốt, phần tử sẽ trong suốt. Các vùng mà hình ảnh mặt nạ là mờ, phần tử mờ.
Giờ đây có trên Chrome
Worklet Paint đã có trong Chrome Canary được một thời gian. Với Chrome 65, tính năng này được bật theo mặc định. Hãy tiếp tục thử nghiệm các khả năng mới mà công cụ vẽ mở ra và cho chúng tôi biết bạn đã tạo ra gì! Để có thêm cảm hứng, hãy xem bộ sưu tập của Vincent De Oliveira.