Hướng dẫn nấu món "Lazy loading image" theo chuẩn 5 sao Google

https://viblo.asia/p/huong-dan-nau-mon-lazy-loading-image-theo-chuan-5-sao-google-maGK7qJMlj2?fbclid=IwAR1iMSW2bbw1DvAPZeLc3bxZbtwSnX3Yv3HnQWavMH6x665nLFV4df4_FHA

Hướng dẫn nấu món "Lazy loading image" theo chuẩn 5 sao Google

Trending JavaScriptReact

Hướng dẫn nấu món "Lazy loading image" theo chuẩn 5 sao Google

Mỵ là một người con gái nết na thùy mị. Vào ngày Tết cuối năm, bạn trai mời Mỵ sang ra mắt bố mẹ chồng. Đây, cơ hội ngàn năm có một, ngày mà Mỵ đã chờ bấy lâu nay, đây là dịp để cô trổ hết tài công dung ngôn hạnh mà đã mất bao tiền của thời gian luyện tập và chuẩn bị. Đề thi mẹ chồng cho nàng dâu như sau "Con hãy làm món lazy load image cho bộ sưu tập của mẹ nhé!". Lúc bấy giờ Mỵ mới giật thót, bấy lâu nay toàn học chế biến những sơn hào hải vị, nhưng giờ gặp món ăn dân giã như này phải làm sao. Quen mà lạ!

Nào giờ ta hãy cùng giúp Mỵ hoàn thành món ăn này một cách xuất sắc nhé.

Bước 1: Chuẩn bị nguyên liệu

  • Để giữ bài toán ở mức đơn giản, ở đây mình sẽ tạo ra một danh sách, bên trong là các thẻ img. Nguồn dữ liệu mình sẽ lẩy từ API của unsplash.com.

  • API sẽ có thể lấy theo page. Mỗi page sẽ trả về 10 ảnh. Mỗi một bức ảnh thì sẽ có các kích thước như thumbnail, small, medium, large.

Bước 2: Sơ chế

  • Là một thư viện ảnh, có nhiều cách để trưng bày chúng. Ở đây mình lựa chọn là một vertical list, ảnh sẽ xếp lần lượt từ trên xuống dưới. Cuộn xuống gần đáy trang, ảnh sẽ được load thêm cho đến mãi mãi

function App() {
  const [images, setImages] = useState([])
  const [page, setPage] = useState(1)

  useEffect(() => {
    APIFetchImages({ page, setImages })
  }, [page])

  useEffect(() => {
    document.addEventListener('scroll', throttle(() => {
      if ((window.innerHeight + window.scrollY + OFFSET_HEIGHT) >= document.body.offsetHeight) {
        setPage(data => data + 1)
      }
    }, 300))

    return () => document.removeEventListener('scroll')
  }, [])

  return (
    <div className="App">
      <div className="image-list">
        {images.map((image, i) => (
          <div className="image-item" key={i}>
            <img src={image.regular} alt="" className="image" />
          </div>
        ))}
      </div>
    </div>
  )
}
  • Ở đây mình đã làm những điều như sau

    • Khi vào trang, gọi đến API unsplash và kéo ảnh về, sau đó set vào list

    • Mỗi khi user cuộn xuống gần đáy trang, update trang page hiện tại lên 1 đơn vị

    • Thấy sự thay đổi về trang API sẽ được gọi lại và tiếp tục đẩy vào danh sách

    • Hàm scroll được thottle, một khi đã được gọi thì sẽ phải chờ ít nhất 300ms tiếp theo thì mới có thể được gọi

    • Event listener sẽ được release khi out khỏi page

  • Với phần infiinty scroll, nhìn chung như thế đã là ổn

Bước 3: Chế biến

  • Trước khi bước vào công đoạn chính, ta sẽ điểm qua một vài vẫn đề trước. Vậy tại sao phải cần lazy load ở đây

    • Không ai muốn phải chờ đợi. Một user khi vào một trang web sẽ luôn muốn mình có thể xem được các nội dung một cách nhanh nhất. Mà thứ tốn thời gian tải của trang web nhất lại thường là các tài nguyên media.

    • Nếu các hình ảnh ở phía dưới, user không có cơ hội hay nhu cầu xem ảnh, cơ sao bắt họ phải tải xuống hết rồi mới cho xem nội dung

    • Không dùng đến mà mình vẫn yều cầu server trả response về thì là lãng phí tài nguyên server

  • Để có thể biến món ăn, ta có 3 cách như sau:

