Tối ưu hóa việc sử dụng hình ảnh khi thiết kế web

Mở đầu

Load ảnh lên trang web của bạn có thể là một vấn đề đối khi ít được developer chú ý, mà thường ốp bừa bãi ảnh lên mà bỏ qua yếu tố dung lương hoặc chất lượng của ảnh (đôi khi là cả hai). Để có thể vừa hiển thị những bức ảnh đẹp, chất lượng, vừa giảm bớt thời gian load ảnh cho browser nhằm tạo ra trải nghiệm tốt nhất cho người dùng thì việc tối ưu hóa những bức ảnh sử dụng trong trang web là một điều đáng không thể bỏ qua (Nhất là đối với những trang web mạng xã hội, trang web chuyên sử dụng ảnh)

Lựa chọn ảnh phù hợp

Trước hết, bạn nên bắt đầu với những bức ảnh chất lượng cao, với định dạng và kích cỡ phù hợp

  • Sử dụng định dạng JPG đối với ảnh chụp, và định dạng PNG đối với những hình vẽ thiết kế đồ họa (như icons, những hình cần nền trong suốt)
  • Sử dụng PNG-8 thay vì PNG-24 đối với những hình thiết kế đơn sắc. Thậm chí có thể giảm độ màu từ 256 xuống 16
  • Sử dụng định dạng SVG (Scalable Vector Graphics) đối với icons và logo. Những hình ảnh dạng này có thể phóng to thu nhỏ mà không làm hỏng chất lượng, cũng như không làm tăng dung lượng ảnh.
  • Không nên sử dụng ảnh có chiều rộng vượt quá kích cỡ của container elements ngoài cùng của trang web

Hardware/software pixels

Có hai loại pixel cần chú ý đến, đó là hardware và software pixel. Lấy thử một chiếc macbook pro 15" ra làm ví dụ, độ phân giải của chiếc máy tính này là 2880x1800. Nhưng ảnh chỉ cần kích cỡ 1440 pixels, khi hiển thị 100% là đã chiếm toàn bộ màn hình chiếc macbook này. Lý do là vì macbook pro retina 15" có pixel density bằng 2: CSS Pixels = Hardware Pixels / Pixel Density Với màn hình có độ phân giải 2880 pixels (Hardware pixels), khi chuyển sang CSS pixel sẽ là 1440 px. Vì thế đối với developer thì các elements trên màn hình sẽ chỉ có chiều rộng tối đa là 1440px thay vì 2880px. Một số thiết bị di động, máy tính bảng còn có pixel density là 3, 4 Quay trờ lại với việc xử lý ảnh, Giả sử như một bức ảnh có kích cỡ 2880px x 600px khi được photoshop xuất ra với quality 75%, có dung lượng là 939KB. Một con số khá lớn đối với bức ảnh cỡ vừa, nếu bạn browser phải tải từ server để load về.

Định dạng WebP

Theo như google, thì WebP là định dạng giúp việc xử lý, nén ảnh trở nên dễ dàng, thuận tiện khi sử dụng cho môi trường web. Ví dụ, như bức ảnh đã đề cập ở trên (2880px x 600px - 939KB). Sau khi nén lại thì bức ảnh nhỏ hơn và cũng sắc nét hơn, dung lượng xuống còn 451KB. Tuy là chỉ còn một nửa so với ban đầu, nhưng định dạng WebP chỉ có Chrome và Opera hỗ trợ, nên phải có một phương pháp dự phòng khác.

Ảnh responsive trên HTML

Thẻ img sử dụng trong HTML có thuộc tính src để trỏ đến đường dẫn của ảnh:

<img src=“image.jpg” alt=“image description”/>

Tuy nhiên, có một thuộc tính khác cũng hỗ trợ cho việc lựa chọn nguồn ảnh dựa trên pixel density của màn hình:

<img srcset=“image_1x.jpg 1x, image_2x.jpg 2x” src=”image_1x.jpg”/>

