Chuyển đến nội dung

Web ​

@loader-kit/web vẽ các indicator của LoaderKit vào một <canvas>. Chọn entry point hợp với app của bạn:

Entry pointBạn nhận được gì
@loader-kit/web/reactcomponent <LoaderKit> cho React 17 trở lên
@loader-kit/web/vuecomponent <LoaderKit> cho Vue 3.3 trở lên
@loader-kit/web/sveltecomponent <LoaderKit> cho Svelte 4 và 5
@loader-kit/web/elementcustom element <loader-kit>, cho HTML thuần và các framework khác
@loader-kit/webclass LoaderKitView, cùng các hàm để bạn tự prepare và vẽ một spec

Các component render element <loader-kit> và tự đăng ký nó, nên bạn không cần setup gì thêm. Mọi entry point đều import an toàn trong lúc server-side rendering. Với bundler dùng ES module, bundle chỉ chứa element của những component bạn import: app chỉ dùng LoaderKit sẽ không kèm <loader-kit-progress>, và ngược lại.

Cài đặt ​

sh
npm install @loader-kit/web
sh
yarn add @loader-kit/web
sh
pnpm add @loader-kit/web

React, Vue và Svelte là peer dependency tùy chọn: chỉ cần cài framework bạn đang dùng.

React ​

tsx
import { LoaderKit } from '@loader-kit/web/react';

export function Saving({ busy }: { busy: boolean }) {
  return <LoaderKit indicator="BallSpinFadeLoader" color="#7c3aed" size={48} animating={busy} />;
}
  • Module được đánh dấu 'use client', nên với App Router của Next.js bạn có thể render <LoaderKit> ngay trong một Server Component, với các props serialize được. onError hoặc ref thì truyền từ một Client Component.
  • ref trỏ tới element <loader-kit> (LoaderKitElementApi), ví dụ để đọc ref.current.time.
  • onError nhận thông báo lỗi khi indicator không vẽ được, và nhận null khi nó vẽ lại được.

Vue ​

vue
<script setup lang="ts">
import { LoaderKit } from '@loader-kit/web/vue';

defineProps<{ busy: boolean }>();

function onError(message: string | null) {
  if (message) console.warn(message);
}
</script>

<template>
  <LoaderKit indicator="BallSpinFadeLoader" color="#7c3aed" :size="48" :animating="busy" @error="onError" />
</template>
  • Đây là một component Vue bình thường: không cần cấu hình compiler, chạy được với Nuxt và server-side rendering.
  • Muốn dùng ở mọi nơi mà không phải import, hãy đăng ký một lần: app.component('LoaderKit', LoaderKit).
  • Template ref trên component expose element, chính là element <loader-kit>.

Svelte ​

svelte
<script lang="ts">
  import { LoaderKit } from '@loader-kit/web/svelte';

  let { busy }: { busy: boolean } = $props();
</script>

<LoaderKit indicator="BallSpinFadeLoader" color="#7c3aed" size={48} animating={busy} />
  • Chạy được với Svelte 4 và 5, và với server-side rendering của SvelteKit. Package đi kèm source của component, plugin Svelte cho Vite sẽ compile nó cùng phần còn lại của app.
  • bind:element cho bạn element <loader-kit>. onError nhận thông báo lỗi, và nhận null khi indicator vẽ lại được.
  • Component được viết không dùng runes để cùng một source compile được với cả hai phiên bản. Nếu config Svelte 5 của bạn bật runes cho mọi file, hãy giới hạn nó trong code của bạn, ví dụ runes: ({ filename }) => filename.split(/[/\\]/).includes('node_modules') ? undefined : true.

Props của component ​

Cả ba component nhận cùng một bộ props:

PropKiểuMặc định
indicatortên một indicator có sẵn'BallPulse'
specIndicatorSpec hoặc chuỗi JSON. Được ưu tiên hơn indicatorkhông có
paramsRecord<string, number> hoặc chuỗi JSONkhông có
colormàu CSS bất kỳCSS color của element
colorsstring[]. Được ưu tiên hơn color khi không rỗngkhông có
speednumber. Từ 0 trở xuống là tạm dừng1
animatingbooleantrue
hidesWhenStoppedbooleantrue
cycleProgresssố từ 0 đến 1 để đứng yên ở frame đó. null thì chạy theo đồng hồnull
respectsReduceMotionbooleantrue
sizesố tính bằng px, hoặc độ dài CSS bất kỳ như '3rem'40px, trừ khi CSS đặt kích thước
onError (React, Svelte), @error (Vue)(message: string | null) => voidkhông có
  • Các attribute khác như class, style, id hay aria-label được chuyển xuống element <loader-kit>.
  • Khi không có size, element rộng 40px, cao 40px theo style của chính nó, nên một class hay rule CSS bất kỳ đều đặt lại được kích thước (ví dụ class="h-12 w-12").
  • spec và params được so sánh dưới dạng JSON, nên truyền một object mới có cùng nội dung ở mỗi lần render sẽ không làm animation chạy lại từ đầu.
  • Prop nào quay về undefined thì trở lại giá trị mặc định.

