Tạo ảnh động cho hiệu ứng làm mờ

Làm mờ là một cách hiệu quả để chuyển hướng người dùng tập trung. Việc làm cho một số thành phần hình ảnh xuất hiện mờ trong khi vẫn giữ các thành phần khác là tâm điểm sẽ chuyển hướng sự tập trung của người dùng một cách tự nhiên. Người dùng bỏ qua nội dung được làm mờ và thay vào đó, tập trung vào nội dung mà họ có thể đọc. Một ví dụ là danh sách biểu tượng hiển thị chi tiết về các mục riêng lẻ khi di chuột qua. Trong thời gian đó, các lựa chọn còn lại có thể được làm mờ để chuyển hướng người dùng đến thông tin mới hiển thị.

TL;DR

Tạo ảnh động cho hiệu ứng làm mờ không thực sự là một lựa chọn vì nó rất chậm. Thay vào đó, hãy tính toán trước một loạt các phiên bản ngày càng được làm mờ và làm mờ giữa các phiên bản đó. Đồng nghiệp của tôi, Yi Gu, đã viết một thư viện để giúp bạn lo mọi thứ! Hãy xem bản minh hoạ của chúng tôi.

Tuy nhiên, kỹ thuật này có thể khá khó chịu khi áp dụng mà không có bất kỳ giai đoạn chuyển đổi nào. Tạo ảnh động làm mờ — chuyển từ chế độ không làm mờ sang làm mờ — có vẻ là một lựa chọn hợp lý, nhưng nếu đã từng thử làm việc này trên web, có thể bạn sẽ thấy rằng các ảnh động vẫn mượt mà, vì bản minh hoạ này cho thấy nếu bạn không có một cỗ máy mạnh mẽ nào. Chúng tôi có thể làm tốt hơn không?

Vấn đề

CPU sẽ biến ký tự đánh dấu thành kết cấu. Hoạ tiết được tải lên GPU. GPU vẽ các hoạ tiết này vào bộ đệm khung bằng chương trình đổ bóng. Hiện tượng làm mờ trong chương trình đổ bóng.

Hiện tại, chúng ta chưa thể tạo ảnh động hiệu quả cho hiệu ứng làm mờ. Tuy nhiên, chúng ta có thể tìm thấy một giải pháp trông đủ tốt, nhưng về mặt kỹ thuật thì chúng ta không thể làm mờ được hiệu ứng động. Để bắt đầu, trước tiên, hãy tìm hiểu lý do hiệu ứng làm mờ ảnh động bị chậm. Để làm mờ các thành phần trên web, có 2 kỹ thuật: Thuộc tính CSS filter và bộ lọc SVG. Nhờ việc hỗ trợ và dễ sử dụng hơn, bộ lọc CSS thường được sử dụng. Rất tiếc, nếu được yêu cầu hỗ trợ Internet Explorer, bạn không có lựa chọn nào khác ngoài việc sử dụng bộ lọc SVG vì IE 10 và 11 hỗ trợ những bộ lọc đó chứ không phải CSS. Tin vui là giải pháp tạo ảnh động cho hiệu ứng làm mờ của chúng tôi áp dụng cho cả hai kỹ thuật. Hãy cùng cố gắng tìm nút thắt cổ chai bằng cách xem Công cụ cho nhà phát triển.

Nếu bật tính năng "Paint Flashing" trong Công cụ cho nhà phát triển, bạn sẽ không thấy bất kỳ đèn flash nào. Có vẻ như không có hoạt động vẽ lại nào đang diễn ra. Về mặt kỹ thuật, điều đó đúng vì "vẽ lại" đề cập đến việc CPU phải vẽ lại kết cấu của một thành phần được quảng bá. Bất cứ khi nào một phần tử được quảng bá làm mờ, độ mờ sẽ được GPU áp dụng bằng chương trình đổ bóng.

Cả bộ lọc SVG và bộ lọc CSS đều sử dụng bộ lọc tích chập để áp dụng hiệu ứng làm mờ. Bộ lọc tích chập khá tốn kém vì đối với mỗi pixel đầu ra, một số pixel đầu vào phải được xem xét. Hình ảnh càng lớn hoặc bán kính làm mờ càng lớn, hiệu ứng càng tốn kém.

