@libs-ui/components-skeleton
v0.2.357-0
Published
> Component hiển thị hiệu ứng loading skeleton với shimmer animation, hỗ trợ cấu hình layout linh hoạt theo dạng rows/cols.
Downloads
3,973
Readme
@libs-ui/components-skeleton
Component hiển thị hiệu ứng loading skeleton với shimmer animation, hỗ trợ cấu hình layout linh hoạt theo dạng rows/cols.
Giới thiệu
LibsUiComponentsSkeletonComponent là standalone Angular component giúp tạo khung xương (skeleton) loading với hiệu ứng shimmer mượt mà. Component tự động tính toán số lượng item cần render dựa trên chiều cao container cha, đồng thời hỗ trợ cấu hình layout phức tạp theo dạng rows và cols lồng nhau để tái hiện cấu trúc nội dung thực tế trước khi dữ liệu được tải về.
Tính năng
- ✅ Hiệu ứng shimmer loading mượt mà (CSS animation)
- ✅ Tự động tính toán số lượng items dựa trên chiều cao container cha
- ✅ Hỗ trợ cấu hình layout phức tạp: rows và cols lồng nhau nhiều cấp
- ✅ Custom class cho từng cấp: container, row, col, item
- ✅ Hỗ trợ repeat ở mỗi cấp (container / row / col)
- ✅ Standalone component, tối ưu hiệu năng với
ChangeDetectionStrategy.OnPush
Khi nào sử dụng
- Khi đang tải dữ liệu từ API và muốn hiển thị trước cấu trúc layout (perceived performance).
- Thay thế loading spinner truyền thống để cải thiện trải nghiệm người dùng.
- Trong danh sách items: comments, posts, products, notifications.
- Trong màn hình chi tiết: user profile, order detail, article.
- Trong bảng dữ liệu: table rows chưa load xong.
Cài đặt
npm install @libs-ui/components-skeletonImport
import { LibsUiComponentsSkeletonComponent, ISkeletonConfig } from '@libs-ui/components-skeleton';
@Component({
standalone: true,
imports: [LibsUiComponentsSkeletonComponent],
// ...
})
export class YourComponent {}Ví dụ sử dụng
1. Basic — Tự động fill container
Component tự động tính toán số lượng dòng skeleton để lấp đầy container. Chỉ cần đặt trong một thẻ có chiều cao cụ thể.
<!-- Template -->
<div class="h-[300px] w-full">
<libs_ui-components-skeleton />
</div>2. Avatar & Lines (Horizontal)
Mô phỏng layout có avatar tròn và hai dòng nội dung bên cạnh — phù hợp cho danh sách người dùng, notifications.
// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
readonly avatarLinesConfig: ISkeletonConfig = {
heightContainer: 118,
classRows: 'bg-[#fff] p-[16px] rounded-[8px] !flex-row',
styleMarginBottom: 16,
rows: [
{
classRowLast: '!w-[40px] !h-[40px] mr-[12px] shrink-0',
item: { classIncludeItem: '!rounded-[40px]' },
},
{
classColsLast: 'h-full flex-col ',
cols: [
{
classColLast: '!h-full mb-[8px]',
item: { classIncludeItem: '!rounded-[4px]' },
},
{
classColLast: '!h-auto',
item: {
classInclude: 'w-full !h-[20px]',
classIncludeItem: '!rounded-[4px]',
},
},
],
},
],
};<!-- Template -->
<div class="h-[300px]">
<libs_ui-components-skeleton [config]="avatarLinesConfig" />
</div>3. Card với Thumbnail nhỏ
Layout card có tiêu đề lớn phía trên và thumbnail kèm nội dung bên dưới — phù hợp cho danh sách bài viết, sản phẩm.
// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
readonly cardThumbConfig: ISkeletonConfig = {
heightContainer: 158,
classRows: 'bg-[#fff] p-[16px] rounded-[8px]',
styleMarginBottom: 16,
rows: [
{
classRowLast: '!h-auto',
item: {
classInclude: 'w-full !h-[34px] mb-[8px]',
classIncludeItem: '!rounded-[4px]',
},
},
{
classColsLast: 'h-full ',
cols: [
{
classColLast: '!w-[80px] mr-[12px] shrink-0',
item: { classIncludeItem: '!rounded-[4px]' },
},
{
item: { classIncludeItem: '!rounded-[4px]' },
},
],
},
],
};<!-- Template -->
<div class="h-[400px]">
<libs_ui-components-skeleton [config]="cardThumbConfig" />
</div>4. Header & Content (phức hợp)
Layout phức hợp: header gồm avatar tròn + tiêu đề, bên dưới là vùng nội dung nhiều dòng — phù hợp cho detail page.
// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
readonly headerContentConfig: ISkeletonConfig = {
heightContainer: 202,
classRows: 'bg-[#fff] p-[16px] rounded-[8px]',
styleMarginBottom: 16,
rows: [
{
classRowLast: '!h-auto ',
cols: [
{
classColLast: '!w-[40px] !h-[40px] mr-[12px] shrink-0',
item: { classIncludeItem: '!rounded-[40px]' },
},
{
classColLast: '!h-auto',
item: {
classInclude: 'w-full !h-[40px] mb-[8px]',
classIncludeItem: '!rounded-[4px]',
},
},
],
},
{
classColsLast: 'h-full flex-col ',
cols: [
{
classColLast: '!h-full mb-[8px]',
item: { classIncludeItem: '!rounded-[4px]' },
},
{
classColLast: '!h-auto',
item: {
classInclude: 'w-full !h-[20px]',
classIncludeItem: '!rounded-[4px]',
},
},
],
},
],
};<!-- Template -->
<div class="h-[500px]">
<libs_ui-components-skeleton [config]="headerContentConfig" />
</div>5. Sectioned Content (Table header + Body)
Layout chia hai vùng: header cố định phía trên (không có shimmer) và vùng nội dung bên dưới — phù hợp cho table loading.
// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
readonly sectionedConfig: ISkeletonConfig = {
rows: [
{
classRowLast: '!h-[40px] bg-[#fff]',
item: { classIncludeItem: '!h-[40px] !rounded-none opacity-[0]' },
},
{
classRowLast: 'h-full flex-col',
item: { classIncludeItem: '!rounded-none' },
},
],
};<!-- Template -->
<div class="h-[400px]">
<libs_ui-components-skeleton [config]="sectionedConfig" />
</div>6. Small Icon & Line
Layout tối giản: icon nhỏ và một dòng nội dung bên cạnh — phù hợp cho menu items, breadcrumb.
// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';
readonly iconLineConfig: ISkeletonConfig = {
heightContainer: 116,
classRows: 'bg-[#fff] rounded-[8px] !flex-row',
styleMarginBottom: 16,
rows: [
{
classRowLast: '!w-[32px] !h-[32px] mr-[12px] shrink-0',
item: { classIncludeItem: '!rounded-[32px]' },
},
{
classColsLast: 'h-full flex-col ',
cols: [
{
classColLast: '!h-full',
item: { classIncludeItem: '!rounded-[4px]' },
},
],
},
],
};<!-- Template -->
<div class="h-[250px]">
<libs_ui-components-skeleton [config]="iconLineConfig" />
</div>@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [config] | ISkeletonConfig | {} | Object cấu hình toàn bộ layout skeleton: số lần lặp, chiều cao, rows/cols lồng nhau và class tuỳ chỉnh cho từng cấp | [config]="avatarLinesConfig" |
Types & Interfaces
import { ISkeletonConfig } from '@libs-ui/components-skeleton';ISkeletonConfig
export interface ISkeletonConfig {
/**
* Số lần lặp lại toàn bộ layout.
* Khi không set, component tự tính dựa trên chiều cao container / heightContainer.
*/
repeat?: number;
/**
* Chiều cao cố định (px) cho mỗi block lặp lại.
* Khi không set, component đọc offsetHeight của container thực.
*/
heightContainer?: number;
/** Class áp dụng lên wrapper container bao ngoài tất cả rows. */
classRows?: string;
/** Khoảng cách margin-bottom (px) giữa các block lặp (trừ block cuối). */
styleMarginBottom?: number;
rows?: Array<{
/** Số lần lặp lại row này. */
repeat?: number;
/** Class cho row (áp dụng cho tất cả row trừ row cuối cùng). */
classRow?: string;
/** Class dành riêng cho row cuối cùng trong vòng lặp repeat. */
classRowLast?: string;
/**
* Config skeleton item trực tiếp trong row (không qua cols).
* Dùng khi row chỉ có 1 skeleton block duy nhất.
*/
item?: ISkeletonItem;
/** Class cho container bao ngoài tất cả cols (áp dụng cho tất cả trừ cuối). */
classCols?: string;
/** Class cho container cols dành riêng cho row cuối. */
classColsLast?: string;
cols?: Array<{
/** Số lần lặp lại col này. */
repeat?: number;
/** Class cho col (áp dụng cho tất cả col trừ col cuối cùng). */
classCol?: string;
/** Class dành riêng cho col cuối cùng trong vòng lặp repeat. */
classColLast?: string;
/** Config skeleton item bên trong col. */
item?: ISkeletonItem;
}>;
}>;
}ISkeletonItem
interface ISkeletonItem {
/**
* Class áp dụng lên thẻ div skeleton trực tiếp (có class .skeleton và animation).
* Dùng để override kích thước, border-radius của skeleton block.
* Ví dụ: '!rounded-[40px]' cho avatar tròn, '!rounded-[4px]' cho text line.
*/
classIncludeItem?: string;
/**
* Class áp dụng lên wrapper bên ngoài bao các skeleton divs.
* Dùng để kiểm soát chiều cao wrapper khi skeleton item có height cố định.
* Ví dụ: 'w-full !h-[20px]' để giới hạn chiều cao vùng chứa.
*/
classInclude?: string;
/** (Legacy) Class không khuyến nghị dùng trong code mới. */
class?: string;
/**
* Khi true, tự động thêm margin-bottom 16px cho tất cả item trừ item cuối cùng.
* Tiện dùng khi repeat > 1 và không muốn config styleMarginBottom ở cấp ngoài.
*/
styleDefault?: boolean;
}Logic ẩn quan trọng
Auto-calculation trong ngAfterViewInit
Component thực hiện tính toán tự động sau khi view khởi tạo (ngAfterViewInit + setTimeout):
- Khi
config.rowsrỗng hoặc không có: Tự tạo 1 row mặc định với item rỗng. - Khi
heightContainerkhông được set: ĐọcoffsetHeightthực tế của container DOM để làm chiều cao mỗi block. - Khi
repeatkhông được set: Tính tự động bằng công thức:
Kết quả: skeleton tự lấp đầy container cha mà không cần set số dòng thủ công.repeat = Math.ceil(offsetHeight / (heightContainer + styleMarginBottom)) - Khi
repeatđược set:heightContainerđược chia đều theorepeatđể các block chia đều chiều cao container.
Cơ chế classRowLast / classColLast
Khi một row hoặc col có repeat > 1, phần tử cuối cùng trong vòng lặp sẽ áp dụng class classRowLast (hoặc classColLast) thay vì classRow (hoặc classCol). Điều này cho phép tuỳ chỉnh style riêng cho phần tử cuối, ví dụ: bỏ margin-bottom ở item cuối cùng.
Lưu ý quan trọng
⚠️ Container phải có chiều cao xác định: Component đọc offsetHeight của container cha để tính toán. Nếu container không có height hoặc min-height cụ thể, offsetHeight = 0 và skeleton sẽ không render đúng. Luôn bọc trong thẻ có chiều cao rõ ràng (h-[300px], min-h-[200px], hoặc h-full trong container đã có height).
⚠️ setTimeout trong ngAfterViewInit: Việc tính toán được bọc trong setTimeout (0ms) để đảm bảo DOM đã render xong và offsetHeight trả về giá trị chính xác. Điều này là bình thường và có chủ ý.
⚠️ classRowLast vs classRow: Khi repeat = 1 (mặc định), phần tử duy nhất được coi là "cả first lẫn last", tức là classRowLast sẽ được áp dụng. Thiết kế này có nghĩa là với row không repeat, bạn chỉ cần dùng classRowLast để set class cho row đó.
⚠️ Input config có transform: Input config được khai báo với transform (value) => value || {}. Truyền undefined hoặc null cho [config] sẽ được tự động chuyển thành {} (config mặc định), component sẽ không crash.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/skeleton
