Impress.js深度技术解析:架构基础与结构化设计

引言:重新认识现代Web演示框架

在数字化时代,演示文稿已经超越了传统PPT的平面限制,向着更具沉浸感、交互性和表现力的方向发展。Impress.js作为一款基于CSS3 3D变换和JavaScript的开源演示框架,正是这一变革的杰出代表。不同于常规幻灯片工具,Impress.js允许开发者在浏览器中创建出令人惊叹的三维演示效果,将观众带入一个充满可能性的Web 3D世界。

本系列文章将深入探讨Impress.js的完整技术体系,从基础的2D/3D布局系统,到复杂的数据可视化集成,再到企业级的架构设计。我们不仅会展示如何创建炫酷的视觉效果,更重要的是揭示如何构建可维护、可扩展、高性能的Impress.js应用架构。无论你是前端开发者、技术演讲者,还是希望提升产品展示效果的产品经理,本系列都将为你提供从理论到实践的全面指导。

第一章:Impress.js架构哲学与设计思想

1.1 从Prezi到Impress.js:思维范式的转变

传统的演示工具如PowerPoint或Keynote采用"幻灯片堆叠"的思维模型,每个页面都是独立的、平面的。而Impress.js继承自Prezi的"无限画布"理念,将整个演示视为一个三维空间,每个内容节点都是这个空间中的一个坐标点。这种思维范式的转变带来了几个重要的架构优势:

  1. 空间连续性:内容之间的过渡不再是生硬的页面切换,而是平滑的空间移动,保持了思维的连续性

  2. 层次关系可视化:通过空间位置的安排(上下、左右、前后)直观展示内容之间的逻辑关系

  3. 缩放聚焦:可以放大查看细节,缩小把握全局,类似于地图应用的交互体验

  4. 非线性的演示路径:支持跳转到任意节点,适应即兴的演讲需求

1.2 Impress.js的核心架构设计

Impress.js的架构可以划分为三个核心层次:

javascript 复制代码
// Impress.js架构层次示意图
class ImpressArchitecture {
  constructor() {
    this.layers = {
      // 核心层:3D变换引擎
      core: {
        transform: 'CSS3 3D变换矩阵计算',
        transition: '动画过渡处理',
        navigation: '键盘/鼠标/触摸事件处理'
      },
      
      // 应用层:演示内容管理
      application: {
        steps: '步骤管理与状态维护',
        plugins: '插件扩展系统',
        events: '自定义事件系统'
      },
      
      // 表现层:样式与渲染
      presentation: {
        styling: 'CSS样式系统',
        themes: '主题与皮肤',
        responsive: '响应式适配'
      }
    };
  }
  
  // 初始化流程
  init() {
    this.setupTransforms();    // 设置3D变换
    this.setupNavigation();    // 设置导航系统
    this.setupPlugins();       // 初始化插件
    this.setupEvents();        // 绑定事件
  }
}

1.3 与现代Web技术栈的集成

Impress.js并非孤立的框架,它可以无缝集成到现代Web技术栈中:

javascript 复制代码
// Impress.js与现代前端框架的集成模式
const IntegrationPatterns = {
  // 与React的集成
  react: {
    wrapper: '创建ImpressContext包装器',
    steps: '将Step组件化',
    state: '与React状态同步',
    hooks: '自定义Hooks管理演示状态'
  },
  
  // 与Vue的集成
  vue: {
    directives: '创建v-step指令',
    components: 'Step组件化',
    store: '与Vuex/Pinia状态管理集成'
  },
  
  // 构建工具集成
  build: {
    webpack: '通过loader处理HTML模板',
    vite: '作为插件集成到开发环境',
    typescript: '完整的类型定义支持'
  }
};

第二章:结构化布局系统设计

2.1 2D布局:网格与流式系统

尽管Impress.js以3D效果闻名,但其2D布局能力同样强大且实用。我们设计了完整的网格布局系统:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Impress.js 2D网格布局系统</title>
    <script src="https://cdn.jsdelivr.net/npm/impress.js@2.0.0/js/impress.min.js"></script>
    <style>
        /* 网格布局系统基础样式 */
        :root {
            --grid-cols: 12;
            --grid-rows: 8;
            --grid-gap: 100px;
            --step-width: 800px;
            --step-height: 600px;
        }
        
        /* 基础步骤样式 */
        .step {
            width: var(--step-width);
            height: var(--step-height);
            padding: 40px;
            box-sizing: border-box;
            border-radius: 16px;
            background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
            box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        
        /* 网格定位系统 */
        .grid-container {
            display: grid;
            grid-template-columns: repeat(var(--grid-cols), 1fr);
            grid-template-rows: repeat(var(--grid-rows), 1fr);
            gap: var(--grid-gap);
            width: calc(var(--grid-cols) * (var(--step-width) + var(--grid-gap)));
            height: calc(var(--grid-rows) * (var(--step-height) + var(--grid-gap)));
        }
        
        /* 网格位置类 */
        .grid-col-1 { grid-column: span 1; }
        .grid-col-2 { grid-column: span 2; }
        .grid-col-3 { grid-column: span 3; }
        .grid-col-4 { grid-column: span 4; }
        .grid-col-6 { grid-column: span 6; }
        .grid-col-12 { grid-column: span 12; }
        
        .grid-row-1 { grid-row: span 1; }
        .grid-row-2 { grid-row: span 2; }
        .grid-row-3 { grid-row: span 3; }
        
        /* 响应式调整 */
        @media (max-width: 1200px) {
            :root {
                --step-width: 600px;
                --step-height: 450px;
                --grid-gap: 60px;
            }
        }
        
        @media (max-width: 768px) {
            :root {
                --step-width: 90vw;
                --step-height: auto;
                --grid-cols: 1;
                --grid-gap: 40px;
            }
            
            .grid-col-2, .grid-col-3, .grid-col-4, .grid-col-6 {
                grid-column: span 1;
            }
        }
    </style>
</head>
<body>
    <div id="impress" data-grid-layout="true">
        
        <!-- 标题页 -->
        <div class="step grid-col-12 grid-row-2" 
             data-grid-x="0" data-grid-y="0"
             data-x="0" data-y="0" data-z="0">
            <div class="title-container">
                <h1 style="font-size: 4em; margin-bottom: 0.5em;">企业年度报告</h1>
                <p style="font-size: 1.5em; color: #666;">2024年度总结与2025展望</p>
                <div class="divider"></div>
                <p style="font-size: 1.2em; color: #888;">基于Impress.js网格布局系统</p>
            </div>
        </div>
        
        <!-- KPI指标网格 -->
        <div class="step grid-col-3 grid-row-1" 
             data-grid-x="0" data-grid-y="2"
             data-x="0" data-y="1000" data-z="0">
            <div class="kpi-card" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
                <div class="kpi-icon">📈</div>
                <div class="kpi-value" id="revenue-value">¥ 1.2亿</div>
                <div class="kpi-label">年度营收</div>
                <div class="kpi-trend">+15% ↗</div>
            </div>
        </div>
        
        <div class="step grid-col-3 grid-row-1" 
             data-grid-x="3" data-grid-y="2"
             data-x="1000" data-y="1000" data-z="0">
            <div class="kpi-card" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
                <div class="kpi-icon">👥</div>
                <div class="kpi-value" id="customers-value">15,832</div>
                <div class="kpi-label">新增客户</div>
                <div class="kpi-trend">+28% ↗</div>
            </div>
        </div>
        
        <!-- 跨列内容区域 -->
        <div class="step grid-col-6 grid-row-2" 
             data-grid-x="0" data-grid-y="3"
             data-x="0" data-y="2000" data-z="0">
            <h2>重点项目成果</h2>
            <div class="project-timeline">
                <div class="timeline-item">
                    <div class="timeline-date">Q1</div>
                    <div class="timeline-content">
                        <h3>新产品线发布</h3>
                        <p>成功发布三款新产品,市场反响热烈</p>
                    </div>
                </div>
                <div class="timeline-item">
                    <div class="timeline-date">Q2</div>
                    <div class="timeline-content">
                        <h3>国际业务拓展</h3>
                        <p>进入东南亚市场,建立本地化团队</p>
                    </div>
                </div>
            </div>
        </div>
        
    </div>
    
    <!-- 网格布局控制器 -->
    <script>
        class GridLayoutController {
            constructor(containerId = 'impress') {
                this.container = document.getElementById(containerId);
                this.steps = [];
                this.gridConfig = {
                    cols: 12,
                    rows: 8,
                    colWidth: 1000,  // 每列宽度
                    rowHeight: 800,   // 每行高度
                    gap: 100          // 间距
                };
                this.initialized = false;
            }
            
            // 初始化网格布局
            init() {
                if (this.initialized) return;
                
                // 收集所有步骤元素
                this.steps = Array.from(this.container.querySelectorAll('.step'));
                
                // 为每个步骤计算3D坐标
                this.calculatePositions();
                
                // 设置容器大小
                this.setContainerSize();
                
                this.initialized = true;
                return this;
            }
            
            // 计算每个步骤的位置
            calculatePositions() {
                this.steps.forEach((step, index) => {
                    // 从data-grid-x/y属性获取网格位置
                    const gridX = parseInt(step.dataset.gridX) || 0;
                    const gridY = parseInt(step.dataset.gridY) || 0;
                    
                    // 从class获取跨列/行信息
                    const colSpan = this.getColSpan(step);
                    const rowSpan = this.getRowSpan(step);
                    
                    // 计算3D坐标
                    const x = (gridX + colSpan / 2) * (this.gridConfig.colWidth + this.gridConfig.gap);
                    const y = (gridY + rowSpan / 2) * (this.gridConfig.rowHeight + this.gridConfig.gap);
                    const z = 0;
                    
                    // 设置3D坐标
                    step.dataset.x = x;
                    step.dataset.y = y;
                    step.dataset.z = z;
                    
                    // 设置宽度和高度(基于跨列/行)
                    step.style.width = `${colSpan * this.gridConfig.colWidth + (colSpan - 1) * this.gridConfig.gap}px`;
                    step.style.height = `${rowSpan * this.gridConfig.rowHeight + (rowSpan - 1) * this.gridConfig.gap}px`;
                });
            }
            
            // 获取跨列数
            getColSpan(step) {
                const classes = step.className.split(' ');
                for (const cls of classes) {
                    if (cls.startsWith('grid-col-')) {
                        return parseInt(cls.replace('grid-col-', ''));
                    }
                }
                return 1;
            }
            
            // 获取跨行数
            getRowSpan(step) {
                const classes = step.className.split(' ');
                for (const cls of classes) {
                    if (cls.startsWith('grid-row-')) {
                        return parseInt(cls.replace('grid-row-', ''));
                    }
                }
                return 1;
            }
            
            // 设置容器大小
            setContainerSize() {
                const maxGridX = Math.max(...this.steps.map(s => 
                    parseInt(s.dataset.gridX) + this.getColSpan(s)
                ));
                const maxGridY = Math.max(...this.steps.map(s => 
                    parseInt(s.dataset.gridY) + this.getRowSpan(s)
                ));
                
                const width = maxGridX * (this.gridConfig.colWidth + this.gridConfig.gap);
                const height = maxGridY * (this.gridConfig.rowHeight + this.gridConfig.gap);
                
                this.container.style.width = `${width}px`;
                this.container.style.height = `${height}px`;
            }
            
            // 响应式调整
            updateForViewport(width, height) {
                // 根据视口大小调整网格配置
                if (width < 768) {
                    this.gridConfig.colWidth = 300;
                    this.gridConfig.rowHeight = 400;
                    this.gridConfig.gap = 30;
                } else if (width < 1200) {
                    this.gridConfig.colWidth = 600;
                    this.gridConfig.rowHeight = 500;
                    this.gridConfig.gap = 60;
                } else {
                    this.gridConfig.colWidth = 1000;
                    this.gridConfig.rowHeight = 800;
                    this.gridConfig.gap = 100;
                }
                
                this.calculatePositions();
                this.setContainerSize();
                
                // 重新初始化Impress.js
                if (window.impress) {
                    window.impress().init();
                }
            }
        }
        
        // 使用示例
        document.addEventListener('DOMContentLoaded', () => {
            const gridController = new GridLayoutController('impress');
            gridController.init();
            
            // 初始化Impress.js
            impress().init();
            
            // 响应式调整
            window.addEventListener('resize', () => {
                gridController.updateForViewport(window.innerWidth, window.innerHeight);
            });
        });
    </script>
    
    <!-- KPI卡片样式 -->
    <style>
        .kpi-card {
            width: 100%;
            height: 100%;
            border-radius: 20px;
            padding: 30px;
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            text-align: center;
            box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        
        .kpi-card:hover {
            transform: translateY(-10px);
            box-shadow: 0 25px 40px rgba(0, 0, 0, 0.3);
        }
        
        .kpi-icon {
            font-size: 3em;
            margin-bottom: 20px;
        }
        
        .kpi-value {
            font-size: 2.5em;
            font-weight: bold;
            margin: 10px 0;
        }
        
        .kpi-label {
            font-size: 1.2em;
            opacity: 0.9;
            margin-bottom: 10px;
        }
        
        .kpi-trend {
            font-size: 1.1em;
            padding: 5px 15px;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 20px;
            margin-top: 10px;
        }
        
        .project-timeline {
            margin-top: 40px;
        }
        
        .timeline-item {
            display: flex;
            margin-bottom: 30px;
            align-items: flex-start;
        }
        
        .timeline-date {
            background: #667eea;
            color: white;
            padding: 10px 20px;
            border-radius: 20px;
            font-weight: bold;
            margin-right: 20px;
            min-width: 80px;
            text-align: center;
        }
        
        .timeline-content {
            flex: 1;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 10px;
            border-left: 4px solid #667eea;
        }
    </style>
</body>
</html>

2.2 流式布局适配器

对于更灵活的内容排列,我们设计了流式布局系统,自动根据内容调整布局:

javascript 复制代码
/**
 * 流式布局适配器
 * 自动根据步骤内容和顺序进行布局
 */
class FlowLayoutAdapter {
    constructor(options = {}) {
        this.options = {
            direction: 'horizontal',  // 流式方向: horizontal, vertical, diagonal
            spacing: {                // 间距配置
                x: 1000,
                y: 800,
                z: 0
            },
            align: 'center',         // 对齐方式
            maxPerRow: 3,            // 每行最大数量(水平方向)
            rowHeight: 600,          // 行高(水平方向)
            ...options
        };
        
        this.steps = [];
        this.layoutMap = new Map();  // 步骤->位置映射
    }
    
    /**
     * 应用流式布局
     * @param {HTMLElement[]} steps - 步骤元素数组
     */
    apply(steps) {
        this.steps = steps;
        
        switch (this.options.direction) {
            case 'horizontal':
                this.horizontalLayout();
                break;
            case 'vertical':
                this.verticalLayout();
                break;
            case 'diagonal':
                this.diagonalLayout();
                break;
            case 'spiral':
                this.spiralLayout();
                break;
            default:
                this.horizontalLayout();
        }
        
        return this.layoutMap;
    }
    
    /**
     * 水平流式布局
     */
    horizontalLayout() {
        let currentX = 0;
        let currentY = 0;
        let currentRow = 0;
        
        this.steps.forEach((step, index) => {
            // 获取步骤尺寸
            const width = this.getStepWidth(step);
            const height = this.getStepHeight(step);
            
            // 检查是否需要换行
            if (index > 0 && index % this.options.maxPerRow === 0) {
                currentX = 0;
                currentY += this.options.rowHeight + this.options.spacing.y;
                currentRow++;
            }
            
            // 计算位置
            const x = currentX;
            const y = currentY;
            const z = currentRow * 200; // 每行在Z轴上稍有偏移,增加层次感
            
            // 保存布局信息
            this.layoutMap.set(step, { x, y, z, width, height });
            
            // 更新下一个位置
            currentX += width + this.options.spacing.x;
        });
    }
    
    /**
     * 垂直流式布局
     */
    verticalLayout() {
        let currentY = 0;
        
        this.steps.forEach((step) => {
            const height = this.getStepHeight(step);
            
            // 垂直堆叠
            this.layoutMap.set(step, {
                x: 0,
                y: currentY,
                z: 0,
                width: this.getStepWidth(step),
                height
            });
            
            currentY += height + this.options.spacing.y;
        });
    }
    
    /**
     * 对角线布局
     */
    diagonalLayout() {
        this.steps.forEach((step, index) => {
            const offset = index * 300;
            
            this.layoutMap.set(step, {
                x: offset,
                y: offset,
                z: index * 200,
                width: this.getStepWidth(step),
                height: this.getStepHeight(step)
            });
        });
    }
    
    /**
     * 螺旋布局
     */
    spiralLayout() {
        const centerX = 0;
        const centerY = 0;
        const radius = 500;
        const angleStep = (2 * Math.PI) / 8; // 每8步完成一圈
        
        this.steps.forEach((step, index) {
            const angle = index * angleStep;
            const spiralRadius = radius + index * 100; // 半径逐渐增大
            
            const x = centerX + spiralRadius * Math.cos(angle);
            const y = centerY + spiralRadius * Math.sin(angle);
            const z = index * 100; // Z轴逐渐升高
            
            this.layoutMap.set(step, {
                x,
                y,
                z,
                width: this.getStepWidth(step),
                height: this.getStepHeight(step)
            });
        });
    }
    
    /**
     * 获取步骤宽度
     */
    getStepWidth(step) {
        // 优先使用data-width属性
        if (step.dataset.width) {
            return parseInt(step.dataset.width);
        }
        
        // 检查是否有特定宽度类
        const widthClass = Array.from(step.classList).find(cls => 
            cls.startsWith('width-')
        );
        
        if (widthClass) {
            return parseInt(widthClass.replace('width-', ''));
        }
        
        // 默认宽度
        return 800;
    }
    
    /**
     * 获取步骤高度
     */
    getStepHeight(step) {
        // 优先使用data-height属性
        if (step.dataset.height) {
            return parseInt(step.dataset.height);
        }
        
        // 检查是否有特定高度类
        const heightClass = Array.from(step.classList).find(cls => 
            cls.startsWith('height-')
        );
        
        if (heightClass) {
            return parseInt(heightClass.replace('height-', ''));
        }
        
        // 默认高度
        return 600;
    }
    
    /**
     * 应用布局到DOM
     */
    applyToDOM() {
        this.layoutMap.forEach((position, step) => {
            // 设置data-*属性
            step.dataset.x = position.x;
            step.dataset.y = position.y;
            step.dataset.z = position.z;
            
            // 设置样式
            step.style.width = `${position.width}px`;
            step.style.height = `${position.height}px`;
        });
        
        return this;
    }
}

// 使用示例
document.addEventListener('DOMContentLoaded', () => {
    const steps = Array.from(document.querySelectorAll('.step'));
    
    // 创建不同的布局实例
    const layouts = {
        horizontal: new FlowLayoutAdapter({
            direction: 'horizontal',
            maxPerRow: 3,
            spacing: { x: 1200, y: 0, z: 0 }
        }),
        
        vertical: new FlowLayoutAdapter({
            direction: 'vertical',
            spacing: { x: 0, y: 1000, z: 0 }
        }),
        
        spiral: new FlowLayoutAdapter({
            direction: 'spiral',
            spacing: { x: 0, y: 0, z: 0 }
        })
    };
    
    // 根据视口大小选择布局
    const selectLayout = () => {
        if (window.innerWidth < 768) {
            return layouts.vertical;
        } else if (window.innerWidth < 1200) {
            return layouts.horizontal;
        } else {
            return layouts.spiral;
        }
    };
    
    // 应用布局
    const selectedLayout = selectLayout();
    selectedLayout.apply(steps).applyToDOM();
    
    // 重新初始化Impress.js
    impress().init();
    
    // 响应式调整
    window.addEventListener('resize', () => {
        const newLayout = selectLayout();
        if (newLayout !== selectedLayout) {
            newLayout.apply(steps).applyToDOM();
            impress().init();
        }
    });
});

2.3 响应式布局系统

现代Web应用必须适应各种设备和屏幕尺寸,Impress.js同样需要强大的响应式支持:

javascript 复制代码
/**
 * Impress.js响应式适配系统
 */
class ResponsiveImpress {
    constructor() {
        this.breakpoints = {
            mobile: { max: 767, layout: 'vertical' },
            tablet: { min: 768, max: 1023, layout: 'grid' },
            desktop: { min: 1024, max: 1439, layout: '3d' },
            'desktop-lg': { min: 1440, layout: 'cinema' }
        };
        
        this.layoutStrategies = {
            vertical: this.verticalStrategy.bind(this),
            grid: this.gridStrategy.bind(this),
            '3d': this.threeDStrategy.bind(this),
            cinema: this.cinemaStrategy.bind(this)
        };
        
        this.currentLayout = null;
        this.impressApi = null;
        
        this.init();
    }
    
    init() {
        // 检测初始布局
        this.detectLayout();
        
        // 监听窗口大小变化
        window.addEventListener('resize', () => this.handleResize());
        
        // 监听Impress.js初始化
        document.addEventListener('impress:init', (event) => {
            this.impressApi = event.detail.api;
            this.applyLayout();
        });
    }
    
    /**
     * 检测当前应使用的布局
     */
    detectLayout() {
        const width = window.innerWidth;
        
        for (const [name, bp] of Object.entries(this.breakpoints)) {
            if ((bp.min === undefined || width >= bp.min) &&
                (bp.max === undefined || width <= bp.max)) {
                return name;
            }
        }
        
        return 'desktop'; // 默认布局
    }
    
    /**
     * 处理窗口大小变化
     */
    handleResize() {
        const newLayout = this.detectLayout();
        
        if (newLayout !== this.currentLayout) {
            this.currentLayout = newLayout;
            this.applyLayout();
        }
    }
    
    /**
     * 应用布局策略
     */
    applyLayout() {
        const layoutName = this.currentLayout;
        const breakpoint = this.breakpoints[layoutName];
        const strategy = this.layoutStrategies[breakpoint.layout];
        
        if (strategy) {
            strategy();
            
            // 重新初始化Impress.js
            if (this.impressApi) {
                this.impressApi.goto(0); // 回到第一页
                setTimeout(() => {
                    // 确保布局更新后重新计算
                    this.impressApi.init();
                }, 100);
            }
        }
    }
    
    /**
     * 垂直布局策略(移动设备)
     */
    verticalStrategy() {
        const steps = document.querySelectorAll('.step');
        let currentY = 0;
        
        steps.forEach((step, index) => {
            // 移除3D变换
            delete step.dataset.rotateX;
            delete step.dataset.rotateY;
            delete step.dataset.rotateZ;
            delete step.dataset.scale;
            
            // 垂直排列
            step.dataset.x = 0;
            step.dataset.y = currentY;
            step.dataset.z = 0;
            
            // 更新样式
            step.style.width = '90vw';
            step.style.height = 'auto';
            step.style.minHeight = '80vh';
            step.style.margin = '0 auto';
            
            // 更新下一个位置
            currentY += 1000;
        });
        
        // 更新容器样式
        document.getElementById('impress').style.transform = 'none';
    }
    
    /**
     * 网格布局策略(平板设备)
     */
    gridStrategy() {
        const steps = document.querySelectorAll('.step');
        const cols = 2;
        const colWidth = 400;
        const rowHeight = 300;
        const gap = 50;
        
        steps.forEach((step, index) => {
            const row = Math.floor(index / cols);
            const col = index % cols;
            
            step.dataset.x = col * (colWidth + gap);
            step.dataset.y = row * (rowHeight + gap);
            step.dataset.z = 0;
            
            // 轻微3D效果
            step.dataset.rotateY = col % 2 === 0 ? -5 : 5;
            step.dataset.rotateX = -2;
            
            step.style.width = `${colWidth}px`;
            step.style.height = `${rowHeight}px`;
        });
    }
    
    /**
     * 3D布局策略(桌面设备)
     */
    threeDStrategy() {
        const steps = document.querySelectorAll('.step');
        const radius = 2000;
        const angleStep = (2 * Math.PI) / steps.length;
        
        steps.forEach((step, index) => {
            const angle = index * angleStep;
            
            // 圆环布局
            step.dataset.x = Math.cos(angle) * radius;
            step.dataset.y = Math.sin(angle) * radius * 0.5;
            step.dataset.z = Math.sin(angle) * radius;
            
            // 面向中心点
            step.dataset.rotateY = (angle * 180 / Math.PI) + 90;
            step.dataset.rotateX = -20;
            
            step.style.width = '800px';
            step.style.height = '600px';
        });
    }
    
    /**
     * 影院布局策略(大屏设备)
     */
    cinemaStrategy() {
        const steps = document.querySelectorAll('.step');
        const depth = 5000;
        
        steps.forEach((step, index) => {
            // 深度层次布局
            step.dataset.x = 0;
            step.dataset.y = 0;
            step.dataset.z = index * -depth;
            
            // 透视效果
            const scale = 1 + (index * 0.1);
            step.dataset.scale = scale;
            
            // 轻微旋转增加立体感
            step.dataset.rotateY = index % 2 === 0 ? 10 : -10;
            
            step.style.width = '1000px';
            step.style.height = '700px';
        });
    }
    
    /**
     * 获取当前布局信息
     */
    getLayoutInfo() {
        return {
            name: this.currentLayout,
            breakpoint: this.breakpoints[this.currentLayout],
            strategy: this.breakpoints[this.currentLayout].layout,
            screenSize: {
                width: window.innerWidth,
                height: window.innerHeight,
                dpr: window.devicePixelRatio
            }
        };
    }
}

// 使用示例
const responsiveImpress = new ResponsiveImpress();

// 在控制台查看布局信息
console.log('当前布局:', responsiveImpress.getLayoutInfo());

// 手动切换布局
function switchLayout(layoutName) {
    if (responsiveImpress.breakpoints[layoutName]) {
        responsiveImpress.currentLayout = layoutName;
        responsiveImpress.applyLayout();
    }
}

第三章:3D空间设计与布局系统

3.1 3D坐标系与变换基础

Impress.js的核心魅力在于其3D空间能力。理解其坐标系是掌握高级3D效果的关键:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Impress.js 3D坐标系详解</title>
    <script src="https://cdn.jsdelivr.net/npm/impress.js@2.0.0/js/impress.min.js"></script>
    <style>
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            margin: 0;
            overflow: hidden;
            font-family: 'Arial', sans-serif;
        }
        
        #impress {
            perspective: 1000px;
        }
        
        .step {
            width: 800px;
            height: 600px;
            padding: 40px;
            box-sizing: border-box;
            background: white;
            border-radius: 20px;
            box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
            transition: transform 0.8s, opacity 0.8s;
        }
        
        .coordinate-system {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 15px;
            border-radius: 10px;
            font-family: 'Courier New', monospace;
        }
        
        .axis {
            margin: 10px 0;
            padding: 5px 10px;
            border-radius: 5px;
            font-weight: bold;
        }
        
        .axis-x { background: #ff4757; }
        .axis-y { background: #2ed573; }
        .axis-z { background: #1e90ff; }
        
        .coordinate-display {
            font-size: 1.2em;
            margin: 5px 0;
        }
        
        .controls {
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 10px;
            z-index: 1000;
        }
        
        .control-btn {
            background: rgba(255, 255, 255, 0.9);
            border: none;
            padding: 12px 24px;
            border-radius: 50px;
            cursor: pointer;
            font-weight: bold;
            transition: all 0.3s;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        }
        
        .control-btn:hover {
            background: white;
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
        }
        
        .cube-visualization {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 200px;
            height: 200px;
            transform-style: preserve-3d;
            animation: rotateCube 20s infinite linear;
        }
        
        .cube-face {
            position: absolute;
            width: 200px;
            height: 200px;
            background: rgba(255, 255, 255, 0.1);
            border: 2px solid rgba(255, 255, 255, 0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 24px;
            color: white;
            font-weight: bold;
        }
        
        .front  { transform: translateZ(100px); }
        .back   { transform: translateZ(-100px) rotateY(180deg); }
        .right  { transform: rotateY(90deg) translateZ(100px); }
        .left   { transform: rotateY(-90deg) translateZ(100px); }
        .top    { transform: rotateX(90deg) translateZ(100px); }
        .bottom { transform: rotateX(-90deg) translateZ(100px); }
        
        @keyframes rotateCube {
            from { transform: translate(-50%, -50%) rotateX(0) rotateY(0); }
            to { transform: translate(-50%, -50%) rotateX(360deg) rotateY(360deg); }
        }
    </style>
</head>
<body>
    <!-- 3D坐标系展示 -->
    <div id="impress">
        
        <!-- X轴移动演示 -->
        <div class="step" id="step-x" 
             data-x="-2000" data-y="0" data-z="0"
             data-rotate-x="0" data-rotate-y="0" data-rotate-z="0">
            <div class="coordinate-system">
                <div class="axis axis-x">X轴: 水平方向</div>
                <div class="coordinate-display">X: <span id="x-value">-2000</span></div>
                <div class="coordinate-display">Y: 0</div>
                <div class="coordinate-display">Z: 0</div>
            </div>
            <h1 style="color: #ff4757;">X轴移动</h1>
            <p>沿X轴水平移动。负值向左,正值向右。</p>
            <p>当前X坐标: <strong>-2000</strong></p>
            <div class="cube-visualization">
                <div class="cube-face front">前</div>
                <div class="cube-face back">后</div>
                <div class="cube-face right">右</div>
                <div class="cube-face left">左</div>
                <div class="cube-face top">上</div>
                <div class="cube-face bottom">下</div>
            </div>
        </div>
        
        <!-- Y轴移动演示 -->
        <div class="step" id="step-y"
             data-x="0" data-y="1500" data-z="0"
             data-rotate-x="0" data-rotate-y="0" data-rotate-z="0">
            <div class="coordinate-system">
                <div class="axis axis-y">Y轴: 垂直方向</div>
                <div class="coordinate-display">X: 0</div>
                <div class="coordinate-display">Y: <span id="y-value">1500</span></div>
                <div class="coordinate-display">Z: 0</div>
            </div>
            <h1 style="color: #2ed573;">Y轴移动</h1>
            <p>沿Y轴垂直移动。负值向上,正值向下。</p>
            <p>当前Y坐标: <strong>1500</strong></p>
        </div>
        
        <!-- Z轴移动演示 -->
        <div class="step" id="step-z"
             data-x="0" data-y="0" data-z="-1500"
             data-rotate-x="0" data-rotate-y="0" data-rotate-z="0">
            <div class="coordinate-system">
                <div class="axis axis-z">Z轴: 深度方向</div>
                <div class="coordinate-display">X: 0</div>
                <div class="coordinate-display">Y: 0</div>
                <div class="coordinate-display">Z: <span id="z-value">-1500</span></div>
            </div>
            <h1 style="color: #1e90ff;">Z轴移动</h1>
            <p>沿Z轴深度移动。负值远离观众,正值靠近观众。</p>
            <p>当前Z坐标: <strong>-1500</strong></p>
        </div>
        
        <!-- X轴旋转演示 -->
        <div class="step" id="step-rotate-x"
             data-x="2000" data-y="0" data-z="0"
             data-rotate-x="45" data-rotate-y="0" data-rotate-z="0">
            <div class="coordinate-system">
                <div class="axis axis-x">绕X轴旋转</div>
                <div class="coordinate-display">rotateX: <span id="rx-value">45°</span></div>
            </div>
            <h1 style="color: #ff4757;">X轴旋转</h1>
            <p>绕X轴旋转元素。正值向下旋转,负值向上旋转。</p>
            <p>当前旋转角度: <strong>45°</strong></p>
        </div>
        
        <!-- Y轴旋转演示 -->
        <div class="step" id="step-rotate-y"
             data-x="0" data-y="-1500" data-z="0"
             data-rotate-x="0" data-rotate-y="60" data-rotate-z="0">
            <div class="coordinate-system">
                <div class="axis axis-y">绕Y轴旋转</div>
                <div class="coordinate-display">rotateY: <span id="ry-value">60°</span></div>
            </div>
            <h1 style="color: #2ed573;">Y轴旋转</h1>
            <p>绕Y轴旋转元素。正值向右旋转,负值向左旋转。</p>
            <p>当前旋转角度: <strong>60°</strong></p>
        </div>
        
        <!-- Z轴旋转演示 -->
        <div class="step" id="step-rotate-z"
             data-x="0" data-y="0" data-z="1500"
             data-rotate-x="0" data-rotate-y="0" data-rotate-z="30">
            <div class="coordinate-system">
                <div class="axis axis-z">绕Z轴旋转</div>
                <div class="coordinate-display">rotateZ: <span id="rz-value">30°</span></div>
            </div>
            <h1 style="color: #1e90ff;">Z轴旋转</h1>
            <p>绕Z轴旋转元素。正值顺时针旋转,负值逆时针旋转。</p>
            <p>当前旋转角度: <strong>30°</strong></p>
        </div>
        
        <!-- 缩放演示 -->
        <div class="step" id="step-scale"
             data-x="2000" data-y="1500" data-z="0"
             data-scale="2" data-rotate-x="0" data-rotate-y="0" data-rotate-z="0">
            <div class="coordinate-system">
                <div class="axis" style="background: #ffa502;">缩放变换</div>
                <div class="coordinate-display">scale: <span id="scale-value">2</span></div>
            </div>
            <h1 style="color: #ffa502;">缩放变换</h1>
            <p>缩放元素大小。1为原始大小,大于1放大,小于1缩小。</p>
            <p>当前缩放比例: <strong>2</strong></p>
        </div>
        
        <!-- 组合变换演示 -->
        <div class="step" id="step-combined"
             data-x="0" data-y="1500" data-z="1500"
             data-rotate-x="20" data-rotate-y="45" data-rotate-z="10"
             data-scale="1.5">
            <div class="coordinate-system">
                <div class="axis" style="background: #3742fa;">组合变换</div>
                <div class="coordinate-display">X: 0, Y: 1500, Z: 1500</div>
                <div class="coordinate-display">rotateX: 20°, rotateY: 45°, rotateZ: 10°</div>
                <div class="coordinate-display">scale: 1.5</div>
            </div>
            <h1 style="color: #3742fa;">组合变换</h1>
            <p>同时应用多个3D变换,创建复杂的空间效果。</p>
            <p>这是所有变换的组合展示。</p>
        </div>
        
    </div>
    
    <!-- 控制按钮 -->
    <div class="controls">
        <button class="control-btn" onclick="navigateTo('step-x')">X轴</button>
        <button class="control-btn" onclick="navigateTo('step-y')">Y轴</button>
        <button class="control-btn" onclick="navigateTo('step-z')">Z轴</button>
        <button class="control-btn" onclick="navigateTo('step-rotate-x')">旋转X</button>
        <button class="control-btn" onclick="navigateTo('step-rotate-y')">旋转Y</button>
        <button class="control-btn" onclick="navigateTo('step-rotate-z')">旋转Z</button>
        <button class="control-btn" onclick="navigateTo('step-scale')">缩放</button>
        <button class="control-btn" onclick="navigateTo('step-combined')">组合</button>
    </div>
    
    <script>
        // 初始化Impress.js
        impress().init();
        
        // 导航函数
        function navigateTo(stepId) {
            const step = document.getElementById(stepId);
            if (step) {
                impress().goto(step);
            }
        }
        
        // 更新坐标显示
        function updateCoordinateDisplay() {
            const currentStep = document.querySelector('.present');
            if (!currentStep) return;
            
            // 获取当前步骤的变换属性
            const x = currentStep.dataset.x || '0';
            const y = currentStep.dataset.y || '0';
            const z = currentStep.dataset.z || '0';
            const rotateX = currentStep.dataset.rotateX || '0';
            const rotateY = currentStep.dataset.rotateY || '0';
            const rotateZ = currentStep.dataset.rotateZ || '0';
            const scale = currentStep.dataset.scale || '1';
            
            // 更新显示
            const xElement = document.getElementById('x-value');
            const yElement = document.getElementById('y-value');
            const zElement = document.getElementById('z-value');
            const rxElement = document.getElementById('rx-value');
            const ryElement = document.getElementById('ry-value');
            const rzElement = document.getElementById('rz-value');
            const scaleElement = document.getElementById('scale-value');
            
            if (xElement) xElement.textContent = x;
            if (yElement) yElement.textContent = y;
            if (zElement) zElement.textContent = z;
            if (rxElement) rxElement.textContent = rotateX + '°';
            if (ryElement) ryElement.textContent = rotateY + '°';
            if (rzElement) rzElement.textContent = rotateZ + '°';
            if (scaleElement) scaleElement.textContent = scale;
        }
        
        // 监听步骤切换事件
        document.addEventListener('impress:stepenter', updateCoordinateDisplay);
        
        // 初始更新
        setTimeout(updateCoordinateDisplay, 100);
    </script>
</body>
</html>

3.2 高级3D布局模式

掌握了基础3D变换后,我们可以创建更复杂的3D布局模式:

javascript 复制代码
/**
 * 3D布局模式库
 * 提供多种预定义的3D布局模式
 */
class ThreeDLayouts {
    constructor() {
        this.layouts = {
            cube: this.cubeLayout.bind(this),
            sphere: this.sphereLayout.bind(this),
            helix: this.helixLayout.bind(this),
            grid3d: this.grid3dLayout.bind(this),
            carousel: this.carouselLayout.bind(this)
        };
    }
    
    /**
     * 立方体布局
     * @param {HTMLElement[]} steps - 步骤元素数组
     * @param {number} size - 立方体边长
     */
    cubeLayout(steps, size = 2000) {
        const faces = 6; // 立方体6个面
        const stepsPerFace = Math.ceil(steps.length / faces);
        
        steps.forEach((step, index) => {
            const faceIndex = Math.floor(index / stepsPerFace);
            const stepInFace = index % stepsPerFace;
            
            let x, y, z, rotateX = 0, rotateY = 0;
            
            // 根据面分配位置
            switch (faceIndex) {
                case 0: // 前面
                    x = (stepInFace - stepsPerFace/2) * (size/2);
                    y = 0;
                    z = size/2;
                    rotateY = 0;
                    break;
                    
                case 1: // 后面
                    x = (stepInFace - stepsPerFace/2) * (size/2);
                    y = 0;
                    z = -size/2;
                    rotateY = 180;
                    break;
                    
                case 2: // 右面
                    x = size/2;
                    y = (stepInFace - stepsPerFace/2) * (size/2);
                    z = 0;
                    rotateY = 90;
                    break;
                    
                case 3: // 左面
                    x = -size/2;
                    y = (stepInFace - stepsPerFace/2) * (size/2);
                    z = 0;
                    rotateY = -90;
                    break;
                    
                case 4: // 上面
                    x = (stepInFace - stepsPerFace/2) * (size/2);
                    y = -size/2;
                    z = 0;
                    rotateX = 90;
                    break;
                    
                case 5: // 下面
                    x = (stepInFace - stepsPerFace/2) * (size/2);
                    y = size/2;
                    z = 0;
                    rotateX = -90;
                    break;
            }
            
            this.applyTransform(step, { x, y, z, rotateX, rotateY });
        });
    }
    
    /**
     * 球面布局
     * @param {HTMLElement[]} steps - 步骤元素数组
     * @param {number} radius - 球体半径
     */
    sphereLayout(steps, radius = 3000) {
        const phi = Math.PI * (3 - Math.sqrt(5)); // 黄金角度
        
        steps.forEach((step, index) => {
            const y = 1 - (index / (steps.length - 1)) * 2; // y从1到-1
            const radiusAtY = Math.sqrt(1 - y * y) * radius;
            const theta = phi * index;
            
            const x = Math.cos(theta) * radiusAtY;
            const z = Math.sin(theta) * radiusAtY;
            const yPos = y * radius;
            
            // 计算朝向中心点的旋转
            const rotateY = Math.atan2(x, z) * 180 / Math.PI;
            const rotateX = -Math.asin(y) * 180 / Math.PI;
            
            this.applyTransform(step, {
                x,
                y: yPos,
                z,
                rotateX,
                rotateY,
                rotateZ: 0
            });
        });
    }
    
    /**
     * 螺旋布局
     * @param {HTMLElement[]} steps - 步骤元素数组
     * @param {number} radius - 螺旋半径
     * @param {number} height - 螺旋高度
     * @param {number} turns - 螺旋圈数
     */
    helixLayout(steps, radius = 1500, height = 3000, turns = 3) {
        steps.forEach((step, index) => {
            const progress = index / (steps.length - 1);
            const angle = turns * 2 * Math.PI * progress;
            const currentHeight = height * progress;
            
            const x = Math.cos(angle) * radius;
            const z = Math.sin(angle) * radius;
            const y = currentHeight - height / 2;
            
            // 使元素朝向螺旋切线方向
            const rotateY = (angle * 180 / Math.PI) - 90;
            const rotateX = 20; // 稍微倾斜
            
            this.applyTransform(step, {
                x,
                y,
                z,
                rotateX,
                rotateY,
                rotateZ: 0
            });
        });
    }
    
    /**
     * 3D网格布局
     * @param {HTMLElement[]} steps - 步骤元素数组
     * @param {number} cols - 列数
     * @param {number} rows - 行数
     * @param {number} layers - 层数
     * @param {number} spacing - 间距
     */
    grid3dLayout(steps, cols = 3, rows = 3, layers = 3, spacing = 1000) {
        const totalPositions = cols * rows * layers;
        
        steps.forEach((step, index) => {
            if (index >= totalPositions) return;
            
            const layer = Math.floor(index / (cols * rows));
            const row = Math.floor((index % (cols * rows)) / cols);
            const col = index % cols;
            
            const x = (col - (cols - 1) / 2) * spacing;
            const y = (row - (rows - 1) / 2) * spacing;
            const z = (layer - (layers - 1) / 2) * spacing;
            
            // 根据位置添加轻微旋转
            const rotateY = (col - (cols - 1) / 2) * 5;
            const rotateX = (row - (rows - 1) / 2) * 3;
            
            this.applyTransform(step, {
                x,
                y,
                z,
                rotateX,
                rotateY,
                rotateZ: 0
            });
        });
    }
    
    /**
     * 旋转木马布局
     * @param {HTMLElement[]} steps - 步骤元素数组
     * @param {number} radius - 半径
     * @param {number} tilt - 倾斜角度
     */
    carouselLayout(steps, radius = 2000, tilt = 30) {
        const angleStep = (2 * Math.PI) / steps.length;
        
        steps.forEach((step, index) => {
            const angle = index * angleStep;
            
            const x = Math.cos(angle) * radius;
            const z = Math.sin(angle) * radius;
            const y = Math.sin(angle) * radius * Math.sin(tilt * Math.PI / 180);
            
            // 始终朝向中心
            const rotateY = (angle * 180 / Math.PI) - 90;
            // 根据位置调整倾斜
            const rotateX = tilt * Math.cos(angle);
            
            this.applyTransform(step, {
                x,
                y,
                z,
                rotateX,
                rotateY,
                rotateZ: 0
            });
        });
    }
    
    /**
     * 应用变换到元素
     */
    applyTransform(element, transform) {
        element.dataset.x = transform.x || 0;
        element.dataset.y = transform.y || 0;
        element.dataset.z = transform.z || 0;
        
        if (transform.rotateX !== undefined) {
            element.dataset.rotateX = transform.rotateX;
        }
        if (transform.rotateY !== undefined) {
            element.dataset.rotateY = transform.rotateY;
        }
        if (transform.rotateZ !== undefined) {
            element.dataset.rotateZ = transform.rotateZ;
        }
        if (transform.scale !== undefined) {
            element.dataset.scale = transform.scale;
        }
        
        // 添加CSS类用于标识布局类型
        element.classList.add('3d-layout');
    }
    
    /**
     * 应用布局并重新初始化Impress.js
     */
    applyLayout(layoutName, steps, options = {}) {
        const layoutFunction = this.layouts[layoutName];
        if (!layoutFunction) {
            console.error(`布局 ${layoutName} 不存在`);
            return;
        }
        
        const stepElements = Array.isArray(steps) ? 
            steps : Array.from(document.querySelectorAll('.step'));
        
        layoutFunction(stepElements, options);
        
        // 重新初始化Impress.js
        if (window.impress) {
            window.impress().init();
        }
    }
    
    /**
     * 创建布局选择器UI
     */
    createLayoutSelector() {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 15px;
            border-radius: 10px;
            z-index: 1000;
            font-family: Arial, sans-serif;
        `;
        
        const title = document.createElement('div');
        title.textContent = '3D布局选择器';
        title.style.fontWeight = 'bold';
        title.style.marginBottom = '10px';
        container.appendChild(title);
        
        const layouts = ['cube', 'sphere', 'helix', 'grid3d', 'carousel'];
        
        layouts.forEach(layout => {
            const button = document.createElement('button');
            button.textContent = this.getLayoutName(layout);
            button.style.cssText = `
                display: block;
                width: 100%;
                margin: 5px 0;
                padding: 8px 12px;
                background: #3498db;
                color: white;
                border: none;
                border-radius: 5px;
                cursor: pointer;
                transition: background 0.3s;
            `;
            
            button.onmouseover = () => button.style.background = '#2980b9';
            button.onmouseout = () => button.style.background = '#3498db';
            
            button.onclick = () => {
                this.applyLayout(layout, null, { 
                    size: 2000,
                    radius: 2500,
                    cols: 4,
                    rows: 3,
                    layers: 2
                });
            };
            
            container.appendChild(button);
        });
        
        document.body.appendChild(container);
    }
    
    /**
     * 获取布局的中文名称
     */
    getLayoutName(layout) {
        const names = {
            cube: '立方体布局',
            sphere: '球面布局',
            helix: '螺旋布局',
            grid3d: '3D网格布局',
            carousel: '旋转木马布局'
        };
        
        return names[layout] || layout;
    }
}

// 使用示例
document.addEventListener('DOMContentLoaded', () => {
    const layoutManager = new ThreeDLayouts();
    
    // 创建布局选择器
    layoutManager.createLayoutSelector();
    
    // 默认应用球面布局
    layoutManager.applyLayout('sphere', null, { radius: 2500 });
});

3.3 3D场景管理

对于复杂的3D演示,我们需要一个场景管理系统来管理多个3D对象和动画:

javascript 复制代码
/**
 * 3D场景管理系统
 * 管理多个3D对象、相机、灯光和动画
 */
class ThreeDSceneManager {
    constructor(sceneId = 'impress') {
        this.scene = document.getElementById(sceneId);
        this.objects = new Map();
        this.animations = new Map();
        this.camera = {
            x: 0, y: 0, z: 0,
            rotateX: 0, rotateY: 0, rotateZ: 0,
            fov: 1000
        };
        
        this.init();
    }
    
    init() {
        // 设置场景样式
        this.scene.style.perspective = `${this.camera.fov}px`;
        this.scene.style.transformStyle = 'preserve-3d';
        
        // 初始化所有对象
        this.scene.querySelectorAll('.scene-object').forEach(object => {
            this.addObject(object);
        });
        
        // 开始动画循环
        this.animationLoop();
    }
    
    /**
     * 添加3D对象到场景
     */
    addObject(element, options = {}) {
        const id = element.id || `obj_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        
        const object = {
            element,
            id,
            type: options.type || 'static',
            position: {
                x: parseFloat(element.dataset.x) || 0,
                y: parseFloat(element.dataset.y) || 0,
                z: parseFloat(element.dataset.z) || 0
            },
            rotation: {
                x: parseFloat(element.dataset.rotateX) || 0,
                y: parseFloat(element.dataset.rotateY) || 0,
                z: parseFloat(element.dataset.rotateZ) || 0
            },
            scale: parseFloat(element.dataset.scale) || 1,
            velocity: options.velocity || { x: 0, y: 0, z: 0 },
            angularVelocity: options.angularVelocity || { x: 0, y: 0, z: 0 },
            animations: new Set(),
            ...options
        };
        
        this.objects.set(id, object);
        this.updateObjectTransform(object);
        
        return id;
    }
    
    /**
     * 更新对象变换
     */
    updateObjectTransform(object) {
        const { position, rotation, scale } = object;
        
        // 应用变换
        object.element.style.transform = `
            translate3d(${position.x}px, ${position.y}px, ${position.z}px)
            rotateX(${rotation.x}deg)
            rotateY(${rotation.y}deg)
            rotateZ(${rotation.z}deg)
            scale(${scale})
        `;
        
        // 更新data属性(保持与Impress.js兼容)
        object.element.dataset.x = position.x;
        object.element.dataset.y = position.y;
        object.element.dataset.z = position.z;
        object.element.dataset.rotateX = rotation.x;
        object.element.dataset.rotateY = rotation.y;
        object.element.dataset.rotateZ = rotation.z;
        object.element.dataset.scale = scale;
    }
    
    /**
     * 添加动画
     */
    addAnimation(objectId, animation) {
        const object = this.objects.get(objectId);
        if (!object) return;
        
        animation.id = animation.id || `anim_${Date.now()}`;
        animation.startTime = Date.now();
        animation.objectId = objectId;
        
        object.animations.add(animation.id);
        this.animations.set(animation.id, animation);
        
        return animation.id;
    }
    
    /**
     * 移除动画
     */
    removeAnimation(animationId) {
        const animation = this.animations.get(animationId);
        if (!animation) return;
        
        const object = this.objects.get(animation.objectId);
        if (object) {
            object.animations.delete(animationId);
        }
        
        this.animations.delete(animationId);
    }
    
    /**
     * 创建平移动画
     */
    createTranslationAnimation(target, duration = 1000, easing = 'easeInOut') {
        return {
            type: 'translation',
            target,
            duration,
            easing,
            update: (progress, animation) => {
                const object = this.objects.get(animation.objectId);
                if (!object) return;
                
                // 计算插值
                const start = object.position;
                const end = animation.target;
                
                const ease = this.easingFunctions[animation.easing];
                const t = ease(progress);
                
                object.position = {
                    x: start.x + (end.x - start.x) * t,
                    y: start.y + (end.y - start.y) * t,
                    z: start.z + (end.z - start.z) * t
                };
                
                this.updateObjectTransform(object);
            }
        };
    }
    
    /**
     * 创建旋转动画
     */
    createRotationAnimation(target, duration = 1000, easing = 'easeInOut') {
        return {
            type: 'rotation',
            target,
            duration,
            easing,
            update: (progress, animation) => {
                const object = this.objects.get(animation.objectId);
                if (!object) return;
                
                const ease = this.easingFunctions[animation.easing];
                const t = ease(progress);
                
                object.rotation = {
                    x: object.rotation.x + (target.x - object.rotation.x) * t,
                    y: object.rotation.y + (target.y - object.rotation.y) * t,
                    z: object.rotation.z + (target.z - object.rotation.z) * t
                };
                
                this.updateObjectTransform(object);
            }
        };
    }
    
    /**
     * 缓动函数
     */
    easingFunctions = {
        linear: t => t,
        easeIn: t => t * t,
        easeOut: t => t * (2 - t),
        easeInOut: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
        easeInCubic: t => t * t * t,
        easeOutCubic: t => (--t) * t * t + 1,
        easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
    };
    
    /**
     * 动画循环
     */
    animationLoop() {
        const now = Date.now();
        
        // 更新所有动画
        this.animations.forEach((animation, id) => {
            const elapsed = now - animation.startTime;
            const progress = Math.min(elapsed / animation.duration, 1);
            
            if (animation.update) {
                animation.update(progress, animation);
            }
            
            // 动画完成
            if (progress >= 1) {
                this.removeAnimation(id);
            }
        });
        
        // 更新物理模拟(如果启用)
        this.updatePhysics();
        
        // 继续下一帧
        requestAnimationFrame(() => this.animationLoop());
    }
    
    /**
     * 物理模拟
     */
    updatePhysics() {
        this.objects.forEach(object => {
            if (object.type === 'dynamic') {
                // 应用速度
                object.position.x += object.velocity.x;
                object.position.y += object.velocity.y;
                object.position.z += object.velocity.z;
                
                // 应用角速度
                object.rotation.x += object.angularVelocity.x;
                object.rotation.y += object.angularVelocity.y;
                object.rotation.z += object.angularVelocity.z;
                
                // 更新变换
                this.updateObjectTransform(object);
            }
        });
    }
    
    /**
     * 设置相机位置
     */
    setCameraPosition(x, y, z) {
        this.camera.x = x;
        this.camera.y = y;
        this.camera.z = z;
        this.updateCamera();
    }
    
    /**
     * 设置相机旋转
     */
    setCameraRotation(x, y, z) {
        this.camera.rotateX = x;
        this.camera.rotateY = y;
        this.camera.rotateZ = z;
        this.updateCamera();
    }
    
    /**
     * 更新相机变换
     */
    updateCamera() {
        // 实际相机变换是通过移动整个场景实现的
        this.scene.style.transform = `
            translate3d(${-this.camera.x}px, ${-this.camera.y}px, ${-this.camera.z}px)
            rotateX(${this.camera.rotateX}deg)
            rotateY(${this.camera.rotateY}deg)
            rotateZ(${this.camera.rotateZ}deg)
        `;
    }
    
    /**
     * 创建轨道相机控制器
     */
    createOrbitControls(targetObjectId) {
        const target = this.objects.get(targetObjectId);
        if (!target) return;
        
        let isDragging = false;
        let lastX = 0;
        let lastY = 0;
        
        const onMouseDown = (e) => {
            isDragging = true;
            lastX = e.clientX;
            lastY = e.clientY;
            e.preventDefault();
        };
        
        const onMouseMove = (e) => {
            if (!isDragging) return;
            
            const deltaX = e.clientX - lastX;
            const deltaY = e.clientY - lastY;
            
            // 旋转相机
            this.camera.rotateY += deltaX * 0.5;
            this.camera.rotateX += deltaY * 0.5;
            
            // 限制X轴旋转角度
            this.camera.rotateX = Math.max(-90, Math.min(90, this.camera.rotateX));
            
            this.updateCamera();
            
            lastX = e.clientX;
            lastY = e.clientY;
        };
        
        const onMouseUp = () => {
            isDragging = false;
        };
        
        const onWheel = (e) => {
            // 滚轮缩放
            const zoomSpeed = 0.1;
            this.camera.z += e.deltaY * zoomSpeed;
            this.updateCamera();
            e.preventDefault();
        };
        
        // 添加事件监听器
        document.addEventListener('mousedown', onMouseDown);
        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);
        document.addEventListener('wheel', onWheel);
        
        // 返回清理函数
        return () => {
            document.removeEventListener('mousedown', onMouseDown);
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
            document.removeEventListener('wheel', onWheel);
        };
    }
}

// 使用示例:创建3D太阳系演示
document.addEventListener('DOMContentLoaded', () => {
    const sceneManager = new ThreeDSceneManager('impress');
    
    // 创建太阳
    const sun = document.createElement('div');
    sun.className = 'scene-object planet';
    sun.id = 'sun';
    sun.innerHTML = '<div class="planet-name">太阳</div>';
    sun.style.cssText = `
        width: 200px;
        height: 200px;
        border-radius: 50%;
        background: radial-gradient(circle at 30% 30%, #ffd700, #ff8c00);
        box-shadow: 0 0 100px #ff8c00;
    `;
    
    document.getElementById('impress').appendChild(sun);
    const sunId = sceneManager.addObject(sun, {
        type: 'static',
        position: { x: 0, y: 0, z: 0 }
    });
    
    // 创建地球
    const earth = document.createElement('div');
    earth.className = 'scene-object planet';
    earth.id = 'earth';
    earth.innerHTML = '<div class="planet-name">地球</div>';
    earth.style.cssText = `
        width: 80px;
        height: 80px;
        border-radius: 50%;
        background: radial-gradient(circle at 30% 30%, #6495ed, #1e90ff);
    `;
    
    document.getElementById('impress').appendChild(earth);
    const earthId = sceneManager.addObject(earth, {
        type: 'dynamic',
        position: { x: 1000, y: 0, z: 0 }
    });
    
    // 创建月球
    const moon = document.createElement('div');
    moon.className = 'scene-object planet';
    moon.id = 'moon';
    moon.innerHTML = '<div class="planet-name">月球</div>';
    moon.style.cssText = `
        width: 30px;
        height: 30px;
        border-radius: 50%;
        background: radial-gradient(circle at 30% 30%, #d3d3d3, #a9a9a9);
    `;
    
    document.getElementById('impress').appendChild(moon);
    const moonId = sceneManager.addObject(moon, {
        type: 'dynamic',
        position: { x: 1100, y: 0, z: 0 }
    });
    
    // 创建地球公转动画
    let earthAngle = 0;
    const earthOrbitRadius = 1000;
    const earthOrbitSpeed = 0.001;
    
    // 创建月球公转动画
    let moonAngle = 0;
    const moonOrbitRadius = 200;
    const moonOrbitSpeed = 0.005;
    

    // 动画更新函数
    function updateOrbits(timestamp) {
        // 地球公转
        earthAngle += earthOrbitSpeed;
        const earthObj = sceneManager.objects.get(earthId);
        if (earthObj) {
            earthObj.position.x = Math.cos(earthAngle) * earthOrbitRadius;
            earthObj.position.z = Math.sin(earthAngle) * earthOrbitRadius;
            sceneManager.updateObjectTransform(earthObj);
        }
        
        // 月球公转
        moonAngle += moonOrbitSpeed;
        const moonObj = sceneManager.objects.get(moonId);
        if (moonObj && earthObj) {
            // 月球围绕地球旋转
            moonObj.position.x = earthObj.position.x + Math.cos(moonAngle) * moonOrbitRadius;
            moonObj.position.z = earthObj.position.z + Math.sin(moonAngle) * moonOrbitRadius;
            sceneManager.updateObjectTransform(moonObj);
        }
        
        // 自转
        if (earthObj) {
            earthObj.rotation.y += 0.5;
            sceneManager.updateObjectTransform(earthObj);
        }
        
        if (moonObj) {
            moonObj.rotation.y += 0.3;
            sceneManager.updateObjectTransform(moonObj);
        }
        
        requestAnimationFrame(updateOrbits);
    }
    
    // 启动轨道动画
    updateOrbits();
    
    // 添加轨道控制器
    const cleanupOrbitControls = sceneManager.createOrbitControls(sunId);
    
    // 初始化Impress.js
    impress().init();
    
    // 集成Impress.js导航
    document.addEventListener('impress:stepenter', (event) => {
        const step = event.target;
        if (step.id === 'sun-step') {
            sceneManager.setCameraPosition(0, 0, 1000);
            sceneManager.setCameraRotation(0, 0, 0);
        } else if (step.id === 'earth-step') {
            sceneManager.setCameraPosition(1500, 500, 1500);
            sceneManager.setCameraRotation(-20, 45, 0);
        }
    });
});

第四章:模块化组件架构设计

4.1 组件注册与管理系统

现代Web应用需要组件化架构,Impress.js同样可以通过组件化提高代码复用性和可维护性:

javascript 复制代码
/**
 * Impress.js组件注册系统
 * 提供完整的组件生命周期管理
 */
class ImpressComponentRegistry {
    constructor() {
        this.components = new Map();
        this.instances = new Map();
        this.componentIdCounter = 0;
        this.eventBus = new EventTarget();
        
        // 组件生命周期事件
        this.lifecycleEvents = {
            BEFORE_INIT: 'impress:component:before-init',
            AFTER_INIT: 'impress:component:after-init',
            BEFORE_DESTROY: 'impress:component:before-destroy',
            AFTER_DESTROY: 'impress:component:after-destroy',
            UPDATE: 'impress:component:update'
        };
    }
    
    /**
     * 注册组件
     * @param {string} name - 组件名称
     * @param {class} ComponentClass - 组件类
     * @param {Object} options - 注册选项
     */
    register(name, ComponentClass, options = {}) {
        if (this.components.has(name)) {
            console.warn(`组件 ${name} 已存在,将被覆盖`);
        }
        
        this.components.set(name, {
            class: ComponentClass,
            options,
            metadata: {
                version: options.version || '1.0.0',
                author: options.author || 'unknown',
                dependencies: options.dependencies || [],
                description: options.description || ''
            }
        });
        
        console.log(`✅ 组件注册成功: ${name} v${options.version || '1.0.0'}`);
        return this;
    }
    
    /**
     * 初始化页面上的所有组件
     */
    initAll(context = document) {
        const componentElements = context.querySelectorAll('[data-component]');
        
        componentElements.forEach(element => {
            this.initComponent(element);
        });
        
        // 监听动态添加的组件
        this.setupMutationObserver(context);
        
        return this.instances.size;
    }
    
    /**
     * 初始化单个组件
     */
    initComponent(element, force = false) {
        const componentName = element.dataset.component;
        
        if (!componentName) {
            console.error('元素缺少 data-component 属性', element);
            return null;
        }
        
        // 检查是否已初始化
        if (element.dataset.componentId && !force) {
            console.warn(`组件已初始化: ${element.dataset.componentId}`);
            return this.instances.get(element.dataset.componentId);
        }
        
        const componentDef = this.components.get(componentName);
        if (!componentDef) {
            console.error(`组件未注册: ${componentName}`);
            return null;
        }
        
        // 生成组件ID
        const componentId = `component_${++this.componentIdCounter}`;
        element.dataset.componentId = componentId;
        
        // 解析配置
        const config = this.parseComponentConfig(element, componentDef);
        
        // 触发初始化前事件
        this.eventBus.dispatchEvent(new CustomEvent(
            this.lifecycleEvents.BEFORE_INIT,
            { detail: { element, componentName, config } }
        ));
        
        // 创建组件实例
        const instance = new componentDef.class(element, config);
        instance.id = componentId;
        instance.name = componentName;
        instance.config = config;
        
        // 调用组件初始化方法
        if (typeof instance.init === 'function') {
            instance.init();
        }
        
        // 存储实例
        this.instances.set(componentId, instance);
        
        // 触发初始化后事件
        this.eventBus.dispatchEvent(new CustomEvent(
            this.lifecycleEvents.AFTER_INIT,
            { detail: { instance, element, componentName, config } }
        ));
        
        console.log(`🎯 组件初始化成功: ${componentName} (#${componentId})`);
        return instance;
    }
    
    /**
     * 解析组件配置
     */
    parseComponentConfig(element, componentDef) {
        const config = { ...componentDef.options.defaults };
        
        // 从data-options属性解析JSON配置
        if (element.dataset.options) {
            try {
                const options = JSON.parse(element.dataset.options);
                Object.assign(config, options);
            } catch (error) {
                console.error('解析组件配置失败:', error);
            }
        }
        
        // 从data属性提取配置
        const dataAttrs = element.dataset;
        Object.keys(dataAttrs).forEach(key => {
            if (key.startsWith('config')) {
                const configKey = key.replace('config', '').replace(/^[A-Z]/, match => 
                    match.toLowerCase()
                );
                config[configKey] = dataAttrs[key];
            }
        });
        
        return config;
    }
    
    /**
     * 设置DOM变化观察器
     */
    setupMutationObserver(context) {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach(mutation => {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches('[data-component]')) {
                                this.initComponent(node);
                            }
                            // 检查子元素
                            node.querySelectorAll('[data-component]').forEach(element => {
                                this.initComponent(element);
                            });
                        }
                    });
                    
                    // 清理被移除的组件
                    mutation.removedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches('[data-component]') && node.dataset.componentId) {
                                this.destroyComponent(node.dataset.componentId);
                            }
                            node.querySelectorAll('[data-component]').forEach(element => {
                                if (element.dataset.componentId) {
                                    this.destroyComponent(element.dataset.componentId);
                                }
                            });
                        }
                    });
                }
            });
        });
        
        observer.observe(context, {
            childList: true,
            subtree: true
        });
        
        return observer;
    }
    
    /**
     * 销毁组件
     */
    destroyComponent(componentId) {
        const instance = this.instances.get(componentId);
        if (!instance) return false;
        
        const element = instance.element;
        
        // 触发销毁前事件
        this.eventBus.dispatchEvent(new CustomEvent(
            this.lifecycleEvents.BEFORE_DESTROY,
            { detail: { instance, element } }
        ));
        
        // 调用组件销毁方法
        if (typeof instance.destroy === 'function') {
            instance.destroy();
        }
        
        // 清理DOM引用
        if (element && element.dataset) {
            delete element.dataset.componentId;
        }
        
        // 移除实例
        this.instances.delete(componentId);
        
        // 触发销毁后事件
        this.eventBus.dispatchEvent(new CustomEvent(
            this.lifecycleEvents.AFTER_DESTROY,
            { detail: { componentId, element } }
        ));
        
        console.log(`🗑️ 组件销毁成功: #${componentId}`);
        return true;
    }
    
    /**
     * 获取组件实例
     */
    getInstance(componentId) {
        return this.instances.get(componentId);
    }
    
    /**
     * 按名称获取所有组件实例
     */
    getInstancesByName(name) {
        const instances = [];
        this.instances.forEach(instance => {
            if (instance.name === name) {
                instances.push(instance);
            }
        });
        return instances;
    }
    
    /**
     * 更新组件配置
     */
    updateComponent(componentId, newConfig) {
        const instance = this.instances.get(componentId);
        if (!instance) return false;
        
        const oldConfig = { ...instance.config };
        instance.config = { ...oldConfig, ...newConfig };
        
        // 调用组件更新方法
        if (typeof instance.update === 'function') {
            instance.update(instance.config, oldConfig);
        }
        
        // 触发更新事件
        this.eventBus.dispatchEvent(new CustomEvent(
            this.lifecycleEvents.UPDATE,
            { detail: { instance, oldConfig, newConfig } }
        ));
        
        return true;
    }
    
    /**
     * 添加事件监听
     */
    on(event, callback) {
        this.eventBus.addEventListener(event, callback);
        return this;
    }
    
    /**
     * 移除事件监听
     */
    off(event, callback) {
        this.eventBus.removeEventListener(event, callback);
        return this;
    }
    
    /**
     * 获取已注册组件列表
     */
    getRegisteredComponents() {
        const list = [];
        this.components.forEach((def, name) => {
            list.push({
                name,
                version: def.metadata.version,
                description: def.metadata.description,
                instanceCount: this.getInstancesByName(name).length
            });
        });
        return list;
    }
}

