0

Image optimization (Phần 1): Tổng quan và HTML <img> tag - Viblo

Tổng quan

Ảnh hưởng của image lên website

  • Performance và UX: Image làm tăng thời gian tải trang, đặc biệt trên mobile hoặc mạng chậm, ảnh hưởng trực tiếp đến trải nghiệm người dùng
  • SEO: Các công cụ tìm kiếm như Google sử dụng page speed làm một yếu tố xếp hạng. Image lớn hoặc load chậm sẽ làm giảm điểm SEO, đặc biệt là chỉ số LCP (Largest Contentful Paint).
  • Cost: Image chưa được tối ưu sẽ tiêu tốn nhiều bandwidth và tài nguyên server, khiến mỗi request “đắt” hơn. Khi traffic tăng, hệ thống sẽ nhanh đạt giới hạn hơn.

Phương pháp chung

  1. Sử dụng các format hiện đại như WebP, AVIF

    Mình thường dùng WebP format vì giảm size đáng kể so với JPEG/PNG (~26–30%) nhưng vẫn giữ chất lượng tốt. Ngoài ra, WebP hỗ trợ transparency và animation, đồng thời tương thích với hầu hết phần mềm chỉnh sửa ảnh và trình duyệt phổ biến.

    AVIF có nhiều ưu điểm hơn so với các định dạng cũ và cả WebP, đặc biệt về khả năng compression và chất lượng hiển thị. Nhưng giới hạn browser support,…. Xem thêm: https://shortpixel.com/blog/avif-vs-webp/

  2. Resize hoặc compress image trước khi upload

    Các tool mà mình thường dùng: Squoosh, TinyPNG, iLoveIMG

    Hai khái niệm này thường bị nhầm lẫn, nhưng về bản chất chúng tối ưu ở hai layer khác nhau:

    • Resize: thay đổi chiều rộng / chiều cao (resolution), giảm số lượng pixel. Thường dùng khi image hiển thị nhỏ trên UI (thumbnail, card…)
    • Compress: giảm file size (có thể mất hoặc giữ chất lượng — lossy / lossless), thường dùng khi image đã đúng kích thước nhưng dung lượng còn lớn.
  3. Sử dụng lazy loading và placeholder

    • Lazy loading: chỉ load khi image gần viewport
    • Placeholder có thể là image hoặc UI tạm (blur image, skeleton, màu nền)
    • Kết hợp lazy loading và placeholder để image load mượt hơn, tránh layout shift (CLS), giúp cải thiện UX và tránh cảm giác trống khi load image
    • Testing Lazy Load
      • Mở DevTools → tab Network → chọn Slow 4G/ 3G → tick Disable cache

      • Filter theo Img

      • Reload page

      • Scroll xuống kiểm tra:

        ✅ Image trong viewport → load ngay

        ✅ Image dưới viewport → chưa có request

      • Dùng Lighthouse trong Chrome DevTools để kiểm tra LCP, đảm bảo image quan trọng không bị lazy load.

  4. Về SEO

    • Không lazy load image quan trọng (LCP image)
    • Luôn set alt (SEO + accessibility): giúp search engine hiểu nội dung của image, đồng thời hỗ trợ screen reader cho người dùng khiếm thị.
      • Mô tả ngắn gọn, đúng nội dung image, tránh keyword stuffing
      • Nếu image mang tính trang trí có thể để alt="" để bỏ qua
    • Đảm bảo image có thể được crawl: Image URL phải accessible (không require auth)
  5. Sử dụng CDN và cache

    • CDN (Content Delivery Network) là hệ thống server phân tán toàn cầu, giúp phân phối image từ vị trí gần người dùng nhất → giảm latency và tăng tốc độ tải.
    • Một số dạng cache phổ biến
      • Browser cache: lưu trực tiếp trên thiết bị người dùng (disk hoặc memory của browser)
      • CDN cache: Lưu tại các edge server của CDN (Cloudflare, Akamai, AWS CloudFront…), phục vụ nhiều user cùng lúc.
      • Server cache: lưu kết quả xử lý image (resize, compress…) ở phía backend / image processing layer

Image optimization đối với HTML <img> tag

  1. lazy loadingsrcset props

    <img
      src="/sm_image_path.webp"
      srcset="sm_image_path.webp 500w, md_image_path.webp 1000w, lg_image_path.webp 1500w"
      sizes="(max-width: 600px) 500px, 1000px"
      loading="lazy"
      alt="Example"
    />
    
    • srcset : chọn image phù hợp với screen size và device pixel ratio (DPR)
      • Screen size: kích thước hiển thị thực tế của image trên layout.
      • Device Pixel Ratio (DPR): tỉ lệ giữa pixel vật lý và pixel CSS trên màn hình (ví dụ: màn hình Retina có DPR = 2 hoặc 3)→ DPR cao cần image có độ phân giải cao hơn để hiển thị sắc nét.
  2. Trigger việc load image với Intersection Observer API

     <img
        class="lazy"
        data-src="/image_path.jpeg"
        alt="Lazy Loaded Image"
        width="600"
        height="400"
      />
    
    function lazyLoadImages() {
      const elements = document.querySelectorAll(".lazy");
      const observer = new IntersectionObserver(
        (entries, obs) => {
          entries.forEach((entry) => {
            if (!entry.isIntersecting) return;
    
            const el = entry.target;
            if (el) {
              el.src = el.dataset.src;
              el.classList.remove("lazy");
              obs.unobserve(el);
            }
          });
        },
        {
          rootMargin: "100px 0px",
          threshold: 0.1,
        },
      );
    
      elements.forEach((element) => {
        observer.observe(element);
      });
    }
    
    document.addEventListener("DOMContentLoaded", lazyLoadImages);
    

    Giải thích Intersection observer options

    • rootMargin: "100px 0px" → mở rộng viewport thêm 100px ở top và bottom để trigger sớm hơn
    • threshold: 0.1 → trigger khi ~10% element nằm trong viewport (bao gồm rootMargin)
    • Kết hợp rootMarginthreshold giúp việc lazy loading mượt hơn, tránh hiện tượng giật khi scroll

    Dùng CSS background-image

    <div class="lazy" id="bgImage"></div>
    
    #bgImage {
      min-height: 300px;
      background-size: cover;
      background-repeat: no-repeat;
      background-color: #eeeeee;
      background-image: url("/bg_image.webp");
    }
    
    #bgImage.lazy {
      background-image: none;
    }
    
    const observer = new IntersectionObserver(
      (entries, obs) => {
        entries.forEach((entry) => {
          if (!entry.isIntersecting) return;
    
          const el = entry.target;
          if (el) {
            el.classList.remove("lazy");
            obs.unobserve(el);
          }
        });
      },
      // Options...
    );
    
  3. Sử dụng JavaScript Library

    Bạn có thể tham khảo thư viện lazyload (vanilla JavaScript). Thư viện này sử dụng Intersection Observer API để theo dõi khi image đi vào viewport, với các tính năng tương tự như cách implement thủ công ở trên.

Lời kết

Cảm ơn bạn đã đọc bài viết. Nếu có góp ý, hãy để lại comment để mình có thể cập nhật và cải thiện nội dung.

Ở phần tiếp theo, mình sẽ nói về image optimization với Next.js Image Component.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí