Làm thế nào để sử dụng SVG như một Placeholder, và các phương pháp Image Loading khác

1188

Tác giả: José M. Pérez

Tôi rất quan tâm đến việc tối ưu hóa chất lượng image và làm sao cho ảnh load nhanh hơn trên web. Một trong những area thú vị nhất là placeholder: khi image chưa load xong thì nên hiển thị cái gì.

Trong những ngày qua tôi có tìm được một số kỹ thuật loading sử dụng SVG, và tôi sẽ kể chi tiết hơn trong bài viết này.

Trong bài này chúng ta sẽ nói về các chủ đề:

  • Sơ lược về các loại placeholder.
  • Các placeholder sử dụng SVG (các edges, shapes và silhouettes)
  • Auto hóa process.

Sơ lược về các dạng placeholder khác nhau 

Khi thực hiện kĩ thuật lazy-load cần phải nghĩ xem cần render cái nào như một placeholder, vì nó có ảnh hưởng lớn đến sản phẩm mà user tiếp nhận. Tôi đã từng đưa ra nhiều options sau:

Có rất nhiều phương pháp để bỏ vào vùng image trước khi nó load.

  • Để trống vùng: Trong xu hướng thiết kế responsive design hiện nay, việc này giúp tránh việc content bị nhảy lung tung. Các thay đổi của layout không chỉ không tốt từ khía cạnh trải nghiệm người dùng, mà còn đối với chất lượng (performance). Browser buộc phải tính toán lại layout mỗi lần nó fetch độ phân giải của hình ảnh, để trống chỗ đó.
  • Placeholder: Hãy tưởng tượng rằng chúng ta đang hiển thị ảnh profile của một user. Chúng ta muốn hiển thị một silhouette ở background. Nó sẽ hiện ra khi hình ảnh đã được load, và khi request bị fail hoặc khi user chưa chọn ảnh profile nào. Những hình ảnh này thường là ảnh vector, và nhờ có size nhỏ nên chúng rất dễ để chèn.
  • Solid Colour (Màu cố định): Chọn một màu từ hình ảnh và dùng nó như màu background của placeholder. Nó có thể là màu trội nhất, sáng nhất,… Mục đích của nó là nó dựa trên màu của ảnh bạn load lên để làm cho sự chuyển tiếp từ không có ảnh đến ảnh đã được load trông mượt mà hơn.
  • Ảnh mờ: Còn được gọi là kĩ thuật blur-up. Bạn render version nhỏ của ảnh rồi chuyển tiếp đến version đầy đủ của nó. Cả pixel và kB của ảnh đầu tiên đều rất nhỏ. Để loại bỏ các artifacts, ảnh sẽ được phóng to ra và làm mờ đi.

Hóa ra là còn rất nhiều dạng khác và rất nhiều người tài năng khác đang phát triển các phương pháp tạo placeholder khác.

Một trong số đó sử dụng màu gradient thay vì màu solid. Màu gradient cho ra bản preview ảnh final chính xác hơn, với dung lượng rất thấp (tăng phần payload).

Sử dụng gradients làm backgrounds. Ảnh Screenshot từ Gradify. Code trên GitHub.

Một phương pháp khác đó là sử dụng các image dựa trên SVG, đang thu hút nhiều sự chú ý sau các thử nghiệm gần đây.

Các Placeholder dùng SVG

Chúng ta đều biết SVG rất thích hợp cho ảnh vector. Đa số chúng ta đều muốn load ảnh bitmap, vì thế câu hỏi ở đây là làm sao để vector hóa hình ảnh. Một vài option gồm có sử dụng edges, shapes và silhouettes.

Edges

Mục tiêu ban đầu của tôi đó là phác thảo ra các vùng, vector hóa hình ảnh, nhưng tôi không biết phải làm sao. Và rồi nhận ra rằng sử dụng edges cũng có thể hiệu quả và tôi đã quyết định animate nó tạo ra hiệu ứng “drawing”.

Shapes

SVG có thể được dùng để vẽ area từ ảnh thay vì edges/ borders. Từ đó, chúng ta sẽ vector hóa một ảnh bitmap để tạo placeholder.

Trước đây tôi đã cố làm gì đó tương tự với những hình tam giác.

Code trên đây là một ví dụ về placeholder sử dụng SVG bao gồm 245 hình triangles. Gốc gác của chúng dựa trên Delaunay triangulation sử dụng polyserver của Possan. Đúng như mong đợi, size của file sẽ càng lớn khi SVG sử dụng càng nhiều triangles.

Primitive và SQIP, kĩ thuật LQIP dựa trên SVG

Tobias Baldauf đang hoàn thiện một kĩ thuật Image Placeholder Chất lượng thấp gọi là SQIP. Trước khi bàn sâu về SQIP tôi sẽ cho bạn một bản sơ lược về Primitive, một thư viện dựa trên SQIP.

Primitive rất tuyệt vời và tôi vô cùng ủng hộ việc bạn thử nó. Nó chuyển ảnh bitmap thành một SVG gồm nhiều shape chồng lên nhau. Size của nó nhỏ nên rất thích hợp cho việc chèn thẳng vào page. Đỡ đi một vòng và ta có được một placeholder ngay trong payload HTML ban đầu.

Xử lý bức ảnh này bằng Primitive, sử dụng 10 shapes và 100 shapes.
Xử lý bức ảnh này bằng Primitive, sử dụng 10 shapes và 100 shapes.

Code cho SVG với 10 shapes là rất nhỏ, khoảng 1030 bytes, có thể giảm xuống 640 bytes khi pass output qua SVGO.