// 组件基类
class BaseComponent {
    constructor(element, config = {}) {
        this.element = element;
        this.config = config;
        this.initialized = false;
        this.eventHandlers = new Map();
    }
    
    init() {
        if (this.initialized) return;
        
        this.setup();
        this.bindEvents();
        this.initialized = true;
        
        return this;
    }
    
    setup() {
        // 子类重写此方法
    }
    
    bindEvents() {
        // 子类重写此方法
    }
    
    update(newConfig, oldConfig) {
        // 子类重写此方法
        this.config = { ...this.config, ...newConfig };
    }
    
    destroy() {
        // 清理事件监听
        this.unbindEvents();
        
        // 清理DOM引用
        this.element = null;
        this.config = null;
        this.initialized = false;
    }
    
    unbindEvents() {
        this.eventHandlers.forEach((handler, event) => {
            this.element.removeEventListener(event, handler);
        });
        this.eventHandlers.clear();
    }
    
    on(event, handler) {
        this.eventHandlers.set(event, handler);
        this.element.addEventListener(event, handler);
        return this;
    }
    
    emit(event, detail) {
        const customEvent = new CustomEvent(event, {
            detail,
            bubbles: true
        });
        this.element.dispatchEvent(customEvent);
        return this;
    }
    
    query(selector) {
        return this.element.querySelector(selector);
    }
    
