Một cuộc lặn sâu vào thế giới tuyệt vời của bộ lọc dịch chuyển SVG

theanh

Administrator
Nhân viên
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 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:
  1. 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;
  2. 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);
  3. 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.
Vì đây sẽ là một bài đọc khá dài, những người thiếu kiên nhẫn có thể muốn hãy xem qua các bản demo mà chúng ta sẽ gặp trước khi tiếp tục. Tất cả các bản demo trong bài viết này đã được tối ưu hóa cho các phiên bản mới nhất của ba công cụ trình duyệt chính.

Để 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ầu hết thời gian, bản đồ sẽ là một số hình ảnh Bitmap, nhưng trong phần tiếp theo, tôi sẽ trình bày cách sử dụng hình ảnh SVG hoặc các đoạn mã 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ề feDisplacementMap lọc của Dirk Weber.
Mã:


  1. 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ào feImage).
  2. 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 xChannelSelectoryChannelSelector 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).
Sau đó, chúng tôi áp dụng bộ lọc bằng CSS:
Mã:
.filtered { filter: url(#displacement-filter);}
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:
"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."
Đượ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:
  • P'(x,y) là tọa độ của một pixel trong kết quả;
  • XY là tọa độ của pixel này trong nguồn;
  • XCYC 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 -).
Chúng ta sẽ chạy một số thí nghiệm đơn giản để xác minh công thức của mình bằng cách đưa các bitmap nguyên thủy vào bộ lọc, chỉ bao gồm một màu duy nhất. Giả sử bản đồ được điền 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?
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)](https://codepen.io/smashingmag/pen/gORrLNw) của Dirk Weber.
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.
Theo trực giác, người ta có xu hướng tin rằng màu đen sẽ không có tác dụng gì, nhưng đến giờ thì rõ ràng là không phải vậy. Trên thực tế, đen và trắng sẽ tạo ra sự dịch chuyển tối đa có thể đến hoặc ra khỏi giá trị tỷ lệ.

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-yChannelSelector không quan trọng.
  1. 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;
  2. Đặt màu nền của tài liệu thành màu đen;
  3. 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) đến rgba(255, 0, 0, 0);
  4. 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) đến rgba(0, 0, 255, 0);
  5. Đặt chế độ hòa trộn cho lớp này thành màn hình.
Và thế là bạn đã xây dựng được một bản đồ tuyệt đối! Bản đồ này sẽ đóng vai trò là nền tảng vững chắc cho mọi loại biến dạng hình ả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](https://codepen.io/smashingmag/pen/KKqzaKo) của Dirk Weber.
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 đến 83.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õ:
“Đô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ọc feOffset 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
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 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ạng map? 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→)
Gợi ý: Đừng bao giờ quên khai báo chiều rộng và chiều cao theo giá trị pixel trong SVG. Nếu không, nó sẽ không hiển thị trong Firefox và sẽ hiển thị mờ trong Chrome.

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 SVGtrình duyệt lỗi hành vi:
  1. 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ứ?)
  2. 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…
  3. Dạng URL dữ liệu:
Mã:
Đ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:
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); });
Bạn có thể muốn đặt 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(…)
Ngoài ra, một đoạn mã có thể được chuyển đổi thành một URL dữ liệu:
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}');
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 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); });
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.

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.


  1. Chèn một nguyên mẫu feImage có tham chiếu đến absolutemap;
  2. 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ại rgba(127, 0, 127, 1);
  3. Chèn feImage thứ hai có tham chiếu đến “kính lúp”;
  4. 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ủa feDisplacementMap. 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";
  5. Thêm một số JavaScript để các thuộc tính xy 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ính rgb(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ằng rgb(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 yheight.
  • Một nguyên hàm feMerge hợp nhất cả hai feFlood thành một đầu ra, cung cấp in2 của feDisplacementMap.

Bản đồ dịch chuyển động​

Vị trí của feImage 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ính href của từng feImage.
  • Thêm hoạt ảnh SMIL cho các thuộc tính width, heighty.
  • Chèn feBlend và pha trộn cả hai feImage 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ển in2 của feDisplacementMap;
  • 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.
“Nghe có vẻ quá dễ. Nhưng vấn đề ở đây là gì?” Tất nhiên, bạn đúng. Phương pháp được mô tả ở trên là con đường lý tưởng, cách mọi thứ nên hoạt động nhưng, có một sự thật kỳ lạ: Nó sẽ không hoạt động ở bất kỳ đâu ngoài Webkit. Để hoạt ảnh của chúng ta chạy trong Blink và Firefox, chúng ta phải triển khai một giải pháp khá hacky và bạn sẽ không thích 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.
Bạn có thể đang nghĩ: "Thật xấu xí! Tôi không thích nó và bạn là một người đáng khinh!". Tôi hiểu nỗi đau của bạn. Phần đầu là một môi trường sống thù địch và đôi khi chúng ta phải làm những điều ghê tởm để tồn tại (sự thật thú vị: phương pháp "xấu xí" hoạt động tốt hơn trong Blink so với phương pháp "thuần túy" trong Webkit).

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ằ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.

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àm feImage.

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ính href, 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});
Đây là một ví dụ rất đơn giản về đầu vào 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
 
Back
Bên trên