@libs-ui/components-minimap
v0.2.357-4
Published
> Directive tạo bản đồ thu nhỏ (minimap) cho các vùng nội dung lớn, hỗ trợ điều hướng trực quan và đồng bộ cuộn hai chiều.
Readme
@libs-ui/components-minimap
Directive tạo bản đồ thu nhỏ (minimap) cho các vùng nội dung lớn, hỗ trợ điều hướng trực quan và đồng bộ cuộn hai chiều.
Giới thiệu
LibsUiComponentsMinimapDirective là một standalone directive Angular giúp tạo ra một minimap nổi (floating) hiển thị toàn bộ nội dung được thu nhỏ theo tỷ lệ, đồng thời cho phép người dùng kéo viewport trên minimap để cuộn nội dung chính. Directive sử dụng MutationObserver để tự động cập nhật khi DOM thay đổi và lắng nghe sự kiện scroll cùng resize để đồng bộ trạng thái liên tục.
Tính năng
- ✅ Standalone Directive: Tích hợp trực tiếp bằng attribute selector, không cần module.
- ✅ Đồng bộ hai chiều: Cuộn nội dung chính cập nhật viewport trên minimap, kéo viewport trên minimap cuộn nội dung chính.
- ✅ Tự động cập nhật DOM: Dùng
MutationObserverđể phát hiện thay đổi cấu trúc hoặc thuộc tính và vẽ lại minimap. - ✅ Hỗ trợ Scale Transform: Nhận diện tỷ lệ phóng to/thu nhỏ của nội dung (qua CSS transform matrix) để tính toán viewport chính xác.
- ✅ Tùy biến cao: Cấu hình vị trí (top/bottom/left/right), kích thước, border, borderRadius, background, z-index cho cả khung minimap và khung viewport.
- ✅ Function Controls: Cung cấp API
show(),hidden(),toggle()để điều khiển minimap từ code. - ✅ Tự dọn bộ nhớ: Dùng
DestroyRef+takeUntilDestroyedvàMutationObserver.disconnect()khi component destroy.
Khi nào sử dụng
- Khi có các vùng chứa nội dung lớn vượt quá kích thước màn hình (sơ đồ, bản đồ văn phòng, diagram editor, canvas đồ họa).
- Khi người dùng cần cái nhìn tổng quan về cấu trúc nội dung và khả năng di chuyển nhanh đến các vùng cụ thể.
- Thay thế hoặc hỗ trợ cho thanh cuộn truyền thống trên các giao diện phức tạp, nhiều node.
- Khi cần tích hợp điều hướng vào các màn hình diagram (flow chart, org chart, network topology).
Cài đặt
npm install @libs-ui/components-minimapImport
import { LibsUiComponentsMinimapDirective } from '@libs-ui/components-minimap';
import { IMiniMapFunctionControl, IMiniMapStyleConfig } from '@libs-ui/components-minimap';
@Component({
standalone: true,
imports: [LibsUiComponentsMinimapDirective],
// ...
})
export class YourComponent {}Ví dụ sử dụng
Ví dụ 1 — Cơ bản
Gán directive vào bất kỳ phần tử nào có overflow: auto hoặc overflow: scroll.
// your.component.ts
import { LibsUiComponentsMinimapDirective } from '@libs-ui/components-minimap';
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsMinimapDirective],
template: `
<div
class="w-full h-[500px] overflow-auto border rounded-lg"
LibsUiComponentsMinimapDirective>
<div class="w-[3000px] h-[3000px] p-10 bg-gray-50">
<p class="text-2xl font-bold">Nội dung lớn</p>
</div>
</div>
`,
})
export class BasicMinimapComponent {}Ví dụ 2 — Tùy chỉnh vị trí và style, điều khiển qua FunctionControl
Đặt minimap ở góc dưới bên phải, tùy chỉnh màu viền viewport, và lấy controls để ẩn/hiện từ nút bấm.
// diagram.component.ts
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import { LibsUiComponentsMinimapDirective, IMiniMapFunctionControl } from '@libs-ui/components-minimap';
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsMinimapDirective],
template: `
<div class="flex gap-2 mb-3">
<button class="px-4 py-2 bg-blue-600 text-white rounded text-sm" (click)="handlerShow($event)">
Hiện Minimap
</button>
<button class="px-4 py-2 border border-blue-600 text-blue-600 rounded text-sm" (click)="handlerHide($event)">
Ẩn Minimap
</button>
<button class="px-4 py-2 border border-gray-300 rounded text-sm" (click)="handlerToggle($event)">
Toggle
</button>
</div>
<div
class="w-full h-[500px] overflow-auto border rounded-xl bg-black relative"
LibsUiComponentsMinimapDirective
[styleImageElement]="{ bottom: 20, right: 20, width: 220, height: 130, background: 'white', border: '1px solid #cbd5e1', zIndex: 10 }"
[styleRectangleElement]="{ border: '2px solid #3b82f6', background: 'rgba(59, 130, 246, 0.1)' }"
[minimapCloneBackground]="'white'"
[timerStartDrawMinimap]="300"
(outFunctionControls)="handlerControlsInit($event)">
<div class="w-[3000px] h-[3000px] p-10">
<div class="grid grid-cols-6 gap-10">
@for (item of items(); track item) {
<div class="h-64 bg-gray-800 rounded-2xl border border-dashed border-gray-600 flex items-center justify-center">
<span class="text-4xl font-black text-gray-500">{{ item }}</span>
</div>
}
</div>
</div>
</div>
`,
})
export class DiagramComponent {
protected items = signal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
private minimapControls?: IMiniMapFunctionControl;
handlerControlsInit(controls: IMiniMapFunctionControl) {
this.minimapControls = controls;
this.minimapControls.show();
}
handlerShow(event: MouseEvent) {
event.stopPropagation();
this.minimapControls?.show();
}
handlerHide(event: MouseEvent) {
event.stopPropagation();
this.minimapControls?.hidden();
}
handlerToggle(event: MouseEvent) {
event.stopPropagation();
this.minimapControls?.toggle();
}
}Ví dụ 3 — Scroll theo phần tử khác (elementScroll)
Khi vùng cuộn thực tế là một phần tử khác với host element của directive.
// canvas-layout.component.ts
import { Component, ChangeDetectionStrategy, ViewChild, ElementRef } from '@angular/core';
import { LibsUiComponentsMinimapDirective, IMiniMapFunctionControl } from '@libs-ui/components-minimap';
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsMinimapDirective],
template: `
<div
LibsUiComponentsMinimapDirective
[elementScroll]="scrollContainer"
[styleImageElement]="{ top: 10, right: 10, width: 180, height: 100 }"
(outFunctionControls)="handlerControlsInit($event)">
<div #scrollContainer class="w-full h-[600px] overflow-auto border rounded">
<div class="w-[4000px] h-[4000px] bg-gray-50 p-8">
<p class="text-xl font-semibold">Canvas nội dung</p>
</div>
</div>
</div>
`,
})
export class CanvasLayoutComponent {
@ViewChild('scrollContainer') scrollContainerRef!: ElementRef<HTMLElement>;
private minimapControls?: IMiniMapFunctionControl;
get scrollContainer(): HTMLElement {
return this.scrollContainerRef?.nativeElement;
}
handlerControlsInit(controls: IMiniMapFunctionControl) {
this.minimapControls = controls;
}
}Ví dụ 4 — Nội dung có scale transform (diagram zoom)
Khi nội dung bên trong có phần tử áp dụng CSS transform: scale(...), truyền class vào classNameFlagElementScale để directive tính toán tỷ lệ đúng.
// zoom-diagram.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { LibsUiComponentsMinimapDirective } from '@libs-ui/components-minimap';
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [LibsUiComponentsMinimapDirective],
template: `
<div
class="w-full h-[600px] overflow-auto border rounded"
LibsUiComponentsMinimapDirective
classNameFlagElementScale="my-diagram-scale-element"
[styleImageElement]="{ bottom: 16, right: 16, width: 200, height: 120 }">
<div class="my-diagram-scale-element" style="transform: scale(0.8); transform-origin: top left;">
<!-- Nội dung diagram được zoom out 80% -->
<div class="w-[5000px] h-[5000px] bg-white">
<p class="text-lg font-bold p-4">Diagram đã scale 80%</p>
</div>
</div>
</div>
`,
})
export class ZoomDiagramComponent {}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [elementScroll] | HTMLElement \| undefined | Host element | Phần tử đích để theo dõi và đồng bộ cuộn. Nếu không truyền, sử dụng host element của directive. | [elementScroll]="scrollContainerRef" |
| [timerStartDrawMinimap] | number \| undefined | 250 | Thời gian delay (ms) trước khi vẽ minimap lần đầu sau ngAfterViewInit. Tăng giá trị nếu nội dung cần thêm thời gian render. | [timerStartDrawMinimap]="500" |
| [styleImageElement] | IMiniMapStyleConfig \| undefined | undefined | Cấu hình style cho khung container bao ngoài của minimap (vị trí, kích thước, màu nền, viền, z-index). | [styleImageElement]="{ bottom: 20, right: 20, width: 220, height: 130 }" |
| [styleRectangleElement] | IMiniMapStyleConfig \| undefined | undefined | Cấu hình style cho khung viewport (hình chữ nhật di động) trên minimap thể hiện vùng đang xem. | [styleRectangleElement]="{ border: '2px solid #3b82f6', background: 'rgba(59,130,246,0.1)' }" |
| [classNameFlagElementScale] | string \| undefined | 'libs-ui-minimap-flag-element-scale' | Tên class CSS dùng để xác định phần tử chứa CSS transform: scale(...). Directive đọc matrix để tính toán tỷ lệ viewport chính xác khi nội dung bị zoom. | classNameFlagElementScale="my-scale-node" |
| [minimapCloneBackground] | string \| undefined | undefined | Override giá trị background của cloned node trên minimap. Hữu ích khi element gốc có nền trong suốt hoặc màu tối, muốn minimap hiển thị với nền sáng hơn. | [minimapCloneBackground]="'white'" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outFunctionControls) | IMiniMapFunctionControl | Phát ra object chứa ba hàm điều khiển minimap sau khi directive khởi tạo xong. Dùng để ẩn/hiện/toggle minimap từ code component. | handlerControlsInit(controls: IMiniMapFunctionControl): void { event.stopPropagation(); this.minimapControls = controls; } | (outFunctionControls)="handlerControlsInit($event)" |
IMiniMapFunctionControl — Methods
Các hàm điều khiển được nhận qua (outFunctionControls):
| Method | Signature | Mô tả |
|---|---|---|
| show() | () => void | Hiển thị minimap (xóa class invisible khỏi container và viewport). |
| hidden() | () => void | Ẩn minimap (thêm class invisible vào container và viewport). |
| toggle() | () => void | Chuyển đổi trạng thái ẩn/hiện dựa trên trạng thái hiện tại. |
// Nhận controls và gọi các method
handlerControlsInit(controls: IMiniMapFunctionControl): void {
this.minimapControls = controls;
this.minimapControls.show(); // Hiện ngay sau khi init
}
handlerToggleMinimap(event: MouseEvent): void {
event.stopPropagation();
this.minimapControls?.toggle();
}Types & Interfaces
import { IMiniMapStyleConfig, IMiniMapFunctionControl } from '@libs-ui/components-minimap';
/**
* Cấu hình style và vị trí cho container minimap hoặc viewport rectangle.
* Tất cả các trường đều là tùy chọn — chỉ truyền những gì cần override.
*/
export interface IMiniMapStyleConfig {
/** Z-index của phần tử. Mặc định container: 1300, viewport: 1301 */
zIndex?: number;
/** CSS border string. VD: '1px solid #E6E7EA' */
border?: string;
/** Border radius tính bằng px. Mặc định container: 4 */
borderRadius?: number;
/** Màu nền. Mặc định container: 'white' */
background?: string;
/** Khoảng cách từ cạnh trên (px). Áp dụng khi không có bottom. */
top?: number;
/** Khoảng cách từ cạnh trái (px). Áp dụng khi không có right. */
left?: number;
/** Khoảng cách từ cạnh dưới (px). Ưu tiên hơn top khi cả hai được truyền. */
bottom?: number;
/** Khoảng cách từ cạnh phải (px). Ưu tiên hơn left khi cả hai được truyền. */
right?: number;
/** Chiều rộng container tính bằng px. Mặc định: 198 */
width?: number;
/** Chiều cao container tính bằng px. Mặc định: 108 */
height?: number;
/** Tọa độ X (dùng nội bộ, không cần truyền thủ công) */
x?: number;
/** Tọa độ Y (dùng nội bộ, không cần truyền thủ công) */
y?: number;
/** CSS overflow. Mặc định: 'hidden' */
overflow?: string;
}
/**
* Object chứa các hàm điều khiển minimap, nhận được qua (outFunctionControls).
*/
export interface IMiniMapFunctionControl {
/** Hiển thị minimap */
show: () => void;
/** Ẩn minimap */
hidden: () => void;
/** Chuyển đổi ẩn/hiện */
toggle: () => void;
}Lưu ý quan trọng
⚠️ Container phải có overflow scroll/auto: Vùng chứa host element (hoặc phần tử truyền vào elementScroll) bắt buộc phải có overflow: auto hoặc overflow: scroll. Nếu không, sự kiện scroll sẽ không được kích hoạt và minimap sẽ không đồng bộ.
⚠️ Minimap được append vào document.body: Container minimap được tạo động và gắn thẳng vào document.body với position: fixed. Mặc định z-index là 1300. Đảm bảo không có phần tử nào trong ứng dụng có z-index cao hơn che khuất minimap ngoài ý muốn.
⚠️ Hiệu năng với DOM lớn: Directive dùng cloneNode(true) để render nội dung lên minimap mỗi khi có thay đổi DOM (MutationObserver). Với các DOM tree cực lớn (hàng nghìn node), tần suất mutation cao có thể ảnh hưởng đến hiệu năng. Cân nhắc giảm số lượng node hoặc tối ưu cấu trúc DOM.
⚠️ timerStartDrawMinimap: Minimap không vẽ ngay lập tức sau ngAfterViewInit mà chờ một khoảng delay (mặc định 250ms) để đảm bảo nội dung đã render xong. Nếu nội dung cần thêm thời gian (load ảnh, animation), tăng giá trị này lên.
⚠️ Scale transform: Khi nội dung bên trong có phần tử áp dụng CSS transform: scale(), truyền đúng tên class vào classNameFlagElementScale. Nếu không, viewport trên minimap sẽ không khớp với vùng thực sự đang hiển thị.
⚠️ Minimap ẩn theo mặc định: Sau khi init, minimap được tạo ra nhưng ở trạng thái invisible. Cần gọi controls.show() trong handler (outFunctionControls) để hiện ngay, hoặc gọi sau theo logic nghiệp vụ.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/components/minimap
hoặc: http://localhost:4500/directives/minimap