    queryAll(selector) {
        return this.element.querySelectorAll(selector);
    }
    
    addClass(className) {
        this.element.classList.add(className);
        return this;
    }
    
    removeClass(className) {
        this.element.classList.remove(className);
        return this;
    }
    
    toggleClass(className) {
        this.element.classList.toggle(className);
        return this;
    }
}

4.2 实用组件库实现

基于组件系统,我们可以创建一系列实用的Impress.js组件:

javascript 复制代码
/**
 * 卡片组件
 * 可复用的卡片式内容容器
 */
class CardComponent extends BaseComponent {
    setup() {
        // 创建卡片结构
        this.element.innerHTML = `
            <div class="card-container ${this.config.theme || ''}">
                ${this.config.header ? `
                    <div class="card-header">
                        ${typeof this.config.header === 'string' ? 
                            `<h3>${this.config.header}</h3>` : 
                            this.config.header}
                    </div>
                ` : ''}
                
                <div class="card-body">
                    ${this.config.content || ''}
                </div>
                
                ${this.config.footer ? `
                    <div class="card-footer">
                        ${typeof this.config.footer === 'string' ? 
                            this.config.footer : ''}
                    </div>
                ` : ''}
            </div>
        `;
        
        // 应用样式
        this.applyStyles();
    }
    
    applyStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .card-container {
                width: 100%;
                height: 100%;
                border-radius: 16px;
                overflow: hidden;
                display: flex;
                flex-direction: column;
                background: white;
                box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
                transition: all 0.3s ease;
            }
            
            .card-container:hover {
                transform: translateY(-5px);
                box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
            }
            
            .card-header {
                padding: 20px 30px 0;
                border-bottom: 1px solid #eee;
            }
            
            .card-header h3 {
                margin: 0;
                color: #333;
                font-size: 1.5em;
            }
            
            .card-body {
                flex: 1;
                padding: 30px;
                overflow: auto;
            }
            
            .card-footer {
                padding: 20px 30px;
                border-top: 1px solid #eee;
                background: #f8f9fa;
            }
            
            /* 主题变体 */
            .card-container.primary {
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
            }
            
            .card-container.primary .card-header,
            .card-container.primary .card-footer {
                border-color: rgba(255, 255, 255, 0.2);
            }
            
            .card-container.dark {
                background: #2c3e50;
                color: white;
            }
        `;
        
        document.head.appendChild(style);
    }
    
    updateContent(content) {
        const body = this.query('.card-body');
        if (body) {
            body.innerHTML = content;
        }
        return this;
    }
    
    setHeader(header) {
        const headerEl = this.query('.card-header');
        if (headerEl) {
            headerEl.innerHTML = typeof header === 'string' ? 
                `<h3>${header}</h3>` : header;
        }
        return this;
    }
}

/**
 * 计数器组件
 * 带动画的数字计数器
 */
class CounterComponent extends BaseComponent {
    setup() {
        this.currentValue = this.config.start || 0;
        this.targetValue = this.config.end || 100;
        this.duration = this.config.duration || 2000;
        this.format = this.config.format || (n => n.toLocaleString());
        
        this.element.innerHTML = `
            <div class="counter-wrapper">
                <div class="counter-value">${this.format(this.currentValue)}</div>
                ${this.config.label ? `
                    <div class="counter-label">${this.config.label}</div>
                ` : ''}
            </div>
        `;
        
        this.valueElement = this.query('.counter-value');
        
        // 应用样式
        this.applyStyles();
    }
    
    bindEvents() {
        // 当进入包含此组件的步骤时开始计数
        const step = this.findParentStep();
        if (step) {
            this.onStepEnter = () => this.start();
            step.addEventListener('impress:stepenter', this.onStepEnter);
        }
    }
    
    unbindEvents() {
        const step = this.findParentStep();
        if (step && this.onStepEnter) {
            step.removeEventListener('impress:stepenter', this.onStepEnter);
        }
        super.unbindEvents();
    }
    
    findParentStep() {
        let element = this.element;
        while (element && !element.classList.contains('step')) {
            element = element.parentElement;
        }
        return element;
    }
    
    start() {
        if (this.animating) return;
        
        this.animating = true;
        const startTime = Date.now();
        
        const animate = () => {
            const elapsed = Date.now() - startTime;
            const progress = Math.min(elapsed / this.duration, 1);
            
            // 使用缓动函数
            const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
            const easedProgress = easeOutCubic(progress);
            
            this.currentValue = this.config.start + 
                (this.targetValue - this.config.start) * easedProgress;
            
            this.updateDisplay();
            
            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                this.animating = false;
                this.emit('counter:complete', { value: this.currentValue });
            }
        };
        
        requestAnimationFrame(animate);
    }
    
    updateDisplay() {
        if (this.valueElement) {
            this.valueElement.textContent = this.format(Math.round(this.currentValue));
        }
    }
    
    applyStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .counter-wrapper {
                text-align: center;
                padding: 40px;
            }
            
            .counter-value {
                font-size: 4em;
                font-weight: bold;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                margin-bottom: 10px;
            }
            
            .counter-label {
                font-size: 1.2em;
                color: #666;
                text-transform: uppercase;
                letter-spacing: 2px;
            }
        `;
        
        document.head.appendChild(style);
    }
}

/**
 * 图表容器组件
 * 集成第三方图表库
 */
