> ## Documentation Index
> Fetch the complete documentation index at: https://inertiajs-vi.tuantq.online/llms.txt
> Use this file to discover all available pages before exploring further.

# Chỉ báo tiến trình

<Warning>Đây là tài liệu Inertia.js v1, phiên bản không còn được duy trì tích cực. Vui lòng tham khảo [tài liệu v3](/v3/getting-started/index).</Warning>

Vì request Inertia được thực hiện qua XHR nên trình duyệt thường không hiển thị chỉ báo tải khi điều hướng từ trang này sang trang khác. Để giải quyết, Inertia hiển thị progress indicator ở đầu trang mỗi khi bạn thực hiện một Inertia visit.

Tất nhiên, nếu muốn, bạn có thể tắt loading indicator mặc định của Inertia và tự triển khai. Chúng ta sẽ trình bày cả hai cách bên dưới.

## Mặc định

Progress indicator mặc định của Inertia là một lớp bọc nhẹ quanh thư viện [NProgress](https://ricostacruz.com/nprogress/). Bạn có thể tùy chỉnh thông qua property `progress` của hàm `createInertiaApp()`.

```js theme={null}
createInertiaApp({
progress: {
// The delay after which the progress bar will appear, in milliseconds...
delay: 250,
// The color of the progress bar...
color: '#29d',
// Whether to include the default NProgress styles...
includeCSS: true,
// Whether the NProgress spinner will be shown...
showSpinner: false,
},
// ...
})
```

Bạn có thể tắt loading indicator mặc định của Inertia bằng cách đặt property `progress` thành `false`.

```js theme={null}
createInertiaApp({
progress: false,
// ...
})
```

## Tùy chỉnh

Bạn cũng có thể tự thiết lập chỉ báo tải trang tùy chỉnh bằng [events](/v1/advanced/events) của Inertia. Hãy xem cách thực hiện với thư viện [NProgress](https://ricostacruz.com/nprogress/) làm ví dụ.

Trước tiên, hãy tắt loading indicator mặc định của Inertia.

```js theme={null}
createInertiaApp({
progress: false,
// ...
})
```

Tiếp theo, cài thư viện NProgress.

```bash theme={null}
npm install nprogress
```

Sau khi cài đặt, bạn cần thêm [style](https://github.com/rstacruz/nprogress/blob/master/nprogress.css) của NProgress vào dự án. Bạn có thể dùng bản style được host trên CDN.

```html theme={null}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css" />
```

Tiếp theo, import cả `NProgress` và `router` của Inertia vào ứng dụng.

<CodeGroup>
  ```js Vue 2 icon="vuejs" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/vue2'
  ```

  ```js Vue 3 icon="vuejs" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/vue3'
  ```

  ```js React icon="react" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/react'
  ```

  ```js Svelte icon="s" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/svelte'
  ```
</CodeGroup>

Sau đó, hãy thêm listener cho sự kiện `start`. Chúng ta sẽ dùng listener này để hiển thị progress bar khi một Inertia visit mới bắt đầu.

```js theme={null}
router.on('start', () => NProgress.start())
```

Tiếp theo, thêm listener cho sự kiện `finish` để ẩn progress bar khi visit hoàn tất.

```js theme={null}
router.on('finish', () => NProgress.done())
```

Vậy là xong. Giờ khi điều hướng giữa các trang, progress bar sẽ tự động được thêm vào và loại bỏ khỏi trang.

### Xử lý visit bị hủy

Mặc dù phần triển khai progress tùy chỉnh này hoạt động tốt với các page visit hoàn tất bình thường, sẽ tốt hơn nếu xử lý cả visit bị hủy. Thứ nhất, với visit bị gián đoạn (bị hủy do một visit mới bắt đầu), progress bar chỉ nên được reset về vị trí bắt đầu. Thứ hai, với visit bị hủy thủ công, progress bar nên được loại bỏ khỏi trang ngay lập tức.

Chúng ta có thể thực hiện bằng cách kiểm tra object `event.detail.visit` được cung cấp cho sự kiện finish.

```js theme={null}
router.on('finish', (event) => {
if (event.detail.visit.completed) {
NProgress.done()
} else if (event.detail.visit.interrupted) {
NProgress.set(0)
} else if (event.detail.visit.cancelled) {
NProgress.done()
NProgress.remove()
}
})
```

### Tiến trình tải file

Hãy tiến thêm một bước. Khi file đang được tải lên, sẽ rất hữu ích nếu loading indicator phản ánh đúng tiến trình upload. Bạn có thể làm việc này bằng sự kiện `progress`.

```js theme={null}
router.on('progress', (event) => {
if (event.detail.progress.percentage) {
NProgress.set((event.detail.progress.percentage / 100) * 0.9)
}
})
```

Giờ thay vì progress bar chỉ tăng dần mang tính ước lượng trong lúc file được tải lên, vị trí của nó sẽ thực sự cập nhật theo tiến trình request. Ở đây chúng ta giới hạn tiến trình ở 90% vì vẫn còn phải chờ response từ máy chủ.

### Trì hoãn loading indicator

Phần cuối cùng chúng ta sẽ triển khai là độ trễ trước khi hiển thị loading indicator. Thông thường nên trì hoãn việc hiển thị cho đến khi request kéo dài hơn 250–500 mili giây. Điều này tránh loading indicator xuất hiện liên tục ở các page visit rất nhanh, vốn có thể gây rối mắt.

Để triển khai độ trễ, chúng ta sẽ dùng các hàm `setTimeout` và `clearTimeout`. Trước tiên, hãy định nghĩa một biến để theo dõi timeout.

```js theme={null}
let timeout = null
```

Tiếp theo, cập nhật listener `start` để tạo timeout mới, hiển thị progress bar sau 250 mili giây.

```js theme={null}
router.on('start', () => {
timeout = setTimeout(() => NProgress.start(), 250)
})
```

Sau đó, cập nhật listener `finish` để xóa mọi timeout còn tồn tại nếu page visit hoàn tất trước khi timeout kết thúc.

```js theme={null}
router.on('finish', (event) => {
clearTimeout(timeout)
// ...
})
```

Trong listener `finish`, chúng ta cần xác định progress bar đã thực sự bắt đầu hiển thị tiến trình hay chưa; nếu không, vô tình chúng ta sẽ khiến nó xuất hiện trước khi timeout kết thúc.

```js theme={null}
router.on('finish', (event) => {
clearTimeout(timeout)
if (!NProgress.isStarted()) {
return
}
// ...
})
```

Cuối cùng, chúng ta cũng cần thực hiện cùng phép kiểm tra đó trong listener `progress`.

```js theme={null}
router.on('progress', event => {
if (!NProgress.isStarted()) {
return
}
// ...
}
```

Vậy là bạn đã có một loading indicator tùy chỉnh đẹp mắt cho trang.

### Ví dụ hoàn chỉnh

Để tiện tham khảo, dưới đây là toàn bộ source code của phiên bản cuối cùng cho loading indicator tùy chỉnh.

<CodeGroup>
  ```js Vue 2 icon="vuejs" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/vue2'

  let timeout = null

  router.on('start', () => {
      timeout = setTimeout(() => NProgress.start(), 250)
  })

  router.on('progress', (event) => {
      if (NProgress.isStarted() && event.detail.progress.percentage) {
          NProgress.set((event.detail.progress.percentage / 100) * 0.9)
      }
  })

  router.on('finish', (event) => {
      clearTimeout(timeout)
      if (!NProgress.isStarted()) {
          return
      } else if (event.detail.visit.completed) {
          NProgress.done()
      } else if (event.detail.visit.interrupted) {
          NProgress.set(0)
      } else if (event.detail.visit.cancelled) {
          NProgress.done()
          NProgress.remove()
      }
  })
  ```

  ```js Vue 3 icon="vuejs" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/vue3'

  let timeout = null

  router.on('start', () => {
      timeout = setTimeout(() => NProgress.start(), 250)
  })

  router.on('progress', (event) => {
      if (NProgress.isStarted() && event.detail.progress.percentage) {
          NProgress.set((event.detail.progress.percentage / 100) * 0.9)
      }
  })

  router.on('finish', (event) => {
      clearTimeout(timeout)
      if (!NProgress.isStarted()) {
          return
      } else if (event.detail.visit.completed) {
          NProgress.done()
      } else if (event.detail.visit.interrupted) {
          NProgress.set(0)
      } else if (event.detail.visit.cancelled) {
          NProgress.done()
          NProgress.remove()
      }
  })
  ```

  ```js React icon="react" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/react'

  let timeout = null

  router.on('start', () => {
      timeout = setTimeout(() => NProgress.start(), 250)
  })

  router.on('progress', (event) => {
      if (NProgress.isStarted() && event.detail.progress.percentage) {
          NProgress.set((event.detail.progress.percentage / 100) * 0.9)
      }
  })

  router.on('finish', (event) => {
      clearTimeout(timeout)
      if (!NProgress.isStarted()) {
          return
      } else if (event.detail.visit.completed) {
          NProgress.done()
      } else if (event.detail.visit.interrupted) {
          NProgress.set(0)
      } else if (event.detail.visit.cancelled) {
          NProgress.done()
          NProgress.remove()
      }
  })
  ```

  ```js Svelte icon="s" theme={null}
  import NProgress from 'nprogress'
  import { router } from '@inertiajs/svelte'

  let timeout = null

  router.on('start', () => {
      timeout = setTimeout(() => NProgress.start(), 250)
  })

  router.on('progress', (event) => {
      if (NProgress.isStarted() && event.detail.progress.percentage) {
          NProgress.set((event.detail.progress.percentage / 100) * 0.9)
      }
  })

  router.on('finish', (event) => {
      clearTimeout(timeout)
      if (!NProgress.isStarted()) {
          return
      } else if (event.detail.visit.completed) {
          NProgress.done()
      } else if (event.detail.visit.interrupted) {
          NProgress.set(0)
      } else if (event.detail.visit.cancelled) {
          NProgress.done()
          NProgress.remove()
      }
  })
  ```
</CodeGroup>

***

## Tài liệu chính thức

Bài dịch này được đối chiếu từ [tài liệu Inertia.js v1 chính thức](https://inertiajs.com/docs/v1/advanced/progress-indicators). Nếu có khác biệt do phiên bản hoặc cập nhật mới, hãy ưu tiên tài liệu chính thức làm nguồn tham chiếu.
