Ngay cả ngày nay, thế giới ma thuật, độc ác của Hiệu ứng bộ lọc SVG phần lớn vẫn là vùng đất chưa được khám phá. Nghệ thuật lọc SVG vẫn được bao quanh bởi một luồng hào quang của thuật giả kim: bạn phải dũng cảm lao vào thế giới đen tối của sự không nhất quán, sự tận tụy của bạn sẽ liên tục bị thử thách bởi các triển khai lỗi và các hiệu ứng phụ gây căng thẳng thần kinh, và bạn phải học các câu thần chú phức tạp. Nhưng, một khi đã thành thạo, nó sẽ mang lại cho bạn sức mạnh chưa từng có — một phương tiện để thay đổi toàn bộ giao diện của các thành phần và trang web chỉ bằng một cái búng tay.
Trong bài viết này, chúng ta sẽ đi sâu vào một trong những hiệu ứng bộ lọc ngoạn mục nhất: bộ lọc SVG
Để tận dụng tối đa bài viết này, bạn nên có hiểu biết cơ bản về bộ lọc SVG. Nếu bạn là người mới sử dụng bộ lọc, bạn có thể muốn xem qua phần giới thiệu của Sara Soueidan hoặc trước tiên hãy đọc quan điểm khiêm tốn của tôi về chủ đề này.
Tuy nhiên, hãy lưu ý: nếu không được áp dụng đúng cách, Bộ lọc SVG có thể làm giảm đáng kể hiệu suất của trang web. Luôn luôn thử nghiệm rộng rãi nếu bạn triển khai một trong các kỹ thuật được mô tả ở đây.