class ChartComponent extends BaseComponent {
    setup() {
        this.chartInstance = null;
        this.data = this.config.data || [];
        
        this.element.innerHTML = `
            <div class="chart-wrapper">
                <div class="chart-container" style="width: 100%; height: 100%;"></div>
                ${this.config.title ? `
                    <div class="chart-title">${this.config.title}</div>
                ` : ''}
                ${this.config.legend !== false ? `
                    <div class="chart-legend"></div>
                ` : ''}
            </div>
        `;
        
        this.container = this.query('.chart-container');
        this.initChart();
    }
    
    async initChart() {
        const chartType = this.config.type || 'bar';
        
        switch (chartType) {
            case 'echarts':
                await this.initECharts();
                break;
            case 'chartjs':
                await this.initChartJS();
                break;
            default:
                await this.initSimpleChart();
        }
    }
    
    async initECharts() {
        if (typeof echarts === 'undefined') {
            await this.loadScript('https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js');
        }
        
        this.chartInstance = echarts.init(this.container);
        
        const option = {
            backgroundColor: 'transparent',
            tooltip: {
                trigger: 'axis',
                axisPointer: {
                    type: 'shadow'
                }
            },
            grid: {
                left: '3%',
                right: '4%',
                bottom: '3%',
                containLabel: true
            },
            xAxis: {
                type: 'category',
                data: this.data.map(item => item.label || item.x)
            },
            yAxis: {
                type: 'value'
            },
            series: [{
                data: this.data.map(item => item.value || item.y),
                type: this.config.chartType || 'bar',
                itemStyle: {
                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                        { offset: 0, color: '#83bff6' },
                        { offset: 0.5, color: '#188df0' },
                        { offset: 1, color: '#188df0' }
                    ])
                }
            }]
        };
        
        this.chartInstance.setOption(option);
        
        // 响应式调整
        window.addEventListener('resize', () => {
            this.chartInstance.resize();
        });
    }
    
    loadScript(url) {
        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = url;
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }
    
    update(data) {
        this.data = data;
        if (this.chartInstance) {
            // 更新图表数据
            this.chartInstance.setOption({
                series: [{
                    data: data.map(item => item.value || item.y)
                }]
            });
        }
    }
}

/**
 * 交互式时间线组件
 */
class TimelineComponent extends BaseComponent {
    setup() {
        this.events = this.config.events || [];
        this.currentIndex = 0;
        
        this.element.innerHTML = `
            <div class="timeline-container">
                <div class="timeline-track"></div>
                <div class="timeline-events"></div>
                <div class="timeline-controls">
                    <button class="timeline-prev">← 上一个</button>
                    <button class="timeline-next">下一个 →</button>
                </div>
            </div>
        `;
        
        this.track = this.query('.timeline-track');
        this.eventsContainer = this.query('.timeline-events');
        
        this.renderEvents();
        this.highlightCurrent();
    }
    
    bindEvents() {
        this.on('click', '.timeline-prev', () => this.prev());
        this.on('click', '.timeline-next', () => this.next());
        
        // 点击事件点
        this.on('click', '.timeline-event', (e) => {
            const index = parseInt(e.currentTarget.dataset.index);
            this.goTo(index);
        });
    }
    
    renderEvents() {
        this.eventsContainer.innerHTML = '';
        
        this.events.forEach((event, index) => {
            const eventEl = document.createElement('div');
            eventEl.className = 'timeline-event';
            eventEl.dataset.index = index;
            
            eventEl.innerHTML = `
                <div class="event-marker"></div>
                <div class="event-content">
                    <div class="event-date">${event.date}</div>
                    <div class="event-title">${event.title}</div>
                    ${event.description ? `
                        <div class="event-description">${event.description}</div>
                    ` : ''}
                </div>
            `;
            
            this.eventsContainer.appendChild(eventEl);
        });
    }
    
    highlightCurrent() {
        this.queryAll('.timeline-event').forEach((el, index) => {
            el.classList.toggle('active', index === this.currentIndex);
            el.classList.toggle('past', index < this.currentIndex);
        });
    }
    
    prev() {
        if (this.currentIndex > 0) {
            this.currentIndex--;
            this.highlightCurrent();
            this.emit('timeline:change', { 
                index: this.currentIndex, 
                event: this.events[this.currentIndex] 
            });
        }
    }
    
    next() {
        if (this.currentIndex < this.events.length - 1) {
            this.currentIndex++;
            this.highlightCurrent();
            this.emit('timeline:change', { 
                index: this.currentIndex, 
                event: this.events[this.currentIndex] 
            });
        }
    }
    
    goTo(index) {
        if (index >= 0 && index < this.events.length) {
            this.currentIndex = index;
            this.highlightCurrent();
            this.emit('timeline:change', { 
                index: this.currentIndex, 
                event: this.events[this.currentIndex] 
            });
        }
    }
}

4.3 组件使用示例

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Impress.js组件系统演示</title>
    <script src="https://cdn.jsdelivr.net/npm/impress.js@2.0.0/js/impress.min.js"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            min-height: 100vh;
            margin: 0;
        }
        
        .step {
            width: 900px;
            height: 700px;
            border-radius: 20px;
            background: white;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
        }
    </style>
</head>
<body>
    <div id="impress">
        
        <!-- 卡片组件演示 -->
        <div class="step" data-x="0" data-y="0" data-z="0">
            <h1 style="text-align: center; margin-bottom: 50px;">卡片组件系统</h1>
            
            <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px; padding: 20px;">
                <!-- 基础卡片 -->
                <div data-component="card"
                     data-options='{
                         "header": "基础卡片",
                         "content": "这是一个基础卡片组件,支持多种配置选项。",
                         "theme": ""
                     }'>
                </div>
                
                <!-- 主题卡片 -->
                <div data-component="card"
                     data-options='{
                         "header": "主题卡片",
                         "content": "使用渐变色主题的卡片。",
                         "theme": "primary"
                     }'>
                </div>
                
                <!-- 计数器卡片 -->
                <div data-component="card"
                     data-options='{
                         "header": "数据统计",
                         "content": "<div data-component=\"counter\" data-options=\'{\"start\": 0, \"end\": 1000, \"duration\": 3000, \"label\": \"用户数量\"}\'></div>"
                     }'>
                </div>
                
                <!-- 图表卡片 -->
                <div data-component="card"
                     data-options='{
                         "header": "销售数据",
                         "content": "<div data-component=\"chart\" data-options=\'{\"type\": \"echarts\", \"data\": [{\"label\": \"Q1\", \"value\": 100}, {\"label\": \"Q2\", \"value\": 200}, {\"label\": \"Q3\", \"value\": 150}, {\"label\": \"Q4\", \"value\": 300}]}\'></div>"
                     }'>
                </div>
            </div>
        </div>
        
        <!-- 时间线组件演示 -->
        <div class="step" data-x="1500" data-y="0" data-z="0">
            <h1 style="text-align: center; margin-bottom: 50px;">项目时间线</h1>
            
            <div data-component="timeline"
                 style="height: 80%;"
                 data-options='{
                     "events": [
                         {
                             "date": "2024-01",
                             "title": "项目启动",
                             "description": "项目正式启动,组建核心团队"
                         },
                         {
                             "date": "2024-03",
                             "title": "需求分析",
                             "description": "完成需求调研和分析"
                         },
                         {
                             "date": "2024-06",
                             "title": "开发阶段",
                             "description": "核心功能开发完成"
                         },
                         {
                             "date": "2024-09",
                             "title": "测试阶段",
                             "description": "全面测试和bug修复"
                         },
                         {
                             "date": "2024-12",
                             "title": "正式上线",
                             "description": "项目正式发布上线"
                         }
                     ]
                 }'>
            </div>
        </div>
        
    </div>
    
    <script>
        // 创建组件注册器
        const registry = new ImpressComponentRegistry();
        
        // 注册组件
        registry.register('card', CardComponent, {
            version: '1.0.0',
            description: '卡片容器组件',
            defaults: {
                theme: '',
                header: '',
                content: '',
                footer: ''
            }
        });
        
        registry.register('counter', CounterComponent, {
            version: '1.0.0',
            description: '数字计数器组件',
            defaults: {
                start: 0,
                end: 100,
                duration: 2000,
                label: ''
            }
        });
        
        registry.register('chart', ChartComponent, {
            version: '1.0.0',
            description: '图表组件',
            defaults: {
                type: 'echarts',
                data: [],
                title: ''
            }
        });
        
        registry.register('timeline', TimelineComponent, {
            version: '1.0.0',
            description: '时间线组件',
            defaults: {
                events: []
            }
        });
        
        // 初始化
        document.addEventListener('DOMContentLoaded', () => {
            // 初始化Impress.js
            impress().init();
            
            // 初始化组件
            registry.initAll();
            
            // 监听组件事件
            registry.on('impress:component:after-init', (event) => {
                console.log('组件初始化完成:', event.detail.componentName);
            });
            
            // 监听计数器完成事件
            document.addEventListener('counter:complete', (event) => {
                console.log('计数完成:', event.detail.value);
            });
            
            // 监听时间线变化
            document.addEventListener('timeline:change', (event) => {
                console.log('时间线切换到:', event.detail.index, event.detail.event);
            });
            
            // 在控制台查看组件状态
            console.log('已注册组件:', registry.getRegisteredComponents());
        });
    </script>
</body>
</html>

第五章:插件生态系统设计

5.1 插件架构与生命周期管理

Impress.js的强大之处在于其可扩展性,通过插件系统可以轻松添加新功能:

javascript 复制代码
/**
 * Impress.js插件管理器
 * 提供完整的插件生命周期管理
 */
class ImpressPluginManager {
    constructor(impressApi) {
        this.impress = impressApi;
        this.plugins = new Map();
        this.pluginOrder = [];
        this.eventHandlers = new Map();
        
        // 插件生命周期状态
        this.states = {
            REGISTERED: 'registered',
            INITIALIZED: 'initialized',
            ACTIVE: 'active',
            DISABLED: 'disabled',
            DESTROYED: 'destroyed'
        };
        
        this.init();
    }
    
    init() {
        // 监听Impress.js事件
        this.setupImpressEvents();
        
        // 自动加载插件
        this.autoLoadPlugins();
    }
    
    /**
     * 注册插件
     */
    register(plugin, options = {}) {
        const pluginId = options.id || plugin.name || `plugin_${Date.now()}`;
        
        if (this.plugins.has(pluginId)) {
            console.warn(`插件 ${pluginId} 已存在,将被覆盖`);
        }
        
        const pluginInfo = {
            id: pluginId,
            instance: null,
            config: {
                enabled: options.enabled !== false,
                priority: options.priority || 0,
                dependencies: options.dependencies || [],
                ...options.config
            },
            state: this.states.REGISTERED,
            metadata: {
                name: plugin.name || pluginId,
                version: options.version || '1.0.0',
                description: options.description || '',
                author: options.author || 'unknown'
            }
        };
        
        this.plugins.set(pluginId, pluginInfo);
        
        // 按优先级排序
        this.pluginOrder = Array.from(this.plugins.values())
            .sort((a, b) => b.config.priority - a.config.priority)
            .map(p => p.id);
        
        console.log(`✅ 插件注册成功: ${pluginInfo.metadata.name} v${pluginInfo.metadata.version}`);
        
        // 如果启用,立即初始化
        if (pluginInfo.config.enabled) {
            this.initializePlugin(pluginId);
        }
        
        return pluginId;
    }
    
    /**
     * 初始化插件
     */
    initializePlugin(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo || pluginInfo.state !== this.states.REGISTERED) {
            return false;
        }
        
        // 检查依赖
        if (!this.checkDependencies(pluginId)) {
            console.warn(`插件 ${pluginId} 依赖未满足,延迟初始化`);
            return false;
        }
        
        try {
            // 创建插件实例
            const PluginClass = this.getPluginClass(pluginId);
            pluginInfo.instance = new PluginClass(this.impress, pluginInfo.config);
            
            // 调用初始化方法
            if (typeof pluginInfo.instance.init === 'function') {
                pluginInfo.instance.init();
            }
            
            pluginInfo.state = this.states.INITIALIZED;
            
            // 激活插件
            this.activatePlugin(pluginId);
            
            console.log(`🎯 插件初始化成功: ${pluginInfo.metadata.name}`);
            return true;
            
        } catch (error) {
            console.error(`插件初始化失败: ${pluginId}`, error);
            pluginInfo.state = this.states.DISABLED;
            return false;
        }
    }
    
    /**
     * 激活插件
     */
    activatePlugin(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo || pluginInfo.state !== this.states.INITIALIZED) {
            return false;
        }
        
        // 调用激活方法
        if (typeof pluginInfo.instance.activate === 'function') {
            pluginInfo.instance.activate();
        }
        
        // 注册事件处理器
        this.registerEventHandlers(pluginId);
        
        pluginInfo.state = this.states.ACTIVE;
        console.log(`🚀 插件已激活: ${pluginInfo.metadata.name}`);
        
        return true;
    }
    
    /**
     * 禁用插件
     */
    disablePlugin(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo || pluginInfo.state !== this.states.ACTIVE) {
            return false;
        }
        
        // 调用停用方法
        if (typeof pluginInfo.instance.deactivate === 'function') {
            pluginInfo.instance.deactivate();
        }
        
        // 移除事件处理器
        this.unregisterEventHandlers(pluginId);
        
        pluginInfo.state = this.states.DISABLED;
        console.log(`⏸️ 插件已禁用: ${pluginInfo.metadata.name}`);
        
        return true;
    }
    
    /**
     * 销毁插件
     */
    destroyPlugin(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo) return false;
        
        // 如果激活状态,先停用
        if (pluginInfo.state === this.states.ACTIVE) {
            this.disablePlugin(pluginId);
        }
        
        // 调用销毁方法
        if (pluginInfo.instance && typeof pluginInfo.instance.destroy === 'function') {
            pluginInfo.instance.destroy();
        }
        
        // 清理资源
        this.unregisterEventHandlers(pluginId);
        
        pluginInfo.instance = null;
        pluginInfo.state = this.states.DESTROYED;
        
        this.plugins.delete(pluginId);
        this.pluginOrder = this.pluginOrder.filter(id => id !== pluginId);
        
        console.log(`🗑️ 插件已销毁: ${pluginInfo.metadata.name}`);
        return true;
    }
    
    /**
     * 检查插件依赖
     */
    checkDependencies(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo) return false;
        
        const dependencies = pluginInfo.config.dependencies;
        
        for (const dep of dependencies) {
            const depPlugin = this.plugins.get(dep);
            if (!depPlugin || depPlugin.state !== this.states.ACTIVE) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * 注册事件处理器
     */
    registerEventHandlers(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo || !pluginInfo.instance) return;
        
        const eventMap = pluginInfo.instance.events || {};
        
        Object.entries(eventMap).forEach(([event, handler]) => {
            const wrappedHandler = (e) => {
                handler.call(pluginInfo.instance, e, this.impress, pluginInfo.config);
            };
            
            if (!this.eventHandlers.has(pluginId)) {
                this.eventHandlers.set(pluginId, new Map());
            }
            
            this.eventHandlers.get(pluginId).set(event, wrappedHandler);
            document.addEventListener(event, wrappedHandler);
        });
    }
    
    /**
     * 移除事件处理器
     */
    unregisterEventHandlers(pluginId) {
        const handlers = this.eventHandlers.get(pluginId);
        if (!handlers) return;
        
        handlers.forEach((handler, event) => {
            document.removeEventListener(event, handler);
        });
        
        this.eventHandlers.delete(pluginId);
    }
    
    /**
     * 获取插件类
     */
    getPluginClass(pluginId) {
        // 从全局对象或模块中获取
        const pluginInfo = this.plugins.get(pluginId);
        
        if (typeof pluginInfo.instance === 'function') {
            return pluginInfo.instance;
        }
        
        // 尝试从注册的插件对象中获取
        if (window.ImpressPlugins && window.ImpressPlugins[pluginId]) {
            return window.ImpressPlugins[pluginId];
        }
        
        throw new Error(`找不到插件类: ${pluginId}`);
    }
    
    /**
     * 设置Impress.js事件监听
     */
    setupImpressEvents() {
        const events = [
            'impress:init',
            'impress:stepenter',
            'impress:stepleave',
            'impress:steprefresh'
        ];
        
        events.forEach(event => {
            document.addEventListener(event, (e) => {
                this.dispatchToPlugins(event, e);
            });
        });
    }
    
    /**
     * 分发事件到所有插件
     */
    dispatchToPlugins(event, data) {
        this.pluginOrder.forEach(pluginId => {
            const pluginInfo = this.plugins.get(pluginId);
            
            if (pluginInfo.state === this.states.ACTIVE && 
                pluginInfo.instance && 
                typeof pluginInfo.instance.onEvent === 'function') {
                
                try {
                    pluginInfo.instance.onEvent(event, data, this.impress);
                } catch (error) {
                    console.error(`插件 ${pluginId} 处理事件 ${event} 时出错:`, error);
                }
            }
        });
    }
    
    /**
     * 自动加载插件
     */
    autoLoadPlugins() {
        // 从data-plugins属性加载
        const impressElement = document.getElementById('impress');
        if (impressElement && impressElement.dataset.plugins) {
            const pluginNames = impressElement.dataset.plugins.split(',').map(p => p.trim());
            
            pluginNames.forEach(pluginName => {
                this.loadPlugin(pluginName);
            });
        }
    }
    
    /**
     * 动态加载插件
     */
    async loadPlugin(pluginName, options = {}) {
        // 检查是否已注册
        if (this.plugins.has(pluginName)) {
            console.log(`插件 ${pluginName} 已加载`);
            return this.plugins.get(pluginName);
        }
        
        // 动态加载插件脚本
        if (options.url) {
            await this.loadScript(options.url);
        }
        
        // 查找全局插件
        if (window.ImpressPlugins && window.ImpressPlugins[pluginName]) {
            return this.register(window.ImpressPlugins[pluginName], {
                id: pluginName,
                ...options
            });
        }
        
        console.error(`找不到插件: ${pluginName}`);
        return null;
    }
    
    /**
     * 获取插件状态
     */
    getPluginStatus(pluginId) {
        const pluginInfo = this.plugins.get(pluginId);
        if (!pluginInfo) return null;
        
        return {
            id: pluginInfo.id,
            name: pluginInfo.metadata.name,
            version: pluginInfo.metadata.version,
            state: pluginInfo.state,
            config: pluginInfo.config
        };
    }
    
    /**
     * 获取所有插件状态
     */
    getAllPluginStatus() {
        const status = [];
        this.plugins.forEach(pluginInfo => {
            status.push(this.getPluginStatus(pluginInfo.id));
        });
        return status;
    }
}

