> For the complete documentation index, see [llms.txt](https://javascriptuse.gitbook.io/javascript/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://javascriptuse.gitbook.io/javascript/huong-dan-nau-mon-lazy-loading-image-theo-chuan-5-sao-google.md).

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

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

[Trending](https://viblo.asia/trending) [JavaScript](https://viblo.asia/tags/javascript)[React](https://viblo.asia/tags/react)

## Hướng dẫn nấu món "Lazy loading image" theo chuẩn 5 sao Google <a href="#huong-dan-nau-mon-lazy-loading-image-theo-chuan-5-sao-google-0" id="huong-dan-nau-mon-lazy-loading-image-theo-chuan-5-sao-google-0"></a>

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é.

![](https://images.viblo.asia/760f8332-e94b-495c-b399-1b7c39660df6.jpg)

### Bước 1: Chuẩn bị nguyên liệu <a href="#buoc-1-chuan-bi-nguyen-lieu-1" id="buoc-1-chuan-bi-nguyen-lieu-1"></a>

* Để 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](http://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ế <a href="#buoc-2-so-che-2" id="buoc-2-so-che-2"></a>

* 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 <a href="#buoc-3-che-bien-3" id="buoc-3-che-bien-3"></a>

* 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` <a href="#cach-1-su-dung-intersection-observer-api-4" id="cach-1-su-dung-intersection-observer-api-4"></a>

* Đâ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 <a href="#cach-2-su-dung-event-scroll-5" id="cach-2-su-dung-event-scroll-5"></a>

* Đâ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 <a href="#cach-3-su-dung-css-background-6" id="cach-3-su-dung-css-background-6"></a>

* 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 <a href="#cach-4-dat-ship-mang-cho-7" id="cach-4-dat-ship-mang-cho-7"></a>

* Đú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í <a href="#buoc-4-trang-tri-8" id="buoc-4-trang-tri-8"></a>

* Đâ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` <a href="#references-9" id="references-9"></a>

* <https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/>
* <https://medium.com/@filipvitas/lazy-load-images-with-zero-javascript-2c5bcb691274>
* <https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/>
* <https://medium.com/bbc-design-engineering/native-lazy-loading-has-arrived-c37a165d70a5>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://javascriptuse.gitbook.io/javascript/huong-dan-nau-mon-lazy-loading-image-theo-chuan-5-sao-google.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