<svg xmlns=”http://www.w3.org/2000/svg" width=”1024" height=”1024"><path fill=”#817c70" d=”M0 0h1024v1024H0z”/><g fill-opacity=”.502"><path fill=”#03020f” d=”M178 994l580 92L402–62"/><path fill=”#f2e2ba” d=”M638 894L614 6l472 440"/><path fill=”#fff8be” d=”M-62 854h300L138–62"/><path fill=”#76c2d9" d=”M410–62L154 530–62 38"/><path fill=”#62b4cf” d=”M1086–2L498–30l484 508"/><path fill=”#010412" d=”M430–2l196 52–76 356"/><path fill=”#eb7d3f” d=”M598 594l488–32–308 520"/><path fill=”#080a18" d=”M198 418l32 304 116–448"/><path fill=”#3f201d” d=”M1086 1062l-344–52 248–148"/><path fill=”#ebd29f” d=”M630 658l-60–372 516 320"/></g></svg>

Những hình ảnh được tạo bằng 100 shapes thì lớn hơn, nặng khoảng 5kB sau khi qua SVGO (trước đó là 8kB). Mức độ chi tiết của chúng cao hơn với payload vẫn nhỏ. Quyết định về số lượng triangles cần dùng sẽ phụ thuộc nhiều vào dạng hình ảnh (ví dụ contrast, lượng màu, complexity) và mức độ chi tiết.

Ta có thể tạo một script tương tự như cpeg-dssim mà kéo lượng shapes được dùng cho đến khi chạm đến ngưỡng tương đồng về cấu trúc ( hoặc một lượng shapes tối đa trong trường hợp xấu nhất).

Những kết quả SVG này cũng tốt khi sử dụng như hình background. Nhờ có size được khống chế và nền tảng dựa trên vector mà chúng rất phù hợp cho hero image và các background lớn mà không hiện các artifact.

SQIP

Theo chính lời của Tobias:

SQIP như một nỗ lực tìm sự cân bằng giữa 2 cực: nó tận dụng Primitive để tạo ra một SVG bao gồm nhiều shapes đơn giản xấp xỉ với features chính hiển thị trong hình, tối ưu hóa SVG bằng SVGO và thêm vào đó một Gaussian Blur filter. Nó giúp cho ra một SVG placeholder nặng chỉ khoảng 800 – 1000 bytes, nhìn mượt hơn trên screen.

Kết quả cho ra thì tương tự như việc sử dụng placeholder image nhỏ trong phương pháp blur-up (như Medium và các sites khác đã làm). Sự khác nhau đó là thay vì sử dụng ảnh bitmap, ví dụ JPG hay WebP, thì placeholder là SVG.

Nếu chúng ta chạy SQIP trên ảnh gốc nó sẽ như thế này:

Các ảnh output sử dụng SQIP cho ảnh đầu tiên và ảnh thứ hai.

Cái SVG output là khoảng 900 bytes, và trong đoạn code chúng ta có thể thấy filter feGaussianBlur được áp dụng với nhóm shapes:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000"><filter id="b"><feGaussianBlur stdDeviation="12" /></filter><path fill="#817c70" d="M0 0h2000v2000H0z"/><g filter="url(#b)" transform="translate(4 4) scale(7.8125)" fill-opacity=".5"><ellipse fill="#000210" rx="1" ry="1" transform="matrix(50.41098 -3.7951 11.14787 148.07886 107 194.6)"/><ellipse fill="#eee3bb" rx="1" ry="1" transform="matrix(-56.38179 17.684 -24.48514 -78.06584 205 110.1)"/><ellipse fill="#fff4bd" rx="1" ry="1" transform="matrix(35.40604 -5.49219 14.85017 95.73337 16.4 123.6)"/><ellipse fill="#79c7db" cx="21" cy="39" rx="65" ry="65"/><ellipse fill="#0c1320" cx="117" cy="38" rx="34" ry="47"/><ellipse fill="#5cb0cd" rx="1" ry="1" transform="matrix(-39.46201 77.24476 -54.56092 -27.87353 219.2 7.9)"/><path fill="#e57339" d="M271 159l-123–16 43 128z"/><ellipse fill="#47332f" cx="214" cy="237" rx="242" ry="19"/></g></svg>

SQIP cũng có thể cho ra một tag ảnh với các content SVG Base 64:

<img width="640" height="640" src="example.jpg” alt="Add descriptive alt text" style="background-size: cover; background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw…<stripped base 64>…PjwvZz48L3N2Zz4=);">

Silhouettes

Chúng ta vừa điểm qua cách dùng SVG cho edges và các primitive shapes. Mikael Ainalem đã chia sẻ một codepen vài ngày trước chỉ cách sử dụng silhouette 2 màu như một placeholder. Kết quả cho ra rất đẹp:

Các SVG trong trường hợp này được vẽ tay, nhưng phương pháp này nhanh chóng sản sinh ra các tool giúp tự động hóa quá trình.

  • Gatsby, một static site generator sử dụng React để hỗ trợ các SVG cần theo dõi này. Nó sử dụng một JS PORT của potrace để vector hóa images.

Summary

Chúng ta đã thấy rất nhiều tool và kĩ thuật tạo SVG từ image và dùng chúng như các placeholder. Chúng ta có thể kiểm soát được mức độ detail (và cả size), nó rất dễ nén và dễ thao tác trên CSS và JS.

Các Resources khác

  • Geometrize là một phần Primitive viết trên Haxe. Đây cũng là một JS implementation mà bạn có thể thử trực tiếp trên browser của mình.
  • Primitive.js, là một phần Primitive viết trên JS. Tương tự, primitive.nextgen, một phần Primitive desktop app sử dụng Primitive.js và Electron.
  • imagetracerjs, một raster image tracer và vectoriser viết bằng JavaScript. Cũng có các bản viết cho Java và Android.

Bài viết gốc được đăng tải tại medium.freecodecamp