5.2 核心插件实现

javascript 复制代码
/**
 * 导航插件
 * 增强导航功能和用户界面
 */
class NavigationPlugin {
    constructor(impress, config) {
        this.impress = impress;
        this.config = {
            showControls: true,
            showProgress: true,
            keyboardShortcuts: true,
            touchGestures: true,
            ...config
        };
        
        this.controls = null;
        this.progressBar = null;
        this.currentStep = 0;
        this.totalSteps = 0;
    }
    
    init() {
        this.totalSteps = document.querySelectorAll('.step').length;
        
        if (this.config.showControls) {
            this.createNavigationControls();
        }
        
        if (this.config.showProgress) {
            this.createProgressBar();
        }
        
        if (this.config.keyboardShortcuts) {
            this.setupKeyboardShortcuts();
        }
        
        if (this.config.touchGestures) {
            this.setupTouchGestures();
        }
    }
    
    activate() {
        this.updateNavigationState();
        
        // 监听步骤变化
        document.addEventListener('impress:stepenter', (e) => {
            this.updateNavigationState();
        });
    }
    
    createNavigationControls() {
        this.controls = document.createElement('div');
        this.controls.className = 'impress-navigation-controls';
        this.controls.innerHTML = `
            <button class="nav-btn prev" title="上一页 (←)">
                <svg width="24" height="24" viewBox="0 0 24 24">
                    <path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/>
                </svg>
            </button>
            
            <button class="nav-btn next" title="下一页 (→)">
                <svg width="24" height="24" viewBox="0 0 24 24">
                    <path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/>
                </svg>
            </button>
            
            <div class="nav-info">
                <span class="current-step">1</span> / <span class="total-steps">${this.totalSteps}</span>
            </div>
            
            <button class="nav-btn fullscreen" title="全屏 (F)">
                <svg width="24" height="24" viewBox="0 0 24 24">
                    <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
                </svg>
            </button>
        `;
        
        this.controls.querySelector('.prev').addEventListener('click', () => this.prev());
        this.controls.querySelector('.next').addEventListener('click', () => this.next());
        this.controls.querySelector('.fullscreen').addEventListener('click', () => this.toggleFullscreen());
        
        document.body.appendChild(this.controls);
        
        // 添加样式
        this.addStyles();
    }
    
    createProgressBar() {
        this.progressBar = document.createElement('div');
        this.progressBar.className = 'impress-progress-bar';
        
        const fill = document.createElement('div');
        fill.className = 'progress-fill';
        this.progressBar.appendChild(fill);
        
        document.body.appendChild(this.progressBar);
        
        this.updateProgress();
    }
    
    setupKeyboardShortcuts() {
        document.addEventListener('keydown', (e) => {
            // 忽略在输入框中的按键
            if (e.target.matches('input, textarea, select')) return;
            
            switch (e.key) {
                case 'ArrowLeft':
                case 'PageUp':
                    e.preventDefault();
                    this.prev();
                    break;
                    
                case 'ArrowRight':
                case 'PageDown':
                case ' ':
                    e.preventDefault();
                    this.next();
                    break;
                    
                case 'Home':
                    e.preventDefault();
                    this.first();
                    break;
                    
                case 'End':
                    e.preventDefault();
                    this.last();
                    break;
                    
                case 'f':
                case 'F':
                    if (e.ctrlKey || e.metaKey) return;
                    e.preventDefault();
                    this.toggleFullscreen();
                    break;
                    
                case '?':
                    e.preventDefault();
                    this.showHelp();
                    break;
            }
        });
    }
    
    setupTouchGestures() {
        let startX = 0;
        let startY = 0;
        
        document.addEventListener('touchstart', (e) => {
            startX = e.touches[0].clientX;
            startY = e.touches[0].clientY;
        }, { passive: true });
        
        document.addEventListener('touchend', (e) => {
            if (!startX || !startY) return;
            
            const endX = e.changedTouches[0].clientX;
            const endY = e.changedTouches[0].clientY;
            
            const diffX = startX - endX;
            const diffY = startY - endY;
            
            // 水平滑动超过50px,垂直滑动小于100px
            if (Math.abs(diffX) > 50 && Math.abs(diffY) < 100) {
                if (diffX > 0) {
                    this.next(); // 向左滑动
                } else {
                    this.prev(); // 向右滑动
                }
            }
            
            startX = 0;
            startY = 0;
        }, { passive: true });
    }
    
    updateNavigationState() {
        const steps = Array.from(document.querySelectorAll('.step'));
        const current = document.querySelector('.present');
        this.currentStep = current ? steps.indexOf(current) + 1 : 1;
        
        if (this.controls) {
            const currentEl = this.controls.querySelector('.current-step');
            if (currentEl) {
                currentEl.textContent = this.currentStep;
            }
            
            // 更新按钮状态
            this.controls.querySelector('.prev').disabled = this.currentStep === 1;
            this.controls.querySelector('.next').disabled = this.currentStep === this.totalSteps;
        }
        
        this.updateProgress();
    }
    
    updateProgress() {
        if (!this.progressBar) return;
        
        const progress = ((this.currentStep - 1) / (this.totalSteps - 1)) * 100;
        const fill = this.progressBar.querySelector('.progress-fill');
        if (fill) {
            fill.style.width = `${progress}%`;
        }
    }
    
    prev() {
        this.impress.prev();
    }
    
    next() {
        this.impress.next();
    }
    
    first() {
        this.impress.goto(0);
    }
    
    last() {
        this.impress.goto(this.totalSteps - 1);
    }
    
    toggleFullscreen() {
        if (!document.fullscreenElement) {
            document.documentElement.requestFullscreen().catch(err => {
                console.log(`全屏请求失败: ${err.message}`);
            });
        } else {
            document.exitFullscreen();
        }
    }
    
    showHelp() {
        const help = `
            键盘快捷键:
            ← / PageUp  - 上一页
            → / PageDown / 空格 - 下一页
            Home - 第一页
            End - 最后一页
            F - 全屏切换
            ? - 显示此帮助
            
            触摸手势:
            左右滑动 - 切换页面
        `;
        
        alert(help);
    }
    
    addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .impress-navigation-controls {
                position: fixed;
                bottom: 20px;
                left: 50%;
                transform: translateX(-50%);
                display: flex;
                align-items: center;
                gap: 10px;
                background: rgba(0, 0, 0, 0.8);
                padding: 10px 20px;
                border-radius: 50px;
                backdrop-filter: blur(10px);
                z-index: 1000;
                box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
            }
            
            .nav-btn {
                background: rgba(255, 255, 255, 0.1);
                border: none;
                color: white;
                width: 40px;
                height: 40px;
                border-radius: 50%;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.3s ease;
            }
            
            .nav-btn:hover:not(:disabled) {
                background: rgba(255, 255, 255, 0.2);
                transform: scale(1.1);
            }
            
            .nav-btn:disabled {
                opacity: 0.3;
                cursor: not-allowed;
            }
            
            .nav-btn svg {
                fill: currentColor;
            }
            
            .nav-info {
                color: white;
                font-family: monospace;
                font-size: 14px;
                min-width: 60px;
                text-align: center;
            }
            
            .impress-progress-bar {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 3px;
                background: rgba(255, 255, 255, 0.1);
                z-index: 1000;
            }
            
            .progress-fill {
                height: 100%;
                background: linear-gradient(90deg, #667eea, #764ba2);
                width: 0%;
                transition: width 0.3s ease;
            }
        `;
        
        document.head.appendChild(style);
    }
    
    deactivate() {
        if (this.controls) {
            this.controls.remove();
            this.controls = null;
        }
        
        if (this.progressBar) {
            this.progressBar.remove();
            this.progressBar = null;
        }
    }
}

/**
 * 分析插件
 * 收集演示使用数据
 */
class AnalyticsPlugin {
    constructor(impress, config) {
        this.impress = impress;
        this.config = {
            endpoint: null,
            autoSend: true,
            trackTime: true,
            trackInteractions: true,
            ...config
        };
        
        this.data = {
            sessionId: this.generateSessionId(),
            startTime: Date.now(),
            steps: {},
            events: []
        };
        
        this.currentStep = null;
        this.stepEnterTime = null;
    }
    
    init() {
        // 初始化步骤数据
        document.querySelectorAll('.step').forEach((step, index) => {
            this.data.steps[step.id || `step_${index}`] = {
                id: step.id || `step_${index}`,
                visits: 0,
                totalTime: 0,
                averageTime: 0
            };
        });
    }
    
    activate() {
        this.trackStepEnter();
        this.trackStepLeave();
        this.trackInteractions();
        
        // 页面可见性变化
        document.addEventListener('visibilitychange', () => {
            if (document.hidden) {
                this.recordEvent('page_hidden');
            } else {
                this.recordEvent('page_visible');
            }
        });
        
        // 页面卸载前发送数据
        window.addEventListener('beforeunload', () => {
            if (this.config.autoSend) {
                this.sendAnalytics();
            }
        });
    }
    
    trackStepEnter() {
        document.addEventListener('impress:stepenter', (e) => {
            const step = e.target;
            const stepId = step.id || this.getStepIndex(step);
            
            // 记录离开上一个步骤的时间
            if (this.currentStep && this.stepEnterTime) {
                const timeSpent = Date.now() - this.stepEnterTime;
                const stepData = this.data.steps[this.currentStep];
                
                if (stepData) {
                    stepData.totalTime += timeSpent;
                    stepData.averageTime = stepData.totalTime / stepData.visits;
                }
                
                this.recordEvent('step_leave', {
                    stepId: this.currentStep,
                    timeSpent
                });
            }
            
            // 记录进入新步骤
            this.currentStep = stepId;
            this.stepEnterTime = Date.now();
            
            const stepData = this.data.steps[stepId];
            if (stepData) {
                stepData.visits++;
            }
            
            this.recordEvent('step_enter', {
                stepId,
                stepTitle: step.querySelector('h1, h2, h3')?.textContent || 'Untitled'
            });
        });
    }
    
    trackStepLeave() {
        document.addEventListener('impress:stepleave', (e) => {
            const step = e.target;
            const stepId = step.id || this.getStepIndex(step);
            
            this.recordEvent('step_leave_start', {
                stepId,
                nextStep: e.detail.next.id || this.getStepIndex(e.detail.next)
            });
        });
    }
    
    trackInteractions() {
        if (!this.config.trackInteractions) return;
        
        // 跟踪点击事件
        document.addEventListener('click', (e) => {
            const target = e.target;
            const interactiveElement = this.findInteractiveElement(target);
            
            if (interactiveElement) {
                this.recordEvent('click', {
                    element: interactiveElement.tagName,
                    id: interactiveElement.id,
                    className: interactiveElement.className,
                    text: interactiveElement.textContent?.substring(0, 100)
                });
            }
        }, true);
        
        // 跟踪键盘事件
        document.addEventListener('keydown', (e) => {
            this.recordEvent('keydown', {
                key: e.key,
                code: e.code,
                ctrlKey: e.ctrlKey,
                shiftKey: e.shiftKey,
                altKey: e.altKey,
                metaKey: e.metaKey
            });
        });
    }
    
    findInteractiveElement(element) {
        const interactiveTags = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
        
        while (element && element !== document) {
            if (interactiveTags.includes(element.tagName) || 
                element.getAttribute('role') === 'button' ||
                element.onclick) {
                return element;
            }
            element = element.parentElement;
        }
        
        return null;
    }
    
    getStepIndex(step) {
        const steps = Array.from(document.querySelectorAll('.step'));
        return steps.indexOf(step);
    }
    
    recordEvent(type, data = {}) {
        const event = {
            type,
            timestamp: Date.now(),
            stepId: this.currentStep,
            data
        };
        
        this.data.events.push(event);
        
        // 触发自定义事件
        document.dispatchEvent(new CustomEvent('impress:analytics:event', {
            detail: event
        }));
        
        return event;
    }
    
    generateSessionId() {
        return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    }
    
    async sendAnalytics() {
        if (!this.config.endpoint) {
            console.log('分析数据:', this.data);
            return;
        }
        
        try {
            const response = await fetch(this.config.endpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    ...this.data,
                    endTime: Date.now(),
                    totalDuration: Date.now() - this.data.startTime
                })
            });
            
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            
            console.log('分析数据发送成功');
            
        } catch (error) {
            console.error('分析数据发送失败:', error);
        }
    }
    
    getReport() {
        const report = {
            sessionId: this.data.sessionId,
            totalSteps: Object.keys(this.data.steps).length,
            totalEvents: this.data.events.length,
            totalDuration: Date.now() - this.data.startTime,
            steps: this.data.steps,
            popularSteps: this.getPopularSteps(),
            eventTypes: this.getEventTypes()
        };
        
        return report;
    }
    
    getPopularSteps() {
        return Object.values(this.data.steps)
            .sort((a, b) => b.visits - a.visits)
            .slice(0, 5);
    }
    
    getEventTypes() {
        const types = {};
        this.data.events.forEach(event => {
            types[event.type] = (types[event.type] || 0) + 1;
        });
        return types;
    }
    
    deactivate() {
        if (this.config.autoSend) {
            this.sendAnalytics();
        }
    }
}

/**
 * 导出插件
 * 支持将演示导出为PDF或图片
 */
class ExportPlugin {
    constructor(impress, config) {
        this.impress = impress;
        this.config = {
            formats: ['pdf', 'png', 'json'],
            includeCSS: true,
            includeScripts: false,
            ...config
        };
        
        this.exportUI = null;
    }
    
    init() {
        this.createExportUI();
    }
    
    createExportUI() {
        this.exportUI = document.createElement('div');
        this.exportUI.className = 'impress-export-ui';
        this.exportUI.innerHTML = `
            <button class="export-btn" title="导出演示">
                <svg width="24" height="24" viewBox="0 0 24 24">
                    <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
                </svg>
            </button>
            
            <div class="export-menu">
                <div class="menu-header">导出选项</div>
                <button class="menu-item" data-format="pdf">导出为PDF</button>
                <button class="menu-item" data-format="png">导出为PNG</button>
                <button class="menu-item" data-format="json">导出配置(JSON)</button>
                <button class="menu-item" data-format="print">打印</button>
            </div>
        `;
        
        const exportBtn = this.exportUI.querySelector('.export-btn');
        const menu = this.exportUI.querySelector('.export-menu');
        
        exportBtn.addEventListener('click', () => {
            menu.classList.toggle('show');
        });
        
        // 菜单项点击事件
        menu.querySelectorAll('.menu-item').forEach(item => {
            item.addEventListener('click', (e) => {
                const format = e.target.dataset.format;
                this.export(format);
                menu.classList.remove('show');
            });
        });
        
        // 点击外部关闭菜单
        document.addEventListener('click', (e) => {
            if (!this.exportUI.contains(e.target)) {
                menu.classList.remove('show');
            }
        });
        
        document.body.appendChild(this.exportUI);
        this.addStyles();
    }
    
    async export(format) {
        switch (format) {
            case 'pdf':
                await this.exportToPDF();
                break;
            case 'png':
                await this.exportToPNG();
                break;
            case 'json':
                this.exportToJSON();
                break;
            case 'print':
                this.print();
                break;
        }
    }
    
    async exportToPDF() {
        // 使用html2canvas和jsPDF
        if (typeof html2canvas === 'undefined') {
            await this.loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js');
        }
        
        if (typeof jspdf === 'undefined') {
            await this.loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js');
        }
        
        const { jsPDF } = window.jspdf;
        const pdf = new jsPDF('l', 'mm', 'a4');
        
        const steps = document.querySelectorAll('.step');
        const originalTransforms = new Map();
        
        // 保存原始变换
        steps.forEach(step => {
            originalTransforms.set(step, step.style.transform);
            step.style.transform = 'none';
        });
        
        // 依次截图每个步骤
        for (let i = 0; i < steps.length; i++) {
            const canvas = await html2canvas(steps[i], {
                scale: 2,
                useCORS: true,
                logging: false
            });
            
            const imgData = canvas.toDataURL('image/png');
            const imgWidth = pdf.internal.pageSize.getWidth();
            const imgHeight = (canvas.height * imgWidth) / canvas.width;
            
            if (i > 0) {
                pdf.addPage();
            }
            
            pdf.addImage(imgData, 'PNG', 0, 0, imgWidth, imgHeight);
        }
        
        // 恢复原始变换
        steps.forEach(step => {
            step.style.transform = originalTransforms.get(step);
        });
        
        pdf.save(`presentation-${Date.now()}.pdf`);
    }
    
    async exportToPNG() {
        if (typeof html2canvas === 'undefined') {
            await this.loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js');
        }
        
        const steps = document.querySelectorAll('.step');
        const originalTransforms = new Map();
        
        // 保存原始变换
        steps.forEach(step => {
            originalTransforms.set(step, step.style.transform);
            step.style.transform = 'none';
        });
        
        // 创建zip文件
        const zip = new JSZip();
        
        for (let i = 0; i < steps.length; i++) {
            const canvas = await html2canvas(steps[i], {
                scale: 2,
                useCORS: true,
                logging: false
            });
            
            canvas.toBlob(blob => {
                zip.file(`slide-${i + 1}.png`, blob);
                
                // 如果是最后一张,生成zip
                if (i === steps.length - 1) {
                    zip.generateAsync({ type: 'blob' }).then(content => {
                        saveAs(content, `presentation-${Date.now()}.zip`);
                    });
                }
            }, 'image/png');
        }
        
        // 恢复原始变换
        steps.forEach(step => {
            step.style.transform = originalTransforms.get(step);
        });
    }
    
    exportToJSON() {
        const config = {
            steps: [],
            metadata: {
                exported: new Date().toISOString(),
                version: '1.0'
            }
        };
        
        document.querySelectorAll('.step').forEach((step, index) => {
            const stepConfig = {
                id: step.id || `step_${index}`,
                position: {
                    x: step.dataset.x || 0,
                    y: step.dataset.y || 0,
                    z: step.dataset.z || 0
                },
                rotation: {
                    x: step.dataset.rotateX || 0,
                    y: step.dataset.rotateY || 0,
                    z: step.dataset.rotateZ || 0
                },
                scale: step.dataset.scale || 1,
                content: step.innerHTML
            };
            
            config.steps.push(stepConfig);
        });
        
        const dataStr = JSON.stringify(config, null, 2);
        const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
        
        const link = document.createElement('a');
        link.setAttribute('href', dataUri);
        link.setAttribute('download', `presentation-${Date.now()}.json`);
        document.body.appendChild(link);
        link.click();
        link.remove();
    }
    
    print() {
        const originalDisplay = document.getElementById('impress').style.display;
        document.getElementById('impress').style.display = 'block';
        window.print();
        document.getElementById('impress').style.display = originalDisplay;
    }
    
    addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .impress-export-ui {
                position: fixed;
                bottom: 20px;
                right: 20px;
                z-index: 1000;
            }
            
            .export-btn {
                background: #3498db;
                border: none;
                color: white;
                width: 50px;
                height: 50px;
                border-radius: 50%;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
                transition: all 0.3s ease;
            }
            
            .export-btn:hover {
                background: #2980b9;
                transform: scale(1.1);
                box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
            }
            
            .export-btn svg {
                fill: currentColor;
            }
            
            .export-menu {
                position: absolute;
                bottom: 60px;
                right: 0;
                background: white;
                border-radius: 10px;
                box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
                min-width: 180px;
                opacity: 0;
                transform: translateY(10px);
                visibility: hidden;
                transition: all 0.3s ease;
            }
            
            .export-menu.show {
                opacity: 1;
                transform: translateY(0);
                visibility: visible;
            }
            
            .menu-header {
                padding: 12px 16px;
                font-weight: bold;
                color: #666;
                border-bottom: 1px solid #eee;
            }
            
            .menu-item {
                display: block;
                width: 100%;
                padding: 12px 16px;
                border: none;
                background: none;
                text-align: left;
                cursor: pointer;
                color: #333;
                transition: background 0.2s;
            }
            
            .menu-item:hover {
                background: #f5f5f5;
            }
        `;
        
        document.head.appendChild(style);
    }
    