Vấn đề nằm ở đó, chúng tôi đang chạy một hoạt động GPU khá tốn kém cho mọi khung hình, làm tăng ngân sách khung hình là 16 mili giây và do đó kết thúc dưới 60 khung hình/giây.

Đi xuống lỗ thỏ

Vậy chúng ta có thể làm gì để quá trình này diễn ra suôn sẻ? Chúng ta có thể sử dụng sự khéo léo! Thay vì tạo ảnh động cho giá trị độ mờ thực tế (bán kính của độ mờ), chúng tôi tính toán trước một số bản sao được làm mờ trong đó giá trị làm mờ tăng theo cấp số nhân, sau đó mờ dần giữa các bản sao đó bằng opacity.

Mờ dần là một loạt các hiệu ứng làm mờ và làm mờ chồng lên nhau. Ví dụ: nếu có bốn giai đoạn làm mờ, chúng ta sẽ làm mờ giai đoạn đầu tiên trong khi làm mờ cùng lúc ở giai đoạn thứ hai. Khi giai đoạn thứ hai đạt độ mờ 100% và giai đoạn đầu tiên đã đạt 0%, chúng ta làm mờ giai đoạn thứ hai và làm mờ trong giai đoạn thứ ba. Sau khi hoàn tất, cuối cùng chúng tôi làm mờ giai đoạn thứ ba và làm mờ trong phiên bản thứ tư và cuối cùng. Trong trường hợp này, mỗi giai đoạn sẽ chiếm một nửa tổng thời lượng mong muốn. Về mặt trực quan, hiệu ứng làm mờ này trông rất giống với hiệu ứng làm mờ thực sự động.

Trong các thử nghiệm của chúng tôi, việc tăng bán kính làm mờ theo cấp số nhân trên mỗi giai đoạn mang lại kết quả hình ảnh tốt nhất. Ví dụ: Nếu có bốn giai đoạn làm mờ, chúng ta sẽ áp dụng filter: blur(2^n) cho mỗi giai đoạn, tức là giai đoạn 0: 1px, giai đoạn 1: 2px, giai đoạn 2: 4px và giai đoạn 3: 8px. Nếu chúng ta buộc từng bản sao được làm mờ này vào lớp riêng của chúng (được gọi là "quảng cáo") bằng cách sử dụng will-change: transform, thì việc thay đổi độ mờ trên các phần tử này sẽ trở nên cực nhanh. Về lý thuyết, điều này sẽ cho phép chúng tôi tải trước công việc làm mờ tốn kém. Hoá ra, lập luận của bạn có sai sót. Nếu chạy bản minh hoạ này, bạn sẽ thấy tốc độ khung hình đó vẫn dưới 60 khung hình/giây và hiệu ứng làm mờ thực sự tệ hơn so với trước đây.

Công cụ cho nhà phát triển hiển thị dấu vết trong đó GPU có thời gian bận trong một thời gian dài.

Xem nhanh Công cụ cho nhà phát triển cho thấy GPU vẫn cực kỳ bận và kéo dài mỗi khung hình xuống ~90 mili giây. Nhưng tại sao? Chúng ta không còn thay đổi giá trị độ mờ nữa, chỉ thay đổi độ mờ, vậy điều gì sẽ xảy ra? Một lần nữa, vấn đề nằm ở bản chất của hiệu ứng làm mờ: Như đã giải thích trước đó, nếu phần tử vừa được quảng bá vừa được làm mờ, thì hiệu ứng sẽ được GPU áp dụng. Vì vậy, mặc dù chúng ta không tạo ảnh động cho giá trị làm mờ nữa, nhưng bản thân hoạ tiết vẫn không được làm mờ và cần được GPU làm mờ lại mọi khung hình. Lý do khiến tốc độ khung hình thậm chí còn tệ hơn trước đây bắt nguồn từ việc so với cách triển khai ngây thơ, GPU thực sự phải làm nhiều việc hơn trước đây, vì hầu hết thời gian 2 kết cấu đều hiển thị cần được làm mờ độc lập.