Ở đây, ảnh được cung cấp cho cả 2 loại màn hình: 1x và 2x. Dựa trên pixel density thực tế, trình duyệt sẽ lựa chọn hình ảnh phù hợp để hiển thị, tránh gây ra tình trạng mờ, nhiễu, giảm chất lượng ảnh trên những màn hình khác nhau. Còn thuộc tính src ở đó để dùng cho trường hợp đề phòng, khi srcset không được hỗ trợ. Hiện tại, hầu hết các trình duyệt đều hỗ trợ thuộc tính srcset, ngoại trừ IE, Edge, Opera Mni. Nhưng như vậy vẫn chưa đủ. Ngoài pixel density, thuộc tính srcset của img cũng chấp nhận giá trị đo chiều rộng có đơn vị w, tương đương với giá trị CSS pixels. Đơn vị w cho phép trình duyệt chọn lựa ảnh phù hợp dựa trên độ phân giải thực tế của màn hình Như vậy, với 2 mốc 600px và 900px, chúng ta có thể sử dụng 3 kích cỡ ảnh như sau:

<img
  srcset=“image-sm.jpg 600w,
  image-md.jpg 900w,
  image-lg.jpg 1440w,
  src=”image_1x.jpg” />

Tuy nhiên, khi browser chọn xem nên lấy hình ảnh nào thì không hề quan tâm tới CSS của chúng ta. Những file CSS chưa được load (vì theo thứ tự, document HTML sẽ được tải từ server xuống trước). Vì thế mặc định chiều rộng element được browser xem xét sẽ là chiều rộng lớn nhất của màn hình (window object). Vậy vấn đề xảy ra khi bạn cần hiển thị ảnh trong một container với chiều rộng < 100vw. Vì thế, chúng ta lại cần sử dụng đến thuộc tính sizes của thẻ img:

<img
   srcset=“image-sm.jpg 600w,
   image-md.jpg 900w,
   image-lg.jpg 1440w”
   sizes=”50vw”
   src=”image_1x.jpg” />

Với thuộc tính sizes="50vw", browser của bạn sẽ nhận biết được rằng bức ảnh này được hiển thị trong vùng chiều rộng 50vw (tức là 1 nửa chiều rộng màn hình nhìn thấy). Dựa trên thông tin này, bức ảnh cần thiết được hiển thị sẽ được browser lựa chọn. Thậm chí, thuộc tính sizes hỗ trợ cả media queries để lựa chọn ảnh phù hợp cho màn hình desktop/mobile của bạn. Ví dụ như bức ảnh ở màn hình desktop sẽ được hiển thị ở container có 50vw, và trên mobile sẽ hiển thị full 100vw. Với mức chiều rộng dưới 600px, trình duyệt sẽ hiển thị ảnh rộng toàn màn hình, còn ở màn hình lớn hơn chỉ cho phép trong vùng 50vw (một nửa màn hình):

<img
 srcset=“image-sm.jpg 600w,
   image-md.jpg 900w,
   image-lg.jpg 1440w”
 size=”(max-width: 600px) 100vw, 50vw”
 src=”image_1x.jpg” />

Chú ý rằng trong đoạn code trên bạn cần phải chỉ rõ cho browser biết, file ảnh nào sẽ được lựa chọn, do browser không nhận biết được style cho từng trường hợp. Đến đây thì chúng ta đã gần như đạt được mục đích của mình. Tuy nhiên, đoạn code trên chưa đụng đến pixel density đã nói đến ở đầu bài, các loại màn hình có pixel density là 1 hay 2 đều sẽ chỉ nhận một loại bức ảnh. Vì thế, tiếp tục tiến đến giải pháp sau đây:

Thẻ <picture> của HTML5

HTML cung cấp một element gọi là <picture>. Trong element này sẽ sử dụng hai elements con khác là <source> vả <img>. source sẽ liệt kê toàn bộ các format mà chúng ta cần sử dụng cho browser. Quay trở lại những phần trước, định dạng WebP là định dạng ảnh khá hữu ích để sử dụng cho hiển thị web, tuy nhiên chỉ hỗ trợ cho chrome và opera mini. Điều này có thể được giải quyết bằng việc sử dụng thẻ source lẫn img làm child element trong picture. Cụ thể là như sau:

<picture>
 <source
   srcset=“image.webp”
   type=“image/webp” >
 <img
   src=“image.jpg”
   type=“image/jpeg”
   alt=”image description”>
</picture>

Thẻ <source> được đặt đầu tiên trong element picture, tiếp theo đó là thẻ <img>, điều này giúp browser nhận diện ảnh được lấy về đầu tiên sẽ là ảnh "image.webp" với định dạng webp. Như vậy nếu trình duyệt không hỗ trợ định dạng webp thì sẽ dùng đến thẻ <img> để thay thế. Ngoài ra, source cũng cho phép browser nhận diện cả media queries bằng cách thêm thuộc tính mediasrcset vào thẻ này. Thuộc tính media sẽ chỉ định chiều rộng tối thiểu để áp dụng hình ảnh được định nghĩa bởi thuộc tính srcset. Với cách này, chúng ta có thể sử dụng nhiều thẻ source để sử dụng ảnh cho các loại màn hình khác nhau:

<picture>
 <source
   media=(min-width: 900px)”
   srcset=“image-lg.webp”
   type=“image/webp” >
 <source
   media=(min-width: 600px)”
   srcset=“image-md.webp”
   type=“image/webp” >
 <source
   srcset=“image-sm.webp”
   type=“image/webp” >
 <img
   src=“image-lg.jpg”
   type=“image/jpeg”
   alt=”image description”>
</picture>

Một tính năng cuối cùng mà thẻ picture hỗ trợ chính lầ chọn lựa ảnh dựa trên pixel density mà bài viết đã nhắc đến ở trên. Với thuộc tính srcset, chúng ta có thể chỉ định file ảnh nào sẽ được trình duyệt load về dựa trên thông số pixel density đi kèm trong giá trị của srcset. Cụ thể, các file ảnh được phân cách với nhau bởi dấu , và sau mỗi tên file là giá trị pixel density của ảnh với đơn vị 1x, 2x, 3x, sau tên file ảnh một dấu cách:

<picture>
 <source
   media=(min-width: 900px)”
   srcset=“image-lg_1x.webp 1x, image-lg_2x.webp 2x”
   type=“image/webp” >
 <source
   media=(min-width: 601px)”
   srcset=“image-md_1x.webp 1x, image-md_2x.webp 2x”
   type=“image/webp” >
 <source
   media=(max-width: 600px)”
   srcset=“image-sm_1x.webp 1x, image-sm_1x.webp 1x”
   type=“image/webp” >
 <img 
   srcset=“image-sm_1x.jpg 600w,
   image-md_1x.jpg 900w,
   image-lg_1x.jpg 1440w”
   src=“image_lg_1x.jpg”
   type=“image/jpeg”
   alt=”image description”>
</picture>

Như có thể thấy, tương tự như thẻ source chúng ta cũng có thể thêm các giá trị ảnh tương ứng với pixel density cần thiết cho thẻ img, trong trường hợp trình duyệt không hỗ trợ thẻ source thì các file ảnh trong tính srcset của image sẽ được sử dụng. Thậm chí trong trường hợp ngay cả srcset không thể sử dụng thì vãn có thể sử dụng ảnh qua thuộc tính src mặc định của thẻ. Chú ý, khi sử dụng thuộc tính srcset trong thẻ img thì srcset phải được khai báo đầu tiên, trước thuộc tính mặc định src. Nếu không, browser sẽ tải file ảnh chỉ định trong thuộc tính src trước, nếu có file ảnh tốt hơn trong thuộc tính srcset thì browser mới tiếp tục tải về. Điều này là thừa thãi khi chúng ta chỉ cần sử dụng ảnh trong thuộc tính srcset.

Ảnh responsive trong CSS

Nếu như trong quá trình phát triển, chúng ta không thể biết chính xác chiều dài và chiều rộng của element chứa ảnh (container) thì có thể sử dụng thẻ thông thường như div cùng với thuộc tính background-image để chỉ đường dẫn đến URL của ảnh. Tương tự, CSS cho phép tối ưu hóa kích cỡ của ảnh Thuộc tính image-set trong CSS có chức năng tương tự như srcset trong HTML. Trong thời điểm hiện tại, tính năng image-set chỉ được các trình duyệt sau hỗ trợ: Chrome, Chrome cho Android, Safari, iOS Safari, và một số trình duyệt khác. Để sử dụng được tính năng này ở những trình duyệt không hỗ trợ thì cần phải sử dụng đến polyfills. Có thể trong tương lại toàn bộ trình duyệt cũng sẽ hỗ trợ thuộc tính này. Nếu không, chúng ta vẫn có thể dự phòng bằng background-image, tương tự như src đối với srcset. Để tạo một bức ảnh full-width chiếm toàn bộ chiều ngang màn hình, với chiều cao 500px, trước hết phải định nghĩa background-image cho CSS. Tiếp theo đó là sử dụng -webkit-image-set, liệt kê ra những ảnh WebP dùng cho từng loại màn hình có pixel density khác nhau. Và tương tự lặp lại quá trình này cho đến khi hoàn thành các loại màn hình có media queries khác nhau tương ứng.

.bg-image {
  width: 100vw;
  height: 500px;

  background-size: cover;
  background-position: center;

  background-image: url(/images/image-lg_1x.jpg)  
  background-image: -webkit-image-set(
    url(/images/image-lg_1x.webp) 1x,
    url(/images/image-lg_2x.webp) 2x
  );
  background-image: image-set(
    url(/images/image-lg_1x.jpg) 1x,
    url(/images/image-lg_2x.jpg) 2x
  );
@media(max-width: 900px) {
    background-image: url(/images/image-md_2x.jpg);
    background-image: -webkit-image-set(
      url(/images/image-md_1x.webp) 1x,
      url(/images/image-md_2x.webp) 2x
    );
    background-image: image-set(
      url(/images/image-md_1x.jpg) 1x,
      url(/images/image-md_2x.jpg) 2x
    );
  }
@media (max-width: 600px) {
    background-image: url(/images/image-sm_2x.jpg);
    background-image: -webkit-image-set(
      url(/images/image-sm_1x.webp) 1x,
      url(/images/image-sm_2x.webp) 2x
    );
    background-image: image-set(
      url(/images/image-sm_1x.jpg) 1x,
      url(/images/image-sm_2x.jpg) 2x
    );
  }
}

Trong đoạn trên, ảnh nền (background image) được căn giữa với element div và hiển thị lên toàn bộ vùng đó. Thuộc tính image-set cho phép gán hai bức ảnh để sử dụng cho 2 loại màn hình có pixel density tương ứng. Trong trường hợp không được hỗ trợ, phương án dự phòng với url thông thường sẽ xử lý ảnh cho các browser không hỗ trợ thuộc tính image-set. Một điều cần đặc biệt chú ý ở đây là phải đặt option dự phòng ở trước phần khai báo background-images có sử dụng thuộc tính image-set. Nếu không, một số browsers như Safari sẽ tải về cả hai file ảnh ở trong thuộc tính image-set lẫn file ảnh dự phòng, nếu tên hai file ảnh khác nhau. Phần còn lại của đoạn css cũng follow tương tự như cách hoạt động của thẻ <picture> trong HTML

Kết luận

Như vậy, bài viết đã tổng kết lại một số phương pháp giúp tối ưu hóa việc xử lý hình ảnh khi sử dụng cho trình duyệt web, nhằm giảm thiểu dung lượng ảnh cần thiết để load về, cũng như duy trì chất lượng ảnh phù hợp cho các loại màn hình khác nhau. Cảm ơn mọi người đã đọc!

Nguồn: https://medium.freecodecamp.org/a-guide-to-responsive-images-with-ready-to-use-templates-c400bd65c433