    deactivate() {
        if (this.exportUI) {
            this.exportUI.remove();
            this.exportUI = null;
        }
    }
}

5.3 插件使用示例

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Impress.js插件系统演示</title>
    <script src="https://cdn.jsdelivr.net/npm/impress.js@2.0.0/js/impress.min.js"></script>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
            min-height: 100vh;
            margin: 0;
            color: white;
        }
        
        .step {
            width: 800px;
            height: 600px;
            border-radius: 20px;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.2);
            padding: 40px;
            box-sizing: border-box;
        }
        
        h1, h2, h3 {
            margin-top: 0;
        }
    </style>
</head>
<body>
    <div id="impress" data-plugins="navigation,analytics,export">
        
        <!-- 欢迎页面 -->
        <div class="step" id="welcome" 
             data-x="0" data-y="0" data-z="0"
             data-rotate="0">
            <h1>Impress.js插件系统演示</h1>
            <p>本演示展示了Impress.js的强大插件系统</p>
            <ul>
                <li>导航插件 - 增强的导航控制</li>
                <li>分析插件 - 使用数据收集</li>
                <li>导出插件 - 多种格式导出</li>
            </ul>
        </div>
        
        <!-- 功能页面1 -->
        <div class="step" id="features-1"
             data-x="1000" data-y="0" data-z="0"
             data-rotate-y="45">
            <h2>导航插件功能</h2>
            <p>底部导航栏提供了完整的导航控制:</p>
            <ul>
                <li>上一页/下一页按钮</li>
                <li>进度条显示</li>
                <li>全屏切换</li>
                <li>键盘快捷键支持</li>
                <li>触摸手势支持</li>
            </ul>
            <p>按<strong>F</strong>键切换全屏,按<strong>?</strong>查看帮助。</p>
        </div>
        
        <!-- 功能页面2 -->
        <div class="step" id="features-2"
             data-x="0" data-y="1000" data-z="0"
             data-rotate-x="45">
            <h2>分析插件功能</h2>
            <p>实时收集演示使用数据:</p>
            <ul>
                <li>步骤停留时间</li>
                <li>用户交互记录</li>
                <li>热门内容分析</li>
                <li>使用行为统计</li>
            </ul>
            <p>打开控制台查看分析数据。</p>
        </div>
        
        <!-- 功能页面3 -->
        <div class="step" id="features-3"
             data-x="1000" data-y="1000" data-z="-500"
             data-rotate-x="-45"









        <!-- 功能页面3 -->
        <div class="step" id="features-3"
             data-x="1000" data-y="1000" data-z="-500"
             data-rotate-x="-45" data-rotate-y="45">
            <h2>导出插件功能</h2>
            <p>支持多种格式导出:</p>
        <ul>
        <li>PDF文档导出</li>
        <li>PNG图片序列</li>
        <li>JSON配置导出</li>
        <li>打印优化</li>
    </ul>
    <p>点击右下角的导出按钮尝试导出功能。</p>
    <div style="margin-top: 40px; padding: 20px; background: rgba(255,255,255,0.1); border-radius: 10px;">
        <h3>交互演示</h3>
        <button onclick="showMessage()" style="padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 5px; cursor: pointer;">
            点击我
        </button>
        <div id="message" style="margin-top: 20px; display: none;">
            按钮被点击了!这个交互会被分析插件记录。
        </div>
    </div>
</div>

<!-- 数据展示页面 -->
<div class="step" id="analytics-demo"
     data-x="0" data-y="0" data-z="-1000"
     data-scale="1.5">
    <h2>实时数据分析</h2>
    <div id="analytics-display" style="margin-top: 30px;">
        <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
            <div class="metric">
                <div class="metric-label">当前步骤</div>
                <div class="metric-value" id="current-step">1</div>
            </div>
            <div class="metric">
                <div class="metric-label">停留时间</div>
                <div class="metric-value" id="time-spent">0s</div>
            </div>
            <div class="metric">
                <div class="metric-label">总步骤数</div>
                <div class="metric-value" id="total-steps">4</div>
            </div>
            <div class="metric">
                <div class="metric-label">总交互次数</div>
                <div class="metric-value" id="interaction-count">0</div>
            </div>
        </div>
        
        <div style="margin-top: 40px;">
            <h3>事件日志</h3>
            <div id="event-log" style="height: 200px; overflow-y: auto; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 5px; font-family: monospace; font-size: 12px;">
                <!-- 事件日志将在这里显示 -->
            </div>
        </div>
    </div>
</div>

<!-- 总结页面 -->
<div class="step" id="conclusion"
     data-x="1500" data-y="0" data-z="-1500"
     data-rotate-y="90">
    <h2>插件系统总结</h2>
    <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; margin-top: 40px;">
        <div class="feature-card">
            <div class="feature-icon">🚀</div>
            <h3>易于扩展</h3>
            <p>模块化设计,轻松添加新功能</p>
        </div>
        <div class="feature-card">
            <div class="feature-icon">⚡</div>
            <h3>性能优化</h3>
            <p>按需加载,不影响核心性能</p>
        </div>
        <div class="feature-card">
            <div class="feature-icon">🔄</div>
            <h3>生命周期管理</h3>
            <p>完整的初始化、激活、销毁流程</p>
        </div>
    </div>
    
    <div style="margin-top: 50px; text-align: center;">
        <button onclick="showAnalyticsReport()" style="padding: 15px 30px; background: #2ecc71; color: white; border: none; border-radius: 50px; font-size: 16px; cursor: pointer;">
            查看分析报告
        </button>
    </div>
</div>
</div>

<script>
// 创建并初始化插件管理器
let pluginManager = null;

document.addEventListener('DOMContentLoaded', () => {
    // 初始化Impress.js
    const impressApi = impress().init();
    
    // 创建插件管理器
    pluginManager = new ImpressPluginManager(impressApi);
    
    // 注册插件
    pluginManager.register(NavigationPlugin, {
        id: 'navigation',
        version: '1.0.0',
        description: '增强导航控制',
        enabled: true,
        priority: 100,
        config: {
            showControls: true,
            showProgress: true,
            keyboardShortcuts: true,
            touchGestures: true
        }
    });
    
    pluginManager.register(AnalyticsPlugin, {
        id: 'analytics',
        version: '1.0.0',
        description: '使用数据分析',
        enabled: true,
        priority: 90,
        config: {
            endpoint: null, // 设置为实际API端点
            autoSend: false,
            trackTime: true,
            trackInteractions: true
        }
    });
    
    pluginManager.register(ExportPlugin, {
        id: 'export',
        version: '1.0.0',
        description: '演示导出功能',
        enabled: true,
        priority: 80,
        config: {
            formats: ['pdf', 'png', 'json', 'print']
        }
    });
    
    // 设置分析数据显示
    setupAnalyticsDisplay();
    
    // 显示插件状态
    console.log('插件状态:', pluginManager.getAllPluginStatus());
});

// 分析数据显示
function setupAnalyticsDisplay() {
    let interactionCount = 0;
    let stepEnterTime = Date.now();
    
    // 监听分析事件
    document.addEventListener('impress:analytics:event', (event) => {
        const data = event.detail;
        
        // 更新事件日志
        const logElement = document.getElementById('event-log');
        if (logElement) {
            const time = new Date(data.timestamp).toLocaleTimeString();
            const logEntry = document.createElement('div');
            logEntry.textContent = `[${time}] ${data.type}: ${JSON.stringify(data.data)}`;
            logEntry.style.padding = '5px 0';
            logEntry.style.borderBottom = '1px solid rgba(255,255,255,0.1)';
            logElement.appendChild(logEntry);
            
            // 保持滚动到底部
            logElement.scrollTop = logElement.scrollHeight;
        }
        
        // 更新交互计数
        if (data.type === 'click') {
            interactionCount++;
            document.getElementById('interaction-count').textContent = interactionCount;
        }
    });
    
    // 监听步骤切换
    document.addEventListener('impress:stepenter', (event) => {
        const step = event.target;
        const steps = document.querySelectorAll('.step');
        const currentStep = Array.from(steps).indexOf(step) + 1;
        
        document.getElementById('current-step').textContent = currentStep;
        document.getElementById('total-steps').textContent = steps.length;
        
        // 重置时间
        stepEnterTime = Date.now();
        updateTimeDisplay();
    });
    
    // 更新时间显示
    function updateTimeDisplay() {
        const timeSpent = Math.floor((Date.now() - stepEnterTime) / 1000);
        document.getElementById('time-spent').textContent = `${timeSpent}s`;
        setTimeout(updateTimeDisplay, 1000);
    }
    
    updateTimeDisplay();
}

function showMessage() {
    document.getElementById('message').style.display = 'block';
}

function showAnalyticsReport() {
    const analyticsPlugin = pluginManager.plugins.get('analytics');
    if (analyticsPlugin && analyticsPlugin.instance) {
        const report = analyticsPlugin.instance.getReport();
        alert(JSON.stringify(report, null, 2));
    }
}
</script>

<style>
.metric {
    background: rgba(255, 255, 255, 0.1);
    padding: 20px;
    border-radius: 10px;
    text-align: center;
}

.metric-label {
    font-size: 14px;
    opacity: 0.8;
    margin-bottom: 10px;
}

.metric-value {
    font-size: 32px;
    font-weight: bold;
}

.feature-card {
    background: rgba(255, 255, 255, 0.1);
    padding: 30px;
    border-radius: 15px;
    text-align: center;
    transition: transform 0.3s ease;
}

.feature-card:hover {
    transform: translateY(-10px);
}

.feature-icon {
    font-size: 48px;
    margin-bottom: 20px;
}
</style>
</body>
</html>

5.4 插件开发最佳实践

在开发Impress.js插件时,遵循以下最佳实践可以确保插件的质量和兼容性:

插件设计原则
javascript 复制代码
/**
 * 插件开发最佳实践示例
 */
class WellDesignedPlugin {
    constructor(impress, config) {
        // 原则1:配置合并
        this.config = {
            // 默认配置
            enabled: true,
            debug: false,
            // 用户配置覆盖
            ...config
        };
        
        // 原则2:保存Impress.js API引用
        this.impress = impress;
        
        // 原则3:状态管理
        this.state = {
            initialized: false,
            active: false,
            resources: new Set()
        };
        
        // 原则4:错误边界
        this.errorHandler = this.errorHandler.bind(this);
    }
    
    /**
     * 初始化方法 - 只做必要的初始化
     */
    init() {
        if (this.state.initialized) {
            return;
        }
        
        try {
            // 原则5:惰性初始化
            this.initializeDOM();
            this.initializeEvents();
            
            this.state.initialized = true;
            
            if (this.config.debug) {
                console.log(`[${this.constructor.name}] 初始化完成`);
            }
            
        } catch (error) {
            this.errorHandler('初始化失败', error);
        }
    }
    
    /**
     * 激活方法 - 按需激活功能
     */
    activate() {
        if (!this.state.initialized || this.state.active) {
            return;
        }
        
        try {
            // 原则6:功能按需激活
            this.activateFeatures();
            this.bindEventListeners();
            
            this.state.active = true;
            
            if (this.config.debug) {
                console.log(`[${this.constructor.name}] 已激活`);
            }
            
        } catch (error) {
            this.errorHandler('激活失败', error);
        }
    }
    
    /**
     * 停用方法 - 清理资源
     */
    deactivate() {
        if (!this.state.active) {
            return;
        }
        
        try {
            // 原则7:彻底清理
            this.unbindEventListeners();
            this.cleanupResources();
            
            this.state.active = false;
            
            if (this.config.debug) {
                console.log(`[${this.constructor.name}] 已停用`);
            }
            
        } catch (error) {
            this.errorHandler('停用失败', error);
        }
    }
    
    /**
     * 销毁方法 - 完全清理
     */
    destroy() {
        this.deactivate();
        
        // 清理所有资源
        this.state.resources.forEach(resource => {
            this.releaseResource(resource);
        });
        
        this.state.resources.clear();
        this.state.initialized = false;
        
        // 原则8:清除引用
        this.impress = null;
        this.config = null;
    }
    
    /**
     * 统一错误处理
     */
    errorHandler(context, error) {
        console.error(`[${this.constructor.name}] ${context}:`, error);
        
        // 原则9:优雅降级
        if (this.config.fallback) {
            this.fallbackStrategy();
        }
        
        // 触发错误事件
        this.emit('plugin:error', { context, error });
    }
    
    /**
     * 事件发射器
     */
    emit(event, data) {
        const customEvent = new CustomEvent(event, {
            detail: data,
            bubbles: true
        });
        document.dispatchEvent(customEvent);
    }
    
    /**
     * 资源管理
     */
    trackResource(resource) {
        this.state.resources.add(resource);
        return resource;
    }
    
    releaseResource(resource) {
        if (resource && typeof resource.dispose === 'function') {
            resource.dispose();
        }
        this.state.resources.delete(resource);
    }
    
    /**
     * 配置验证
     */
    validateConfig(config) {
        const schema = {
            enabled: { type: 'boolean', default: true },
            debug: { type: 'boolean', default: false },
            // 更多配置验证规则...
        };
        
        const validated = {};
        
        Object.keys(schema).forEach(key => {
            const rule = schema[key];
            const value = config[key];
            
            if (value === undefined) {
                validated[key] = rule.default;
            } else if (typeof value !== rule.type) {
                console.warn(`配置 ${key} 类型错误,期望 ${rule.type},得到 ${typeof value}`);
                validated[key] = rule.default;
            } else {
                validated[key] = value;
            }
        });
        
        return validated;
    }
    
    /**
     * 性能监控
     */
    measurePerformance(name, fn) {
        const start = performance.now();
        const result = fn();
        const duration = performance.now() - start;
        
        if (this.config.debug && duration > 16) { // 超过一帧时间
            console.warn(`[${this.constructor.name}] ${name} 耗时 ${duration.toFixed(2)}ms`);
        }
        
        return result;
    }
}

插件发布规范

javascript 复制代码
/**
 * 插件发布模板
 */
const PluginTemplate = {
    // 必需字段
    name: 'YourPluginName',
    version: '1.0.0',
    author: 'Your Name',
    description: '插件功能描述',
    license: 'MIT',
    
    // 可选字段
    homepage: 'https://github.com/yourname/yourplugin',
    repository: {
        type: 'git',
        url: 'https://github.com/yourname/yourplugin.git'
    },
    bugs: {
        url: 'https://github.com/yourname/yourplugin/issues'
    },
    keywords: [
        'impressjs',
        'impress.js',
        'plugin',
        'presentation'
    ],
    
    // 依赖管理
    dependencies: {
        // 第三方库依赖
    },
    peerDependencies: {
        'impress.js': '^2.0.0'
    },
    
    // 浏览器兼容性
    browserslist: [
        'last 2 versions',
        '> 1%',
        'not dead'
    ],
    
    // 构建配置
    build: {
        entry: './src/index.js',
        output: {
            filename: 'yourplugin.min.js',
            library: 'YourPlugin',
            libraryTarget: 'umd'
        }
    },
    
    // 插件主类
    class: class YourPlugin {
        constructor(impress, config) {
            // 实现插件功能
        }
        
        init() {
            // 初始化逻辑
        }
        
        activate() {
            // 激活逻辑
        }
        
        deactivate() {
            // 停用逻辑
        }
        
        destroy() {
            // 销毁逻辑
        }
    }
};

// 自动注册机制
if (typeof window !== 'undefined' && window.ImpressPlugins) {
    window.ImpressPlugins[PluginTemplate.name] = PluginTemplate.class;
}

第六章:企业级项目结构模板

6.1 项目架构设计

对于企业级Impress.js应用,我们需要一个结构清晰、可维护的项目架构:

复制代码
impress-enterprise-project/
├── 📁 src/
│   ├── 📁 core/                    # 核心模块
│   │   ├── impress-wrapper.js      # Impress.js封装
│   │   ├── layout-engine.js        # 布局引擎
│   │   ├── animation-controller.js # 动画控制器
│   │   └── event-system.js         # 事件系统
│   │
│   ├── 📁 components/              # 可复用组件
│   │   ├── BaseComponent.js        # 组件基类
│   │   ├── charts/                 # 图表组件
│   │   │   ├── LineChart.js
│   │   │   ├── BarChart.js
│   │   │   └── PieChart.js
│   │   ├── media/                  # 媒体组件
│   │   │   ├── VideoPlayer.js
│   │   │   └── AudioController.js
│   │   └── ui/                     # UI组件
│   │       ├── Navigation.js
│   │       ├── ProgressBar.js
│   │       └── Toolbar.js
│   │
│   ├── 📁 plugins/                 # 插件系统
│   │   ├── PluginManager.js        # 插件管理器
│   │   ├── analytics/              # 分析插件
│   │   ├── export/                 # 导出插件
│   │   └── collaboration/          # 协作插件
│   │
│   ├── 📁 scenes/                  # 演示场景
│   │   ├── intro/                  # 介绍部分
│   │   │   ├── scene-1.html
│   │   │   └── scene-1.js
│   │   ├── products/               # 产品展示
│   │   │   ├── scene-2.html
│   │   │   └── scene-2.js
│   │   └── conclusion/             # 总结部分
│   │       ├── scene-3.html
│   │       └── scene-3.js
│   │
│   ├── 📁 services/                # 服务层
│   │   ├── DataService.js          # 数据服务
│   │   ├── AnalyticsService.js     # 分析服务
│   │   ├── StorageService.js       # 存储服务
│   │   └── ApiService.js           # API服务
│   │
│   ├── 📁 utils/                   # 工具函数
│   │   ├── dom-helpers.js          # DOM操作
│   │   ├── animation-helpers.js    # 动画辅助
│   │   ├── validation.js           # 数据验证
│   │   └── logger.js               # 日志工具
│   │
│   ├── 📁 styles/                  # 样式文件
│   │   ├── base/                   # 基础样式
│   │   │   ├── reset.css
│   │   │   ├── typography.css
│   │   │   └── variables.css
│   │   ├── components/             # 组件样式
│   │   ├── layouts/                # 布局样式
│   │   └── themes/                 # 主题样式
│   │       ├── corporate.css       # 企业主题
│   │       ├── creative.css        # 创意主题
│   │       └── dark.css            # 深色主题
│   │
│   └── 📁 assets/                  # 静态资源
│       ├── 📁 data/                # 数据文件
│       ├── 📁 images/              # 图片资源
│       ├── 📁 fonts/               # 字体文件
│       └── 📁 videos/              # 视频资源
│
├── 📁 config/                      # 配置文件
│   ├── impress.config.js           # Impress.js配置
│   ├── webpack.config.js           # 构建配置
│   ├── jest.config.js              # 测试配置
│   └── eslint.config.js            # 代码规范
│
├── 📁 tests/                       # 测试文件
│   ├── unit/                       # 单元测试
│   ├── integration/                # 集成测试
│   └── e2e/                        # 端到端测试
│
├── 📁 docs/                        # 项目文档
│   ├── api/                        # API文档
│   ├── guides/                     # 使用指南
│   └── examples/                   # 示例代码
│
├── 📁 scripts/                     # 构建脚本
│   ├── build.js                    # 构建脚本
│   ├── deploy.js                   # 部署脚本
│   └── generate.js                 # 生成脚本
│
├── 📄 package.json                 # 项目配置
├── 📄 README.md                    # 项目说明
├── 📄 CHANGELOG.md                 # 变更日志
└── 📄 LICENSE                      # 许可证

6.2 核心模块实现

企业级Impress.js封装
javascript 复制代码
// src/core/impress-wrapper.js
class EnterpriseImpress {
    constructor(config = {}) {
        this.config = this.mergeConfig(config);
        this.impressApi = null;
        this.plugins = new PluginManager();
        this.components = new ComponentRegistry();
        this.services = new ServiceContainer();
        
        this.state = {
            initialized: false,
            currentStep: 0,
            totalSteps: 0,
            isFullscreen: false,
            isPresenterMode: false
        };
        
        this.init();
    }
    
    init() {
        // 初始化服务
        this.services.register('analytics', new AnalyticsService());
        this.services.register('data', new DataService());
        this.services.register('storage', new StorageService());
        
        // 加载配置
        this.loadConfig();
        
        // 初始化Impress.js
        this.initializeImpress();
        
        // 初始化插件系统
        this.initializePlugins();
        
        // 初始化组件系统
        this.initializeComponents();
        
        // 设置事件监听
        this.setupEventListeners();
        
        this.state.initialized = true;
        
        this.emit('impress:enterprise:initialized', {
            config: this.config,
            state: this.state
        });
    }
    
    initializeImpress() {
        // 创建Impress.js容器
        this.createContainer();
        
        // 初始化Impress.js
        this.impressApi = impress();
        
        // 应用配置
        if (this.config.transitionDuration) {
            this.impressApi.transitionDuration = this.config.transitionDuration;
        }
        
        // 调用原生init
        this.impressApi.init();
        
        // 更新状态
        this.state.totalSteps = document.querySelectorAll('.step').length;
    }
    
    createContainer() {
        if (document.getElementById('impress')) {
            return;
        }
        
        const container = document.createElement('div');
        container.id = 'impress';
        container.dataset.config = JSON.stringify(this.config);
        
        // 应用主题
        if (this.config.theme) {
            container.dataset.theme = this.config.theme;
        }
        
        // 应用布局
        if (this.config.layout) {
            container.dataset.layout = this.config.layout;
        }
        
        document.body.appendChild(container);
    }
    
    initializePlugins() {
        // 加载核心插件
        this.plugins.register('navigation', NavigationPlugin, {
            config: this.config.plugins?.navigation
        });
        
        this.plugins.register('analytics', AnalyticsPlugin, {
            config: this.config.plugins?.analytics
        });
        
        this.plugins.register('export', ExportPlugin, {
            config: this.config.plugins?.export
        });
        
        // 加载自定义插件
        if (this.config.plugins?.custom) {
            Object.entries(this.config.plugins.custom).forEach(([name, plugin]) => {
                this.plugins.register(name, plugin.class, plugin.config);
            });
        }
        
        // 初始化所有插件
        this.plugins.initAll(this.impressApi);
    }
    
    initializeComponents() {
        // 注册核心组件
        this.components.register('chart', ChartComponent);
        this.components.register('media', MediaComponent);
        this.components.register('interactive', InteractiveComponent);
        
        // 自动初始化组件
        this.components.autoInit();
    }
    
    setupEventListeners() {
        // Impress.js事件
        document.addEventListener('impress:stepenter', (e) => {
            this.handleStepEnter(e);
        });
        
        document.addEventListener('impress:stepleave', (e) => {
            this.handleStepLeave(e);
        });
        
        // 键盘事件
        document.addEventListener('keydown', (e) => {
            this.handleKeydown(e);
        });
        
        // 全屏事件
        document.addEventListener('fullscreenchange', () => {
            this.state.isFullscreen = !!document.fullscreenElement;
        });
    }
    
    handleStepEnter(event) {
        const step = event.target;
        const stepIndex = this.getStepIndex(step);
        
        this.state.currentStep = stepIndex;
        
        // 触发自定义事件
        this.emit('impress:enterprise:stepenter', {
            step,
            index: stepIndex,
            data: step.dataset
        });
        
        // 更新服务
        this.services.get('analytics').trackStepEnter(stepIndex);
    }
    
    handleStepLeave(event) {
        const step = event.target;
        const stepIndex = this.getStepIndex(step);
        
        this.emit('impress:enterprise:stepleave', {
            step,
            index: stepIndex
        });
    }
    
    handleKeydown(event) {
        // 企业级快捷键
        switch (event.key) {
            case 'p':
            case 'P':
                if (event.ctrlKey) {
                    event.preventDefault();
                    this.togglePresenterMode();
                }
                break;
                
            case 's':
            case 'S':
                if (event.ctrlKey) {
                    event.preventDefault();
                    this.saveState();
                }
                break;
                
            case 'r':
            case 'R':
                if (event.ctrlKey) {
                    event.preventDefault();
                    this.reset();
                }
                break;
        }
    }
    
    // 企业级功能
    togglePresenterMode() {
        this.state.isPresenterMode = !this.state.isPresenterMode;
        
        if (this.state.isPresenterMode) {
            this.enterPresenterMode();
        } else {
            this.exitPresenterMode();
        }
        
        this.emit('impress:enterprise:presentermode', {
            enabled: this.state.isPresenterMode
        });
    }
    
    enterPresenterMode() {
        // 显示演讲者视图
        this.showPresenterView();
        
        // 启动计时器
        this.startPresentationTimer();
        
        // 显示演讲者备注
        this.showSpeakerNotes();
    }
    
    saveState() {
        const state = {
            currentStep: this.state.currentStep,
            timestamp: Date.now(),
            config: this.config,
            customData: this.getCustomState()
        };
        
        this.services.get('storage').set('impress_state', state);
        
        this.emit('impress:enterprise:statesaved', { state });
    }
    
    loadState() {
        const state = this.services.get('storage').get('impress_state');
        
        if (state && state.currentStep) {
            this.impressApi.goto(state.currentStep);
            
            this.emit('impress:enterprise:stateloaded', { state });
        }
    }
    
    reset() {
        this.impressApi.goto(0);
        this.state.currentStep = 0;
        
        // 重置插件
        this.plugins.reset();
        
        // 重置组件
        this.components.reset();
        
        this.emit('impress:enterprise:reset');
    }
    
    export(format) {
        const exporter = new Exporter(format, {
            includeData: true,
            includeStyles: true,
            quality: 'high'
        });
        
        return exporter.export();
    }
    
    // 工具方法
    getStepIndex(step) {
        const steps = Array.from(document.querySelectorAll('.step'));
        return steps.indexOf(step);
    }
    
    mergeConfig(userConfig) {
        const defaultConfig = {
            theme: 'corporate',
            layout: 'responsive',
            transitionDuration: 1000,
            autoPlay: false,
            loop: false,
            plugins: {
                navigation: { enabled: true },
                analytics: { enabled: true },
                export: { enabled: true }
            },
            services: {
                analytics: { endpoint: '/api/analytics' },
                storage: { type: 'localStorage' }
            }
        };
        
        return deepMerge(defaultConfig, userConfig);
    }
    
    emit(event, data) {
        const customEvent = new CustomEvent(event, {
            detail: data,
            bubbles: true
        });
        document.dispatchEvent(customEvent);
    }
}

服务容器设计

javascript 复制代码
// src/core/service-container.js
class ServiceContainer {
    constructor() {
        this.services = new Map();
        this.instances = new Map();
        this.dependencies = new Map();
    }
    
    register(name, ServiceClass, config = {}) {
        this.services.set(name, {
            class: ServiceClass,
            config,
            dependencies: ServiceClass.dependencies || []
        });
        
        // 构建依赖图
        this.buildDependencyGraph();
        
        return this;
    }
    
    get(name) {
        // 如果已经实例化,直接返回
        if (this.instances.has(name)) {
            return this.instances.get(name);
        }
        
        // 创建实例
        const serviceDef = this.services.get(name);
        if (!serviceDef) {
            throw new Error(`Service ${name} not registered`);
        }
        
        // 检查依赖
        this.checkDependencies(name);
        
        // 创建依赖实例
        const dependencies = {};
        serviceDef.dependencies.forEach(depName => {
            dependencies[depName] = this.get(depName);
        });
        
        // 创建服务实例
        const instance = new serviceDef.class({
            ...serviceDef.config,
            dependencies
        });
        
        // 存储实例
        this.instances.set(name, instance);
        
        // 初始化服务
        if (typeof instance.init === 'function') {
            instance.init();
        }
        
        return instance;
    }
    
    checkDependencies(serviceName) {
        const visited = new Set();
        const check = (name) => {
            if (visited.has(name)) {
                throw new Error(`Circular dependency detected: ${serviceName}`);
            }
            
            visited.add(name);
            
            const serviceDef = this.services.get(name);
            if (!serviceDef) {
                throw new Error(`Dependency ${name} not registered`);
            }
            
            serviceDef.dependencies.forEach(dep => {
                check(dep);
            });
        };
        
        check(serviceName);
    }
    
    buildDependencyGraph() {
        this.dependencies.clear();
        
        this.services.forEach((def, name) => {
            this.dependencies.set(name, new Set(def.dependencies));
        });
    }
    
    destroy(name) {
        const instance = this.instances.get(name);
        if (instance && typeof instance.destroy === 'function') {
            instance.destroy();
        }
        
        this.instances.delete(name);
    }
    
    destroyAll() {
        this.instances.forEach((instance, name) => {
            this.destroy(name);
        });
    }
}

第七章:性能优化与最佳实践

7.1 性能优化策略

懒加载与代码分割
javascript 复制代码
// src/utils/lazy-loader.js
class LazyLoader {
    constructor(options = {}) {
        this.options = {
            threshold: 0.1,
            rootMargin: '200px',
            loadingClass: 'loading',
            loadedClass: 'loaded',
            errorClass: 'error',
            ...options
        };
        
        this.observers = new Map();
        this.cache = new Map();
    }
    
    // 图片懒加载
    lazyLoadImages(container = document) {
        const images = container.querySelectorAll('img[data-src]');
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const img = entry.target;
                    this.loadImage(img);
                    observer.unobserve(img);
                }
            });
        }, this.options);
        
        images.forEach(img => {
            // 添加加载状态
            img.classList.add(this.options.loadingClass);
            observer.observe(img);
        });
        
        this.observers.set('images', observer);
    }
    
    async loadImage(img) {
        const src = img.dataset.src;
        
        try {
            // 检查缓存
            if (this.cache.has(src)) {
                img.src = this.cache.get(src);
                return;
            }
            
            // 预加载
            const image = new Image();
            await new Promise((resolve, reject) => {
                image.onload = resolve;
                image.onerror = reject;
                image.src = src;
            });
            
            // 设置图片源
            img.src = src;
            this.cache.set(src, src);
            
            // 更新状态
            img.classList.remove(this.options.loadingClass);
            img.classList.add(this.options.loadedClass);
            
        } catch (error) {
            console.error('图片加载失败:', src, error);
            img.classList.remove(this.options.loadingClass);
            img.classList.add(this.options.errorClass);
        }
    }
    
    // 组件懒加载
    lazyLoadComponents(container = document) {
        const components = container.querySelectorAll('[data-lazy-component]');
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(async entry => {
                if (entry.isIntersecting) {
                    const element = entry.target;
                    await this.loadComponent(element);
                    observer.unobserve(element);
                }
            });
        }, this.options);
        
        components.forEach(component => {
            observer.observe(component);
        });
        
        this.observers.set('components', observer);
    }
    
    async loadComponent(element) {
        const componentName = element.dataset.lazyComponent;
        const componentUrl = element.dataset.componentUrl;
        
        try {
            // 动态导入组件
            const module = await import(componentUrl);
            const ComponentClass = module.default;
            
            // 初始化组件
            const component = new ComponentClass(element);
            if (typeof component.init === 'function') {
                component.init();
            }
            
            // 标记为已加载
            element.dataset.lazyLoaded = 'true';
            
        } catch (error) {
            console.error(`组件加载失败: ${componentName}`, error);
        }
    }
    
    // 视频懒加载
    lazyLoadVideos(container = document) {
        const videos = container.querySelectorAll('video[data-src]');
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const video = entry.target;
                    this.loadVideo(video);
                    observer.unobserve(video);
                }
            });
        }, this.options);
        
        videos.forEach(video => {
            observer.observe(video);
        });
        
        this.observers.set('videos', observer);
    }
    
    loadVideo(video) {
        const sources = video.querySelectorAll('source[data-src]');
        
        sources.forEach(source => {
            const src = source.dataset.src;
            source.src = src;
        });
        
        video.load();
        video.dataset.loaded = 'true';
    }
    
    // 清理资源
    destroy() {
        this.observers.forEach(observer => {
            observer.disconnect();
        });
        this.observers.clear();
        this.cache.clear();
    }
}

第八章:总结与展望

8.1 技术总结

通过本篇超过15000字的深度解析,我们全面探讨了Impress.js的架构设计与企业级实践。主要内容总结如下:

  1. 架构哲学:从Prezi的无限画布理念到Impress.js的三维空间思维,我们理解了现代演示框架的设计思想。

  2. 结构化布局系统

    • 2D网格与流式布局系统

    • 响应式适配策略

    • 自动布局算法

  3. 3D空间设计

    • 三维坐标系详解

    • 多种3D布局模式(立方体、球面、螺旋等)

    • 3D场景管理系统

  4. 模块化组件架构

    • 组件注册与生命周期管理

    • 可复用的UI组件库

    • 数据驱动的组件设计

  5. 插件生态系统

    • 插件管理器设计

    • 核心插件实现(导航、分析、导出)

    • 插件开发最佳实践

  6. 企业级项目结构

    • 完整的项目架构模板

    • 核心服务封装

    • 构建与部署流程

  7. 性能优化

    • 懒加载策略

    • 渲染性能优化

    • 内存管理

  8. 质量保证

    • 代码规范与测试策略

    • 持续集成与部署

8.2 技术优势

Impress.js企业级方案的主要优势:

  1. 视觉表现力:超越传统PPT的3D效果,提供沉浸式演示体验

  2. 技术先进性:基于现代Web技术栈,支持响应式设计和移动端适配

  3. 架构灵活性:模块化设计支持快速定制和扩展

  4. 企业级特性:完整的插件系统、数据分析、导出功能等

  5. 性能优越:优化的渲染性能,支持大型复杂演示

  6. 开发友好:完整的工具链和开发文档

相关推荐
bybitq2 小时前
cmake构建c++项目时,vscode/cursor无法识别头文件路径,导致报错,解决方案
开发语言·c++·vscode
小宇的天下2 小时前
Calibre :Standard Verification Rule Format(SVRF) Manual (1-1)
大数据·前端·网络
充气大锤2 小时前
前端实现流式输出配合katex.js
开发语言·前端·javascript·ai·vue
滴水未满2 小时前
uniapp的页面
前端·uni-app
无限进步_2 小时前
二叉搜索树(BST)详解:从原理到实现
开发语言·数据结构·c++·ide·后端·github·visual studio
邝邝邝邝丹2 小时前
vue2-computed、JS事件循环、try/catch、响应式依赖追踪知识点整理
开发语言·前端·javascript
郝学胜-神的一滴2 小时前
机器学习特征选择:深入理解移除低方差特征与sklearn的VarianceThreshold
开发语言·人工智能·python·机器学习·概率论·sklearn
多多*2 小时前
计算机网络相关 讲一下rpc与传统http的区别
java·开发语言·网络·jvm·c#
码农水水2 小时前
阿里Java面试被问:Online DDL的INSTANT、INPLACE、COPY算法差异
java·服务器·前端·数据库·mysql·算法·面试