Cách 1: sử dụng Intersection Observer API

  • Đây là một API có sẵn của WebAPI, giúp bạn có thể đặt một callback mỗi khi mà có một element nào đó đi vào viewport màn hình hay một vùng nào đó bạn quy định. Nhờ vào cơ chế này, bạn có thể để một callback cho các hình ảnh sẽ được load khi chúng được user cuộn và xuất hiện trên màn hình.

const lazyImageObserver = new IntersectionObserver(function (entries) {
  entries.forEach(function (entry) {
    if (entry.isIntersecting) {
      const lazyImage = entry.target
      lazyImage.src = lazyImage.dataset.src
      lazyImage.classList.remove('lazy')
      lazyImageObserver.unobserve(lazyImage)
    }
  })
})
  • IntersectionObserver có thể lắng nghe được nhiều element cùng một lúc

  • Mỗi khi một element vào viewport thì chúng sẽ được update data-src vào src và bỏ observe của chúng đi

  • Ở mỗi image sẽ nhận prop lazyImageObserver trên và gắn observe tương ứng.

function Image({ src, lazyImageObserver, id }) {
  const imageElement = useRef(null)

  useEffect(() => {
    lazyImageObserver.observe(imageElement.current)
  }, [lazyImageObserver])

  return (
    <img ref={imageElement} data-id={id} data-src={src} className="image lazy" alt="lazy-img" />
  )
}
  • Đây là một API không phải trình duyệt nào cũng hỗ trợ, chính vì thế bạn sẽ có thể cần dùng đến polyfill hay sử dụng các cách khác phía bên dưới

Cách 2: sử dụng event scroll

  • Đây là cách rất thông thuộc với các bạn và có thể support trên mọi trình duyệt rồi

  • Ta sẽ add một event mỗi khi user scroll. Lúc đó các image sẽ được check xem đã được vào viewport chưa và load hình ảnh

lazyImages.forEach(function (lazyImage) {
  if ((lazyImage.getBoundingClientRect().top <= window.innerHeight
    && lazyImage.getBoundingClientRect().bottom >= 0)) {
    lazyImage.src = lazyImage.dataset.src
    lazyImage.classList.remove('lazy')
  }
})
  • Khác với cách trên, cách này sẽ phải cận thận để check với đủ các case scroll resize orientationchange để tránh bị mẹ chồng bắt BUG nhé

Cách 3: sử dụng css background

  • Trong khi với hầu hết các trường hợp, ta sẽ sử dụng thẻ img để có thể show ra ảnh. Mình còn có một cách khác đó là dùng đến thuộc tính background-image.

  • Không giống với cách hoạt động của img là luôn được load và hiện thị cho dù có được xem thấy hay không, background-image hoạt động một cách đặc biệt hơn. Khi document và CSS object modal đã được build, browser lúc đó mới apply các CSS đó trước rồi mới quyết định gọi đến các request bên ngoài. Chính vì vậy để link ảnh vào thuộc tính background-image có thể tăng tốc first loading cho user.

  • Cùng kết hợp với 2 cách trên, ta có thể ra được một phong cách lazy load khác tương tự

.lazy-background {
  background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}

.lazy-background.visible {
  background-image: url("hero.jpg"); /* The final image */
}

Cách 4: Đặt ship mang cho

  • Đúng như cách gọi của nó, ở cách này hầu như bạn chẳng phải làm gì cả, chỉ cần nằm và tận hưởng

  • Ở version Chrome76, Google đã giới thiệu một hàm native mới support hoàn toàn những công việc này. Bạn có thể enable và trải nghiệm tính năng này khi nhập dòng này vào URL chrome://flags/#enable-lazy-image-loading

  • Cụ thể bạn có thể đọc và tìm hiểu thêm ở đây nhé https://web.dev/native-lazy-loading/

Bước 4: Trang trí

  • Đây chính là công đoạn cuối cùng cho tác phầm này rồi. Từ bước này trở đi, bạn có thể thoải mái và sáng tạo sao cho hợp mắt.

  • Với mỗi mình image, mình có thể tạo một place holder hiệu ứng loading chẳng hạn Hy vọng với các hướng dẫn step by step như trên, Mỵ đã có thể ghi điểm tuyệt đối trong mắt mẹ chồng. Nếu không được hoàn hảo, hoàng tử kia cũng sẽ tha thứ cho người con gái đã cố gắng nỗ lực hết mình rồi.

References

Last updated