Framework khác ​

Với Angular, Solid, Lit, HTML thuần hay bất cứ gì khác, hãy dùng custom element. Trong Angular, cho phép custom element trong component và import entry của element một lần:

ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@loader-kit/web/element';

@Component({
  selector: 'app-saving',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `<loader-kit indicator="BallSpinFadeLoader" [animating]="busy"></loader-kit>`,
})
export class SavingComponent {
  busy = true;
}

Custom element ​

Dùng element trực tiếp trong HTML thuần, hoặc trong framework chưa có component ở trên. Import entry của element một lần. Nó sẽ định nghĩa <loader-kit> nếu tag này chưa được định nghĩa.

ts
import '@loader-kit/web/element';
html
<loader-kit indicator="BallSpinFadeLoader" color="#7c3aed" speed="1.5"></loader-kit>
<loader-kit indicator="BallPulse" params='{"count":5}' colors="#f43f5e, #f59e0b, #10b981"></loader-kit>
<loader-kit cycle-progress="0.25" hides-when-stopped="false" animating="false"></loader-kit>

Attribute ​

AttributeGiá trịMặc định
indicatortên một indicator có sẵnBallPulse
specmột spec tùy chỉnh dạng JSON. Được ưu tiên hơn indicatorkhông có
paramsmột object JSON, ví dụ {"count":5}không có
colormàu CSS bất kỳCSS color của phần tử
colorscác màu CSS, cách nhau bằng dấu phẩy. Được ưu tiên hơn colorkhông có
speedmột con số. Bằng 0 hoặc nhỏ hơn thì tạm dừng1
animating"false" để dừngđang chạy
hides-when-stopped"false" để frame đứng yên vẫn hiển thịẩn khi dừng
cycle-progressmột số từ 0 đến 1, freeze frame tại đókhông có
respects-reduce-motion"false" để bỏ qua prefers-reduced-motioncó tôn trọng

Mỗi attribute cũng là một property viết kiểu camelCase: indicator, spec, params, color, colors, speed, animating, hidesWhenStopped, cycleProgress, respectsReduceMotion. Các property spec, params và colors còn nhận cả object và array:

ts
const el = document.querySelector('loader-kit')!;
el.params = { count: 5 };
el.colors = ['#f43f5e', '#10b981'];
el.spec = mySpec; // truyền object luôn, không cần stringify

Kích thước và màu ​

Mặc định phần tử có kích thước 40px x 40px. Chỉnh kích thước bằng CSS. Nếu không có attribute color, nó dùng CSS color của phần tử, nên tự ăn theo màu chữ và theme của bạn.

css
loader-kit {
  width: 64px;
  height: 64px;
  color: var(--vp-c-brand-1);
}

Lỗi ​

Khi spec không vẽ được, phần tử sẽ không vẽ gì và dispatch event loaderkit-error. detail.message là thông báo lỗi, hoặc null khi một thay đổi sau đó đã sửa được lỗi.

ts
el.addEventListener('loaderkit-error', (event) => {
  const { message } = (event as CustomEvent<{ message: string | null }>).detail;
  if (message) console.warn(message);
});

Accessibility ​

Phần tử có role="progressbar" không kèm giá trị (indeterminate) và aria-label="Loading", trừ khi bạn tự đặt aria-label. Khi bị ẩn, nó có aria-hidden.

Đổi tên tag ​

ts
import { defineLoaderKitElement } from '@loader-kit/web/element';

defineLoaderKitElement('my-loader');

Module này cũng export class LoaderKitElement.

Class LoaderKitView ​

LoaderKitView vẽ vào một <canvas> mà nó tự tạo bên trong phần tử host. Nếu host chính là một <canvas>, nó vẽ thẳng vào host.

ts
import { LoaderKitView } from '@loader-kit/web';

const view = new LoaderKitView(document.querySelector('#loader')!, {
  indicator: 'BallPulse',
  params: { count: 5 },
  color: '#7c3aed',
  onError: (message) => {
    if (message) console.warn(message);
  },
});

view.speed = 2;
view.stop();
view.start();
view.destroy(); // dừng frame loop, gỡ các observer và canvas mà nó đã tạo

Option và property ​

Mỗi option cũng là một property mà bạn có thể đọc và gán lại sau.