Những gì chúng tôi nghĩ ra tuy không đẹp nhưng giúp ảnh động trở nên nhanh như chớp. Chúng tôi sẽ không quảng bá phần tử bị làm mờ nữa, mà thay vào đó quảng bá một trình bao bọc mẹ. Nếu một phần tử vừa được làm mờ vừa được quảng bá, thì hiệu ứng đó sẽ được GPU áp dụng. Đây là lý do khiến bản minh hoạ của chúng ta bị chậm. Nếu phần tử được làm mờ nhưng không được quảng bá, thì hiệu ứng làm mờ sẽ được tạo điểm ảnh thành hoạ tiết mẹ gần nhất. Trong trường hợp của chúng ta, đó là phần tử trình bao bọc mẹ được quảng bá. Hình ảnh được làm mờ hiện là hoạ tiết của phần tử mẹ và có thể sử dụng lại cho tất cả khung hình trong tương lai. Cách này chỉ hiệu quả vì chúng ta biết rằng các thành phần được làm mờ không phải là ảnh động và việc lưu chúng vào bộ nhớ đệm thực sự mang lại lợi ích. Đây là bản minh hoạ triển khai kỹ thuật này. Tôi tự hỏi Moto G4 nghĩ gì về phương pháp này? Cảnh báo tiết lộ nội dung: ứng dụng cho rằng rất tuyệt:

Công cụ cho nhà phát triển hiển thị dấu vết cho thấy GPU có nhiều thời gian không hoạt động.

Giờ đây, chúng tôi đã có rất nhiều khoảng trống trên GPU và tốc độ 60 khung hình/giây mượt mà. Chúng tôi đã làm được!

Sản xuất

Trong bản minh hoạ, chúng tôi đã sao chép cấu trúc DOM nhiều lần để có các bản sao của nội dung cần làm mờ ở nhiều độ mạnh. Có thể bạn đang thắc mắc về cách hoạt động của giải pháp này trong môi trường phát hành chính thức vì việc đó có thể có một số tác dụng phụ ngoài ý muốn với kiểu CSS của tác giả hay thậm chí là JavaScript của họ. Chính xác. Nhập DOM bóng!

Mặc dù hầu hết mọi người đều nghĩ rằng Shadow DOM là một cách để đính kèm các phần tử "nội bộ" vào Phần tử tuỳ chỉnh, nhưng đây cũng là một cách tách biệt và hiệu suất cơ bản! JavaScript và CSS không thể xuyên qua ranh giới DOM bóng, điều này cho phép chúng tôi sao chép nội dung mà không can thiệp vào kiểu của nhà phát triển hoặc logic ứng dụng. Chúng ta đã có một phần tử <div> cho từng bản sao để tạo điểm ảnh và hiện sử dụng các <div> này làm máy chủ bóng. Chúng ta tạo ShadowRoot bằng attachShadow({mode: 'closed'}) và đính kèm bản sao của nội dung vào ShadowRoot thay vì chính <div>. Chúng tôi cũng phải sao chép tất cả biểu định kiểu vào ShadowRoot để đảm bảo rằng các bản sao của chúng ta được tạo kiểu giống như bản gốc.

Một số trình duyệt không hỗ trợ Shadow DOM v1 và đối với những trình duyệt đó, chúng tôi quay lại việc chỉ sao chép nội dung và hy vọng đảm bảo không có gì xảy ra. Chúng tôi có thể sử dụng polyfill DOM bóng bằng ShadyCSS, nhưng chúng tôi không triển khai tính năng này trong thư viện.

Vậy là xong. Sau khi tìm hiểu về quy trình kết xuất hình ảnh của Chrome, chúng tôi đã tìm ra cách có thể tạo hiệu ứng làm mờ hiệu quả trên các trình duyệt!

Kết luận

Bạn không nên xem nhẹ loại hiệu ứng này. Do thực tế là chúng ta sao chép các phần tử DOM và buộc các phần tử đó vào lớp riêng, nên chúng ta có thể đẩy giới hạn của các thiết bị cấp thấp hơn. Việc sao chép tất cả biểu định kiểu vào mỗi ShadowRoot cũng có thể gây ra rủi ro về hiệu suất. Vì vậy, bạn nên quyết định xem bạn muốn điều chỉnh logic và kiểu để không bị các bản sao trong LightDOM ảnh hưởng hay sử dụng kỹ thuật ShadowDOM của chúng tôi. Nhưng đôi khi kỹ thuật của chúng tôi có thể đáng để đầu tư. Hãy xem đoạn mã trong kho lưu trữ GitHub cũng như bản minh hoạ và liên hệ với tôi trên Twitter nếu bạn có thắc mắc!