Để đạt được hiệu ứng biến dạng, bộ lọc cần hai hình ảnh làm đầu vào:
Hãy xem điều gì xảy ra khi chúng ta sử dụng hình ảnh của La Sagrada Familia nổi tiếng của Barcelona để "bóp méo" bức tranh Mona Lisa:
Xem Bút [Ví dụ đơn giản về bộ lọc `feDisplacementMap`](https://codepen.io/smashingmag/pen/NWgNbmg) của Dirk Weber.
Xem Bút Ví dụ đơn giản về

Có thể rất thú vị khi làm méo hình ảnh theo cách này nhưng không thể đoán trước được kết quả sẽ như thế nào và hầu hết thời gian, nó không hề đẹp về mặt thẩm mỹ. Có cách nào để kiểm soát hoàn hảo từng pixel đối với đầu ra không? Sau đây là nội dung của spec:
Xem Bút
Điểm ảnh kết quả sẽ được di chuyển đến tọa độ 130⁄130. Nó sẽ được dịch chuyển 30px sang phải và 30px sang dưới cùng. Điều gì xảy ra khi chúng ta thay đổi bản đồ thành màu xám 50% như trong
Xem Bút [`feDisplacementMap` lọc áp dụng 50%](https://codepen.io/smashingmag/pen/zYzqogy) của Dirk Weber.
Xem Bút
Rõ ràng là màu trung tính không có hiệu ứng dễ nhận biết. Các điểm ảnh kết quả vẫn giữ nguyên vị trí. Và nếu chúng ta thay đổi giá trị màu thành giá trị nào đó trên 128, hãy nói
Xem Bút [`feDisplacementMap` lọc áp dụng rgb(204, 204, 204)](https://codepen.io/smashingmag/pen/qBjZRWe) của Dirk Weber.
Xem Bút
Các tọa độ đã được dịch chuyển 30px sang trái và 30px lên trên cùng.
Bây giờ chúng ta đã học đủ để tóm tắt cơ chế bên trong của bộ lọc dịch chuyển trong ba câu đơn giản sau:

Để chia tỷ lệ hình ảnh đều nhau theo mọi hướng, giá trị màu phải giảm dần từ giá trị lớn nhất ở một cạnh xuống giá trị nhỏ nhất ở cạnh đối diện. Chúng ta sẽ sử dụng màu đỏ cho
Xem Bút Các biến thể của bản đồ dịch chuyển và ứng dụng của chúng của Dirk Weber.


Điều này xảy ra do một số lý do sau:
Xem Bút [`FeDisplacementMap`: xử lý các cạnh răng cưa](https://codepen.io/smashingmag/pen/PojNWwW) của Dirk Weber.
Xem Bút
Xem Bút [SVG Dispacementquiz](https://codepen.io/smashingmag/pen/wveGgmm) của Dirk Weber.
Xem Bút SVG Dispacementquiz của Dirk Weber.
Sau đây là công thức về cách xây dựng bản đồ tuyệt đối trong SVG:
Xem Bút [Universal SVG Identitymap](https://codepen.io/smashingmag/pen/QWgNdba) của Dirk Weber.
Xem Bút Universal SVG Identitymap của Dirk Weber.
Bản đồ SVG đã sẵn sàng, nhưng việc đưa nó vào bộ lọc không đơn giản như bạn có thể nghĩ vậy. Về cơ bản có 3 cách để tham chiếu SVG từ
Điều này có nghĩa là bản đồ SVG luôn phải được mã hóa URL trước (thủ công hoặc bằng công cụ xây dựng) hoặc quá trình chuyển đổi phải diễn ra trên máy khách như minh họa trong ví dụ này:
Bạn có thể muốn đặt
Ngoài ra, một đoạn mã có thể được chuyển đổi thành một URL dữ liệu:
Gợi ý: Bất kỳ phần tử SVG nào cũng có thể là một đoạn mã. Nhưng một đoạn mã hóa URL phải là một phần tử SVG có thuộc tính
Đối với SVG hoặc bitmap rất lớn được tải từ một miền khác, blob có thể là lựa chọn tốt hơn:
Lưu ý: Đây là một mẹo hữu ích để tránh các vấn đề liên miền với hình ảnh trên CodePen.


Bạn có thể tạo một số bản đồ đẹp theo cách này, nhưng bạn nên nhớ rằng làm mờ có tác động đến hiệu suất kết xuất. Firefox thậm chí còn có ngưỡng 100px về mức độ làm mờ được phép.
Bộ lọc SVG có thể được hoạt hình hóa và chuyển tiếp. Một vấn đề lớn là thực tế là các giá trị bộ lọc tham chiếu đến URL sẽ không được nội suy mà được hoán đổi ngay lập tức mà không có bất kỳ chuyển tiếp nào ở giữa, một hành vi phù hợp với thông số kỹ thuật. Có thể ổn trong một số trường hợp, nhưng hầu hết thời gian đều nhàm chán. Chúng tôi muốn có các vòng xoáy, gợn sóng, cong vênh và biến hình động!
Khi nghĩ đến bản đồ động, điều đầu tiên xuất hiện trong đầu là ảnh gif động hoặc WebP. Vâng, hình ảnh động sẽ hoạt động trên mọi trình duyệt bằng cách nào đó. Nhưng hiệu suất thay đổi rất nhiều từ khá tệ đến cực kỳ tệ. Và sau đó là những hạn chế liên quan đến nền tảng: ví dụ: Blink không thể áp dụng bộ lọc dịch chuyển động này cho các thành phần chứa các thành phần động khác. Và chúng ta vẫn chưa nói về kích thước tệp. Thay vào đó, chúng ta sẽ tập trung vào hai kỹ thuật hoạt hình đáng tin cậy nhất theo ý kiến cá nhân của tôi: SMIL (đúng vậy, SMIL vẫn là một thứ ngày nay) và JavaScript.
Một bộ lọc thường sẽ được xây dựng từ nhiều nguyên mẫu khác nhau và mọi thuộc tính được thêm vào một nút, như
Xem Bút [`deDisplacementMap`: Một lỗi đơn giản](https://codepen.io/smashingmag/pen/XWgdpXO) của Dirk Weber.
Xem Bút

Hiệu ứng này có thể được tận dụng nhiều hơn nữa bằng cách thêm mặt nạ, hiệu ứng làm mờ và một số màu vào hỗn hợp. Sau đây là phiên bản hiệu ứng được nâng cấp sử dụng các kỹ thuật tương tự, nhưng theo cách tiên tiến hơn.

Bạn có thể nhận thấy rằng tùy thuộc vào trình duyệt và CPU của bạn, hiệu suất của các bản demo có thể thay đổi đáng kể. Thật đáng thất vọng khi Bộ lọc SVG vẫn chưa được tối ưu hóa về hiệu suất. GPU của bạn sẽ tăng tốc một số nguyên hàm đơn giản (ví dụ: thao tác màu), nhưng khi bạn xây dựng chuỗi bộ lọc hợp chất và hợp nhất nhiều nguyên hàm, bạn sẽ nhanh chóng thấy tốc độ khung hình giảm và quạt tăng — đặc biệt là trong WebKit và Firefox. Các nhà cung cấp trình duyệt có rất nhiều chủ đề trong danh sách việc cần làm của họ và Hiệu ứng bộ lọc SVG không có mức ưu tiên cao nhất ở đó, đặc biệt là khi chúng vẫn chưa thường xuyên xuất hiện ngoài tự nhiên.
Điều này không có nghĩa là bạn không thể sử dụng Bộ lọc SVG động ngay bây giờ, nhưng bạn nên áp dụng chúng một cách có trách nhiệm: tốt nhất là giới hạn kích thước của vùng sơn động thành hình chữ nhật nhỏ nhất có thể, giới hạn số lần lặp lại ở mức tối thiểu, cẩn thận với các thao tác làm mờ và pha trộn và thử nghiệm thử nghiệm trên nhiều trình duyệt và thiết bị.
Một trường hợp sử dụng tốt cho hiệu ứng bộ lọc động là các hoạt ảnh nhỏ, bị hạn chế cục bộ được áp dụng cho các thành phần UI. Dưới đây là bản trình bày về cách sử dụng hiệu ứng
Xem Bút [SVG `feDisplacementMap`: Tải xuống Thanh tiến trình](https://codepen.io/smashingmag/pen/wveGgzr) của Dirk Weber.
Xem Bút SVG
Đây là một ví dụ khác về thành phần UI, được cải tiến với hiệu ứng nhỏ và đơn giản. Nút
Xem Bút [SVG `feDisplacementmap`: Phát](https://codepen.io/smashingmag/pen/abwNpmg) của Dirk Weber.
Xem Bút SVG
Lần này, bản đồ dịch chuyển được tạo bằng cách làm mờ một số nguyên mẫu feFlood như trong hình ảnh bên dưới, sau đó hoạt ảnh hóa thuộc tính


Thật không may, hoạt hình SMIL và CSS trong hình ảnh SVG được sử dụnglàm đầu vào cho
Xem Pen [SVG `feDisplacementmap`: Điểm bắt đầu và điểm kết thúc của Warp](https://codepen.io/smashingmag/pen/jOwqyVx) của Dirk Weber.
Xem Bút SVG
Và đây là cách một đường cong hoạt hình từ bản đồ này sang bản đồ khác sẽ trông như thế nào:
Xem Bút [SVG `feDisplacementmap`: Đường cong hoạt hình](https://codepen.io/smashingmag/pen/PojNWWj) của Dirk Weber.
Xem Bút SVG
Bằng cách đo các giá trị màu trong hình ảnh được kết xuất, chúng ta có thể tìm ra liệu trình duyệt hiện tại có hỗ trợ việc có các đoạn SVG làm đầu vào trong feImage hay không, do đó, chúng ta có thể quyết định nên sử dụng phiên bản hoạt ảnh nào.
Một
May mắn thay cho chúng ta, thư viện hoạt ảnh JavaScript Animejs tuyệt vời được định sẵn cho chính loại nhiệm vụ này. Bên cạnh việc có mọi thứ mong đợi từ một thư viện hoạt hình (chẳng hạn như các hàm easing, khung hình chính, dòng thời gian và nhiều hơn nữa), nó có thể thay đổi các giá trị trong một đối tượng JavaScript và gọi hàm
Hãy cùng tìm hiểu mã:
Đây là một ví dụ rất đơn giản về đầu vào

2. Sử dụng hiệu ứng lưới của Animejs Hiệu ứng này được tạo ra bằng cách làm hoạt hình cho một lưới các vòng tròn:
Xin lưu ý rằng các bản demo được hiển thị ở trên mang tính thử nghiệm cao và chủ yếu nhằm mục đích chứng minh khái niệm về bản đồ dịch chuyển động. Nếu bạn muốn sử dụng bất kỳ kỹ thuật nào trong số này trong một dự án trực tiếp, hãy luôn ghi nhớ các khuyến nghị trước đó của tôi. Và hãy tha thứ cho tôi vì đã không đi sâu vào từng ví dụ — điều đó sẽ vượt quá phạm vi của bài viết này.
Trong bài viết này, chúng ta sẽ đi sâu vào một trong những hiệu ứng bộ lọc ngoạn mục nhất: bộ lọc SVG
feDisplacementMap
nguyên thủy. Để giúp bạn dễ hiểu hơn, tôi đã chia bài viết thành ba phần để chúng ta cùng tìm hiểu:- cách thức hoạt động của
feDisplacementMap
, tức là cách áp dụng và cách kiểm soát đầu ra của nó theo cách có thể dự đoán được; - sau đó chúng ta sẽ tìm hiểu các phương pháp để tạo bản đồ dịch chuyển đẹp mắt trong SVG (thú vị hơn một chút vì chúng ta sẽ bắt đầu sử dụng JavaScript);
- và cuối cùng, chúng ta sẽ xem xét một số phương pháp để tạo hiệu ứng hoạt hình cho bộ lọc và tạo hiệu ứng hình ảnh ấn tượng.
Để tận dụng tối đa bài viết này, bạn nên có hiểu biết cơ bản về bộ lọc SVG. Nếu bạn là người mới sử dụng bộ lọc, bạn có thể muốn xem qua phần giới thiệu của Sara Soueidan hoặc trước tiên hãy đọc quan điểm khiêm tốn của tôi về chủ đề này.
Tuy nhiên, hãy lưu ý: nếu không được áp dụng đúng cách, Bộ lọc SVG có thể làm giảm đáng kể hiệu suất của trang web. Luôn luôn thử nghiệm rộng rãi nếu bạn triển khai một trong các kỹ thuật được mô tả ở đây.

Bài viết ngắn về bộ lọc Displacement
Vậy bộ lọc Displacement là gì? Một thao tác Displacement có thể làm biến dạng trực quan bất kỳ đồ họa nào được áp dụng. Bạn có thể tạo hiệu ứng cong vênh, xoáy hoặc gợn sóng giống như bạn làm với bộ lọc biến dạng Photoshop. Lọc dịch chuyển là một công cụ quan trọng trong VFX và rất có thể bạn đã thấy một số thao tác ánh xạ dịch chuyển trên phim và TV, được tạo bằng công cụ VFX như After Effects hoặc GiantRed.Để đạt được hiệu ứng biến dạng, bộ lọc cần hai hình ảnh làm đầu vào:
- Đồ họa nguồn thực tế phải bị biến dạng (từ giờ chỉ cần "nguồn");
- "Bản đồ dịch chuyển" (từ giờ chỉ cần "bản đồ"). Bản đồ này chứa thông tin về cách chúng ta muốn nguồn bị bóp méo.
Hãy xem điều gì xảy ra khi chúng ta sử dụng hình ảnh của La Sagrada Familia nổi tiếng của Barcelona để "bóp méo" bức tranh Mona Lisa:
Xem Bút [Ví dụ đơn giản về bộ lọc `feDisplacementMap`](https://codepen.io/smashingmag/pen/NWgNbmg) của Dirk Weber.
Xem Bút Ví dụ đơn giản về
feDisplacementMap
lọc của Dirk Weber.
Mã:

- Nguyên thủy bộ lọc đầu tiên là
feImage
giữ tham chiếu đến bản đồ (có những nguyên thủy bộ lọc khác có thể được sử dụng làm đầu vào. Bạn sẽ tìm thấy một số bản demo hấp dẫn ngoài kia, trong đófeTurbulence
được sử dụng làm bản đồ dịch chuyển, nhưng trong bài viết này, chúng ta sẽ chủ yếu tập trung vàofeImage
). - Sau đó, feImage này được đưa vào một nguyên hàm
feDisplacementMap
nơi xảy ra hiện tượng biến dạng thực tế:- Thuộc tính
scale
dương hoặc âm xác định cường độ của hiện tượng biến dạng. - Mục đích của
xChannelSelector
vàyChannelSelector
là xác định kênh bốn màu nào của hình ảnh đầu vào (đỏ, lục, lam, alpha) sẽ được áp dụng cho trục nào để biến dạng. Cả hai thuộc tính đều mặc định là kênh alpha của bản đồ (có nghĩa là nếu bạn đang sử dụng bản đồ không có kênh alpha và bỏ qua các thuộc tính này, bạn sẽ không thấy gì ngoài sự dịch chuyển chéo của nguồn).
- Thuộc tính
Mã:
.filtered { filter: url(#displacement-filter);}
Được rồi, thoạt nhìn có vẻ phức tạp, nhưng thực ra khá dễ hiểu khi phân tích:"Bộ lọc nguyên thủy này sử dụng các giá trị pixel từ hình ảnh từ in2 để dịch chuyển không gian hình ảnh từ in. Đây là phép biến đổi cần thực hiện:
P'(x,y) ← P( x + scale * (XC(x,y) - .5), y + scale * (YC(x,y) - .5))
Bản đồ dịch chuyển, in2, xác định nghịch đảo của phép ánh xạ được thực hiện."
-
P'(x,y)
là tọa độ của một pixel trong kết quả; -
X
vàY
là tọa độ của pixel này trong nguồn; -
XC
vàYC
là các giá trị màu RGB(1/255)
được chuẩn hóa của điểm ảnh đã cho trong bản đồ; - Cuối cùng, kết quả của phép toán phải được đảo ngược (về cơ bản có nghĩa là mọi
+
trong công thức phải được thay thế bằng-
).
rgb(51, 51, 51)
, chúng ta mong đợi tọa độ của một điểm ảnh nguồn tại x=100 / y=100
được chuyển đổi như thế nào khi đưa vào một nguyên mẫu dịch chuyển có giá trị tỷ lệ là 100?Xem Bút [`feDisplacementMap` lọc áp dụng rgb(51, 51, 51)](https://codepen.io/smashingmag/pen/gORrLNw) của Dirk Weber.X: 100 - 100 * (51/255 - .5) = 130
Y: 100 - 100 * (51/255 - .5) = 130
Xem Bút
feDisplacementMap
lọc áp dụng rgb(51, 51, 51) của Dirk Weber.Điểm ảnh kết quả sẽ được di chuyển đến tọa độ 130⁄130. Nó sẽ được dịch chuyển 30px sang phải và 30px sang dưới cùng. Điều gì xảy ra khi chúng ta thay đổi bản đồ thành màu xám 50% như trong
rgb(127,127, 127)
?Xem Bút [`feDisplacementMap` lọc áp dụng 50%](https://codepen.io/smashingmag/pen/zYzqogy) của Dirk Weber.
Xem Bút
feDisplacementMap
lọc áp dụng 50% của Dirk Weber.Rõ ràng là màu trung tính không có hiệu ứng dễ nhận biết. Các điểm ảnh kết quả vẫn giữ nguyên vị trí. Và nếu chúng ta thay đổi giá trị màu thành giá trị nào đó trên 128, hãy nói
rgb(204, 204, 204)
?Xem Bút [`feDisplacementMap` lọc áp dụng rgb(204, 204, 204)](https://codepen.io/smashingmag/pen/qBjZRWe) của Dirk Weber.
Xem Bút
feDisplacementMap
lọc áp dụng rgb(204, 204, 204) của Dirk Weber.Các tọa độ đã được dịch chuyển 30px sang trái và 30px lên trên cùng.
Bây giờ chúng ta đã học đủ để tóm tắt cơ chế bên trong của bộ lọc dịch chuyển trong ba câu đơn giản sau:
- Bất kỳ giá trị màu nào trên 127 sẽ dịch chuyển điểm ảnh tương ứng theo hướng của giá trị tỷ lệ;
- Bất kỳ giá trị màu nào dưới 127 sẽ dịch chuyển điểm ảnh tương ứng theo hướng ngược lại;
- Giá trị màu 127 sẽ không có tác dụng.
Bản đồ tuyệt đối
Tại thời điểm này, tôi xin giới thiệu với các bạn một bản đồ đặc biệt sẽ là nền tảng cho tất cả các hiệu ứng mà chúng ta sẽ thấy từ bây giờ. Đây là bản đồ sẽ thực hiện một sự biến dạng rất đơn giản: tỷ lệ hình ảnh theo tỷ lệ. Chúng ta sẽ gọi nó làbản đồ nhận dạng
hoặc bản đồ tuyệt đối
từ bây giờ.
Để chia tỷ lệ hình ảnh đều nhau theo mọi hướng, giá trị màu phải giảm dần từ giá trị lớn nhất ở một cạnh xuống giá trị nhỏ nhất ở cạnh đối diện. Chúng ta sẽ sử dụng màu đỏ cho
X
và màu xanh lam cho Y
từ bây giờ, nhưng cuối cùng, màu nào bạn chọn cho x-
và yChannelSelector
không quan trọng.- Trong trình chỉnh sửa hình ảnh yêu thích của bạn, hãy mở một tài liệu mới;
- Đặt màu nền của tài liệu thành màu đen;
- Tạo một lớp mới và tô màu chuyển màu từ trái sang phải từ
rgb(255, 0, 0)
đếnrgba(255, 0, 0, 0)
; - Thêm một lớp thứ hai và thêm màu chuyển màu từ trên xuống dưới từ
rgb (0, 0, 255)
đếnrgba(0, 0, 255, 0)
; - Đặt chế độ hòa trộn cho lớp này thành
màn hình
.
- Bằng cách áp dụng các bộ lọc biến dạng giống Photoshop cho bản đồ này, chúng ta có thể sử dụng các hiệu ứng này trong các bộ lọc CSS!
- Chúng ta có thể kiểm soát tỷ lệ của trục x và trục y một cách độc lập bằng cách thay đổi độ trong suốt của gradient màu xanh lam hoặc đỏ.
- Có thể "che" các phần của bản đồ bằng màu "trung tính" (
rgb(127, 0 ,127)
hoặc#7F007F
) để ngăn các phần tương ứng trong hình ảnh bị dịch chuyển.
Xem Bút Các biến thể của bản đồ dịch chuyển và ứng dụng của chúng của Dirk Weber.
Khám phá các bản đồ khác nhau trong hành động
Để hiểu rõ hơn về quy trình này, tôi đã tạo một ứng dụng nhỏ để khám phá nhiều bản đồ dịch chuyển khác nhau. Tất cả các bản đồ đều được tạo bằng cách áp dụng các bộ lọc biến dạng Photoshop đơn giản cho bản đồ tuyệt đối.
Vấn đề với các cạnh răng cưa
Bạn có thể đã nhận thấy các cạnh bị vỡ đôi khi xuất hiện trong hình ảnh đầu ra. Đặc biệt là tài liệu nguồn có độ tương phản cao, ví dụ: typography hoặc tác phẩm nghệ thuật vector dễ bị ảnh hưởng bởi hiệu ứng này.
Điều này xảy ra do một số lý do sau:
- Bộ lọc sẽ lấy hình ảnh nguồn dưới dạng bitmap:
Nếu có các cạnh khử răng cưa trong nguồn, bộ lọc sẽ không "khởi tạo lại" chúng sau khi dịch chuyển nguồn. Bất kỳ pixel nào cũng sẽ được chuyển đổi sang vị trí mới của nó, thế là xong. - Lỗi làm tròn:
Có thể một pixel từ100,100
phải được dịch chuyển đến83.276, 124.217
. Bộ lọc phải ánh xạ các tọa độ này thành các giá trị pixel không phải thập phân theo cách nào đó. - Khoảng trống sau khi dịch chuyển:
Có thể hai pixel lân cận, chẳng hạn như tại tọa độx1:100
,x2:101
được dịch chuyển đến các vị trí khác nhau, có thểx1:207.4
,x2: 211.3
. Bộ lọc sẽ lấp đầy khoảng trống ở giữa như thế nào? Gợi ý: không hề. Đặc tả nêu rõ:
Vũ khí tôi chọn để khắc phục sự cố này là thêm một chút mờ, sau đó tăng độ tương phản bằng“Đôi khi các nguyên hàm lọc tạo ra các pixel không xác định. Ví dụ, nguyên hàm lọcfeOffset
có thể dịch chuyển hình ảnh xuống dưới và sang phải, để lại các pixel không xác định ở trên cùng và bên trái. Trong những trường hợp này, các pixel không xác định được đặt thành màu đen trong suốt.”
— Mô-đun hiệu ứng bộ lọc Cấp độ 1, W3C
feConvolveMatrix
. Không hoàn hảo, nhưng đủ tốt cho hầu hết các tình huống. Đây là bản demo trên CodePen:Xem Bút [`FeDisplacementMap`: xử lý các cạnh răng cưa](https://codepen.io/smashingmag/pen/PojNWwW) của Dirk Weber.
Xem Bút
FeDisplacementMap
: xử lý các cạnh răng cưa của Dirk Weber.Đừng từ bỏ Webkit!
Và sau đó là WebKit. Đây là trình duyệt mà bạn sẽ dành phần lớn thời gian để gỡ lỗi bộ lọc của mình. Kể từ bài viết đầu tiên của tôi về chủ đề này, WebKit đã được cải thiện đáng kể. Một trong những phần thú vị nhất của bộ lọc SVG là áp dụng chúng vào nội dung HTML thông qua CSS và trên thực tế, Webkit hiện có thể áp dụng ngay cả các bộ lọc phức tạp vào HTML. Tại thời điểm viết bài này, thật đáng buồn là điều này vẫn không đúng đối với bất kỳ bộ lọc nào cófeImage
trong chuỗi kết xuất của nó. Webkit sẽ không hiển thị phần tử nào cả. Đôi khi, việc áp dụng bộ lọc cho
đã được bao quanh nội dung HTML của bạn sẽ hữu ích, nhưng hiện tại, có một lỗi khác trong WebKit khiến bất kỳ phần tử nào có thuộc tính CSS position
hoặc transform
đều không được lọc, do đó phương pháp này không hoàn hảo. Trong phạm vi bài viết này, chúng ta sẽ tránh lọc các phần tử HTML.Bài kiểm tra
Chúng ta sẽ kết thúc chương này bằng một bài kiểm tra nhỏ: bạn mong đợi hình ảnh bị dịch chuyển sẽ trông như thế nào sau khi gradient này được áp dụng dưới dạngmap
? Hãy suy nghĩ một chút trước khi xem qua giải pháp.Xem Bút [SVG Dispacementquiz](https://codepen.io/smashingmag/pen/wveGgmm) của Dirk Weber.
Xem Bút SVG Dispacementquiz của Dirk Weber.
Tạo bản đồ dịch chuyển SVG và đưa chúng vào bộ lọc
Chúng tôi muốn có thể tạo bản đồ dịch chuyển theo cách cho phép chúng tôi thay đổi chúng bằng JavaScript và CSS một cách động và chúng tôi muốn có thể hoạt hình hóa chúng. Còn gì hợp lý hơn việc tạo bản đồ trong SVG hoàn toàn?Sau đây là công thức về cách xây dựng bản đồ tuyệt đối trong SVG:
- Tạo hai hình chữ nhật;
- Áp dụng các gradient;
- Gộp chúng bằng CSS
mix-blend-mode: screen
; - Bạn đã hoàn tất! (xem trên CodePen→)
Xem Bút [Universal SVG Identitymap](https://codepen.io/smashingmag/pen/QWgNdba) của Dirk Weber.
Xem Bút Universal SVG Identitymap của Dirk Weber.
Bản đồ SVG đã sẵn sàng, nhưng việc đưa nó vào bộ lọc không đơn giản như bạn có thể nghĩ vậy. Về cơ bản có 3 cách để tham chiếu SVG từ
feImage
, trong đó hai cách đầu tiên đang gặp phải sự kết hợp không may của mối lo ngại về bảo mật liên quan đến bộ lọc SVG và trình duyệt lỗi hành vi:- Là tài nguyên bên ngoài:
, một phương pháp không hoạt động trong Webkit/Safari (ai mà đoán được chứ?)
- Dạng đoạn SVG:
, một phương pháp không hoạt động ở bất kỳ đâu ngoại trừ Safari (bạn không thấy điều này xảy ra, phải không?), điều này khiến chúng ta chỉ còn phương pháp đáng tin cậy duy nhất trên nhiều trình duyệt…
- Dạng URL dữ liệu:
Mã:
Mã:
const feImage = document.querySelector('#myFeImage');const url = feImage.getAttribute('href');fetch(url) .then((response) => { return response.text(); }) .then((svgText) => { const uri = encodeURIComponent(svgText); feImage.setAttribute('href', `data:image/svg+xml;charset=utf-8,${uri}`); }) .catch((error) => { feImage.setAttribute('href', someFallbackURI); });
mode
thành CORS
nếu bạn cần tải hình ảnh từ một miền khác hoặc CDN:
Mã:
fetch('mymap.svg', {mode: 'cors'}) .then(…)
Mã:
const myFragmentId = myFeImage.getAttribute('href');const myFragmentHTML = document.getElementById(myFragmentId).outerHTML;const myFragmentDataURL = encodeURIComponent(myFragmentHTML);myFeImage.setAttribute('href', 'data:image/svg+xml;charset=utf-8,${myFragmentDataURL}');
không gian tên
.Đối với SVG hoặc bitmap rất lớn được tải từ một miền khác, blob có thể là lựa chọn tốt hơn:
Mã:
fetch('mymap.svg') .then((response) => { return response.blob(); }) .then((blob) => { const objURL = URL.createObjectURL(blob); feImage.setAttribute('href', objURL); });
Xây dựng kính lúp Glass
Đã đến lúc áp dụng kiến thức mới của chúng ta vào thực tế. Bản demo này cho thấy cách thay đổi động một bản đồ dịch chuyển SVG đã được áp dụng cho toàn cảnh sao Hỏa tuyệt đẹp của NASA với tàu đổ bộ Curiosity ở trung tâm.
- Chèn một nguyên mẫu
feImage
có tham chiếu đếnabsolutemap
; - Tạo "kính lúp", một SVG chứa một hình tròn được tô bằng một gradient xuyên tâm, bắt đầu từ
rgba(127, 0, 127, 0)
và kết thúc tạirgba(127, 0, 127, 1)
; - Chèn
feImage
thứ hai có tham chiếu đến “kính lúp”; - Gộp cả hai hình ảnh thành một nguyên mẫu feMerge và biến kết quả thành
in2
củafeDisplacementMap
. Như bạn có thể nhận thấy, chúng tôi đang sử dụng hệ số tỷ lệ âm ở đây để đảm bảo hình ảnh sẽ được thu nhỏ ở bên ngoài và được hiển thị ở kích thước bình thường bên trong "kính lúp"; - Thêm một số JavaScript để các thuộc tính
x
vày
của feImage tham chiếu đến "kính lúp" khớp với vị trí chuột.
Tạo bản đồ tùy ý bằng đường dẫn mờ
Một cách hoàn toàn khác để xây dựng bản đồ dịch chuyển SVG là sử dụng các đường dẫn bezier mờ cực dày thay vì các gradient. Đây là một ứng dụng nhỏ cho phép bạn thay đổi các điểm neo bezier trong bản đồ được tạo theo cách này.
Bạn có thể tạo một số bản đồ đẹp theo cách này, nhưng bạn nên nhớ rằng làm mờ có tác động đến hiệu suất kết xuất. Firefox thậm chí còn có ngưỡng 100px về mức độ làm mờ được phép.
Hoạt ảnh
Bây giờ chúng ta đã biết mọi thứ về các nguyên tắc chính đằng sau bộ lọc dịch chuyển và cách tạo bản đồ dịch chuyển trong SVG. Chúng ta đã sẵn sàng cho phần thú vị: cách thiết lập mọi thứ chuyển động.Bộ lọc SVG có thể được hoạt hình hóa và chuyển tiếp. Một vấn đề lớn là thực tế là các giá trị bộ lọc tham chiếu đến URL sẽ không được nội suy mà được hoán đổi ngay lập tức mà không có bất kỳ chuyển tiếp nào ở giữa, một hành vi phù hợp với thông số kỹ thuật. Có thể ổn trong một số trường hợp, nhưng hầu hết thời gian đều nhàm chán. Chúng tôi muốn có các vòng xoáy, gợn sóng, cong vênh và biến hình động!
Khi nghĩ đến bản đồ động, điều đầu tiên xuất hiện trong đầu là ảnh gif động hoặc WebP. Vâng, hình ảnh động sẽ hoạt động trên mọi trình duyệt bằng cách nào đó. Nhưng hiệu suất thay đổi rất nhiều từ khá tệ đến cực kỳ tệ. Và sau đó là những hạn chế liên quan đến nền tảng: ví dụ: Blink không thể áp dụng bộ lọc dịch chuyển động này cho các thành phần chứa các thành phần động khác. Và chúng ta vẫn chưa nói về kích thước tệp. Thay vào đó, chúng ta sẽ tập trung vào hai kỹ thuật hoạt hình đáng tin cậy nhất theo ý kiến cá nhân của tôi: SMIL (đúng vậy, SMIL vẫn là một thứ ngày nay) và JavaScript.
Một bộ lọc thường sẽ được xây dựng từ nhiều nguyên mẫu khác nhau và mọi thuộc tính được thêm vào một nút, như
x
, y
, width
, height
hoặc scale
đều có thể được hoạt hình hóa bằng SMIL.Hiệu ứng lỗi đơn giản
Đây là một ví dụ rất đơn giản: sử dụng nguyên mẫu feFlood hoạt hình để tạo hiệu ứng lỗi cơ bản:Xem Bút [`deDisplacementMap`: Một lỗi đơn giản](https://codepen.io/smashingmag/pen/XWgdpXO) của Dirk Weber.
Xem Bút
deDisplacementMap
: Một lỗi đơn giản của Dirk Weber.- Có hai nguyên hàm
feFlood
trong bộ lọc này. Bản đồ đầu tiên bao phủ toàn bộ nguồn và được điền bằng bản đồ trung tínhrgb(127, 0127)
để đảm bảo không có sự dịch chuyển nào xảy ra ở đây. - Bản đồ
feFlood
thứ hai được điền bằngrgb(255, 0, 127)
để tạo ra sự dịch chuyển theo chiều ngang và chỉ lấy một phần nhỏ chiều cao của bộ lọc. - Bây giờ, thật dễ dàng để thêm các nút hoạt ảnh SMIL cho các thuộc tính
y
vàheight
. - Một nguyên hàm
feMerge
hợp nhất cả hai feFlood thành một đầu ra, cung cấpin2
củafeDisplacementMap
.
Bản đồ dịch chuyển động
Vị trí củafeImage
có thể hoạt hình được. Trong ví dụ này, chúng tôi tạo ra một hình ảnh động kiểu ảo giác kỳ ảo bằng cách di chuyển một bản đồ cong vênh lặp lại dọc theo trục x
:
Hiệu ứng này có thể được tận dụng nhiều hơn nữa bằng cách thêm mặt nạ, hiệu ứng làm mờ và một số màu vào hỗn hợp. Sau đây là phiên bản hiệu ứng được nâng cấp sử dụng các kỹ thuật tương tự, nhưng theo cách tiên tiến hơn.

Bạn có thể nhận thấy rằng tùy thuộc vào trình duyệt và CPU của bạn, hiệu suất của các bản demo có thể thay đổi đáng kể. Thật đáng thất vọng khi Bộ lọc SVG vẫn chưa được tối ưu hóa về hiệu suất. GPU của bạn sẽ tăng tốc một số nguyên hàm đơn giản (ví dụ: thao tác màu), nhưng khi bạn xây dựng chuỗi bộ lọc hợp chất và hợp nhất nhiều nguyên hàm, bạn sẽ nhanh chóng thấy tốc độ khung hình giảm và quạt tăng — đặc biệt là trong WebKit và Firefox. Các nhà cung cấp trình duyệt có rất nhiều chủ đề trong danh sách việc cần làm của họ và Hiệu ứng bộ lọc SVG không có mức ưu tiên cao nhất ở đó, đặc biệt là khi chúng vẫn chưa thường xuyên xuất hiện ngoài tự nhiên.
Điều này không có nghĩa là bạn không thể sử dụng Bộ lọc SVG động ngay bây giờ, nhưng bạn nên áp dụng chúng một cách có trách nhiệm: tốt nhất là giới hạn kích thước của vùng sơn động thành hình chữ nhật nhỏ nhất có thể, giới hạn số lần lặp lại ở mức tối thiểu, cẩn thận với các thao tác làm mờ và pha trộn và thử nghiệm thử nghiệm trên nhiều trình duyệt và thiết bị.
Một trường hợp sử dụng tốt cho hiệu ứng bộ lọc động là các hoạt ảnh nhỏ, bị hạn chế cục bộ được áp dụng cho các thành phần UI. Dưới đây là bản trình bày về cách sử dụng hiệu ứng
feImage
hoạt hình ở trên để làm cho thanh tiến trình khá nhàm chán trở nên thú vị hơn:Xem Bút [SVG `feDisplacementMap`: Tải xuống Thanh tiến trình](https://codepen.io/smashingmag/pen/wveGgzr) của Dirk Weber.
Xem Bút SVG
feDisplacementMap
: Tải xuống Thanh tiến trình của Dirk Weber.Đây là một ví dụ khác về thành phần UI, được cải tiến với hiệu ứng nhỏ và đơn giản. Nút
phát
biến đổi thành sóng âm thanh động nhấp nháy:Xem Bút [SVG `feDisplacementmap`: Phát](https://codepen.io/smashingmag/pen/abwNpmg) của Dirk Weber.
Xem Bút SVG
feDisplacementmap
: Phát của Dirk Weber.Lần này, bản đồ dịch chuyển được tạo bằng cách làm mờ một số nguyên mẫu feFlood như trong hình ảnh bên dưới, sau đó hoạt ảnh hóa thuộc tính
scale
của feDisplacementMap
.
Sự chuyển đổi lỗi giữa các phần tử
- Tạo 2 SVG khác nhau cho mỗi kênh trong bản đồ của chúng ta. Đối với mỗi màu, hãy tạo một lưới các hình chữ nhật với cường độ màu thay đổi ngẫu nhiên.
- Tạo 2 nguyên mẫu
feImage
khác nhau. Mã hóa URL cho từng SVG, sau đó đưa vào thuộc tínhhref
của từngfeImage
. - Thêm hoạt ảnh SMIL cho các thuộc tính
width
,height
vày
. - Chèn
feBlend
và pha trộn cả haifeImage
thành một đầu ra duy nhất. - Thêm một số
feDropShadows
có màu để tạo hiệu ứng tách màu thú vị. - Pha trộn mọi thứ, sau đó đưa vào
feDisplacementmap
. - Làm hoạt ảnh cho thuộc tính
scale
bằng SMIL. - Thử nghiệm thoải mái bằng cách thay đổi hình dạng (ví dụ: sử dụng hình tròn thay vì hình chữ nhật), áp dụng thời gian khác nhau, thêm hiệu ứng làm mờ, v.v.

Hoạt hình bản đồ
Cho đến nay, chúng ta đã biết rằng hoạt hình hóa các thuộc tính bộ lọc bằng SMIL có thể giúp chúng ta đạt được hiệu ứng hình ảnh thực sự thú vị. Mặt khác, chúng ta đã thấy cách các phân đoạn SVG có thể được sử dụng làm bản đồ dịch chuyển. Vì SVG có thể hoạt hình bằng JavaScript, SMIL và CSS, nên có vẻ như chúng ta có thể áp dụng hoạt hình trực tiếp vào bản đồ SVG phải không?Thật không may, hoạt hình SMIL và CSS trong hình ảnh SVG được sử dụnglàm đầu vào cho
feImage
sẽ không chạy khi SVG hoặc đoạn mã đượcmã hóa URL. Chúng ta sẽ cần viết một số JavaScript để có giải pháp đáng tin cậy và lưu ý rằng cần có hai cách tiếp cận khác nhau cho trình duyệt Webkit và Blink/Quantum. Trong bước đầu tiên, chúng ta hãy xem cách "lý tưởng" để hoạt hình hóa bản đồ sẽ trông như thế nào:- Tạo đoạn mã SVG chứa bản đồ của bạn;
- Tham chiếu đoạn mã đó từ
feImage
điều khiểnin2
củafeDisplacementMap
; - Bạn có thể thoải mái hoạt hình hóa mọi thứ trong đoạn mã của mình bằng JavaScript theo ý muốn. Tự tạo script của riêng bạn hoặc sử dụng thư viện yêu thích của bạn.
- Tạo đoạn SVG chứa bản đồ của bạn.
- Trong mỗi khung hình hoạt ảnh của bạn, hãy thay đổi tất cả các giá trị của mọi thuộc tính hoạt ảnh.
- Trong mỗi khung hình hoạt ảnh của bạn, hãy tạo một chuỗi được mã hóa URL mới chứa "ảnh chụp nhanh" của đoạn và ghi nó vào thuộc tính
href
của feImages.
Hãy bắt đầu nào!
Hãy giải quyết một vấn đề thực tế bằng cách tiếp cận này: đây là những gì xảy ra với "The Rock" khi chúng ta áp dụng hai bản đồ dịch chuyển đơn giản này:Xem Pen [SVG `feDisplacementmap`: Điểm bắt đầu và điểm kết thúc của Warp](https://codepen.io/smashingmag/pen/jOwqyVx) của Dirk Weber.
Xem Bút SVG
feDisplacementmap
: Điểm bắt đầu và điểm kết thúc của Warp của Dirk Weber.Và đây là cách một đường cong hoạt hình từ bản đồ này sang bản đồ khác sẽ trông như thế nào:
Xem Bút [SVG `feDisplacementmap`: Đường cong hoạt hình](https://codepen.io/smashingmag/pen/PojNWWj) của Dirk Weber.
Xem Bút SVG
feDisplacementmap
: Animated Warp của Dirk Weber.Phân tích Hoạt ảnh
Điều đầu tiên cần làm là phát hiện tính năng, để chúng ta có thể quyết định cách tiếp cận nào trong hai cách tiếp cận phải được áp dụng. Sau đây là một tập lệnh nhỏ vẽ một SVG nhỏ vào một canvas HTML5:
Mã:
async function testSVGFragmentToFeImg() { if (!document.createElement("canvas").getContext) { return false; } const testCode = ' '; const imgURI = 'dữ liệu:hình ảnh/svg+xml;bộ ký tự=utf-8,${encodeURIComponent(testCode)}'; const cnvs = document.createElement("canvas"); const ctx = cnvs.getContext("2d"); cnvs.width = 10; cnvs.height = 10; ctx.fillStyle = "rgb(0,0,0)"; ctx.fillRect(0, 0, 10, 10); const isSupported = new Promise((giải quyết) => { const svg2img = new Image(10, 10); svg2img.onload = () => { ctx.drawImage(svg2img, 0, 0); const colA = ctx.getImageData(1, 1, 1, 1).data; const colB = ctx.getImageData(9, 1, 1, 1).data; giải quyết(colA[1] !== colB[1]); }; svg2img.onerror = () => giải quyết(false); svg2img.src = imgURI; }); return await isSupported;}
Bản đồ SVG
Bản đồ được xây dựng từ hai phần tử: một bản đồ tuyệt đối và một polyline SVG hoạt hình. Vì bản đồ tuyệt đối sẽ không thay đổi trong quá trình hoạt hình, chúng ta sẽ chuyển đổi nó thành một data-URI và đưa trực tiếp vào một nguyên hàmfeImage
.Một
feImage
thứ hai sẽ chứa tham chiếu đến polyline. Sau đó, cả hai nguyên hàm được hợp nhất thành một với sự trợ giúp của nguyên hàm feMerge
:
Mã:
Hoạt ảnh
Tùy thuộc vào trình duyệt chạy, tập lệnh của chúng tôi phải thực hiện những việc khác nhau trên mỗi khung hình: trong các trình duyệt dựa trên Blink/Quantum, nó phải cập nhật một chuỗi và một thuộc tínhhref
, trong WebKit, nó phải cập nhật thuộc tính point
của polyline. Tôi đã đề cập rằng hoạt ảnh vẫn phải trông giống nhau trong mọi môi trường chưa?May mắn thay cho chúng ta, thư viện hoạt ảnh JavaScript Animejs tuyệt vời được định sẵn cho chính loại nhiệm vụ này. Bên cạnh việc có mọi thứ mong đợi từ một thư viện hoạt hình (chẳng hạn như các hàm easing, khung hình chính, dòng thời gian và nhiều hơn nữa), nó có thể thay đổi các giá trị trong một đối tượng JavaScript và gọi hàm
update
trên mọi khung hình. Chính xác là những gì chúng ta cần ở đây.Hãy cùng tìm hiểu mã:
Mã:
// Nguyên hàm bộ lọc feImage sẽ lấy tham chiếu đến polyline:const feImagePolyline = document.getElementById('feimage-polyline');// Tọa độ bắt đầu thuộc tính "points" của polyline:const pStart = '141,90 220,168 118,210 138,210 36,168';// Tọa độ kết thúc thuộc tính "points" của polyline:const pEnd = '140,40 230,105 30,190 220,190 26,85';// Một đối tượng cấu hình animejs chứa các giá trị cơ sở:const animeBaseConfig = { duration: 4000, loop: 100, direction: 'alternate', easing: 'easeInOutQuad', round: 10};// Chúng ta tạo một mảng với hai phân đoạn chuỗi chứa các phần của đoạn SVG:let polyTpl = '
'.split(‘~');// Biến này sẽ lưu trữ các thiết lập cấu hình animejs cụ thể cho hoạt ảnh:let animeConfig;// Đến lúc hành động. Chúng tôi gọi tập lệnh phát hiện tính năng và,// ngay khi lời hứa được thực hiện,// tạo một đối tượng cấu hình animejs có điều kiện:testSVGFragmentToFeImage().then((fragmentInFeImageSupported) => { if (!fragmentInFeImageSupported) { // Các đoạn mã trong feImage không được hỗ trợ. Đây phải là trình duyệt dựa trên Blink/Quantum. // Chúng tôi lưu trữ tọa độ điểm của polyline trong đối tượng JavaScript này. // Đây là mục tiêu hoạt ảnh cho Animejs sẽ được cập nhật trong mọi khung hình const points = { p: pStart }; // Tất nhiên chúng tôi không muốn mã hóa url chuỗi trên mọi // khung hình lặp đi lặp lại (hiệu suất!), chúng tôi chỉ thực hiện một lần trước: polyTpl = polyTpl.map(part => encodeURIComponent(part)); // Cấu hình animejs cho trình duyệt dựa trên Blink/Quantum: animeConfig = { targets: points, p: pEnd, update: function () { // hàm này được gọi trong mọi khung hình của hoạt ảnh. // Nó sẽ cập nhật giá trị "href" của feImage với "ảnh chụp nhanh" của polyline hiện tại: const href = `data:image/svg+xml;charset=utf-8,${polyTpl[0]}${points.p}${polyTpl[1]}`; feImagePolyline.setAttribute('href', href); } };} else { // Đây phải là trình duyệt Webkit. Hãy xử lý nó theo cách khác: const filter = document.getElementById('filter'); // Cấu hình animejs cho trình duyệt dựa trên Webkit: animeConfig = { targets: '#line', points: pEnd }; // Cuối cùng, chúng ta chèn Fragment vào DOM: filter.insertAdjacentHTML('beforebegin', `${polyTpl[0]}${pStart}${polyTpl[1]}`); feImagePolyline.setAttribute('href', '#polylinemap');}// Bây giờ chúng ta có thể kích hoạt hoạt ảnh bằng cách gọi animejs với// các đối tượng cấu hình cơ sở và cụ thể đã hợp nhất:anime({ ...animeBaseConfig, ...animeConfig});
feDisplacementFilter
hoạt ảnh. Chúng ta hãy kết thúc phần tìm hiểu sâu này bằng cách xem ba ví dụ nữa về hoạt ảnh bộ lọc.1. Hiệu ứng mờ "Ripple" được áp dụng cho hộp thoại modal
Chúng ta đều quen với các hộp thoại modal mờ dần vào và ra. Tại sao không sử dụng hiệu ứng nước để làm cho hộp thoại xuất hiện? Ở đây, chúng tôi tạo hiệu ứng chuyển màu xuyên tâm trong bản đồ để tạo ví dụ về hiệu ứng chuyển màu gợn sóng:
2. Sử dụng hiệu ứng lưới của Animejs Hiệu ứng này được tạo ra bằng cách làm hoạt hình cho một lưới các vòng tròn:

3. Menu “Cờ tung bay” Hiệu ứng mờ dần đạt được bằng cách di chuyển bản đồ sọc theo chiều ngang:

Xin lưu ý rằng các bản demo được hiển thị ở trên mang tính thử nghiệm cao và chủ yếu nhằm mục đích chứng minh khái niệm về bản đồ dịch chuyển động. Nếu bạn muốn sử dụng bất kỳ kỹ thuật nào trong số này trong một dự án trực tiếp, hãy luôn ghi nhớ các khuyến nghị trước đó của tôi. Và hãy tha thứ cho tôi vì đã không đi sâu vào từng ví dụ — điều đó sẽ vượt quá phạm vi của bài viết này.Đọc thêm
- Làm chủ SVG Arcs
- Thế tiến thoái lưỡng nan của Design Leader
- Regexes Got Good: Lịch sử và tương lai của Regular Expression trong JavaScript
- Làm chủ Typography trong Thiết kế Logo