OptionKiểuMặc định
indicatortên indicator có sẵn'BallPulse'
specIndicatorSpec, chuỗi JSON, hoặc null. Được ưu tiên hơn indicatornull
paramsRecord<string, number>{}
colormàu CSS bất kỳ, hoặc nullCSS color của host (currentColor)
colorsstring[] hoặc null. Được ưu tiên hơn color khi không rỗngnull
speednumber. Bằng 0 hoặc nhỏ hơn thì tạm dừng1
animatingbooleantrue
hidesWhenStoppedbooleantrue
cycleProgressmột số từ 0 đến 1, hoặc null để chạy theo đồng hồnull
respectsReduceMotionbooleantrue
onError(message: string | null) => voidkhông có

Các member chỉ đọc:

MemberÝ nghĩa
canvasHTMLCanvasElement đang được vẽ
specErrorthông báo lỗi hiện tại, hoặc null
timethời điểm trong spec đang được vẽ, tính bằng giây

View lấy kích thước canvas theo host, nên bạn hãy đặt kích thước cho host bằng CSS.

Server-side rendering ​

  • Mọi entry point đều import an toàn trên server: không entry nào đụng tới DOM global lúc import, và element chỉ được đăng ký ở nơi có customElements.
  • Các component React, Vue và Svelte render <loader-kit> kèm đầy đủ attribute ngay trên server. Trong trình duyệt, element vẽ đúng indicator đó ngay khi được upgrade, và hydration giữ nguyên element mà server đã render.
  • Khi JavaScript chưa load xong, element chưa có kích thước riêng. Hãy đặt size hoặc kích thước CSS để trang không bị xô lệch khi nó bắt đầu vẽ.
  • Chỉ tạo LoaderKitView trong trình duyệt, ví dụ trong useEffect, onMounted hoặc onMount.

Progress indicator ​

LoaderKitProgress cho biết một tác vụ đã chạy được bao nhiêu: 30 mẫu thuộc 9 type, có value hoặc vô định, đổi value mượt.

html
<script type="module">
  import '@loader-kit/web/progress-element';
</script>

<loader-kit-progress type="linear" value="0.4"></loader-kit-progress>
<loader-kit-progress type="circular" show-label size="64"></loader-kit-progress>
ts
import { LoaderKitProgressView } from '@loader-kit/web';

const view = new LoaderKitProgressView(host, { type: 'linear', variant: 'wavy' });
view.value = 0.4;    // chạy mượt tới 0.4; null là vô định
view.destroy();

LoaderKitProgress cũng được export từ @loader-kit/web/react, @loader-kit/web/vue và @loader-kit/web/svelte, với props giống các attribute nhưng viết camelCase, và children cho nội dung ở giữa hoặc bên trong border. Giống <loader-kit>, phần tử này render được trên server và bắt đầu vẽ khi được upgrade.

Xem Progress indicator để biết mọi type, variant và option.

Spec tùy chỉnh ​

Truyền spec dưới dạng object hoặc JSON. Xem Indicator tùy chỉnh để biết cách viết.

ts
import { LoaderKitView, validate } from '@loader-kit/web';

const json = await (await fetch('/specs/typing-dots.json')).text();

const problems = validate(JSON.parse(json));
if (problems.length > 0) console.warn(problems);

const view = new LoaderKitView(host, { spec: json, params: { count: 4 } });
tsx
import { LoaderKit } from '@loader-kit/web/react';
import typingDots from './typing-dots.json';

<LoaderKit spec={typingDots} params={{ count: 4 }} />
html
<loader-kit spec='{"schemaVersion":1,"name":"Blink","duration":1,"layout":{"type":"row","count":3,"gap":0.1},"shape":{"type":"circle"},"stagger":{"each":0.2},"tracks":[{"property":"opacity","keyTimes":[0,0.5,1],"values":[1,0.2,1]}]}'></loader-kit>

Vẽ không cần view ​

prepare() và drawIndicator() cho phép bạn vẽ lên canvas của riêng mình, ví dụ trong game loop hoặc trong worker với OffscreenCanvas.

ts
import { prepare, drawIndicator } from '@loader-kit/web';

const prepared = prepare({ indicator: 'BallPulse' }, { count: 4 });
const ctx = canvas.getContext('2d')!;

function frame(now: number) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawIndicator(ctx, prepared, now / 1000, { width: canvas.width, height: canvas.height, color: '#7c3aed' });
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
  • prepare(source, params?) resolve một tên indicator có sẵn hoặc một spec kèm params, validate nó và kiểm tra các giới hạn. Nó throw InvalidIndicatorError (từ @loader-kit/spec), trong đó errors liệt kê mọi lỗi.
  • drawIndicator(ctx, prepared, t, frame) vẽ một frame tại thời điểm spec t (giây) vào hình chữ nhật x, y, width, height của context. Hàm này không xóa canvas và không đụng tới DOM. frame.colors hoạt động giống option colors.

Package cũng re-export validate, BUILTIN_INDICATOR_NAMES và các type IndicatorSpec, Params, BuiltinIndicatorName từ @loader-kit/spec.

Phát hành theo giấy phép MIT.