API CSS Paint

Các khả năng mới trong Chrome 65

CSS Paint API (còn gọi là "CSS Custom Paint" hoặc "công việc sơn 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 công nghệ này? Và cách thức hoạt động ra sao? Chà, hãy đọc tiếp, bạn...

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 thuộc tính CSS yêu cầu một 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 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 một lớp vẽ.

Viết hồ sơ công việc sơn

Để xác định một tập công việc vẽ có tên myPainter, chúng ta cần tải tệp công việc 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 paint worklet:

class MyPainter {
  paint(ctx, geometry, properties) {
    // ...
  }
}

registerPaint('myPainter', MyPainter);

Trong lệnh gọi lại paint(), chúng ta có thể sử dụng ctx giống như CanvasRenderingContext2D như đã biết trong <canvas>. Nếu biết cách vẽ trong <canvas>, bạn có thể vẽ trong một Worklet công việc vẽ! geometry cho chúng tôi biết chiều rộng và chiều cao của canvas mà chúng ta tuỳ ý sử dụng. properties Tôi sẽ giải thích ở phần sau của bài viết này.

Ví dụ mở đầu: hãy viết một công việc tô màu bàn cờ và sử dụng tệp đó làm hình nền của <textarea>. (Tôi đang sử dụng vùng văn bản vì vùng này có thể đổi kích thước theo mặc định.):

<!-- 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 trước đây bạn đã sử dụng <canvas>, mã này sẽ trông quen thuộc. Xem bản minh hoạ trực tiếp tại đây.

Textarea có hoa văn bàn cờ làm hình nền
Textarea có mẫu bàn cờ làm hình nền.

Sự khác biệt so với việc sử dụng hình nền phổ biến ở đâ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 vùng văn bản. Điều này có nghĩa là hình nền luôn lớn đúng như cần thiết, bao gồm cả phần bù cho màn hình với mật độ điểm ảnh cao.

Thật thú vị nhưng cũng khá tĩnh. Chúng ta có muốn viết một công việc mới mỗi lần muốn 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

Thật may là công việ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ý các thay đổi đối với bất kỳ thuộc tính CSS nào, bao gồm cả thuộc tính tuỳ chỉnh. Các giá trị sẽ được cung cấp cho bạn thông qua thông 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 thậm chí còn tốt hơn, giờ đây chúng tôi có thể chuyển sang Công cụ cho nhà phát triển và điều chỉnh các giá trị cho đến khi tìm được giao diện phù hợp.

Các trình duyệt không hỗ trợ paint worklet

Tại thời điểm viết, chỉ Chrome mới triển khai vẽ được. Tất cả các nhà cung cấp trình duyệt khác đều nhận được những tín hiệu tích cực, nhưng vẫn có tiến bộ rất ít. Để cập nhật thông tin, hãy thường xuyên kiểm tra trang Houdini Sẵn sàng chưa?. Trong thời gian chờ đợi, hãy nhớ sử dụng tính năng nâng cao luỹ tiến để mã của bạn tiếp tục chạy ngay cả khi không hỗ trợ công việc vẽ. Để đảm bảo mọi thứ hoạt động như mong đợi, bạn phải điều chỉnh mã ở hai vị trí: CSS và JS.

Bạn có thể phát hiện tính năng hỗ trợ công việc tô màu trong JS bằng cách kiểm tra đối tượng CSS: js if ('paintWorklet' in CSS) { CSS.paintWorklet.addModule('mystuff.js'); } Về phía CSS, bạn có 2 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ộ phần 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 2 lần — trước tiên là không cần vẽ, sau đó là với lệnh vẽ — bạn sẽ có được tính năng nâng cao tăng dần:

textarea {
  background-image: linear-gradient(0, red, blue);
  background-image: paint(myGradient, red, blue);
}

Trong các trình duyệt hỗ trợ công việc tô màu, nội dung khai báo thứ hai của background-image sẽ ghi đè nội dung khai báo đầu tiên. Trong các trình duyệt không hỗ trợ công việc vẽ, nội dung khai báo thứ hai sẽ không hợp lệ và sẽ bị loại bỏ, giữ lại nội dung khai báo đầu tiên có hiệu lực.

Màu vẽ polyfill CSS

Nếu có nhiều mục đích sử dụng, bạn cũng có thể sử dụng CSS Paint Polyfill. Công cụ này sẽ thêm khả năng hỗ trợ CSS Custom Paint và Paint Worklet cho 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 lớp vẽ, một số trường hợp rõ ràng hơn so với các trường hợp khác. Một trong những điều dễ thấy hơn là sử dụng công việc tô màu để giảm kích thước của DOM. Thông thường, các phần tử chỉ được thêm vào để tạo phần trang trí bằng CSS. Ví dụ: trong Material Design Lite, nút có hiệu ứng gợn sóng chứa 2 phần tử <span> bổ sung để triển khai chính hiệu ứng gợn sóng. Nếu bạn có nhiều nút, việc này có thể bổ sung lên đến khá nhiều phần tử DOM và có thể làm giảm hiệu suất trên thiết bị di động. Thay vào đó, nếu triển khai hiệu ứng gợn sóng bằng cách sử dụng worklet (trình vẽ), thì bạn sẽ có 0 phần tử bổ sung và chỉ có một tô màu. Ngoài ra, bạn có một thứ gì đó dễ tuỳ chỉnh và tham số hơn nhiều.

Một ưu điểm khác của việc sử dụng paint worklet là (trong hầu hết các trường hợp), một giải pháp sử dụng paint worklet sẽ có kích thước nhỏ (tính theo byte). Tất nhiên, có một sự đánh đổi: mã sơn 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 chương trình tô vẽ ra khỏi luồng chính để ngay cả các chương trình 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à công việc tô màu cho phép chèn nhiều tính năng CSS hiệu quả mà trình duyệt chưa có. Một ví dụ là các hiệu ứng chuyển màu conic polyfill cho đến khi chúng được chuyển đến Chrome vốn có. Một ví dụ khác: trong một cuộc họp CSS, chúng tôi đã quyết định rằng bây giờ 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 việc tô màu.

Tư duy vượt ra ngoài khuôn khổ

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 việc tô màu. Một trường hợp sử dụng ít trực quan hơn cho công việc sơn là mask-image để làm cho các phần tử DOM có hình dạng tuỳ ý. Ví dụ: kim cương:

Phần tử DOM có hình kim cương.
Một phần tử DOM có hình kim cương.

mask-image lấy ảnh có kích thước của phần tử. Những vùng mà hình ảnh mặt nạ trong suốt thì phần tử trong suốt. Các khu vực mà hình ảnh mặt nạ bị mờ, phần tử mờ.

Hiện đã có trên Chrome

Paint worklet đã tồn tại 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 và thử các khả năng mới mà công việc sơn mở ra và cho chúng tôi thấy những gì bạn đã tạo! Để có thêm nguồn cảm hứng, hãy xem qua bộ sưu tập của Vincent De Oliveira.