Tối ưu hóa hiển thị hình ảnh bằng blur placeholder và lazyload

Tối ưu hóa hiển thị hình ảnh bằng blur placeholder và lazyload

Threads
  • Có bạn nào trong lúc code mà bị "ngẫn tò te" hoặc "lú lẫn" trong mớ hỗn độn của mình viết ra chưa. Những lúc như thế tôi thường hay ngừng lại, thư giãn đầu óc, hoặc để mai tính tiếp. Ấy vậy mà rất hiệu quả nhé! Lúc sau xem lại cái tự nhiên thấy vấn đề ở đâu ngay.

    Hoặc cũng có thể thử áp dụng phương pháp "chú vịt vàng" - thử giải thích vấn đề đang gặp phải với vịt xem, nó sẽ cho thấy mâu thuẫn trong lời nói ra.

    Còn không có tiền mua vịt to thì xe túi mù ra vịt bé giống mình nè 😅

    » Xem thêm
  • Ơ buồn cười thật. Ai cũng biết GIF là định dạng ảnh động thường được dùng thay cho video clip để hiển thị các nội dung ngắn trên nền tảng web. GIF tiện hơn các nội dung dạng video là vì nó được hiển thị như một bức ảnh và được hỗ trợ rộng rãi. Cơ mà GIF có dung lượng nặng quá.

    Nói thật nhiều lúc mình có vài cái hành động muốn hiển thị lên web cho mọi người xem, cơ mà định dạng GIF nó nặng với cả cũng không biết cách tối ưu cho nhẹ xuống. Hôm nay lên mạng tìm hiểu xem định dạng nào có khả năng thay thế GIF trong tương lai thì mọi người biết đó là gì không? Là WEBP (webp)!!! Đúng vậy, là định dạng ảnh mà mình đang dùng trên blog lâu nay luôn á, mà giờ mới biết là nó hiển thị được cả ảnh động nữa, hơi quê 😆

    Kết hợp với ffmpeg nữa là chuyển được tất tần tật video clip thành webp được ngay. Để vài nửa ngồi chế lại cái cli một tí là dùng ngon luôn mọi người ạ 🤪

    » Xem thêm
  • Cảm giác như Github Copilot đang cố gắng mở rộng thị trường cho anh em developer á. Mới trước họ ra mắt Github Open Copilot Chat thì mới đây lại thêm cái Using GitHub Copilot in the command line dùng để giải thích hoặc gợi ý lệnh trong terminal.

    Đây, cách dùng rất đơn giản thôi, ví dụ muốn nó giải thích câu lệnh sudo apt-get để làm gì, thì:

    $ gh copilot explain "sudo apt-get"

    Hoặc nhờ nó gợi ý lệnh mong muốn, sử dụng tiếng Việt được luôn nhé (kể cả tiếng Việt không dấu vẫn hiểu 😳)

    $ gh copilot suggest "xoá commit chưa push"

    Mình đã kiểm tra và thấy lệnh ra rất đúng, xịn thật 🤓

    » Xem thêm

Vấn đề

Tuần vừa rồi là một tuần bận rộn, có nhiều biến động mà tôi không tiện nói ra. Quay đi ngoảnh lại mà đã hơn 1 tuần rồi chưa viết lách thêm được bài nào. Điều gì đang xảy ra?

Tôi vừa tối ưu lại hình ảnh cho blog! Như bạn biết, thống kê chỉ ra rằng hình ảnh chiếm một lượng lớn dung lượng được truyền tải qua Internet khi truy cập vào các trang web. Nhờ có hình ảnh mà nhiều thông điệp được truyền tải, tạo thêm độ sinh động, trực quan hơn so với văn bản thông thường, thậm chí hình ảnh còn kích thích sự tò mò cho người dùng thực hiện hành vi bấm vào nó.

Nếu bạn là một độc giả lâu năm của 2coffee.dev, bạn sẽ nhận ra trước đây trang chủ chỉ bao gồm toàn chữ, một dòng tiêu đề kèm theo một dòng trích đoạn nội dung của bài viết. Đặt chúng lại với nhau tạo thành một danh sách. Trong thời buổi hiện đại, việc có thêm một hình ảnh đại diện cho bài viết không còn quá xa lạ. Kết hợp hình ảnh và tiêu đề sẽ kích thích sự tò mò của người đọc hơn là chỉ dùng mỗi văn bản. Gần đây, tôi tiến hành nâng cấp để bổ sung thêm khả năng hiển thị hình ảnh bên cạnh mỗi bài viết. Trang chủ giờ đây đã bớt "nhàm chán" hơn so với trước.

Thời gian đầu, việc kiếm một nơi để lưu trữ hình ảnh là khá vất vả. Tôi từng tự mình triển khai một máy chủ hình ảnh đơn giản bằng Node.js nhưng đó lại là một gánh nặng cho việc bảo trì. Có nhiều thứ cần phải quan tâm mà tôi thì không muốn mất nhiều thời gian cho việc đó. Tình cờ tôi tìm được trang web imgur.com - cho tôi tải lên hình ảnh mà không có nhiều hạn chế, hơn nữa thời điểm đó tốc độ hiển thị hình ảnh từ imgur khá tốt cho nên sử dụng nó làm "host" không có nhiều lăn tăn.

Hình ảnh sau khi tải lên có đường dẫn dạng như https://i.imgur.com/Z4ERHfq.jpg, tôi kẹp chúng vào thẻ ![] của markdown để hiển thị hình ảnh. Mọi thứ có vẻ hoạt động tốt và tôi hài lòng với phương án này trong một thời gian dài. Nhưng thực tế, imgur không phải là CDN, họ cũng lên tiếng đính chính rằng mình không phải là một dịch vụ CDN cho nên đôi lúc, tốc độ tải xuống hình ảnh từ đây không ổn định. Cụ thể có những khoảng thời gian như đứt cáp hoặc người dùng từ khu vực khác mất thời gian tải lâu hơn, biểu tượng "loading" liên tục xoay vòng trên trình duyệt khiến cho trải nghiệm về tốc độ thật tồi tệ.

Tôi liên tục tìm kiếm các nhà cung cấp dịch vụ CDN miễn phí. Có một vài cái tên nổi bật như Cloudimage, Imgix, Bunny... nhưng chúng đi kèm với nhiều hạn chế về dung lượng và tính năng, ở một khía cạnh khác còn khó sử dụng, hay là phức tạp hơn những gì mà tôi cần.

Vài tháng trở lại đây, khi biết đến dịch vụ R2 của Cloudflare - một dạng Object Storage giống S3 của Amazone cho phép sử dụng miễn phí 10GB bộ nhớ với băng thông không giới hạn. Giới hạn chỉ nằm ở số lượng truy vấn, ví dụ bạn sẽ có 1 triệu truy vấn đến R2 miễn phí trong vòng 1 tháng, một con số không lớn cũng không nhỏ, nhưng chắc chắn 1 triệu là quá thoải mái với trang blog này.

Ngay lập tức tôi đã viết một vài đoạn mã để sử dụng được R2 như một nơi lưu trữ hình ảnh mới của mình. Tốc độ hiển thị hình ảnh từ R2 tương đối nhanh. Vì R2 là của Cloudflare, mà Cloudflare thì có bán thêm một dịch vụ CDN cho hình ảnh nên tốc độ từ R2 có khả năng phải theo sau CDN một bậc, nhưng chắc chắn là nhanh hơn imgur. Tôi cũng viết một CLI phục vụ cho việc tải lên hình ảnh, chỉ cần chạy một lệnh, hình ảnh được tải lên, đường dẫn đến hình ảnh được trả về và "copy" sẵn vào clipboard.

Đến đây vấn đề bắt đầu phát sinh nhiều hơn. Mặc dù hình ảnh tải lên R2 đã qua bước tối ưu và nén nhằm mục đích giảm dung lượng với chất lượng hiển thị tốt nhưng lượng hình ảnh cũ từ imgur trước đó thì không. Tất cả liên kết đến hình ảnh thumbnail ngoài trang chủ đều là ảnh gốc từ banner trong bài viết, cho nên nếu truy cập lần đầu, không ngạc nhiên khi dung lượng tải về của cả trang web có thể lên đến vài trăm, vài MB dữ liệu. Quả là một con số gây sát thương cho tốc độ, đặc biệt là với những người có mạng chậm, mang không ổn định hoặc trong giờ cao điểm...

Không làm thì thôi nhưng một khi làm lại phải đau đầu. Đây là lần thứ n tôi tối ưu hình ảnh cho trang web này, công cuộc tối ưu không diễn ra ngay lập tức mà có thể coi là rải rác trong suốt thời gian vận hành, nhưng để chung quy lại thì có hai cột mốc lớn mà tôi sẽ kể tiếp ngay sau đây.

Tối ưu hóa hiển thị hình ảnh lần một

Khi kích thước hình ảnh lớn, tốc độ mạng chậm, khu vực chứa hình ảnh sẽ bị hiển thị một dấu vết đen ngòm do đặt nền màu đen, như vậy thì trông không đẹp đẽ cho lắm.

Có nhiều kỹ thuật để khắc phục vấn đề này, đơn cử như là tạo một biểu thị vòng xoay để báo cho người dùng biết hình ảnh đang được tải, đặt một hình ảnh có kích thước nhỏ gọi là placeholder để "giữ chỗ trước"... và cách mà tôi chọn là dùng một hình ảnh placeholder.

Hình này cần có kích thước nhỏ nhất có thể, thật sáng tạo khi chọn luôn logo, chỉ cần đặt nó trên một hình ảnh có nền màu trùng với logo thì ngay lập tức đã có hình ảnh để sử dụng, kích thước ảnh lúc này chỉ chỉ rơi vào vài KB. Lần tải đầu tiên, hình ảnh placeholder sẽ được hiển thị trước, chờ cho đến khi hình ảnh gốc được tải xong thì thư viện lazysizes sẽ giúp tôi hiển thị hình ảnh đó lên thay thế cho ảnh cũ một cách tự động.

Về công cụ tải lên hình ảnh vào R2, tôi cũng khéo léo sử dụng thêm thư viện @squoosh/cli để chuyển ảnh về định dạng nén webp, đồng thời giảm chất lượng ảnh xuống 80% để tiết kiệm dung lượng mà vẫn cho chất lượng hiển thị tốt. Việc còn lại chỉ là dẫn liên kết hình ảnh vào trong bài viết. Lúc này gần như tôi đã tự tin về khả năng hiển thị hình ảnh của mình.

Tối ưu hóa hiển thị hình ảnh lần hai

Mặc dù hình ảnh placeholder với chất lượng thấp đã giúp giải quyết được một phần hiển thị hình ảnh mượt mà hơn, tuy nhiên người dùng vẫn cần phải tải xuống một dung lượng lớn hình ảnh từ lần tải đầu tiên. Ở một khía cạnh nào đó, điều này tương đối lãng phí.

Mỗi bài viết có một hình ảnh hiển đại diện, gọi là ảnh banner. Hình ảnh này góp phần truyền tải thông điệp, giúp cho bài viết trở nên sinh động hơn cùng với một vài lợi ích khác. Nếu sử dụng Chrome, ngay tại trang chủ của trình duyệt, ở màn hình bắt đầu một cửa sổ duyệt web mới, kéo xuống dưới bạn sẽ thấy một danh sách các bài viết, đây là những bài viết được Google gợi ý cho bạn dựa trên nội dung mà bạn đã tiếp cận gần đây. Theo như họ giới thiệu trong bài viết Discover and your website - Search Central, không có cách thủ công nào để đưa bài viết của bạn vào danh sách này, nó hoàn toàn tự động do Google đề xuất dựa trên một số tiêu chí mà họ đề ra, một trong số đó là bài viết cần có hình ảnh đại diện đạt kích thước tối thiểu 1200x900 pixel.

Thông thường, một hình ảnh đạt tiêu chuẩn đó, khi được tối ưu hóa ở chất lượng 75% với định dạng webp sẽ rơi vào đâu đó cỡ 100KB. Tính sơ cua, trang chủ có 6 bài viết thì tổng dung lượng cần tải là 600KB, một con số mà tôi đánh giá là khá "lãng phí".

Để ý kỹ, bạn sẽ thấy hình ảnh thumbnail bên ngoài trang chủ có kích thước hiển thị nhỏ hơn rất nhiều so với kích thước nguyên bản ở bên trong chi tiết bài viết. Vì vậy, nếu điều chỉnh được kích thước ảnh thumbnail xuống mức cần thiết thì sẽ tiết kiệm được một lượng lớn dung lượng. Mặc dù mục đích là giảm dung lượng tải xuống nhưng chất lượng hiển thị và trải nghiệm vẫn cần được quan tâm, không ai muốn nhìn một hình ảnh mờ ảo và có vẻ chắp vá. Lúc này cần cân bằng được giữa dung lượng và chất lượng hiển thị.

Ý tưởng lúc đó của tôi là cắt nhỏ hình ảnh banner trong bài viết xuống kích thước vừa vặn hơn trong khung hiển thị hình ảnh thumbnail bên ngoài trang chủ. Nếu như kích thước banner cần 1200x900 thì thumbnail chỉ cần thiết ở mức 700x525, hơn nữa nếu thay thế được hình ảnh placeholder trước đó là logo của trang web thành hình ảnh chất lượng thấp của ảnh gốc thì sẽ mang lại trải nghiệm mới mẻ mà lại hợp lý hơn nhiều.

