本章概述
Bootstrap 提供了丰富的 JavaScript 插件,用于创建交互式的用户界面组件。这些插件基于现代 JavaScript 技术,提供了模态框、工具提示、弹出框、轮播图等功能。本章将深入学习如何使用和自定义这些插件。
学习目标
- 掌握 Bootstrap JavaScript 插件的基本使用
- 学会通过 JavaScript API 控制组件行为
- 理解事件系统和回调函数
- 掌握插件的配置和自定义
- 学会创建自定义插件
JavaScript 插件基础
1. 插件引入和初始化
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bootstrap JavaScript 插件</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- 内容区域 -->
<div class="container my-5">
<h1>Bootstrap JavaScript 插件示例</h1>
<!-- 模态框触发按钮 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
打开模态框
</button>
<!-- 工具提示按钮 -->
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" title="这是一个工具提示">
悬停显示提示
</button>
<!-- 弹出框按钮 -->
<button type="button" class="btn btn-info" data-bs-toggle="popover" data-bs-placement="right" data-bs-content="这是弹出框的内容" title="弹出框标题">
点击显示弹出框
</button>
</div>
<!-- 模态框 -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">模态框标题</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
这是模态框的内容区域。
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary">保存更改</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 初始化工具提示
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
// 初始化弹出框
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));
</script>
</body>
</html>
2. 通过 JavaScript API 控制组件
// 模态框 API 示例
class ModalController {
constructor() {
this.modal = null;
this.init();
}
init() {
// 获取模态框元素
const modalElement = document.getElementById('exampleModal');
// 创建模态框实例
this.modal = new bootstrap.Modal(modalElement, {
backdrop: 'static', // 静态背景,点击背景不关闭
keyboard: false // 禁用 ESC 键关闭
});
// 绑定事件监听器
this.bindEvents();
}
bindEvents() {
// 监听模态框显示事件
document.getElementById('exampleModal').addEventListener('show.bs.modal', (event) => {
console.log('模态框即将显示');
});
// 监听模态框显示完成事件
document.getElementById('exampleModal').addEventListener('shown.bs.modal', (event) => {
console.log('模态框已显示');
// 自动聚焦到第一个输入框
const firstInput = event.target.querySelector('input');
if (firstInput) {
firstInput.focus();
}
});
// 监听模态框隐藏事件
document.getElementById('exampleModal').addEventListener('hide.bs.modal', (event) => {
console.log('模态框即将隐藏');
});
// 监听模态框隐藏完成事件
document.getElementById('exampleModal').addEventListener('hidden.bs.modal', (event) => {
console.log('模态框已隐藏');
// 清理表单数据
const form = event.target.querySelector('form');
if (form) {
form.reset();
}
});
}
// 显示模态框
show() {
this.modal.show();
}
// 隐藏模态框
hide() {
this.modal.hide();
}
// 切换模态框显示状态
toggle() {
this.modal.toggle();
}
// 销毁模态框实例
dispose() {
this.modal.dispose();
}
}
// 工具提示 API 示例
class TooltipController {
constructor() {
this.tooltips = new Map();
this.init();
}
init() {
// 初始化所有工具提示
const tooltipElements = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipElements.forEach(element => {
const tooltip = new bootstrap.Tooltip(element, {
delay: { show: 500, hide: 100 }, // 延迟显示和隐藏
html: true, // 允许 HTML 内容
placement: 'auto' // 自动定位
});
this.tooltips.set(element, tooltip);
});
}
// 显示指定元素的工具提示
show(element) {
const tooltip = this.tooltips.get(element);
if (tooltip) {
tooltip.show();
}
}
// 隐藏指定元素的工具提示
hide(element) {
const tooltip = this.tooltips.get(element);
if (tooltip) {
tooltip.hide();
}
}
// 更新工具提示内容
updateContent(element, newTitle) {
const tooltip = this.tooltips.get(element);
if (tooltip) {
element.setAttribute('data-bs-original-title', newTitle);
tooltip.setContent({ '.tooltip-inner': newTitle });
}
}
// 销毁所有工具提示
disposeAll() {
this.tooltips.forEach(tooltip => {
tooltip.dispose();
});
this.tooltips.clear();
}
}
// 轮播图 API 示例
class CarouselController {
constructor(carouselId) {
this.carouselElement = document.getElementById(carouselId);
this.carousel = new bootstrap.Carousel(this.carouselElement, {
interval: 3000, // 自动切换间隔
wrap: true, // 循环播放
touch: true // 触摸支持
});
this.bindEvents();
}
bindEvents() {
// 监听轮播图滑动事件
this.carouselElement.addEventListener('slide.bs.carousel', (event) => {
console.log(`从第 ${event.from} 张切换到第 ${event.to} 张`);
});
// 监听轮播图滑动完成事件
this.carouselElement.addEventListener('slid.bs.carousel', (event) => {
console.log(`已切换到第 ${event.to} 张`);
this.updateIndicators(event.to);
});
}
// 切换到指定幻灯片
to(index) {
this.carousel.to(index);
}
// 切换到下一张
next() {
this.carousel.next();
}
// 切换到上一张
prev() {
this.carousel.prev();
}
// 暂停自动播放
pause() {
this.carousel.pause();
}
// 开始自动播放
cycle() {
this.carousel.cycle();
}
// 更新指示器
updateIndicators(activeIndex) {
const indicators = this.carouselElement.querySelectorAll('.carousel-indicators button');
indicators.forEach((indicator, index) => {
if (index === activeIndex) {
indicator.classList.add('active');
indicator.setAttribute('aria-current', 'true');
} else {
indicator.classList.remove('active');
indicator.removeAttribute('aria-current');
}
});
}
}
// 使用示例
document.addEventListener('DOMContentLoaded', function() {
// 初始化控制器
const modalController = new ModalController();
const tooltipController = new TooltipController();
// 绑定自定义按钮事件
document.getElementById('showModalBtn')?.addEventListener('click', () => {
modalController.show();
});
document.getElementById('hideModalBtn')?.addEventListener('click', () => {
modalController.hide();
});
});
高级插件应用
1. 动态内容模态框
<!-- 动态内容模态框 -->
<div class="container my-5">
<h2>动态内容模态框</h2>
<!-- 用户列表 -->
<div class="row" id="userList">
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">张三</h5>
<p class="card-text">前端开发工程师</p>
<button class="btn btn-primary" data-user-id="1" onclick="showUserDetails(this)">查看详情</button>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">李四</h5>
<p class="card-text">后端开发工程师</p>
<button class="btn btn-primary" data-user-id="2" onclick="showUserDetails(this)">查看详情</button>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">王五</h5>
<p class="card-text">UI/UX 设计师</p>
<button class="btn btn-primary" data-user-id="3" onclick="showUserDetails(this)">查看详情</button>
</div>
</div>
</div>
</div>
</div>
<!-- 动态模态框 -->
<div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="userModalLabel">用户详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="userModalBody">
<!-- 加载指示器 -->
<div class="text-center" id="loadingIndicator">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
<p class="mt-2">正在加载用户信息...</p>
</div>
<!-- 用户详情内容 -->
<div id="userContent" style="display: none;">
<!-- 动态内容将在这里插入 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="editUserBtn">编辑用户</button>
</div>
</div>
</div>
</div>
<script>
// 用户数据模拟
const userData = {
1: {
id: 1,
name: '张三',
position: '前端开发工程师',
email: 'zhangsan@example.com',
phone: '138-0000-0001',
department: '技术部',
joinDate: '2022-01-15',
skills: ['JavaScript', 'Vue.js', 'React', 'CSS3', 'HTML5'],
avatar: 'https://via.placeholder.com/150x150?text=张三'
},
2: {
id: 2,
name: '李四',
position: '后端开发工程师',
email: 'lisi@example.com',
phone: '138-0000-0002',
department: '技术部',
joinDate: '2021-08-20',
skills: ['Java', 'Spring Boot', 'MySQL', 'Redis', 'Docker'],
avatar: 'https://via.placeholder.com/150x150?text=李四'
},
3: {
id: 3,
name: '王五',
position: 'UI/UX 设计师',
email: 'wangwu@example.com',
phone: '138-0000-0003',
department: '设计部',
joinDate: '2023-03-10',
skills: ['Figma', 'Sketch', 'Adobe XD', 'Photoshop', 'Illustrator'],
avatar: 'https://via.placeholder.com/150x150?text=王五'
}
};
// 动态模态框控制器
class DynamicModalController {
constructor() {
this.modal = new bootstrap.Modal(document.getElementById('userModal'));
this.modalBody = document.getElementById('userModalBody');
this.loadingIndicator = document.getElementById('loadingIndicator');
this.userContent = document.getElementById('userContent');
this.currentUserId = null;
this.bindEvents();
}
bindEvents() {
// 监听模态框隐藏事件,清理内容
document.getElementById('userModal').addEventListener('hidden.bs.modal', () => {
this.clearContent();
});
// 编辑用户按钮事件
document.getElementById('editUserBtn').addEventListener('click', () => {
this.editUser(this.currentUserId);
});
}
// 显示用户详情
async showUserDetails(userId) {
this.currentUserId = userId;
this.modal.show();
// 显示加载指示器
this.showLoading();
try {
// 模拟 API 调用延迟
await this.delay(1000);
// 获取用户数据
const user = await this.fetchUserData(userId);
// 渲染用户详情
this.renderUserDetails(user);
// 隐藏加载指示器,显示内容
this.showContent();
} catch (error) {
console.error('加载用户数据失败:', error);
this.showError('加载用户数据失败,请稍后重试。');
}
}
// 模拟 API 调用
async fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = userData[userId];
if (user) {
resolve(user);
} else {
reject(new Error('用户不存在'));
}
}, 500);
});
}
// 渲染用户详情
renderUserDetails(user) {
const skillsHtml = user.skills.map(skill =>
`<span class="badge bg-primary me-1">${skill}</span>`
).join('');
this.userContent.innerHTML = `
<div class="row">
<div class="col-md-4 text-center">
<img src="${user.avatar}" alt="${user.name}" class="img-fluid rounded-circle mb-3" style="max-width: 150px;">
<h4>${user.name}</h4>
<p class="text-muted">${user.position}</p>
</div>
<div class="col-md-8">
<h5>基本信息</h5>
<table class="table table-borderless">
<tr>
<td><strong>邮箱:</strong></td>
<td>${user.email}</td>
</tr>
<tr>
<td><strong>电话:</strong></td>
<td>${user.phone}</td>
</tr>
<tr>
<td><strong>部门:</strong></td>
<td>${user.department}</td>
</tr>
<tr>
<td><strong>入职日期:</strong></td>
<td>${user.joinDate}</td>
</tr>
</table>
<h5 class="mt-4">技能标签</h5>
<div class="skills-container">
${skillsHtml}
</div>
</div>
</div>
`;
}
// 显示加载状态
showLoading() {
this.loadingIndicator.style.display = 'block';
this.userContent.style.display = 'none';
}
// 显示内容
showContent() {
this.loadingIndicator.style.display = 'none';
this.userContent.style.display = 'block';
}
// 显示错误信息
showError(message) {
this.userContent.innerHTML = `
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-triangle"></i>
${message}
</div>
`;
this.showContent();
}
// 清理内容
clearContent() {
this.userContent.innerHTML = '';
this.currentUserId = null;
}
// 编辑用户
editUser(userId) {
// 这里可以打开编辑表单或跳转到编辑页面
console.log(`编辑用户 ${userId}`);
alert(`编辑用户 ${userId} 的功能将在这里实现`);
}
// 延迟函数
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 初始化动态模态框控制器
const dynamicModalController = new DynamicModalController();
// 全局函数,供 HTML 调用
function showUserDetails(button) {
const userId = button.getAttribute('data-user-id');
dynamicModalController.showUserDetails(userId);
}
</script>
事件系统与回调
1. Bootstrap 事件系统
// Bootstrap 事件管理器
class BootstrapEventManager {
constructor() {
this.eventListeners = new Map();
this.init();
}
init() {
this.setupGlobalEventListeners();
}
setupGlobalEventListeners() {
// 模态框事件
this.addModalEventListeners();
// 轮播图事件
this.addCarouselEventListeners();
// 折叠面板事件
this.addCollapseEventListeners();
// 下拉菜单事件
this.addDropdownEventListeners();
}
addModalEventListeners() {
document.addEventListener('show.bs.modal', (event) => {
console.log('模态框即将显示:', event.target.id);
this.triggerCustomEvent('modal:beforeShow', {
modal: event.target,
relatedTarget: event.relatedTarget
});
});
document.addEventListener('shown.bs.modal', (event) => {
console.log('模态框已显示:', event.target.id);
this.triggerCustomEvent('modal:afterShow', {
modal: event.target
});
// 自动聚焦到第一个可聚焦元素
this.focusFirstElement(event.target);
});
document.addEventListener('hide.bs.modal', (event) => {
console.log('模态框即将隐藏:', event.target.id);
this.triggerCustomEvent('modal:beforeHide', {
modal: event.target
});
});
document.addEventListener('hidden.bs.modal', (event) => {
console.log('模态框已隐藏:', event.target.id);
this.triggerCustomEvent('modal:afterHide', {
modal: event.target
});
// 清理模态框数据
this.cleanupModalData(event.target);
});
}
addCarouselEventListeners() {
document.addEventListener('slide.bs.carousel', (event) => {
console.log(`轮播图滑动: 从第 ${event.from} 张到第 ${event.to} 张`);
this.triggerCustomEvent('carousel:slide', {
carousel: event.target,
from: event.from,
to: event.to,
direction: event.direction
});
});
document.addEventListener('slid.bs.carousel', (event) => {
console.log(`轮播图滑动完成: 当前第 ${event.to} 张`);
this.triggerCustomEvent('carousel:slid', {
carousel: event.target,
from: event.from,
to: event.to
});
// 更新相关 UI
this.updateCarouselUI(event.target, event.to);
});
}
addCollapseEventListeners() {
document.addEventListener('show.bs.collapse', (event) => {
console.log('折叠面板即将展开:', event.target.id);
this.triggerCustomEvent('collapse:beforeShow', {
collapse: event.target
});
});
document.addEventListener('shown.bs.collapse', (event) => {
console.log('折叠面板已展开:', event.target.id);
this.triggerCustomEvent('collapse:afterShow', {
collapse: event.target
});
});
document.addEventListener('hide.bs.collapse', (event) => {
console.log('折叠面板即将收起:', event.target.id);
this.triggerCustomEvent('collapse:beforeHide', {
collapse: event.target
});
});
document.addEventListener('hidden.bs.collapse', (event) => {
console.log('折叠面板已收起:', event.target.id);
this.triggerCustomEvent('collapse:afterHide', {
collapse: event.target
});
});
}
addDropdownEventListeners() {
document.addEventListener('show.bs.dropdown', (event) => {
console.log('下拉菜单即将显示');
this.triggerCustomEvent('dropdown:beforeShow', {
dropdown: event.target
});
});
document.addEventListener('shown.bs.dropdown', (event) => {
console.log('下拉菜单已显示');
this.triggerCustomEvent('dropdown:afterShow', {
dropdown: event.target
});
});
document.addEventListener('hide.bs.dropdown', (event) => {
console.log('下拉菜单即将隐藏');
this.triggerCustomEvent('dropdown:beforeHide', {
dropdown: event.target
});
});
document.addEventListener('hidden.bs.dropdown', (event) => {
console.log('下拉菜单已隐藏');
this.triggerCustomEvent('dropdown:afterHide', {
dropdown: event.target
});
});
}
// 触发自定义事件
triggerCustomEvent(eventName, data) {
const customEvent = new CustomEvent(eventName, {
detail: data,
bubbles: true,
cancelable: true
});
document.dispatchEvent(customEvent);
}
// 注册事件监听器
on(eventName, callback) {
if (!this.eventListeners.has(eventName)) {
this.eventListeners.set(eventName, []);
}
this.eventListeners.get(eventName).push(callback);
document.addEventListener(eventName, callback);
}
// 移除事件监听器
off(eventName, callback) {
if (this.eventListeners.has(eventName)) {
const listeners = this.eventListeners.get(eventName);
const index = listeners.indexOf(callback);
if (index > -1) {
listeners.splice(index, 1);
document.removeEventListener(eventName, callback);
}
}
}
// 辅助方法
focusFirstElement(modal) {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length > 0) {
focusableElements[0].focus();
}
}
cleanupModalData(modal) {
// 清理表单数据
const forms = modal.querySelectorAll('form');
forms.forEach(form => {
form.reset();
});
// 清理验证状态
const validatedInputs = modal.querySelectorAll('.is-valid, .is-invalid');
validatedInputs.forEach(input => {
input.classList.remove('is-valid', 'is-invalid');
});
}
updateCarouselUI(carousel, activeIndex) {
// 更新自定义指示器
const customIndicators = document.querySelectorAll(`[data-carousel-target="#${carousel.id}"] .custom-indicator`);
customIndicators.forEach((indicator, index) => {
if (index === activeIndex) {
indicator.classList.add('active');
} else {
indicator.classList.remove('active');
}
});
}
}
// 初始化事件管理器
const eventManager = new BootstrapEventManager();
// 使用示例:注册自定义事件监听器
eventManager.on('modal:afterShow', (event) => {
const modal = event.detail.modal;
console.log('自定义事件:模态框显示完成', modal.id);
// 执行自定义逻辑
if (modal.id === 'userModal') {
// 加载用户数据
console.log('开始加载用户数据...');
}
});
eventManager.on('carousel:slid', (event) => {
const { carousel, to } = event.detail;
console.log('自定义事件:轮播图切换完成', carousel.id, to);
// 执行自定义逻辑
if (carousel.id === 'productCarousel') {
// 更新产品信息
console.log('更新产品信息...');
}
});
2. 回调函数与 Promise 支持
// 带回调和 Promise 支持的插件包装器
class BootstrapPluginWrapper {
constructor() {
this.plugins = new Map();
}
// 模态框包装器
modal(element, options = {}) {
return new Promise((resolve, reject) => {
try {
const modal = new bootstrap.Modal(element, options);
this.plugins.set(element, modal);
// 监听显示完成事件
element.addEventListener('shown.bs.modal', () => {
resolve(modal);
}, { once: true });
// 监听显示失败事件
element.addEventListener('show.bs.modal', (event) => {
if (event.defaultPrevented) {
reject(new Error('模态框显示被阻止'));
}
}, { once: true });
modal.show();
} catch (error) {
reject(error);
}
});
}
// 工具提示包装器
tooltip(element, options = {}) {
return new Promise((resolve, reject) => {
try {
const tooltip = new bootstrap.Tooltip(element, options);
this.plugins.set(element, tooltip);
resolve(tooltip);
} catch (error) {
reject(error);
}
});
}
// 轮播图包装器
carousel(element, options = {}) {
return new Promise((resolve, reject) => {
try {
const carousel = new bootstrap.Carousel(element, options);
this.plugins.set(element, carousel);
resolve(carousel);
} catch (error) {
reject(error);
}
});
}
// 异步显示模态框
async showModal(elementId, options = {}) {
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`找不到 ID 为 ${elementId} 的元素`);
}
try {
const modal = await this.modal(element, options);
console.log('模态框显示成功');
return modal;
} catch (error) {
console.error('模态框显示失败:', error);
throw error;
}
}
// 异步隐藏模态框
async hideModal(element) {
return new Promise((resolve, reject) => {
const modal = this.plugins.get(element);
if (!modal) {
reject(new Error('找不到模态框实例'));
return;
}
// 监听隐藏完成事件
element.addEventListener('hidden.bs.modal', () => {
resolve();
}, { once: true });
modal.hide();
});
}
// 批量初始化工具提示
async initializeTooltips(selector = '[data-bs-toggle="tooltip"]') {
const elements = document.querySelectorAll(selector);
const promises = Array.from(elements).map(element =>
this.tooltip(element)
);
try {
const tooltips = await Promise.all(promises);
console.log(`成功初始化 ${tooltips.length} 个工具提示`);
return tooltips;
} catch (error) {
console.error('工具提示初始化失败:', error);
throw error;
}
}
// 链式操作支持
chain(element) {
return new PluginChain(element, this);
}
// 获取插件实例
getInstance(element) {
return this.plugins.get(element);
}
// 销毁插件实例
dispose(element) {
const plugin = this.plugins.get(element);
if (plugin && typeof plugin.dispose === 'function') {
plugin.dispose();
this.plugins.delete(element);
}
}
// 销毁所有插件实例
disposeAll() {
this.plugins.forEach((plugin, element) => {
if (typeof plugin.dispose === 'function') {
plugin.dispose();
}
});
this.plugins.clear();
}
}
// 链式操作类
class PluginChain {
constructor(element, wrapper) {
this.element = element;
this.wrapper = wrapper;
this.operations = [];
}
modal(options = {}) {
this.operations.push(() => this.wrapper.modal(this.element, options));
return this;
}
tooltip(options = {}) {
this.operations.push(() => this.wrapper.tooltip(this.element, options));
return this;
}
carousel(options = {}) {
this.operations.push(() => this.wrapper.carousel(this.element, options));
return this;
}
async execute() {
const results = [];
for (const operation of this.operations) {
const result = await operation();
results.push(result);
}
return results;
}
}
// 使用示例
const pluginWrapper = new BootstrapPluginWrapper();
// 异步使用示例
async function demonstrateAsyncUsage() {
try {
// 显示模态框
const modal = await pluginWrapper.showModal('exampleModal', {
backdrop: 'static'
});
console.log('模态框已显示');
// 等待 3 秒后隐藏
setTimeout(async () => {
await pluginWrapper.hideModal(document.getElementById('exampleModal'));
console.log('模态框已隐藏');
}, 3000);
// 批量初始化工具提示
const tooltips = await pluginWrapper.initializeTooltips();
console.log('工具提示初始化完成');
} catch (error) {
console.error('操作失败:', error);
}
}
// 链式操作示例
async function demonstrateChainUsage() {
const element = document.getElementById('multiPluginElement');
try {
const results = await pluginWrapper
.chain(element)
.tooltip({ placement: 'top' })
.execute();
console.log('链式操作完成:', results);
} catch (error) {
console.error('链式操作失败:', error);
}
}
性能优化
1. 插件懒加载
// 插件懒加载管理器
class PluginLazyLoader {
constructor() {
this.loadedPlugins = new Set();
this.observers = new Map();
this.init();
}
init() {
this.setupIntersectionObserver();
this.setupMutationObserver();
}
setupIntersectionObserver() {
// 创建交叉观察器,用于检测元素进入视口
this.intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadPluginForElement(entry.target);
this.intersectionObserver.unobserve(entry.target);
}
});
},
{
rootMargin: '50px', // 提前 50px 开始加载
threshold: 0.1
}
);
}
setupMutationObserver() {
// 创建变化观察器,用于检测新添加的元素
this.mutationObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.scanForLazyPlugins(node);
}
});
});
});
this.mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
}
// 扫描需要懒加载的插件
scanForLazyPlugins(container = document) {
const lazyElements = container.querySelectorAll('[data-lazy-plugin]');
lazyElements.forEach(element => {
if (!this.loadedPlugins.has(element)) {
this.intersectionObserver.observe(element);
}
});
}
// 为元素加载插件
loadPluginForElement(element) {
const pluginType = element.getAttribute('data-lazy-plugin');
const pluginOptions = this.parseOptions(element.getAttribute('data-plugin-options'));
switch (pluginType) {
case 'tooltip':
this.loadTooltip(element, pluginOptions);
break;
case 'popover':
this.loadPopover(element, pluginOptions);
break;
case 'carousel':
this.loadCarousel(element, pluginOptions);
break;
case 'modal':
this.loadModal(element, pluginOptions);
break;
default:
console.warn(`未知的插件类型: ${pluginType}`);
}
this.loadedPlugins.add(element);
}
loadTooltip(element, options = {}) {
const tooltip = new bootstrap.Tooltip(element, options);
console.log('懒加载工具提示:', element);
return tooltip;
}
loadPopover(element, options = {}) {
const popover = new bootstrap.Popover(element, options);
console.log('懒加载弹出框:', element);
return popover;
}
loadCarousel(element, options = {}) {
const carousel = new bootstrap.Carousel(element, options);
console.log('懒加载轮播图:', element);
return carousel;
}
loadModal(element, options = {}) {
const modal = new bootstrap.Modal(element, options);
console.log('懒加载模态框:', element);
return modal;
}
parseOptions(optionsString) {
if (!optionsString) return {};
try {
return JSON.parse(optionsString);
} catch (error) {
console.warn('解析插件选项失败:', optionsString);
return {};
}
}
// 手动触发加载
loadAll() {
const lazyElements = document.querySelectorAll('[data-lazy-plugin]');
lazyElements.forEach(element => {
if (!this.loadedPlugins.has(element)) {
this.loadPluginForElement(element);
}
});
}
// 销毁懒加载器
destroy() {
this.intersectionObserver.disconnect();
this.mutationObserver.disconnect();
this.loadedPlugins.clear();
}
}
// 初始化懒加载器
const pluginLazyLoader = new PluginLazyLoader();
// 扫描现有元素
pluginLazyLoader.scanForLazyPlugins();
2. 内存管理和清理
// 插件内存管理器
class PluginMemoryManager {
constructor() {
this.pluginInstances = new WeakMap();
this.eventListeners = new Map();
this.timers = new Set();
this.observers = new Set();
this.setupCleanupListeners();
}
setupCleanupListeners() {
// 页面卸载时清理
window.addEventListener('beforeunload', () => {
this.cleanup();
});
// 页面隐藏时清理不必要的资源
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.pauseNonEssentialOperations();
} else {
this.resumeOperations();
}
});
}
// 注册插件实例
registerPlugin(element, plugin, type) {
if (!this.pluginInstances.has(element)) {
this.pluginInstances.set(element, new Map());
}
this.pluginInstances.get(element).set(type, plugin);
}
// 获取插件实例
getPlugin(element, type) {
const plugins = this.pluginInstances.get(element);
return plugins ? plugins.get(type) : null;
}
// 注册事件监听器
registerEventListener(element, event, handler) {
const key = `${element.id || 'anonymous'}-${event}`;
if (!this.eventListeners.has(key)) {
this.eventListeners.set(key, []);
}
this.eventListeners.get(key).push({ element, event, handler });
element.addEventListener(event, handler);
}
// 注册定时器
registerTimer(timer) {
this.timers.add(timer);
return timer;
}
// 注册观察器
registerObserver(observer) {
this.observers.add(observer);
return observer;
}
// 清理指定元素的资源
cleanupElement(element) {
// 清理插件实例
const plugins = this.pluginInstances.get(element);
if (plugins) {
plugins.forEach(plugin => {
if (typeof plugin.dispose === 'function') {
plugin.dispose();
}
});
this.pluginInstances.delete(element);
}
// 清理事件监听器
const elementId = element.id || 'anonymous';
this.eventListeners.forEach((listeners, key) => {
if (key.startsWith(elementId)) {
listeners.forEach(({ element: el, event, handler }) => {
el.removeEventListener(event, handler);
});
this.eventListeners.delete(key);
}
});
}
// 暂停非必要操作
pauseNonEssentialOperations() {
// 暂停轮播图
document.querySelectorAll('.carousel').forEach(carousel => {
const carouselInstance = bootstrap.Carousel.getInstance(carousel);
if (carouselInstance) {
carouselInstance.pause();
}
});
// 清理定时器
this.timers.forEach(timer => {
clearTimeout(timer);
clearInterval(timer);
});
this.timers.clear();
console.log('已暂停非必要操作');
}
// 恢复操作
resumeOperations() {
// 恢复轮播图
document.querySelectorAll('.carousel[data-bs-ride="carousel"]').forEach(carousel => {
const carouselInstance = bootstrap.Carousel.getInstance(carousel);
if (carouselInstance) {
carouselInstance.cycle();
}
});
console.log('已恢复操作');
}
// 全面清理
cleanup() {
// 清理所有事件监听器
this.eventListeners.forEach(listeners => {
listeners.forEach(({ element, event, handler }) => {
element.removeEventListener(event, handler);
});
});
this.eventListeners.clear();
// 清理定时器
this.timers.forEach(timer => {
clearTimeout(timer);
clearInterval(timer);
});
this.timers.clear();
// 清理观察器
this.observers.forEach(observer => {
if (typeof observer.disconnect === 'function') {
observer.disconnect();
}
});
this.observers.clear();
console.log('内存清理完成');
}
// 获取内存使用情况
getMemoryUsage() {
return {
pluginInstances: this.pluginInstances.size || 0,
eventListeners: this.eventListeners.size,
timers: this.timers.size,
observers: this.observers.size
};
}
}
// 全局内存管理器
const memoryManager = new PluginMemoryManager();
// 使用示例
function createManagedTooltip(element, options = {}) {
const tooltip = new bootstrap.Tooltip(element, options);
memoryManager.registerPlugin(element, tooltip, 'tooltip');
// 注册相关事件监听器
const showHandler = () => console.log('工具提示显示');
memoryManager.registerEventListener(element, 'shown.bs.tooltip', showHandler);
return tooltip;
}
// 定期检查内存使用情况
const memoryCheckTimer = setInterval(() => {
const usage = memoryManager.getMemoryUsage();
console.log('内存使用情况:', usage);
}, 30000); // 每 30 秒检查一次
memoryManager.registerTimer(memoryCheckTimer);
可访问性支持
1. 键盘导航支持
// 键盘导航管理器
class KeyboardNavigationManager {
constructor() {
this.focusableElements = [
'button',
'[href]',
'input',
'select',
'textarea',
'[tabindex]:not([tabindex="-1"])'
].join(', ');
this.init();
}
init() {
this.setupGlobalKeyboardListeners();
this.setupModalKeyboardNavigation();
this.setupCarouselKeyboardNavigation();
this.setupDropdownKeyboardNavigation();
}
setupGlobalKeyboardListeners() {
document.addEventListener('keydown', (event) => {
// ESC 键关闭模态框、下拉菜单等
if (event.key === 'Escape') {
this.handleEscapeKey(event);
}
// Tab 键焦点管理
if (event.key === 'Tab') {
this.handleTabKey(event);
}
});
}
setupModalKeyboardNavigation() {
document.addEventListener('shown.bs.modal', (event) => {
const modal = event.target;
this.trapFocusInModal(modal);
});
}
setupCarouselKeyboardNavigation() {
document.querySelectorAll('.carousel').forEach(carousel => {
carousel.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowLeft':
event.preventDefault();
bootstrap.Carousel.getInstance(carousel)?.prev();
break;
case 'ArrowRight':
event.preventDefault();
bootstrap.Carousel.getInstance(carousel)?.next();
break;
case 'Home':
event.preventDefault();
bootstrap.Carousel.getInstance(carousel)?.to(0);
break;
case 'End':
event.preventDefault();
const items = carousel.querySelectorAll('.carousel-item');
bootstrap.Carousel.getInstance(carousel)?.to(items.length - 1);
break;
}
});
});
}
setupDropdownKeyboardNavigation() {
document.addEventListener('shown.bs.dropdown', (event) => {
const dropdown = event.target;
const menu = dropdown.querySelector('.dropdown-menu');
if (menu) {
this.setupDropdownMenuNavigation(menu);
}
});
}
handleEscapeKey(event) {
// 关闭最顶层的模态框
const openModals = document.querySelectorAll('.modal.show');
if (openModals.length > 0) {
const topModal = openModals[openModals.length - 1];
const modalInstance = bootstrap.Modal.getInstance(topModal);
if (modalInstance) {
modalInstance.hide();
}
return;
}
// 关闭下拉菜单
const openDropdowns = document.querySelectorAll('.dropdown-menu.show');
if (openDropdowns.length > 0) {
openDropdowns.forEach(dropdown => {
const toggle = dropdown.previousElementSibling;
if (toggle) {
bootstrap.Dropdown.getInstance(toggle)?.hide();
}
});
}
}
handleTabKey(event) {
// 检查是否在模态框内
const activeModal = document.querySelector('.modal.show');
if (activeModal) {
this.handleModalTabNavigation(event, activeModal);
}
}
trapFocusInModal(modal) {
const focusableElements = modal.querySelectorAll(this.focusableElements);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// 聚焦到第一个元素
if (firstElement) {
firstElement.focus();
}
// 设置焦点陷阱
modal.addEventListener('keydown', (event) => {
if (event.key === 'Tab') {
if (event.shiftKey) {
// Shift + Tab
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement?.focus();
}
} else {
// Tab
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement?.focus();
}
}
}
});
}
handleModalTabNavigation(event, modal) {
const focusableElements = modal.querySelectorAll(this.focusableElements);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
if (event.shiftKey) {
// Shift + Tab
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement?.focus();
}
} else {
// Tab
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement?.focus();
}
}
}
setupDropdownMenuNavigation(menu) {
const menuItems = menu.querySelectorAll('.dropdown-item:not(.disabled)');
let currentIndex = -1;
menu.addEventListener('keydown', (event) => {
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
currentIndex = (currentIndex + 1) % menuItems.length;
menuItems[currentIndex].focus();
break;
case 'ArrowUp':
event.preventDefault();
currentIndex = currentIndex <= 0 ? menuItems.length - 1 : currentIndex - 1;
menuItems[currentIndex].focus();
break;
case 'Home':
event.preventDefault();
currentIndex = 0;
menuItems[currentIndex].focus();
break;
case 'End':
event.preventDefault();
currentIndex = menuItems.length - 1;
menuItems[currentIndex].focus();
break;
case 'Enter':
case ' ':
event.preventDefault();
if (currentIndex >= 0) {
menuItems[currentIndex].click();
}
break;
}
});
}
// 设置焦点指示器
setupFocusIndicators() {
const style = document.createElement('style');
style.textContent = `
.focus-visible {
outline: 2px solid #007bff;
outline-offset: 2px;
}
.modal .focus-visible {
outline-color: #ffffff;
}
.btn:focus-visible {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
`;
document.head.appendChild(style);
}
}
// 初始化键盘导航管理器
const keyboardNavManager = new KeyboardNavigationManager();
keyboardNavManager.setupFocusIndicators();
2. ARIA 标签优化
// ARIA 标签管理器
class AriaManager {
constructor() {
this.init();
}
init() {
this.enhanceModals();
this.enhanceCarousels();
this.enhanceDropdowns();
this.enhanceTooltips();
this.enhanceCollapses();
}
enhanceModals() {
document.querySelectorAll('.modal').forEach(modal => {
// 确保模态框有正确的 ARIA 属性
if (!modal.getAttribute('aria-labelledby') && !modal.getAttribute('aria-label')) {
const title = modal.querySelector('.modal-title');
if (title) {
const titleId = title.id || `modal-title-${Date.now()}`;
title.id = titleId;
modal.setAttribute('aria-labelledby', titleId);
}
}
// 设置模态框描述
const body = modal.querySelector('.modal-body');
if (body && !modal.getAttribute('aria-describedby')) {
const bodyId = body.id || `modal-body-${Date.now()}`;
body.id = bodyId;
modal.setAttribute('aria-describedby', bodyId);
}
// 监听模态框状态变化
modal.addEventListener('shown.bs.modal', () => {
modal.setAttribute('aria-hidden', 'false');
this.announceToScreenReader('模态框已打开');
});
modal.addEventListener('hidden.bs.modal', () => {
modal.setAttribute('aria-hidden', 'true');
this.announceToScreenReader('模态框已关闭');
});
});
}
enhanceCarousels() {
document.querySelectorAll('.carousel').forEach(carousel => {
// 设置轮播图角色和标签
carousel.setAttribute('role', 'region');
carousel.setAttribute('aria-label', '图片轮播');
// 增强指示器
const indicators = carousel.querySelectorAll('.carousel-indicators button');
indicators.forEach((indicator, index) => {
indicator.setAttribute('aria-label', `转到第 ${index + 1} 张幻灯片`);
});
// 增强控制按钮
const prevBtn = carousel.querySelector('.carousel-control-prev');
const nextBtn = carousel.querySelector('.carousel-control-next');
if (prevBtn) {
prevBtn.setAttribute('aria-label', '上一张幻灯片');
}
if (nextBtn) {
nextBtn.setAttribute('aria-label', '下一张幻灯片');
}
// 监听轮播图变化
carousel.addEventListener('slid.bs.carousel', (event) => {
const currentSlide = event.to + 1;
const totalSlides = carousel.querySelectorAll('.carousel-item').length;
this.announceToScreenReader(`当前第 ${currentSlide} 张,共 ${totalSlides} 张幻灯片`);
});
});
}
enhanceDropdowns() {
document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(toggle => {
const menu = document.querySelector(toggle.getAttribute('data-bs-target') ||
toggle.getAttribute('href'));
if (menu) {
// 设置下拉菜单关系
toggle.setAttribute('aria-haspopup', 'true');
toggle.setAttribute('aria-expanded', 'false');
const menuId = menu.id || `dropdown-menu-${Date.now()}`;
menu.id = menuId;
toggle.setAttribute('aria-controls', menuId);
// 监听下拉菜单状态
toggle.addEventListener('shown.bs.dropdown', () => {
toggle.setAttribute('aria-expanded', 'true');
this.announceToScreenReader('下拉菜单已展开');
});
toggle.addEventListener('hidden.bs.dropdown', () => {
toggle.setAttribute('aria-expanded', 'false');
this.announceToScreenReader('下拉菜单已收起');
});
}
});
}
enhanceTooltips() {
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => {
// 设置工具提示描述关系
element.setAttribute('aria-describedby', element.getAttribute('data-bs-target') ||
`tooltip-${Date.now()}`);
});
}
enhanceCollapses() {
document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(toggle => {
const target = document.querySelector(toggle.getAttribute('data-bs-target') ||
toggle.getAttribute('href'));
if (target) {
// 设置折叠面板关系
toggle.setAttribute('aria-expanded', target.classList.contains('show') ? 'true' : 'false');
const targetId = target.id || `collapse-${Date.now()}`;
target.id = targetId;
toggle.setAttribute('aria-controls', targetId);
// 监听折叠状态
target.addEventListener('shown.bs.collapse', () => {
toggle.setAttribute('aria-expanded', 'true');
this.announceToScreenReader('面板已展开');
});
target.addEventListener('hidden.bs.collapse', () => {
toggle.setAttribute('aria-expanded', 'false');
this.announceToScreenReader('面板已收起');
});
}
});
}
// 向屏幕阅读器宣布消息
announceToScreenReader(message) {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.className = 'sr-only';
announcement.textContent = message;
document.body.appendChild(announcement);
// 短暂延迟后移除
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
}
// 创建屏幕阅读器专用样式
createScreenReaderStyles() {
const style = document.createElement('style');
style.textContent = `
.sr-only {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
.sr-only-focusable:focus {
position: static !important;
width: auto !important;
height: auto !important;
padding: inherit !important;
margin: inherit !important;
overflow: visible !important;
clip: auto !important;
white-space: normal !important;
}
`;
document.head.appendChild(style);
}
}
// 初始化 ARIA 管理器
const ariaManager = new AriaManager();
ariaManager.createScreenReaderStyles();
本章总结
本章深入学习了 Bootstrap JavaScript 插件的使用和自定义开发:
主要内容
- 插件基础使用 - 掌握了模态框、工具提示、轮播图等核心插件的基本用法
- JavaScript API 控制 - 学会了通过编程方式控制插件行为和状态
- 高级插件应用 - 实现了动态内容模态框和智能工具提示系统
- 插件配置与自定义 - 了解了插件配置选项和自定义插件开发
- 事件系统 - 掌握了 Bootstrap 事件机制和回调函数使用
- 性能优化 - 学习了插件懒加载和内存管理技术
- 可访问性支持 - 实现了键盘导航和 ARIA 标签优化
核心技能
- 熟练使用 Bootstrap JavaScript 插件
- 能够通过 API 控制插件行为
- 掌握事件监听和处理机制
- 具备自定义插件开发能力
- 了解性能优化和可访问性最佳实践
实际应用
- 创建交互式用户界面
- 开发动态内容展示系统
- 实现无障碍访问支持
- 优化页面性能和用户体验
练习题
基础练习
- 创建一个图片画廊,使用模态框显示大图,支持键盘导航
- 实现一个多步骤表单,使用模态框展示每个步骤
- 创建一个产品展示轮播图,包含缩略图导航和自动播放控制
进阶练习
- 开发一个消息通知系统,支持不同类型的通知和自动关闭
- 实现一个数据表格,使用工具提示显示详细信息
- 创建一个可拖拽的模态框,支持调整大小和位置
高级练习
- 开发一个完整的后台管理界面,集成所有 Bootstrap 插件
- 实现一个无障碍访问的在线购物车系统
- 创建一个响应式的数据可视化仪表板,支持实时更新
<!-- 高级轮播图 -->
<div class="container my-5">
<h2>高级轮播图控制</h2>
<div id="advancedCarousel" class="carousel slide" data-bs-ride="carousel">
<!-- 指示器 -->
<div class="carousel-indicators">
<button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
<button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="1" aria-label="Slide 2"></button>
<button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="2" aria-label="Slide 3"></button>
<button type="button" data-bs-target="#advancedCarousel" data-bs-slide-to="3" aria-label="Slide 4"></button>
</div>
<!-- 轮播内容 -->
<div class="carousel-inner">
<div class="carousel-item active">
<img src="https://via.placeholder.com/800x400/007bff/ffffff?text=Slide+1" class="d-block w-100" alt="Slide 1">
<div class="carousel-caption d-none d-md-block">
<h5>第一张幻灯片</h5>
<p>这是第一张幻灯片的描述内容。</p>
</div>
</div>
<div class="carousel-item">
<img src="https://via.placeholder.com/800x400/28a745/ffffff?text=Slide+2" class="d-block w-100" alt="Slide 2">
<div class="carousel-caption d-none d-md-block">
<h5>第二张幻灯片</h5>
<p>这是第二张幻灯片的描述内容。</p>
</div>
</div>
<div class="carousel-item">
<img src="https://via.placeholder.com/800x400/ffc107/000000?text=Slide+3" class="d-block w-100" alt="Slide 3">
<div class="carousel-caption d-none d-md-block">
<h5>第三张幻灯片</h5>
<p>这是第三张幻灯片的描述内容。</p>
</div>
</div>
<div class="carousel-item">
<img src="https://via.placeholder.com/800x400/dc3545/ffffff?text=Slide+4" class="d-block w-100" alt="Slide 4">
<div class="carousel-caption d-none d-md-block">
<h5>第四张幻灯片</h5>
<p>这是第四张幻灯片的描述内容。</p>
</div>
</div>
</div>
<!-- 控制按钮 -->
<button class="carousel-control-prev" type="button" data-bs-target="#advancedCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#advancedCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<!-- 轮播控制面板 -->
<div class="mt-4">
<div class="row">
<div class="col-md-6">
<h5>播放控制</h5>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary" id="playBtn">播放</button>
<button type="button" class="btn btn-outline-secondary" id="pauseBtn">暂停</button>
<button type="button" class="btn btn-outline-info" id="prevBtn">上一张</button>
<button type="button" class="btn btn-outline-info" id="nextBtn">下一张</button>
</div>
</div>
<div class="col-md-6">
<h5>播放设置</h5>
<div class="mb-2">
<label for="intervalRange" class="form-label">播放间隔: <span id="intervalValue">3000</span>ms</label>
<input type="range" class="form-range" id="intervalRange" min="1000" max="10000" step="500" value="3000">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="autoplayCheck" checked>
<label class="form-check-label" for="autoplayCheck">自动播放</label>
</div>
</div>
</div>
</div>
<!-- 缩略图导航 -->
<div class="mt-4">
<h5>缩略图导航</h5>
<div class="row" id="thumbnailNav">
<div class="col-3">
<img src="https://via.placeholder.com/200x100/007bff/ffffff?text=Thumb+1"
class="img-fluid thumbnail-img active"
data-slide-to="0"
alt="Thumbnail 1">
</div>
<div class="col-3">
<img src="https://via.placeholder.com/200x100/28a745/ffffff?text=Thumb+2"
class="img-fluid thumbnail-img"
data-slide-to="1"
alt="Thumbnail 2">
</div>
<div class="col-3">
<img src="https://via.placeholder.com/200x100/ffc107/000000?text=Thumb+3"
class="img-fluid thumbnail-img"
data-slide-to="2"
alt="Thumbnail 3">
</div>
<div class="col-3">
<img src="https://via.placeholder.com/200x100/dc3545/ffffff?text=Thumb+4"
class="img-fluid thumbnail-img"
data-slide-to="3"
alt="Thumbnail 4">
</div>
</div>
</div>
</div>
<script>
// 高级轮播图控制器
class AdvancedCarouselController {
constructor(carouselId) {
this.carouselElement = document.getElementById(carouselId);
this.carousel = new bootstrap.Carousel(this.carouselElement, {
interval: 3000,
wrap: true,
touch: true,
pause: 'hover'
});
this.currentSlide = 0;
this.totalSlides = this.carouselElement.querySelectorAll('.carousel-item').length;
this.isPlaying = true;
this.bindEvents();
this.initializeThumbnails();
this.initializeControls();
}
bindEvents() {
// 监听轮播图事件
this.carouselElement.addEventListener('slide.bs.carousel', (event) => {
this.currentSlide = event.to;
this.updateThumbnails(event.to);
this.updateProgress(event.to);
});
this.carouselElement.addEventListener('slid.bs.carousel', (event) => {
console.log(`轮播图切换到第 ${event.to + 1} 张`);
});
}
initializeThumbnails() {
const thumbnails = document.querySelectorAll('.thumbnail-img');
thumbnails.forEach((thumbnail, index) => {
thumbnail.addEventListener('click', () => {
this.carousel.to(index);
});
// 添加悬停效果
thumbnail.addEventListener('mouseenter', () => {
thumbnail.style.opacity = '0.8';
});
thumbnail.addEventListener('mouseleave', () => {
thumbnail.style.opacity = '1';
});
});
}
initializeControls() {
// 播放控制按钮
document.getElementById('playBtn').addEventListener('click', () => {
this.play();
});
document.getElementById('pauseBtn').addEventListener('click', () => {
this.pause();
});
document.getElementById('prevBtn').addEventListener('click', () => {
this.prev();
});
document.getElementById('nextBtn').addEventListener('click', () => {
this.next();
});
// 播放间隔控制
const intervalRange = document.getElementById('intervalRange');
const intervalValue = document.getElementById('intervalValue');
intervalRange.addEventListener('input', (e) => {
const interval = parseInt(e.target.value);
intervalValue.textContent = interval;
this.setInterval(interval);
});
// 自动播放开关
document.getElementById('autoplayCheck').addEventListener('change', (e) => {
if (e.target.checked) {
this.play();
} else {
this.pause();
}
});
}
updateThumbnails(activeIndex) {
const thumbnails = document.querySelectorAll('.thumbnail-img');
thumbnails.forEach((thumbnail, index) => {
if (index === activeIndex) {
thumbnail.classList.add('active');
} else {
thumbnail.classList.remove('active');
}
});
}
updateProgress(activeIndex) {
const progress = ((activeIndex + 1) / this.totalSlides) * 100;
console.log(`播放进度: ${progress.toFixed(1)}%`);
}
play() {
this.carousel.cycle();
this.isPlaying = true;
// 更新按钮状态
document.getElementById('playBtn').disabled = true;
document.getElementById('pauseBtn').disabled = false;
}
pause() {
this.carousel.pause();
this.isPlaying = false;
// 更新按钮状态
document.getElementById('playBtn').disabled = false;
document.getElementById('pauseBtn').disabled = true;
}
prev() {
this.carousel.prev();
}
next() {
this.carousel.next();
}
setInterval(interval) {
// 重新创建轮播图实例以应用新的间隔
this.carousel.dispose();
this.carousel = new bootstrap.Carousel(this.carouselElement, {
interval: interval,
wrap: true,
touch: true,
pause: 'hover'
});
if (this.isPlaying) {
this.carousel.cycle();
}
}
// 跳转到指定幻灯片
goToSlide(index) {
if (index >= 0 && index < this.totalSlides) {
this.carousel.to(index);
}
}
// 获取当前幻灯片索引
getCurrentSlide() {
return this.currentSlide;
}
// 获取总幻灯片数量
getTotalSlides() {
return this.totalSlides;
}
}
// 初始化高级轮播图控制器
const advancedCarouselController = new AdvancedCarouselController('advancedCarousel');
// 添加缩略图样式
const carouselStyle = document.createElement('style');
carouselStyle.textContent = `
.thumbnail-img {
cursor: pointer;
border: 3px solid transparent;
border-radius: 0.375rem;
transition: all 0.3s ease;
}
.thumbnail-img:hover {
border-color: #007bff;
transform: scale(1.05);
}
.thumbnail-img.active {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.carousel-control-prev,
.carousel-control-next {
width: 5%;
}
.carousel-indicators {
margin-bottom: 1rem;
}
`;
document.head.appendChild(carouselStyle);
</script>
插件配置与自定义
1. 插件配置选项
// Bootstrap 插件通用配置模式
class PluginConfigManager {
constructor() {
this.defaultConfigs = {
modal: {
backdrop: true, // 背景遮罩
keyboard: true, // ESC 键关闭
focus: true, // 自动聚焦
show: true // 初始化时显示
},
tooltip: {
animation: true, // 动画效果
delay: 0, // 延迟时间
html: false, // HTML 内容
placement: 'top', // 位置
selector: false, // 选择器
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
title: '', // 标题
trigger: 'hover focus', // 触发方式
offset: [0, 0], // 偏移量
container: false, // 容器
fallbackPlacements: ['top', 'right', 'bottom', 'left'],
boundary: 'clippingParents',
customClass: '',
sanitize: true,
allowList: {
// 允许的 HTML 标签和属性
'b': [],
'br': [],
'code': [],
'em': [],
'i': [],
'mark': [],
'small': [],
'span': [],
'strong': [],
'sub': [],
'sup': []
},
sanitizeFn: null,
popperConfig: null
},
carousel: {
interval: 5000, // 自动切换间隔
keyboard: true, // 键盘控制
pause: 'hover', // 暂停条件
ride: false, // 自动开始
wrap: true, // 循环播放
touch: true // 触摸支持
},
collapse: {
parent: null, // 父容器
toggle: true // 切换状态
},
dropdown: {
boundary: 'clippingParents',
reference: 'toggle',
display: 'dynamic',
popperConfig: null,
autoClose: true
}
};
}
// 获取默认配置
getDefaultConfig(pluginName) {
return { ...this.defaultConfigs[pluginName] };
}
// 合并配置
mergeConfig(pluginName, userConfig) {
const defaultConfig = this.getDefaultConfig(pluginName);
return { ...defaultConfig, ...userConfig };
}
// 验证配置
validateConfig(pluginName, config) {
const validators = {
modal: this.validateModalConfig,
tooltip: this.validateTooltipConfig,
carousel: this.validateCarouselConfig
};
const validator = validators[pluginName];
if (validator) {
return validator(config);
}
return { valid: true, errors: [] };
}
validateModalConfig(config) {
const errors = [];
if (typeof config.backdrop !== 'boolean' && config.backdrop !== 'static') {
errors.push('backdrop 必须是 boolean 或 "static"');
}
if (typeof config.keyboard !== 'boolean') {
errors.push('keyboard 必须是 boolean');
}
return {
valid: errors.length === 0,
errors
};
}
validateTooltipConfig(config) {
const errors = [];
const validPlacements = ['auto', 'top', 'right', 'bottom', 'left'];
if (!validPlacements.includes(config.placement)) {
errors.push(`placement 必须是以下值之一: ${validPlacements.join(', ')}`);
}
if (typeof config.delay !== 'number' && typeof config.delay !== 'object') {
errors.push('delay 必须是 number 或 object');
}
return {
valid: errors.length === 0,
errors
};
}
validateCarouselConfig(config) {
const errors = [];
if (typeof config.interval !== 'number' && config.interval !== false) {
errors.push('interval 必须是 number 或 false');
}
if (config.interval !== false && config.interval < 1000) {
errors.push('interval 不能小于 1000ms');
}
return {
valid: errors.length === 0,
errors
};
}
}
// 使用配置管理器
const configManager = new PluginConfigManager();
// 示例:创建自定义配置的模态框
function createCustomModal(modalId, userConfig = {}) {
const config = configManager.mergeConfig('modal', userConfig);
const validation = configManager.validateConfig('modal', config);
if (!validation.valid) {
console.error('模态框配置错误:', validation.errors);
return null;
}
const modalElement = document.getElementById(modalId);
return new bootstrap.Modal(modalElement, config);
}
// 示例:创建自定义配置的工具提示
function createCustomTooltip(element, userConfig = {}) {
const config = configManager.mergeConfig('tooltip', userConfig);
const validation = configManager.validateConfig('tooltip', config);
if (!validation.valid) {
console.error('工具提示配置错误:', validation.errors);
return null;
}
return new bootstrap.Tooltip(element, config);
}
// 使用示例
const customModal = createCustomModal('exampleModal', {
backdrop: 'static',
keyboard: false,
focus: true
});
const customTooltip = createCustomTooltip(document.getElementById('customTooltipBtn'), {
placement: 'bottom',
delay: { show: 1000, hide: 500 },
html: true,
title: '<strong>自定义</strong> 工具提示内容'
});
2. 自定义插件开发
// 自定义插件:消息通知系统
class NotificationPlugin {
constructor(options = {}) {
this.options = {
container: 'body',
position: 'top-right',
autoClose: true,
closeDelay: 5000,
maxNotifications: 5,
animation: 'fade',
...options
};
this.notifications = [];
this.container = null;
this.init();
}
init() {
this.createContainer();
this.injectStyles();
}
createContainer() {
this.container = document.createElement('div');
this.container.className = `notification-container position-${this.options.position}`;
const targetContainer = typeof this.options.container === 'string'
? document.querySelector(this.options.container)
: this.options.container;
targetContainer.appendChild(this.container);
}
injectStyles() {
if (document.getElementById('notification-styles')) return;
const style = document.createElement('style');
style.id = 'notification-styles';
style.textContent = `
.notification-container {
position: fixed;
z-index: 1060;
pointer-events: none;
}
.notification-container.position-top-right {
top: 1rem;
right: 1rem;
}
.notification-container.position-top-left {
top: 1rem;
left: 1rem;
}
.notification-container.position-bottom-right {
bottom: 1rem;
right: 1rem;
}
.notification-container.position-bottom-left {
bottom: 1rem;
left: 1rem;
}
.notification-item {
pointer-events: auto;
margin-bottom: 0.5rem;
min-width: 300px;
max-width: 400px;
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
}
.notification-item.show {
opacity: 1;
transform: translateX(0);
}
.notification-item.hide {
opacity: 0;
transform: translateX(100%);
}
.notification-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background-color: rgba(255, 255, 255, 0.3);
transition: width linear;
}
`;
document.head.appendChild(style);
}
show(message, type = 'info', options = {}) {
const notification = this.createNotification(message, type, options);
// 检查最大通知数量
if (this.notifications.length >= this.options.maxNotifications) {
this.remove(this.notifications[0]);
}
this.notifications.push(notification);
this.container.appendChild(notification.element);
// 触发显示动画
setTimeout(() => {
notification.element.classList.add('show');
}, 10);
// 自动关闭
if (this.options.autoClose && notification.options.autoClose !== false) {
this.startAutoClose(notification);
}
return notification;
}
createNotification(message, type, options) {
const id = 'notification-' + Date.now() + Math.random().toString(36).substr(2, 9);
const notificationOptions = {
autoClose: this.options.autoClose,
closeDelay: this.options.closeDelay,
showProgress: true,
...options
};
const element = document.createElement('div');
element.className = `notification-item alert alert-${type} alert-dismissible position-relative`;
element.setAttribute('role', 'alert');
element.innerHTML = `
<div class="d-flex align-items-center">
<div class="flex-grow-1">${message}</div>
<button type="button" class="btn-close" aria-label="Close"></button>
</div>
${notificationOptions.showProgress ? '<div class="notification-progress"></div>' : ''}
`;
const notification = {
id,
element,
type,
message,
options: notificationOptions,
timer: null,
progressTimer: null
};
// 绑定关闭按钮事件
const closeBtn = element.querySelector('.btn-close');
closeBtn.addEventListener('click', () => {
this.remove(notification);
});
// 鼠标悬停暂停自动关闭
element.addEventListener('mouseenter', () => {
this.pauseAutoClose(notification);
});
element.addEventListener('mouseleave', () => {
this.resumeAutoClose(notification);
});
return notification;
}
startAutoClose(notification) {
if (!notification.options.autoClose) return;
const delay = notification.options.closeDelay;
// 设置自动关闭定时器
notification.timer = setTimeout(() => {
this.remove(notification);
}, delay);
// 显示进度条
if (notification.options.showProgress) {
const progressBar = notification.element.querySelector('.notification-progress');
if (progressBar) {
progressBar.style.width = '100%';
progressBar.style.transitionDuration = delay + 'ms';
setTimeout(() => {
progressBar.style.width = '0%';
}, 10);
}
}
}
pauseAutoClose(notification) {
if (notification.timer) {
clearTimeout(notification.timer);
notification.timer = null;
}
const progressBar = notification.element.querySelector('.notification-progress');
if (progressBar) {
progressBar.style.animationPlayState = 'paused';
}
}
resumeAutoClose(notification) {
if (!notification.options.autoClose || notification.timer) return;
// 重新开始自动关闭
this.startAutoClose(notification);
const progressBar = notification.element.querySelector('.notification-progress');
if (progressBar) {
progressBar.style.animationPlayState = 'running';
}
}
remove(notification) {
if (notification.timer) {
clearTimeout(notification.timer);
}
notification.element.classList.remove('show');
notification.element.classList.add('hide');
setTimeout(() => {
if (notification.element.parentNode) {
notification.element.parentNode.removeChild(notification.element);
}
const index = this.notifications.indexOf(notification);
if (index > -1) {
this.notifications.splice(index, 1);
}
}, 300);
}
// 便捷方法
success(message, options) {
return this.show(message, 'success', options);
}
error(message, options) {
return this.show(message, 'danger', options);
}
warning(message, options) {
return this.show(message, 'warning', options);
}
info(message, options) {
return this.show(message, 'info', options);
}
// 清除所有通知
clear() {
this.notifications.forEach(notification => {
this.remove(notification);
});
}
// 销毁插件
destroy() {
this.clear();
if (this.container && this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
}
}
// 全局通知实例
const notification = new NotificationPlugin({
position: 'top-right',
maxNotifications: 3,
closeDelay: 4000
});
// 使用示例
function showNotificationExamples() {
notification.success('操作成功完成!');
setTimeout(() => {
notification.warning('这是一个警告消息');
}, 1000);
setTimeout(() => {
notification.error('发生了一个错误');
}, 2000);
setTimeout(() => {
notification.info('这是一条信息提示', {
autoClose: false // 不自动关闭
});
}, 3000);
}
2. 智能工具提示系统
<!-- 智能工具提示系统 -->
<div class="container my-5">
<h2>智能工具提示系统</h2>
<div class="row">
<div class="col-md-6">
<h4>表单帮助提示</h4>
<form id="smartForm">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input type="text"
class="form-control"
id="username"
data-smart-tooltip="true"
data-tooltip-type="validation"
data-validation-rules='["required", "minLength:3", "maxLength:20"]'
placeholder="请输入用户名">
</div>
<div class="mb-3">
<label for="email" class="form-label">邮箱地址</label>
<input type="email"
class="form-control"
id="email"
data-smart-tooltip="true"
data-tooltip-type="validation"
data-validation-rules='["required", "email"]'
placeholder="请输入邮箱地址">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input type="password"
class="form-control"
id="password"
data-smart-tooltip="true"
data-tooltip-type="validation"
data-validation-rules='["required", "minLength:8", "hasNumber", "hasSpecialChar"]'
placeholder="请输入密码">
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
<div class="col-md-6">
<h4>功能说明提示</h4>
<div class="feature-demo">
<button class="btn btn-outline-primary me-2"
data-smart-tooltip="true"
data-tooltip-type="feature"
data-feature-title="保存功能"
data-feature-description="点击此按钮可以保存当前的工作进度。支持自动保存和手动保存两种模式。">
<i class="bi bi-save"></i> 保存
</button>
<button class="btn btn-outline-success me-2"
data-smart-tooltip="true"
data-tooltip-type="feature"
data-feature-title="导出功能"
data-feature-description="将当前数据导出为 Excel、PDF 或 CSV 格式。支持自定义导出字段和格式。">
<i class="bi bi-download"></i> 导出
</button>
<button class="btn btn-outline-warning"
data-smart-tooltip="true"
data-tooltip-type="feature"
data-feature-title="分享功能"
data-feature-description="生成分享链接,可以通过邮件、社交媒体或直接复制链接的方式分享给他人。">
<i class="bi bi-share"></i> 分享
</button>
</div>
</div>
</div>
</div>
<script>
// 智能工具提示系统
class SmartTooltipSystem {
constructor() {
this.tooltips = new Map();
this.validationRules = {
required: (value) => value.trim() !== '',
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
minLength: (value, length) => value.length >= parseInt(length),
maxLength: (value, length) => value.length <= parseInt(length),
hasNumber: (value) => /\d/.test(value),
hasSpecialChar: (value) => /[!@#$%^&*(),.?":{}|<>]/.test(value)
};
this.init();
}
init() {
this.initializeTooltips();
this.bindEvents();
}
initializeTooltips() {
const elements = document.querySelectorAll('[data-smart-tooltip="true"]');
elements.forEach(element => {
const tooltipType = element.getAttribute('data-tooltip-type');
switch (tooltipType) {
case 'validation':
this.createValidationTooltip(element);
break;
case 'feature':
this.createFeatureTooltip(element);
break;
default:
this.createBasicTooltip(element);
}
});
}
createValidationTooltip(element) {
const tooltip = new bootstrap.Tooltip(element, {
title: '请输入有效的值',
placement: 'right',
trigger: 'manual',
html: true,
customClass: 'validation-tooltip'
});
this.tooltips.set(element, {
instance: tooltip,
type: 'validation'
});
}
createFeatureTooltip(element) {
const title = element.getAttribute('data-feature-title');
const description = element.getAttribute('data-feature-description');
const content = `
<div class="feature-tooltip">
<h6 class="mb-2">${title}</h6>
<p class="mb-0 small">${description}</p>
</div>
`;
const tooltip = new bootstrap.Tooltip(element, {
title: content,
placement: 'top',
html: true,
delay: { show: 500, hide: 100 },
customClass: 'feature-tooltip'
});
this.tooltips.set(element, {
instance: tooltip,
type: 'feature'
});
}
createBasicTooltip(element) {
const tooltip = new bootstrap.Tooltip(element);
this.tooltips.set(element, {
instance: tooltip,
type: 'basic'
});
}
bindEvents() {
// 绑定表单验证事件
document.querySelectorAll('[data-tooltip-type="validation"]').forEach(element => {
element.addEventListener('input', (e) => {
this.validateField(e.target);
});
element.addEventListener('blur', (e) => {
this.validateField(e.target);
});
element.addEventListener('focus', (e) => {
this.showValidationHint(e.target);
});
});
}
validateField(element) {
const rules = JSON.parse(element.getAttribute('data-validation-rules') || '[]');
const value = element.value;
const errors = [];
rules.forEach(rule => {
if (rule.includes(':')) {
const [ruleName, ruleValue] = rule.split(':');
if (!this.validationRules[ruleName](value, ruleValue)) {
errors.push(this.getErrorMessage(ruleName, ruleValue));
}
} else {
if (!this.validationRules[rule](value)) {
errors.push(this.getErrorMessage(rule));
}
}
});
this.updateValidationTooltip(element, errors);
}
showValidationHint(element) {
const rules = JSON.parse(element.getAttribute('data-validation-rules') || '[]');
const hints = rules.map(rule => {
if (rule.includes(':')) {
const [ruleName, ruleValue] = rule.split(':');
return this.getHintMessage(ruleName, ruleValue);
} else {
return this.getHintMessage(rule);
}
});
const tooltipData = this.tooltips.get(element);
if (tooltipData && hints.length > 0) {
const content = `
<div class="validation-hint">
<h6 class="mb-2">输入要求:</h6>
<ul class="mb-0 small">
${hints.map(hint => `<li>${hint}</li>`).join('')}
</ul>
</div>
`;
tooltipData.instance.setContent({ '.tooltip-inner': content });
tooltipData.instance.show();
}
}
updateValidationTooltip(element, errors) {
const tooltipData = this.tooltips.get(element);
if (!tooltipData) return;
if (errors.length > 0) {
element.classList.add('is-invalid');
element.classList.remove('is-valid');
const content = `
<div class="validation-error">
<h6 class="mb-2 text-danger">输入错误:</h6>
<ul class="mb-0 small">
${errors.map(error => `<li class="text-danger">${error}</li>`).join('')}
</ul>
</div>
`;
tooltipData.instance.setContent({ '.tooltip-inner': content });
tooltipData.instance.show();
} else {
element.classList.remove('is-invalid');
element.classList.add('is-valid');
tooltipData.instance.hide();
}
}
getErrorMessage(rule, value) {
const messages = {
required: '此字段为必填项',
email: '请输入有效的邮箱地址',
minLength: `最少需要 ${value} 个字符`,
maxLength: `最多允许 ${value} 个字符`,
hasNumber: '必须包含至少一个数字',
hasSpecialChar: '必须包含至少一个特殊字符'
};
return messages[rule] || '输入格式不正确';
}
getHintMessage(rule, value) {
const hints = {
required: '此字段不能为空',
email: '格式:example@domain.com',
minLength: `至少 ${value} 个字符`,
maxLength: `最多 ${value} 个字符`,
hasNumber: '包含数字',
hasSpecialChar: '包含特殊字符 (!@#$%^&* 等)'
};
return hints[rule] || '请按要求输入';
}
// 销毁所有工具提示
dispose() {
this.tooltips.forEach(tooltipData => {
tooltipData.instance.dispose();
});
this.tooltips.clear();
}
}
// 初始化智能工具提示系统
const smartTooltipSystem = new SmartTooltipSystem();
// 添加自定义样式
const style = document.createElement('style');
style.textContent = `
.validation-tooltip .tooltip-inner {
background-color: #dc3545;
color: white;
max-width: 300px;
}
.validation-tooltip .tooltip-arrow::before {
border-right-color: #dc3545;
}
.feature-tooltip .tooltip-inner {
background-color: #495057;
color: white;
max-width: 350px;
text-align: left;
}
.validation-hint ul,
.validation-error ul {
padding-left: 1rem;
margin-bottom: 0;
}
.validation-hint li,
.validation-error li {
margin-bottom: 0.25rem;
}
`;
document.head.appendChild(style);
</script>