一個基於 Angular 和 Ionic 的導覽指引元件,提供互動式的功能導覽體驗。
- 🎯 智能定位:自動計算並調整提示框位置
- 🎨 完全可自定義的樣式
- ⌨️ 支援鍵盤導航(方向鍵和 ESC)
- ♿ 無障礙支援(ARIA 標籤)
- 🔄 響應式設計:自動處理視窗大小變化
- ✅ 進階驗證和條件控制
- 即時驗證 (validate)
- 步驟前檢查 (beforeNext)
- 條件觸發 (triggerWhen)
- 🔒 TypeScript 型別支援
- 📱 支援 Ionic 框架
- 🌐 支援 Angular 14-19 版本
npm install ng-tour-guide
- 在你的模組中導入 TourComponent:
import { TourComponent } from "ng-tour-guide";
@NgModule({
imports: [TourComponent], // 已經是 standalone component
})
export class YourModule {}
- 在組件中定義導覽步驟:
import { TourStep } from "ng-tour-guide";
export class YourComponent {
isTourVisible = false;
tourSteps: TourStep[] = [
{
target: '.title-button', // CSS 選擇器
title: '開始按鈕',
content: '點擊這裡開始使用系統',
placement: 'bottom', // 提示框位置
ariaLabel: '開始按鈕導覽', // 無障礙標籤
},
{
target: '[data-tour="design-version"]',
title: '版次清單',
content: '這裡可以看到所有的版次',
placement: 'right',
validate: () => this.isMenuReady(), // 驗證函數
beforeNext: async () => {
// 下一步前的檢查
return await this.checkMenuStatus();
},
}
];
onTourFinish() {
this.isTourVisible = false;
console.log('導覽完成');
}
}
- 在模板中使用:
<app-tour
[steps]="tourSteps"
[(visible)]="isTourVisible"
(stepChange)="onStepChange($event)"
(finishTour)="onTourFinish()"
>
</app-tour>
<ion-button class="title-button" [class.active-button]="num === 0" (click)="chTitle(0)">開始</ion-button>
<ion-button
*ngIf="num === 0"
class="title-button"
(click)="titleChange(0)"
data-tour="design-version"
>設計版次</ion-button
>
interface TourStep {
target: string; // 目標元素的 CSS 選擇器
title: string; // 步驟標題
content: string; // 步驟內容
placement?: "top" | "bottom" | "left" | "right"; // 提示框位置
validate?: () => boolean; // 驗證函數
beforeNext?: () => boolean | Promise<boolean>; // 下一步前的檢查
triggerWhen?: () => boolean | Promise<boolean>; // 觸發條件
ariaLabel?: string; // 無障礙標籤
}
用於驗證當前步驟是否可以繼續。
「這個步驟的 target 元素是否存在或顯示出來」的同步檢查。如果返回 false
,下一步按鈕將被禁用。
在切換到這個步驟之前,會進行一次驗證,用來確認這個步驟的 DOM 目標是否「準備好」。
確保 .title-button:first-child 真的已經出現在 DOM 上
確保某個 tab 切換後才有的內容已經可見
tourSteps: TourStep[] = [
{
target: "#form-submit",
title: "表單提交",
content: "填寫完表單後點擊提交",
validate: () => {
// 檢查表單是否已填寫
return this.form.valid;
}
}
];
validate: () => {
const el = document.querySelector('.some-class');
return el !== null && el.offsetParent !== null; // 避免導覽找不到目標
}
「當使用者點擊【下一步】前」,是否允許進入下一步(可加強互動,通常會是 非同步的確認或警告)
當你按下「下一步」按鈕時,在跳下一步之前會先執行它。
跳出確認視窗(是否已經了解這一步的重要性?)
做一些非同步檢查(例如確保資料填完或儲存成功)
tourSteps: TourStep[] = [
{
target: '[data-tour="design-version"]', // 設計版次按鈕
title: '設計版次',
content: '在這裡可以查看和管理不同版本的設計',
placement: 'bottom',
// 進入下一步前確認用戶已了解設計版次的重要性
beforeNext: async () => {
const confirmed = await this.presentAlert({
header: '重要提醒',
message: '設計版次管理對於追蹤設計變更非常重要,您確定要繼續嗎?',
buttons: ['取消', '確定'],
});
return confirmed;
},
},
];
async presentAlert(alertOptions: { header: string; message: string; buttons: string[] }) {
const alert = await this.alertCtrl.create({
header: alertOptions.header,
message: alertOptions.message,
buttons: [
{
text: alertOptions.buttons[0],
role: 'cancel',
cssClass: 'secondary'
},
{
text: alertOptions.buttons[1],
handler: () => {
return true;
}
}
],
cssClass: 'custom-alert',
backdropDismiss: false
});
await alert.present();
const { role } = await alert.onDidDismiss();
return role !== 'cancel';
}
「是否進入這個步驟」的條件判斷(可以是非同步)。只有當返回 true
或 Promise 解析為 true
時,該步驟才會顯示。
在導覽流程還沒顯示這個步驟時,就會執行這個函式來判斷「是否跳過這個步驟」。
登入檢查(導覽只能在登入狀態下執行)
等待某些資料或元件載入完成(例如圖表、地圖)
triggerWhen: async () => {
return await this.isReady(); // 等待某項前置條件
}
tourSteps: TourStep[] = [
{
target: "#advanced-feature",
title: "進階功能",
content: "這是進階功能說明",
triggerWhen: async () => {
// 檢查用戶權限
const hasPermission = await this.checkUserPermission();
return hasPermission;
}
}
];
export class YourComponent {
tourSteps: TourStep[] = [
{
target: '.title-button:first-child', // 規劃設計按鈕
title: '功能選擇',
content: '這裡可以選擇不同的功能區域:規劃設計、監造測試、使用檢修',
placement: 'bottom',
// 確保用戶已經登入才能開始導覽
triggerWhen: () => {
// return this.authService.isLoggedIn();
return this.login_name !== '';
},
// 確保按鈕元素已經完全載入
validate: () => {
const button = document.querySelector('.title-button:first-child') as HTMLElement;
return button !== null && button.offsetParent !== null;
},
},
{
target: '[data-tour="design-version"]', // 設計版次按鈕
title: '設計版次',
content: '在這裡可以查看和管理不同版本的設計',
placement: 'bottom',
// 基本驗證:確保在規劃設計模式
validate: () => {
return this.num === 0;
},
// 進入下一步前確認用戶已了解設計版次的重要性
beforeNext: async () => {
const confirmed = await this.presentAlert({
header: '重要提醒',
message: '設計版次管理對於追蹤設計變更非常重要,您確定要繼續嗎?',
buttons: ['取消', '確定'],
});
return confirmed;
},
},
{
target: '[data-tour="analysis-tools"]', // 分析工具按鈕
title: '分析工具',
content: '這裡提供各種分析工具',
placement: 'bottom',
// 基本驗證:確保在規劃設計模式
validate: () => {
return this.num === 0;
},
// 等待分析工具載入完成
triggerWhen: async () => {
// 假設有一個方法檢查分析工具是否已載入
const toolsLoaded = await this.checkAnalysisToolsLoaded();
return toolsLoaded;
},
// 進入下一步前確認用戶已了解分析工具的使用方式
beforeNext: async () => {
const confirmed = await this.presentAlert({
header: '使用提示',
message: '分析工具需要正確的數據才能提供準確的分析結果,您確定要繼續嗎?',
buttons: ['取消', '確定'],
});
return confirmed;
},
},
];
}
-
validate 使用建議:
- 用於即時驗證,如表單驗證
- 避免執行耗時操作
- 返回結果應該立即可用
-
beforeNext 使用建議:
- 用於執行必要的操作,如保存數據
- 可以包含異步操作
- 適合處理需要用戶確認的操作
-
triggerWhen 使用建議:
- 用於控制步驟的顯示時機
- 可以根據用戶權限或系統狀態決定
- 適合實現條件性導覽流程
-
錯誤處理:
- 在 beforeNext 中妥善處理錯誤
- 提供適當的錯誤提示
- 考慮重試機制
-
性能優化:
- 避免在 validate 中執行耗時操作
- 合理使用異步操作
- 考慮使用緩存機制
屬性 | 類型 | 描述 |
---|---|---|
steps | TourStep[] | 導覽步驟陣列 |
visible | boolean | 控制導覽的顯示/隱藏 |
事件 | 類型 | 描述 |
---|---|---|
visibleChange | EventEmitter | 可見性變更事件 |
stepChange | EventEmitter | 步驟變更事件 |
finishTour | EventEmitter | 導覽完成事件 |
你可以通過覆蓋以下 CSS 類別來自定義樣式:
.tour-overlay {
// 覆蓋層樣式
// 預設: 半透明黑色背景
background: rgba(0, 0, 0, 0.4);
z-index: 9999;
}
.tour-highlight {
// 高亮區域樣式
// 包含脈衝動畫效果
&::after {
border: 2px solid #007bff;
animation: pulse 2s infinite;
}
}
// 脈衝動畫效果
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(0, 123, 255, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0);
}
}
.tour-popup {
// 提示框基本樣式
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
// 不同位置的變體
&.placement-top {
transform-origin: bottom center;
}
&.placement-bottom {
transform-origin: top center;
}
&.placement-left {
transform-origin: right center;
}
&.placement-right {
transform-origin: left center;
}
}
.tour-header {
// 標題區域樣式
border-bottom: 1px solid #eee;
h3 {
font-size: 18px;
color: #333;
}
.close-button {
// 關閉按鈕樣式
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
.tour-button {
// 基本按鈕樣式
border: 1px solid #ddd;
background: white;
&:hover {
background: #f5f5f5;
}
&.primary {
// 主要按鈕樣式
background: linear-gradient(45deg, #007bff, #0056b3);
color: white;
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2);
&:hover {
background: linear-gradient(45deg, #0056b3, #004094);
transform: translateY(-1px);
}
}
}
.tour-progress {
.progress-dot {
// 進度點樣式
background: #ddd;
&.active {
background: linear-gradient(45deg, #007bff, #0056b3);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.2);
}
}
}
// 無障礙焦點樣式
:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
// 動畫效能優化
.tour-overlay,
.tour-popup,
.tour-highlight {
will-change: transform, opacity;
}
你可以通過修改以下顏色和效果來自定義主題:
- 主色調:修改
#007bff
為你的品牌色 - 背景透明度:調整
rgba(0, 0, 0, 0.4)
的透明度 - 陰影效果:調整
box-shadow
參數 - 動畫時間:修改
transition
和animation
的持續時間
-
→
或Enter
:下一步 -
←
:上一步 -
Esc
:關閉導覽
- Chrome (最新版)
- Firefox (最新版)
- Safari (最新版)
- Edge (最新版)
- 確保目標元素在導覽開始時是可見的
- 建議在元素完全渲染後再啟動導覽
- 使用
validate
和beforeNext
來確保更好的用戶體驗 - 考慮使用
ariaLabel
來提供更好的無障礙支援
MIT SHIH MING
如果您發現任何問題或有改進建議,請透過以下方式聯繫我們:
- Email: ars37111337@gmail.com
- GitHub Issues: 提交問題
我們歡迎任何形式的貢獻,包括但不限於:
- 代碼貢獻
- 文檔改進
- 問題回報
- 功能建議