blur là một kỹ thuật làm mờ hình ảnh, đồng thời giảm dung lượng hình ảnh xuống thấp một cách đáng ngạc nhiên. blur hoạt động bằng cách phóng to pixel của hình ảnh lên từ đó làm giảm độ chi tiết của hình ảnh xuống, số lượng màu sắc cũng giảm xuống từ đó tiết kiệm được dung lượng lưu trữ. Sharp là một thư viện xử lý hình ảnh phổ biến trong Node.js và nó hỗ trợ hàm blur. Nếu qua hàm blur, kích thước ảnh lúc này chỉ tính trên con số vài KB dữ liệu, quả là hợp lý để làm hình ảnh placeholder cho bài viết.

Một vấn đề khác cần được giải quyết là không phải bài viết nào cũng có hình ảnh banner, vì như đã nói ngay từ đầu chưa xác định sử dụng hình ảnh. Để tạo được placeholder, tôi cần phải bổ sung thêm banner cho tất cả bài viết bị thiếu. Nhưng không thể ngồi để tạo từng ảnh một cách thủ công cho hơn 170 bài viết trước đó, mà nếu tính thêm cả phiên bản tiếng Anh, con số có thể vượt qua 340 bài viết, điều đó mang lại thách thức lớn về thời gian.

Đã đến lúc cần một giải pháp tự động hóa. Sau một vài giờ suy nghĩ thì tôi đã cho ra phương án "khổ trước sướng sau" - đó là sửa lại toàn bộ bài viết.

Ý tưởng ban đầu là nếu như lưu lại được toàn bộ hình ảnh gốc trong tất cả bài viết, phân loại chúng vào thư mục trùng với url của bài (vì mỗi bài là một url duy nhất). Khi đó hình ảnh banner sẽ có dạng url-bai-viet, các hình ảnh khác trong bài cũng có dạng tương tự ví dụ như url-bai-viet_anh_1, url-bai-viet_anh_2... Không cần quan tâm đến định dạng ảnh gốc là gì, vì sau khi tạo một công cụ định dạng lại ảnh, tất cả chúng sẽ về đuôi .webp.

Công cụ định dạng ảnh này khá đơn giản, sử dụng thư viện Sharp, khi nhận một hình ảnh đầu vào, nó sẽ cho ra 4 hình ảnh đầu ra. Trong đó 2 ảnh có kích thước 1200x900 để làm blur placeholder và ảnh banner, 2 ảnh có kích thước nhỏ hơn để hiển thị ngoài trang chủ. Sau đó nó tiếp tục tải lên 4 ảnh vừa tạo ra vào R2, rồi trả về đường dẫn liên kết đến mỗi ảnh.

Việc lưu trữ lại ảnh gốc cũng là một lợi thế, giả sử sau này có nghĩ ra cách tối ưu ảnh tốt hơn, tôi có thể viết lại một công cụ mới để xử lý ảnh và cho ra những bức ảnh khác với đường dẫn không thay đổi, hạn chế thay đổi trong bài viết.

Thách thức tiếp theo là dành thời gian sửa lại hơn 300 bài viết. Thay thế những đường dẫn ảnh trước đó về dạng mới, sửa lại một chút mã và thế là... chúng ta đã có hiệu ứng tải ảnh mới như các bạn đang trải nghiệm.

Tổng kết

Tối ưu là một bài toán dài hơi, cần có thời gian và cả nguồn lực. Có nhiều cách để giải quyết một vấn đề nào đó, ví như trên tôi đã chọn cách sửa lại liên kết đến hình ảnh trong hơn 300 bài viết, cùng với việc tạo ra hình ảnh có kích thức nhỏ hơn để làm placeholder, phục vụ cho mục đích tăng tốc tải trang và tăng trải nghiệm người dùng. Vậy nếu là bạn, bạn có cách nào hay hơn nữa không? Hãy để lại bình luận phía dưới bài viết nhé!

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

Bình luận (1)

Nội dung bình luận...
Avatar
Ẩn danh10 tháng trước
"Như bạn biết, thống kê chỉ ra rằng hình ảnh chiếm một lượng lớn dung lượng được truyền tải qua Internet khi truy cập vào các trang web. Nhờ có hình ảnh mà nhiều thông điệp được truyền tải, tạo thêm độ sinh động, trực quan hơn so với văn bản thông thường...", e thấy toàn mèo thôi a ạ :))
Trả lời
Avatar
Xuân Hoài Tống9 tháng trước
Vậy là "mèo chiếm một lượng lớn..." 🤣🤣🤣