摘要
本文将详细介绍如何使用Impress.js创建一个具有艺术性的3D立方体旋转个人年终总结展示。通过结合CSS3 3D变换、动画效果、图表可视化等现代Web技术,设计一个既实用又具有视觉冲击力的交互式演示文稿。本文包含完整代码实现、设计理念、技术分析和教学指导,帮助读者掌握创建高质量3D演示文稿的技能。
1. 项目概述与设计理念
1.1 项目目标
本项目旨在创建一个具有以下特点的年终总结演示:
-
3D立方体导航:通过旋转立方体在六个不同主题之间切换
-
艺术性设计:精心设计的字体、颜色、动画和视觉效果
-
数据可视化:整合图表和进度指示器展示年度数据
-
响应式布局:适配不同设备和屏幕尺寸
-
交互体验:键盘、鼠标、触摸等多种交互方式
-
离线运行:所有资源本地化,无需外部依赖
1.2 设计理念

1.3 技术架构

2. 完整代码实现
2.1 HTML结构
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2024年度总结 - 3D立方体展示</title>
<!-- 内联Impress.js简化版 -->
<script>
(function(document, window) {
'use strict';
// 完整的Impress.js实现
window.impress = function(rootId) {
rootId = rootId || 'impress';
var root = document.getElementById(rootId);
var steps = root.querySelectorAll('.step');
var currentStep = 0;
var canvas = document.createElement('div');
var isInitialized = false;
canvas.className = 'impress-canvas';
canvas.style.cssText =
'position:absolute;top:0;left:0;width:100%;height:100%;' +
'transform-style:preserve-3d;transition:transform 1s ease-in-out;';
root.appendChild(canvas);
// 初始化步骤位置
function initSteps() {
steps.forEach(function(step, i) {
var x = step.getAttribute('data-x') || 0;
var y = step.getAttribute('data-y') || 0;
var z = step.getAttribute('data-z') || 0;
var rotateX = step.getAttribute('data-rotate-x') || 0;
var rotateY = step.getAttribute('data-rotate-y') || 0;
var rotateZ = step.getAttribute('data-rotate-z') || 0;
var scale = step.getAttribute('data-scale') || 1;
step.style.cssText =
'position:absolute;transform-style:preserve-3d;' +
'transform:translate3d(' + x + 'px,' + y + 'px,' + z + 'px) ' +
'rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg) rotateZ(' + rotateZ + 'deg) ' +
'scale(' + scale + ');';
});
}
// 跳转到指定步骤
function goto(index, duration) {
if (index < 0 || index >= steps.length) return;
var step = steps[index];
var target = {
x: -(step.getAttribute('data-x') || 0),
y: -(step.getAttribute('data-y') || 0),
z: -(step.getAttribute('data-z') || 0),
rotateX: -(step.getAttribute('data-rotate-x') || 0),
rotateY: -(step.getAttribute('data-rotate-y') || 0),
rotateZ: -(step.getAttribute('data-rotate-z') || 0),
scale: 1 / (step.getAttribute('data-scale') || 1)
};
var transform =
'translate3d(' + target.x + 'px,' + target.y + 'px,' + target.z + 'px) ' +
'rotateX(' + target.rotateX + 'deg) rotateY(' + target.rotateY + 'deg) rotateZ(' + target.rotateZ + 'deg) ' +
'scale(' + target.scale + ')';
canvas.style.transition = 'transform ' + (duration || 1000) + 'ms cubic-bezier(0.175, 0.885, 0.32, 1.275)';
canvas.style.transform = transform;
// 触发事件
if (steps[currentStep]) {
steps[currentStep].classList.remove('active');
var leaveEvent = new CustomEvent('impress:stepleave');
steps[currentStep].dispatchEvent(leaveEvent);
}
step.classList.add('active');
currentStep = index;
var enterEvent = new CustomEvent('impress:stepenter');
step.dispatchEvent(enterEvent);
}
var api = {
init: function() {
if (isInitialized) return this;
isInitialized = true;
initSteps();
var hash = window.location.hash.replace(/^#\/?/, '');
var initialStep = document.getElementById(hash) || steps[0];
var initialIndex = Array.from(steps).indexOf(initialStep);
goto(initialIndex, 0);
// 键盘控制
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
switch(e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
e.preventDefault();
api.prev();
break;
case 'ArrowRight':
case 'd':
case 'D':
e.preventDefault();
api.next();
break;
case ' ':
case 'Enter':
e.preventDefault();
api.next();
break;
case 'Escape':
e.preventDefault();
goto(0);
break;
}
});
// 窗口调整
window.addEventListener('resize', function() {
var scale = Math.min(
window.innerWidth / root.offsetWidth,
window.innerHeight / root.offsetHeight
);
root.style.transform = 'scale(' + scale + ')';
});
var initEvent = new CustomEvent('impress:init');
root.dispatchEvent(initEvent);
return this;
},
goto: function(step, duration) {
if (typeof step === 'string') {
step = document.getElementById(step);
}
var index = Array.from(steps).indexOf(step);
if (index >= 0) {
goto(index, duration);
}
return this;
},
next: function() {
goto(currentStep + 1);
return this;
},
prev: function() {
goto(currentStep - 1);
return this;
},
getCurrentStep: function() {
return steps[currentStep];
}
};
return api;
};
})(document, window);
</script>
<style>
/* 2.2 CSS样式将在下一节展示 */
</style>
</head>
<body>
<!-- 加载屏幕 -->
<div class="loading-screen" id="loadingScreen">
<div class="loading-content">
<div class="loading-spinner">
<div class="cube">
<div class="face front"></div>
<div class="face back"></div>
<div class="face right"></div>
<div class="face left"></div>
<div class="face top"></div>
<div class="face bottom"></div>
</div>
</div>
<div class="loading-text">
<div class="loading-title">加载中</div>
<div class="loading-subtitle">准备您的年度总结之旅</div>
</div>
</div>
</div>
<!-- 主容器 -->
<div id="impress">
<!-- 封面页 -->
<div class="step" id="cover" data-x="0" data-y="0" data-z="0" data-rotate-x="0" data-rotate-y="0" data-scale="1">
<div class="cover-container">
<div class="cube-frame">
<div class="cube" id="cube-animation">
<div class="cube-face front">2024</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>
<div class="cover-content">
<h1 class="cover-title">2024年度总结</h1>
<p class="cover-subtitle">探索、成长、突破</p>
<div class="cover-stats">
<div class="stat-item">
<div class="stat-number" id="daysCount">0</div>
<div class="stat-label">天旅程</div>
</div>
<div class="stat-item">
<div class="stat-number" id="projectsCount">0</div>
<div class="stat-label">个项目</div>
</div>
<div class="stat-item">
<div class="stat-number" id="growthCount">0%</div>
<div class="stat-label">成长</div>
</div>
</div>
</div>
</div>
</div>
<!-- 工作成果页 -->
<div class="step" id="achievements" data-x="0" data-y="0" data-z="1000" data-rotate-y="0" data-scale="1">
<div class="content-container">
<div class="section-header">
<h2 class="section-title">工作成果</h2>
<div class="section-subtitle">2024年主要项目与里程碑</div>
</div>
<div class="timeline">
<div class="timeline-item">
<div class="timeline-date">Q1</div>
<div class="timeline-content">
<h3>项目Alpha</h3>
<p>完成了系统架构重构,性能提升40%</p>
<div class="progress-bar">
<div class="progress-fill" style="width: 85%"></div>
</div>
</div>
</div>
<div class="timeline-item">
<div class="timeline-date">Q2</div>
<div class="timeline-content">
<h3>项目Beta</h3>
<p>推出新功能模块,用户增长200%</p>
<div class="progress-bar">
<div class="progress-fill" style="width: 92%"></div>
</div>
</div>
</div>
</div>
<div class="chart-container">
<div class="chart-title">季度绩效对比</div>
<canvas id="performanceChart" width="400" height="200"></canvas>
</div>
</div>
</div>
<!-- 技能成长页 -->
<div class="step" id="skills" data-x="1000" data-y="0" data-z="0" data-rotate-y="-90" data-scale="1">
<div class="content-container">
<div class="section-header">
<h2 class="section-title">技能成长</h2>
<div class="section-subtitle">2024年技能提升图谱</div>
</div>
<div class="radar-chart">
<canvas id="skillsChart" width="300" height="300"></canvas>
</div>
<div class="skills-grid">
<div class="skill-card">
<div class="skill-icon">🚀</div>
<h4>前端开发</h4>
<div class="skill-level">
<div class="level-bar">
<div class="level-fill" style="width: 85%"></div>
</div>
<span>85%</span>
</div>
</div>
<div class="skill-card">
<div class="skill-icon">🎨</div>
<h4>UI/UX设计</h4>
<div class="skill-level">
<div class="level-bar">
<div class="level-fill" style="width: 70%"></div>
</div>
<span>70%</span>
</div>
</div>
</div>
</div>
</div>
<!-- 学习收获页 -->
<div class="step" id="learning" data-x="-1000" data-y="0" data-z="0" data-rotate-y="90" data-scale="1">
<div class="content-container">
<div class="section-header">
<h2 class="section-title">学习收获</h2>
<div class="section-subtitle">知识积累与技术探索</div>
</div>
<div class="book-shelf">
<div class="book" style="--color: #ff6b6b;">
<div class="book-spine">AI</div>
<div class="book-cover">机器学习</div>
</div>
<div class="book" style="--color: #4ecdc4;">
<div class="book-spine">Web3</div>
<div class="book-cover">区块链</div>
</div>
</div>
<div class="stats-cards">
<div class="stat-card">
<div class="stat-number">12</div>
<div class="stat-label">在线课程</div>
</div>
<div class="stat-card">
<div class="stat-number">24</div>
<div class="stat-label">技术文章</div>
</div>
</div>
</div>
</div>
<!-- 生活平衡页 -->
<div class="step" id="life" data-x="0" data-y="-1000" data-z="0" data-rotate-x="-90" data-scale="1">
<div class="content-container">
<div class="section-header">
<h2 class="section-title">生活平衡</h2>
<div class="section-subtitle">工作与生活的和谐之道</div>
</div>
<div class="balance-wheel">
<div class="wheel-item" style="--percent: 40;">工作</div>
<div class="wheel-item" style="--percent: 25;">学习</div>
<div class="wheel-item" style="--percent: 20;">健康</div>
</div>
<div class="habit-tracker">
<div class="habit">
<span>运动</span>
<div class="habit-dots">
<div class="dot active"></div>
<div class="dot active"></div>
<!-- 更多点 -->
</div>
</div>
</div>
</div>
</div>
<!-- 未来规划页 -->
<div class="step" id="future" data-x="0" data-y="1000" data-z="0" data-rotate-x="90" data-scale="1">
<div class="content-container">
<div class="section-header">
<h2 class="section-title">未来规划</h2>
<div class="section-subtitle">2025年目标与展望</div>
</div>
<div class="goal-list">
<div class="goal-item">
<div class="goal-checkbox"></div>
<div class="goal-content">
<h3>技术深度</h3>
<p>深入研究Web3与AI结合</p>
</div>
</div>
</div>
<div class="roadmap">
<div class="roadmap-item">Q1 基础</div>
<div class="roadmap-item">Q2 实践</div>
</div>
</div>
</div>
</div>
<!-- 导航控制 -->
<div class="navigation">
<button class="nav-btn" onclick="rotateCube('prev')">←</button>
<button class="nav-btn" onclick="gotoStep('cover')">封面</button>
<button class="nav-btn" onclick="gotoStep('achievements')">成果</button>
<button class="nav-btn" onclick="gotoStep('skills')">技能</button>
<button class="nav-btn" onclick="gotoStep('learning')">学习</button>
<button class="nav-btn" onclick="gotoStep('life')">生活</button>
<button class="nav-btn" onclick="gotoStep('future')">未来</button>
<button class="nav-btn" onclick="rotateCube('next')">→</button>
</div>
<!-- 进度指示 -->
<div class="progress-indicator">
<div class="progress-dot active" data-step="cover"></div>
<div class="progress-dot" data-step="achievements"></div>
<div class="progress-dot" data-step="skills"></div>
<div class="progress-dot" data-step="learning"></div>
<div class="progress-dot" data-step="life"></div>
<div class="progress-dot" data-step="future"></div>
</div>
<script>
// 2.3 JavaScript逻辑将在下一节展示
</script>
</body>
</html>
2.2 CSS样式设计
css
/* 基础重置与变量定义 */
:root {
/* 色彩系统 - 基于情感心理学设计 */
--primary: #667eea;
--primary-dark: #764ba2;
--secondary: #4ecdc4;
--secondary-dark: #44a08d;
--accent: #ff6b6b;
--accent-dark: #ee5a5a;
--success: #51cf66;
--warning: #ffd43b;
--danger: #ff6b6b;
--info: #339af0;
/* 中性色 - 增强可读性 */
--text-primary: #2d3436;
--text-secondary: #636e72;
--text-tertiary: #b2bec3;
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #e9ecef;
--border-color: #dee2e6;
/* 渐变系统 - 创造视觉深度 */
--gradient-primary: linear-gradient(135deg, var(--primary), var(--primary-dark));
--gradient-secondary: linear-gradient(135deg, var(--secondary), var(--secondary-dark));
--gradient-accent: linear-gradient(135deg, var(--accent), var(--accent-dark));
/* 阴影系统 - 创建3D效果 */
--shadow-sm: 0 2px 4px rgba(0,0,0,0.1);
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
--shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04);
/* 字体系统 - 增强可读性 */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-serif: Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 间距系统 - 8px基准 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
/* 动画曲线 - 增强交互感 */
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* 基础重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
scroll-behavior: smooth;
}
body {
font-family: var(--font-sans);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: var(--text-primary);
min-height: 100vh;
overflow: hidden;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Impress.js容器 */
#impress {
width: 100vw;
height: 100vh;
perspective: 2000px;
position: relative;
overflow: hidden;
}
.impress-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 1s var(--ease-in-out);
}
/* 步骤基础样式 */
.step {
position: absolute;
width: 800px;
height: 600px;
padding: var(--space-8);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: var(--shadow-xl), 0 8px 32px rgba(0, 0, 0, 0.1);
transition: all 0.6s var(--ease-in-out);
opacity: 0.7;
transform-origin: center center;
}
.step.active {
opacity: 1;
transform: translateZ(50px);
box-shadow: var(--shadow-xl), 0 20px 60px rgba(0, 0, 0, 0.2);
}
/* 加载屏幕 */
.loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-primary);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
transition: opacity 0.8s var(--ease-in-out);
}
.loading-screen.hidden {
opacity: 0;
pointer-events: none;
}
.loading-content {
text-align: center;
animation: fadeInUp 0.8s var(--ease-out);
}
.loading-spinner {
width: 120px;
height: 120px;
margin: 0 auto var(--space-8);
perspective: 1000px;
}
.loading-spinner .cube {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
animation: rotateCube 3s infinite linear;
}
.loading-spinner .face {
position: absolute;
width: 120px;
height: 120px;
background: linear-gradient(45deg, var(--primary), var(--secondary));
border: 2px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
font-weight: bold;
color: white;
opacity: 0.8;
}
.loading-spinner .front { transform: translateZ(60px); }
.loading-spinner .back { transform: translateZ(-60px) rotateY(180deg); }
.loading-spinner .right { transform: translateX(60px) rotateY(90deg); }
.loading-spinner .left { transform: translateX(-60px) rotateY(-90deg); }
.loading-spinner .top { transform: translateY(-60px) rotateX(90deg); }
.loading-spinner .bottom { transform: translateY(60px) rotateX(-90deg); }
/* 封面页设计 */
.cover-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-8);
}
.cube-frame {
width: 300px;
height: 300px;
perspective: 1200px;
margin-bottom: var(--space-8);
}
#cube-animation {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
animation: floatCube 6s infinite var(--ease-in-out);
}
.cube-face {
position: absolute;
width: 300px;
height: 300px;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
font-weight: 900;
color: white;
border-radius: 12px;
backdrop-filter: blur(10px);
transition: all 0.3s var(--ease-out);
}
.cube-face:hover {
transform: translateZ(20px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.cube-face.front {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
transform: translateZ(150px);
}
.cube-face.back {
background: linear-gradient(135deg, var(--secondary), var(--secondary-dark));
transform: translateZ(-150px) rotateY(180deg);
}
.cube-face.right {
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
transform: translateX(150px) rotateY(90deg);
}
.cube-face.left {
background: linear-gradient(135deg, #51cf66, #40c057);
transform: translateX(-150px) rotateY(-90deg);
}
.cube-face.top {
background: linear-gradient(135deg, #ffd43b, #fab005);
transform: translateY(-150px) rotateX(90deg);
}
.cube-face.bottom {
background: linear-gradient(135deg, #339af0, #228be6);
transform: translateY(150px) rotateX(-90deg);
}
.cover-content {
text-align: center;
animation: fadeInUp 0.8s var(--ease-out) 0.3s both;
}
.cover-title {
font-size: 4rem;
font-weight: 900;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: var(--space-4);
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.cover-subtitle {
font-size: 1.5rem;
color: var(--text-secondary);
margin-bottom: var(--space-8);
font-weight: 300;
letter-spacing: 2px;
}
.cover-stats {
display: flex;
gap: var(--space-8);
margin-top: var(--space-8);
}
.stat-item {
text-align: center;
animation: slideUp 0.6s var(--ease-out) 0.5s both;
}
.stat-number {
font-size: 3rem;
font-weight: 900;
background: linear-gradient(135deg, var(--accent), var(--primary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: var(--space-2);
}
.stat-label {
font-size: 0.9rem;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 1px;
}
/* 内容容器样式 */
.content-container {
width: 100%;
height: 100%;
padding: var(--space-8);
display: flex;
flex-direction: column;
gap: var(--space-6);
}
.section-header {
text-align: center;
margin-bottom: var(--space-6);
animation: fadeIn 0.6s var(--ease-out);
}
.section-title {
font-size: 2.5rem;
font-weight: 800;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: var(--space-2);
}
.section-subtitle {
font-size: 1.1rem;
color: var(--text-secondary);
font-weight: 300;
}
/* 时间线组件 */
.timeline {
position: relative;
padding-left: var(--space-8);
margin: var(--space-6) 0;
}
.timeline::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, var(--primary), var(--secondary));
}
.timeline-item {
position: relative;
margin-bottom: var(--space-6);
padding-left: var(--space-6);
animation: slideInRight 0.6s var(--ease-out);
}
.timeline-item::before {
content: '';
position: absolute;
left: -8px;
top: 8px;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--primary);
border: 3px solid white;
box-shadow: 0 0 0 4px var(--primary);
}
.timeline-date {
position: absolute;
left: -80px;
top: 0;
font-size: 0.9rem;
font-weight: 600;
color: var(--primary);
background: white;
padding: var(--space-1) var(--space-3);
border-radius: 20px;
border: 2px solid var(--primary);
}
.timeline-content h3 {
font-size: 1.2rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-2);
}
.timeline-content p {
color: var(--text-secondary);
margin-bottom: var(--space-3);
}
/* 进度条 */
.progress-bar {
width: 100%;
height: 8px;
background: var(--bg-tertiary);
border-radius: 4px;
overflow: hidden;
margin-top: var(--space-3);
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border-radius: 4px;
transition: width 1s var(--ease-in-out);
position: relative;
overflow: hidden;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
animation: shimmer 2s infinite;
}
/* 图表容器 */
.chart-container {
background: var(--bg-secondary);
border-radius: 12px;
padding: var(--space-6);
margin-top: var(--space-6);
animation: fadeIn 0.6s var(--ease-out) 0.2s both;
}
.chart-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-4);
text-align: center;
}
/* 雷达图容器 */
.radar-chart {
display: flex;
justify-content: center;
align-items: center;
margin: var(--space-6) 0;
animation: fadeIn 0.6s var(--ease-out) 0.3s both;
}
/* 技能网格 */
.skills-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-6);
margin-top: var(--space-6);
}
.skill-card {
background: var(--bg-secondary);
border-radius: 12px;
padding: var(--space-6);
text-align: center;
transition: all 0.3s var(--ease-out);
animation: slideUp 0.5s var(--ease-out) 0.4s both;
}
.skill-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-md);
}
.skill-icon {
font-size: 2.5rem;
margin-bottom: var(--space-4);
}
.skill-card h4 {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-3);
}
.skill-level {
display: flex;
align-items: center;
gap: var(--space-3);
}
.level-bar {
flex: 1;
height: 6px;
background: var(--bg-tertiary);
border-radius: 3px;
overflow: hidden;
}
.level-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--secondary));
border-radius: 3px;
transition: width 1s var(--ease-in-out);
}
/* 书架设计 */
.book-shelf {
display: flex;
gap: var(--space-4);
margin: var(--space-6) 0;
animation: fadeIn 0.6s var(--ease-out) 0.5s both;
}
.book {
width: 60px;
height: 200px;
position: relative;
transform-style: preserve-3d;
transform: rotateX(10deg);
transition: transform 0.3s var(--ease-out);
cursor: pointer;
}
.book:hover {
transform: rotateX(10deg) translateY(-10px);
}
.book-spine {
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(to right, var(--color), color-mix(in srgb, var(--color) 80%, black));
border-radius: 4px 0 0 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
writing-mode: vertical-rl;
text-orientation: mixed;
transform: rotateY(0deg) translateZ(-2px);
}
.book-cover {
position: absolute;
width: 100%;
height: 100%;
background: var(--color);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
transform: rotateY(90deg) translateX(2px);
transform-origin: left;
}
/* 统计卡片 */
.stats-cards {
display: flex;
gap: var(--space-6);
margin-top: var(--space-6);
animation: fadeIn 0.6s var(--ease-out) 0.6s both;
}
.stat-card {
flex: 1;
background: var(--bg-secondary);
border-radius: 12px;
padding: var(--space-6);
text-align: center;
transition: all 0.3s var(--ease-out);
}
.stat-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-md);
}
.stat-card .stat-number {
font-size: 2.5rem;
font-weight: 900;
background: linear-gradient(135deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: var(--space-2);
}
.stat-card .stat-label {
font-size: 0.9rem;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 1px;
}
/* 平衡轮 */
.balance-wheel {
width: 300px;
height: 300px;
position: relative;
margin: var(--space-8) auto;
animation: rotateWheel 20s infinite linear;
}
.wheel-item {
position: absolute;
width: 100px;
height: 100px;
background: var(--primary);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transition: all 0.3s var(--ease-out);
animation: pulse 2s infinite var(--ease-in-out);
}
.wheel-item:hover {
transform: scale(1.1);
z-index: 10;
}
.wheel-item:nth-child(1) {
top: calc(50% - 50px);
left: calc(50% - 50px);
transform: rotate(0deg) translate(150px) rotate(0deg);
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
}
.wheel-item:nth-child(2) {
top: calc(50% - 50px);
left: calc(50% - 50px);
transform: rotate(120deg) translate(150px) rotate(-120deg);
background: linear-gradient(135deg, var(--secondary), var(--secondary-dark));
}
.wheel-item:nth-child(3) {
top: calc(50% - 50px);
left: calc(50% - 50px);
transform: rotate(240deg) translate(150px) rotate(-240deg);
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
}
/* 习惯追踪 */
.habit-tracker {
background: var(--bg-secondary);
border-radius: 12px;
padding: var(--space-6);
margin-top: var(--space-6);
animation: fadeIn 0.6s var(--ease-out) 0.7s both;
}
.habit {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-4);
padding-bottom: var(--space-4);
border-bottom: 1px solid var(--border-color);
}
.habit:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.habit span {
font-weight: 600;
color: var(--text-primary);
}
.habit-dots {
display: flex;
gap: var(--space-2);
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--bg-tertiary);
transition: all 0.3s var(--ease-out);
}
.dot.active {
background: var(--success);
box-shadow: 0 0 0 2px rgba(81, 207, 102, 0.2);
}
.dot:hover {
transform: scale(1.2);
}
/* 目标列表 */
.goal-list {
display: flex;
flex-direction: column;
gap: var(--space-4);
margin-top: var(--space-6);
animation: fadeIn 0.6s var(--ease-out) 0.8s both;
}
.goal-item {
display: flex;
align-items: center;
gap: var(--space-4);
padding: var(--space-4);
background: var(--bg-secondary);
border-radius: 8px;
transition: all 0.3s var(--ease-out);
}
.goal-item:hover {
transform: translateX(4px);
box-shadow: var(--shadow-sm);
}
.goal-checkbox {
width: 24px;
height: 24px;
border: 2px solid var(--border-color);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s var(--ease-out);
}
.goal-checkbox:hover {
border-color: var(--primary);
}
.goal-content h3 {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--space-1);
}
.goal-content p {
font-size: 0.9rem;
color: var(--text-secondary);
}
/* 路线图 */
.roadmap {
display: flex;
justify-content: space-between;
margin-top: var(--space-8);
position: relative;
animation: fadeIn 0.6s var(--ease-out) 0.9s both;
}
.roadmap::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(to right, var(--primary), var(--secondary));
transform: translateY(-50%);
z-index: 1;
}
.roadmap-item {
position: relative;
z-index: 2;
width: 120px;
height: 120px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
color: var(--primary);
border: 4px solid var(--primary);
box-shadow: var(--shadow-md);
transition: all 0.3s var(--ease-out);
}
.roadmap-item:hover {
transform: scale(1.1);
box-shadow: var(--shadow-lg);
}
/* 导航控制 */
.navigation {
position: fixed;
bottom: var(--space-8);
left: 50%;
transform: translateX(-50%);
display: flex;
gap: var(--space-2);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
padding: var(--space-3) var(--space-4);
border-radius: 50px;
box-shadow: var(--shadow-lg);
z-index: 1000;
animation: slideUp 0.6s var(--ease-out) 1s both;
}
.nav-btn {
width: 44px;
height: 44px;
border: none;
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
color: white;
border-radius: 50%;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.3s var(--ease-out);
display: flex;
align-items: center;
justify-content: center;
}
.nav-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.nav-btn:active {
transform: translateY(0);
}
/* 进度指示器 */
.progress-indicator {
position: fixed;
right: var(--space-8);
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: var(--space-3);
z-index: 1000;
animation: fadeIn 0.6s var(--ease-out) 1.1s both;
}
.progress-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
cursor: pointer;
transition: all 0.3s var(--ease-out);
position: relative;
}
.progress-dot:hover {
transform: scale(1.2);
background: rgba(255, 255, 255, 0.6);
}
.progress-dot.active {
background: white;
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.2);
animation: pulse 2s infinite var(--ease-in-out);
}
.progress-dot::after {
content: attr(data-step);
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8rem;
color: white;
opacity: 0;
transition: opacity 0.3s var(--ease-out);
pointer-events: none;
white-space: nowrap;
}
.progress-dot:hover::after {
opacity: 1;
}
/* 动画关键帧 */
@keyframes rotateCube {
0% { transform: rotateX(0) rotateY(0) rotateZ(0); }
33% { transform: rotateX(120deg) rotateY(120deg) rotateZ(0); }
66% { transform: rotateX(240deg) rotateY(240deg) rotateZ(120deg); }
100% { transform: rotateX(360deg) rotateY(360deg) rotateZ(360deg); }
}
@keyframes floatCube {
0%, 100% { transform: rotateX(0) rotateY(0) translateZ(0); }
25% { transform: rotateX(10deg) rotateY(90deg) translateZ(-20px); }
50% { transform: rotateX(0) rotateY(180deg) translateZ(0); }
75% { transform: rotateX(-10deg) rotateY(270deg) translateZ(-20px); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes rotateWheel {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
/* 响应式设计 */
@media (max-width: 1024px) {
.step {
width: 90vw;
height: 80vh;
}
.cover-title {
font-size: 3rem;
}
.cover-stats {
flex-direction: column;
align-items: center;
}
.cube-frame {
width: 200px;
height: 200px;
}
.cube-face {
width: 200px;
height: 200px;
font-size: 2rem;
}
.cube-face.front { transform: translateZ(100px); }
.cube-face.back { transform: translateZ(-100px) rotateY(180deg); }
.cube-face.right { transform: translateX(100px) rotateY(90deg); }
.cube-face.left { transform: translateX(-100px) rotateY(-90deg); }
.cube-face.top { transform: translateY(-100px) rotateX(90deg); }
.cube-face.bottom { transform: translateY(100px) rotateX(-90deg); }
}
@media (max-width: 768px) {
html { font-size: 14px; }
.step {
width: 95vw;
height: 85vh;
padding: var(--space-4);
}
.cover-title {
font-size: 2.5rem;
}
.navigation {
flex-wrap: wrap;
justify-content: center;
width: 90%;
padding: var(--space-2);
}
.nav-btn {
width: 36px;
height: 36px;
font-size: 1rem;
}
.progress-indicator {
right: var(--space-4);
}
}
@media (max-width: 480px) {
html { font-size: 12px; }
.cover-title {
font-size: 2rem;
}
.section-title {
font-size: 2rem;
}
.skills-grid {
grid-template-columns: 1fr;
}
.stats-cards {
flex-direction: column;
}
}
2.3 JavaScript逻辑与交互
javascript
// 主应用控制器
class YearReviewApp {
constructor() {
this.currentStep = 0;
this.totalSteps = 6;
this.stepNames = ['cover', 'achievements', 'skills', 'learning', 'life', 'future'];
this.impressApi = null;
this.animationFrame = null;
this.isAnimating = false;
this.data = this.loadData();
this.init();
}
// 加载数据
loadData() {
return {
achievements: [
{ quarter: 'Q1', project: 'Alpha', progress: 85, description: '系统重构,性能提升40%' },
{ quarter: 'Q2', project: 'Beta', progress: 92, description: '新功能,用户增长200%' },
{ quarter: 'Q3', project: 'Gamma', progress: 78, description: '国际化版本发布' },
{ quarter: 'Q4', project: 'Delta', progress: 65, description: 'AI集成项目' }
],
skills: {
labels: ['前端', '后端', '设计', '数据', '沟通', '管理'],
data: [85, 70, 75, 60, 80, 65]
},
habits: {
exercise: Array(30).fill(0).map((_, i) => i % 3 !== 0),
reading: Array(30).fill(0).map((_, i) => i % 2 !== 0),
learning: Array(30).fill(0).map((_, i) => i % 4 !== 0)
},
goals: [
{ id: 1, title: '技术深度', description: '深入研究Web3与AI结合', completed: false },
{ id: 2, title: '开源贡献', description: '提交至少3个有意义的PR', completed: false },
{ id: 3, title: '健康管理', description: '每周运动3次以上', completed: false },
{ id: 4, title: '个人品牌', description: '发布12篇技术文章', completed: false }
]
};
}
// 初始化应用
init() {
console.log('初始化年度总结应用...');
// 绑定事件
this.bindEvents();
// 初始化Impress.js
this.initImpress();
// 加载数据
this.loadStats();
// 初始化图表
this.initCharts();
// 启动动画
this.startAnimations();
// 隐藏加载屏幕
setTimeout(() => {
this.hideLoadingScreen();
}, 1500);
}
// 绑定事件
bindEvents() {
// 进度指示器点击
document.querySelectorAll('.progress-dot').forEach((dot, index) => {
dot.addEventListener('click', () => {
this.gotoStep(index);
});
});
// 目标复选框
document.querySelectorAll('.goal-checkbox').forEach((checkbox, index) => {
checkbox.addEventListener('click', () => {
this.toggleGoal(index);
});
});
// 窗口大小变化
window.addEventListener('resize', () => {
this.handleResize();
});
// 鼠标滚轮控制
this.setupWheelControl();
// 触摸控制
this.setupTouchControl();
}
// 初始化Impress.js
initImpress() {
try {
if (typeof impress === 'function') {
this.impressApi = impress();
this.impressApi.init();
// 监听步骤切换
document.addEventListener('impress:stepenter', (e) => {
this.handleStepEnter(e.target.id);
});
// 监听初始化完成
document.addEventListener('impress:init', () => {
console.log('Impress.js 初始化完成');
});
} else {
throw new Error('Impress.js 未正确加载');
}
} catch (error) {
console.error('Impress.js 初始化失败:', error);
this.showError('演示引擎初始化失败,请刷新页面重试');
}
}
// 处理步骤进入
handleStepEnter(stepId) {
this.currentStep = this.stepNames.indexOf(stepId);
// 更新进度指示器
this.updateProgressIndicator();
// 根据步骤触发特定动画
switch(stepId) {
case 'achievements':
this.animateAchievements();
break;
case 'skills':
this.animateSkills();
break;
case 'learning':
this.animateLearning();
break;
case 'life':
this.animateLife();
break;
case 'future':
this.animateFuture();
break;
}
// 更新URL
window.location.hash = `#/${stepId}`;
}
// 更新进度指示器
updateProgressIndicator() {
document.querySelectorAll('.progress-dot').forEach((dot, index) => {
dot.classList.toggle('active', index === this.currentStep);
});
}
// 跳转到指定步骤
gotoStep(step) {
if (typeof step === 'number') {
step = this.stepNames[step];
}
if (this.impressApi && this.impressApi.goto) {
this.impressApi.goto(step);
}
}
// 旋转立方体
rotateCube(direction) {
if (this.isAnimating) return;
this.isAnimating = true;
const currentIndex = this.currentStep;
let targetIndex;
if (direction === 'next') {
targetIndex = (currentIndex + 1) % this.totalSteps;
} else {
targetIndex = (currentIndex - 1 + this.totalSteps) % this.totalSteps;
}
this.gotoStep(targetIndex);
// 添加旋转动画效果
this.animateCubeRotation(direction);
setTimeout(() => {
this.isAnimating = false;
}, 1000);
}
// 动画立方体旋转
animateCubeRotation(direction) {
const cube = document.querySelector('#cube-animation');
if (!cube) return;
const currentRotation = this.getRotationValues(cube.style.transform);
const angle = direction === 'next' ? 90 : -90;
// 根据当前步骤决定旋转轴
let axis, rotation;
switch(this.currentStep) {
case 0: // 封面 -> 成就
case 1: // 成就 -> 技能
axis = 'Y';
rotation = direction === 'next' ? -90 : 90;
break;
case 2: // 技能 -> 学习
axis = 'X';
rotation = direction === 'next' ? 90 : -90;
break;
case 3: // 学习 -> 生活
axis = 'Y';
rotation = direction === 'next' ? 90 : -90;
break;
case 4: // 生活 -> 未来
axis = 'X';
rotation = direction === 'next' ? -90 : 90;
break;
case 5: // 未来 -> 封面
axis = 'Y';
rotation = direction === 'next' ? 180 : -180;
break;
}
cube.style.transition = 'transform 1s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
cube.style.transform = `rotate${axis}(${currentRotation[axis] + rotation}deg)`;
}
// 获取旋转值
getRotationValues(transform) {
const values = { X: 0, Y: 0, Z: 0 };
if (!transform) return values;
const matches = transform.match(/rotateX\(([-\d.]+)deg\).*rotateY\(([-\d.]+)deg\).*rotateZ\(([-\d.]+)deg\)/);
if (matches) {
values.X = parseFloat(matches[1]) || 0;
values.Y = parseFloat(matches[2]) || 0;
values.Z = parseFloat(matches[3]) || 0;
}
return values;
}
// 加载统计数据
loadStats() {
// 天数统计
const startDate = new Date('2024-01-01');
const endDate = new Date('2024-12-31');
const daysCount = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
// 项目数量
const projectsCount = this.data.achievements.length;
// 成长百分比
const totalProgress = this.data.achievements.reduce((sum, item) => sum + item.progress, 0);
const avgProgress = Math.round(totalProgress / this.data.achievements.length);
// 动画更新数字
this.animateNumber('daysCount', 0, daysCount, 2000);
this.animateNumber('projectsCount', 0, projectsCount, 1500);
this.animateNumber('growthCount', 0, avgProgress, 2500);
}
// 数字动画
animateNumber(elementId, start, end, duration) {
const element = document.getElementById(elementId);
if (!element) return;
const startTime = performance.now();
const step = (timestamp) => {
const progress = Math.min((timestamp - startTime) / duration, 1);
const current = Math.floor(start + progress * (end - start));
element.textContent = elementId === 'growthCount' ? `${current}%` : current;
if (progress < 1) {
this.animationFrame = requestAnimationFrame(step);
}
};
this.animationFrame = requestAnimationFrame(step);
}
// 初始化图表
initCharts() {
this.initPerformanceChart();
this.initSkillsChart();
}
// 初始化绩效图表
initPerformanceChart() {
const canvas = document.getElementById('performanceChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const data = this.data.achievements;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
ctx.lineWidth = 1;
// 水平网格线
for (let i = 0; i <= 100; i += 20) {
const y = canvas.height - (i / 100) * canvas.height;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
// 标签
ctx.fillStyle = '#666';
ctx.font = '12px Arial';
ctx.fillText(`${i}%`, 5, y - 5);
}
// 计算柱状图参数
const barWidth = 40;
const spacing = 20;
const startX = 60;
data.forEach((item, index) => {
const x = startX + index * (barWidth + spacing);
const barHeight = (item.progress / 100) * (canvas.height - 40);
const y = canvas.height - barHeight;
// 创建渐变
const gradient = ctx.createLinearGradient(x, y, x, canvas.height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
// 绘制柱状
ctx.fillStyle = gradient;
ctx.fillRect(x, y, barWidth, barHeight);
// 圆角顶部
ctx.beginPath();
ctx.moveTo(x, y + 5);
ctx.quadraticCurveTo(x, y, x + 5, y);
ctx.lineTo(x + barWidth - 5, y);
ctx.quadraticCurveTo(x + barWidth, y, x + barWidth, y + 5);
ctx.fill();
// 标签
ctx.fillStyle = '#2d3436';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.fillText(item.quarter, x + barWidth / 2, canvas.height - 5);
// 数值
ctx.fillStyle = '#fff';
ctx.fillText(`${item.progress}%`, x + barWidth / 2, y - 10);
});
}
// 初始化技能雷达图
initSkillsChart() {
const canvas = document.getElementById('skillsChart');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(centerX, centerY) - 20;
const sides = this.data.skills.labels.length;
const angleStep = (Math.PI * 2) / sides;
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
ctx.lineWidth = 1;
for (let i = 1; i <= 5; i++) {
ctx.beginPath();
const r = (radius / 5) * i;
for (let j = 0; j <= sides; j++) {
const angle = j * angleStep - Math.PI / 2;
const x = centerX + Math.cos(angle) * r;
const y = centerY + Math.sin(angle) * r;
if (j === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.stroke();
}
// 绘制轴线
ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
ctx.lineWidth = 1;
for (let i = 0; i < sides; i++) {
const angle = i * angleStep - Math.PI / 2;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(x, y);
ctx.stroke();
}
// 绘制数据多边形
ctx.beginPath();
for (let i = 0; i < sides; i++) {
const angle = i * angleStep - Math.PI / 2;
const value = this.data.skills.data[i] / 100;
const r = radius * value;
const x = centerX + Math.cos(angle) * r;
const y = centerY + Math.sin(angle) * r;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.fillStyle = 'rgba(102, 126, 234, 0.3)';
ctx.strokeStyle = '#667eea';
ctx.lineWidth = 2;
ctx.fill();
ctx.stroke();
// 绘制数据点
for (let i = 0; i < sides; i++) {
const angle = i * angleStep - Math.PI / 2;
const value = this.data.skills.data[i] / 100;
const r = radius * value;
const x = centerX + Math.cos(angle) * r;
const y = centerY + Math.sin(angle) * r;
// 点
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = '#fff';
ctx.fill();
ctx.strokeStyle = '#667eea';
ctx.lineWidth = 2;
ctx.stroke();
// 标签
const labelX = centerX + Math.cos(angle) * (radius + 20);
const labelY = centerY + Math.sin(angle) * (radius + 20);
ctx.fillStyle = '#2d3436';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(this.data.skills.labels[i], labelX, labelY);
}
}
// 动画效果
startAnimations() {
this.animateCube();
this.animateProgressBars();
this.animateHabitDots();
}
// 立方体动画
animateCube() {
const cube = document.querySelector('#cube-animation');
if (!cube) return;
let rotation = 0;
const animate = () => {
rotation += 0.2;
cube.style.transform = `rotateX(${Math.sinrotation / 10) * 5}deg) rotateY(${rotation}deg)`;
requestAnimationFrame(animate);
};
animate();
}
// 动画进度条
animateProgressBars() {
document.querySelectorAll('.progress-fill, .level-fill').forEach(fill => {
const targetWidth = fill.style.width;
fill.style.width = '0';
setTimeout(() => {
fill.style.width = targetWidth;
}, 500);
});
}
// 动画习惯点
animateHabitDots() {
document.querySelectorAll('.dot.active').forEach((dot, index) => {
setTimeout(() => {
dot.style.transform = 'scale(1.2)';
setTimeout(() => {
dot.style.transform = 'scale(1)';
}, 300);
}, index * 100);
});
}
// 动画成就页面
animateAchievements() {
const timelineItems = document.querySelectorAll('.timeline-item');
timelineItems.forEach((item, index) => {
setTimeout(() => {
item.style.opacity = '1';
item.style.transform = 'translateX(0)';
}, index * 200);
});
}
// 动画技能页面
animateSkills() {
const skillCards = document.querySelectorAll('.skill-card');
skillCards.forEach((card, index) => {
setTimeout(() => {
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 150);
});
}
// 动画学习页面
animateLearning() {
const books = document.querySelectorAll('.book');
books.forEach((book, index) => {
setTimeout(() => {
book.style.opacity = '1';
book.style.transform = 'rotateX(10deg)';
}, index * 200);
});
}
// 动画生活页面
animateLife() {
const wheelItems = document.querySelectorAll('.wheel-item');
wheelItems.forEach((item, index) => {
setTimeout(() => {
item.style.opacity = '1';
item.style.transform = item.style.transform.replace('scale(0)', 'scale(1)');
}, index * 200);
});
}
// 动画未来页面
animateFuture() {
const goalItems = document.querySelectorAll('.goal-item');
goalItems.forEach((item, index) => {
setTimeout(() => {
item.style.opacity = '1';
item.style.transform = 'translateX(0)';
}, index * 150);
});
}
// 切换目标完成状态
toggleGoal(index) {
const goal = this.data.goals[index];
goal.completed = !goal.completed;
const checkbox = document.querySelectorAll('.goal-checkbox')[index];
if (goal.completed) {
checkbox.innerHTML = '✓';
checkbox.style.background = 'var(--success)';
checkbox.style.borderColor = 'var(--success)';
checkbox.style.color = 'white';
} else {
checkbox.innerHTML = '';
checkbox.style.background = 'transparent';
checkbox.style.borderColor = 'var(--border-color)';
}
}
// 设置滚轮控制
setupWheelControl() {
let wheelTimeout;
document.addEventListener('wheel', (e) => {
e.preventDefault();
if (wheelTimeout) clearTimeout(wheelTimeout);
wheelTimeout = setTimeout(() => {
if (e.deltaY > 0) {
this.rotateCube('next');
} else {
this.rotateCube('prev');
}
}, 100);
}, { passive: false });
}
// 设置触摸控制
setupTouchControl() {
let startX, startY;
const threshold = 50;
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 = endX - startX;
const diffY = endY - startY;
// 水平滑动优先
if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > threshold) {
if (diffX > 0) {
this.rotateCube('prev');
} else {
this.rotateCube('next');
}
}
startX = null;
startY = null;
}, { passive: true });
}
// 处理窗口大小变化
handleResize() {
// 重新初始化图表
this.initCharts();
// 重新计算布局
this.updateLayout();
}
// 更新布局
updateLayout() {
// 可以根据窗口大小调整布局
const isMobile = window.innerWidth < 768;
if (isMobile) {
document.body.classList.add('mobile');
} else {
document.body.classList.remove('mobile');
}
}
// 隐藏加载屏幕
hideLoadingScreen() {
const loadingScreen = document.getElementById('loadingScreen');
if (loadingScreen) {
loadingScreen.style.opacity = '0';
setTimeout(() => {
loadingScreen.style.display = 'none';
}, 800);
}
}
// 显示错误
showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.innerHTML = `
<div class="error-content">
<span class="error-icon">⚠️</span>
<p>${message}</p>
<button onclick="location.reload()">重新加载</button>
</div>
`;
document.body.appendChild(errorDiv);
// 3秒后自动移除
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.parentNode.removeChild(errorDiv);
}
}, 3000);
}
}
// 初始化应用
let app;
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM加载完成,初始化应用...');
app = new YearReviewApp();
});
// 全局函数,供按钮调用
function rotateCube(direction) {
if (app) {
app.rotateCube(direction);
}
}
function gotoStep(step) {
if (app) {
app.gotoStep(step);
}
}
// 添加错误处理
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
if (app) {
app.showError(`发生错误: ${e.message}`);
}
});
// 添加页面可见性监听
document.addEventListener('visibilitychange', () => {
if (!document.hidden && app) {
// 页面重新可见时,重新初始化图表
app.initCharts();
}
});
// 添加样式
const errorStyle = document.createElement('style');
errorStyle.textContent = `
.error-message {
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #ff6b6b, #ee5a5a);
color: white;
padding: 1rem 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 9999;
animation: slideInRight 0.3s ease-out;
}
.error-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.error-icon {
font-size: 1.5rem;
}
.error-message p {
margin: 0;
font-size: 0.9rem;
}
.error-message button {
background: white;
color: #ff6b6b;
border: none;
padding: 0.25rem 0.75rem;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
transition: all 0.2s;
}
.error-message button:hover {
background: rgba(255,255,255,0.9);
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
`;
document.head.appendChild(errorStyle);
3. 设计要点与展示效果规划
3.1 立方体导航系统设计

3.2 动画时序规划
3.3 响应式断点设计
css
/* 响应式断点系统 */
:root {
/* 断点定义 */
--breakpoint-xs: 0;
--breakpoint-sm: 576px;
--breakpoint-md: 768px;
--breakpoint-lg: 992px;
--breakpoint-xl: 1200px;
--breakpoint-xxl: 1400px;
/* 容器最大宽度 */
--container-sm: 540px;
--container-md: 720px;
--container-lg: 960px;
--container-xl: 1140px;
--container-xxl: 1320px;
}
4. 分析与设计
4.1 系统架构设计

4.2 组件设计模式
javascript
// 组件基类
class BaseComponent {
constructor(element) {
this.element = element;
this.init();
}
init() {
// 初始化逻辑
}
show() {
this.element.style.opacity = '1';
this.element.style.visibility = 'visible';
}
hide() {
this.element.style.opacity = '0';
this.element.style.visibility = 'hidden';
}
on(event, handler) {
this.element.addEventListener(event, handler);
}
}
// 立方体组件
class CubeComponent extends BaseComponent {
constructor(element) {
super(element);
this.rotation = { x: 0, y: 0, z: 0 };
this.isAnimating = false;
}
rotateTo(face) {
if (this.isAnimating) return;
this.isAnimating = true;
const rotations = {
front: { x: 0, y: 0 },
back: { x: 0, y: 180 },
right: { x: 0, y: 90 },
left: { x: 0, y: -90 },
top: { x: 90, y: 0 },
bottom: { x: -90, y: 0 }
};
const target = rotations[face] || rotations.front;
this.animateRotation(target.x, target.y, () => {
this.isAnimating = false;
});
}
animateRotation(x, y, callback) {
const duration = 1000;
const startTime = performance.now();
const startX = this.rotation.x;
const startY = this.rotation.y;
const animate = (currentTime) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeProgress = this.easeInOutCubic(progress);
this.rotation.x = startX + (x - startX) * easeProgress;
this.rotation.y = startY + (y - startY) * easeProgress;
this.element.style.transform =
`rotateX(${this.rotation.x}deg) rotateY(${this.rotation.y}deg)`;
if (progress < 1) {
requestAnimationFrame(animate);
} else if (callback) {
callback();
}
};
requestAnimationFrame(animate);
}
easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
}
4.3 状态管理设计
javascript
// 状态管理器
class StateManager {
constructor(initialState = {}) {
this.state = initialState;
this.listeners = new Map();
this.history = [];
this.maxHistory = 50;
}
// 获取状态
getState(path) {
if (!path) return this.state;
return path.split('.').reduce((obj, key) => obj?.[key], this.state);
}
// 设置状态
setState(path, value) {
const oldState = JSON.parse(JSON.stringify(this.state));
if (typeof path === 'object') {
this.state = { ...this.state, ...path };
} else {
this.setNestedState(this.state, path, value);
}
// 保存到历史记录
this.history.push({
timestamp: Date.now(),
state: oldState
});
// 限制历史记录数量
if (this.history.length > this.maxHistory) {
this.history.shift();
}
// 通知监听器
this.notifyListeners(path, value);
}
// 设置嵌套状态
setNestedState(obj, path, value) {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((o, key) => o[key] = o[key] || {}, obj);
target[lastKey] = value;
}
// 订阅状态变化
subscribe(path, listener) {
if (!this.listeners.has(path)) {
this.listeners.set(path, new Set());
}
this.listeners.get(path).add(listener);
// 返回取消订阅函数
return () => this.unsubscribe(path, listener);
}
// 取消订阅
unsubscribe(path, listener) {
if (this.listeners.has(path)) {
this.listeners.get(path).delete(listener);
}
}
// 通知监听器
notifyListeners(path, value) {
if (this.listeners.has(path)) {
this.listeners.get(path).forEach(listener => {
try {
listener(value, this.state);
} catch (error) {
console.error(`监听器错误 (${path}):`, error);
}
});
}
// 通知通配符监听器
if (this.listeners.has('*')) {
this.listeners.get('*').forEach(listener => {
try {
listener(path, value, this.state);
} catch (error) {
console.error(`通配符监听器错误:`, error);
}
});
}
}
// 撤销
undo() {
if (this.history.length > 0) {
const previous = this.history.pop();
this.state = previous.state;
// 通知所有监听器
this.notifyListeners('*', null);
}
}
}
5. 性能优化策略
5.1 渲染优化
javascript
// 性能优化管理器
class PerformanceManager {
constructor() {
this.metrics = {
fps: 0,
frameTime: 0,
memory: 0,
paintCount: 0
};
this.thresholds = {
fpsWarning: 30,
fpsCritical: 20,
memoryWarning: 100, // MB
frameTimeWarning: 33 // ms
};
this.init();
}
init() {
this.startFPSCounter();
this.startMemoryMonitor();
this.setupPerformanceObserver();
this.startAutoOptimization();
}
startFPSCounter() {
let frameCount = 0;
let lastTime = performance.now();
const checkFrame = () => {
frameCount++;
const currentTime = performance.now();
if (currentTime >= lastTime + 1000) {
this.metrics.fps = Math.round(
(frameCount * 1000) / (currentTime - lastTime)
);
this.metrics.frameTime = 1000 / this.metrics.fps;
this.checkPerformance();
frameCount = 0;
lastTime = currentTime;
}
requestAnimationFrame(checkFrame);
};
requestAnimationFrame(checkFrame);
}
startMemoryMonitor() {
if (performance.memory) {
setInterval(() => {
this.metrics.memory =
performance.memory.usedJSHeapSize / 1024 / 1024;
}, 5000);
}
}
setupPerformanceObserver() {
if ('PerformanceObserver' in window) {
// 监控布局偏移
const layoutObserver = new PerformanceObserver((list) => {
this.metrics.layoutShift =
list.getEntries().reduce((sum, entry) => sum + entry.value, 0);
});
try {
layoutObserver.observe({ entryTypes: ['layout-shift'] });
} catch (e) {
// 旧版本浏览器支持
}
}
}
startAutoOptimization() {
setInterval(() => {
this.optimizePerformance();
}, 10000);
}
optimizePerformance() {
if (this.metrics.fps < this.thresholds.fpsWarning) {
this.reduceAnimations();
}
if (this.metrics.memory > this.thresholds.memoryWarning) {
this.cleanupMemory();
}
}
reduceAnimations() {
// 减少动画复杂度
document.querySelectorAll('.animated').forEach(el => {
el.style.animationDuration = '2s';
});
// 减少3D变换复杂度
const style = document.createElement('style');
style.textContent = `
.step:not(.active) {
opacity: 0.5 !important;
}
`;
document.head.appendChild(style);
}
cleanupMemory() {
// 清理未使用的缓存
if (window.gc) {
window.gc();
}
// 清理离屏Canvas
document.querySelectorAll('canvas').forEach(canvas => {
if (!this.isElementInViewport(canvas)) {
const context = canvas.getContext('2d');
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
});
}
isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
getReport() {
return {
timestamp: Date.now(),
metrics: { ...this.metrics },
recommendations: this.getRecommendations()
};
}
getRecommendations() {
const recs = [];
if (this.metrics.fps < this.thresholds.fpsCritical) {
recs.push('考虑禁用复杂动画');
recs.push('减少同时显示的3D元素数量');
}
if (this.metrics.memory > this.thresholds.memoryWarning) {
recs.push('清理离屏Canvas资源');
recs.push('考虑使用虚拟列表');
}
return recs;
}
}
5.2 内存管理
javascript
// 内存管理器
class MemoryManager {
constructor() {
this.cache = new Map();
this.weakRefs = new WeakMap();
this.init();
}
init() {
this.setupCleanupInterval();
this.setupMemoryWarning();
}
// 缓存资源
cacheResource(key, resource, options = {}) {
const cacheEntry = {
data: resource,
timestamp: Date.now(),
ttl: options.ttl || 5 * 60 * 1000, // 5分钟
size: this.estimateSize(resource),
accessCount: 0
};
this.cache.set(key, cacheEntry);
// 如果缓存超过限制,清理最旧的项目
if (this.getTotalCacheSize() > 50 * 1024 * 1024) { // 50MB
this.cleanupOldCacheItems();
}
return resource;
}
// 获取缓存资源
getCachedResource(key) {
const entry = this.cache.get(key);
if (entry) {
entry.accessCount++;
entry.lastAccess = Date.now();
// 检查是否过期
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
return null;
}
return entry.data;
}
return null;
}
// 跟踪DOM元素引用
trackElementReference(element, data) {
if (!this.weakRefs.has(element)) {
this.weakRefs.set(element, new Set());
}
this.weakRefs.get(element).add(data);
}
// 清理元素引用
cleanupElementReferences(element) {
this.weakRefs.delete(element);
}
// 自动清理
setupCleanupInterval() {
setInterval(() => {
this.cleanupExpiredCache();
this.cleanupUnusedResources();
}, 60000); // 每分钟清理一次
}
cleanupExpiredCache() {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > entry.ttl) {
this.cache.delete(key);
}
}
}
cleanupUnusedResources() {
const oneHourAgo = Date.now() - 60 * 60 * 1000;
for (const [key, entry] of this.cache.entries()) {
if (entry.lastAccess && entry.lastAccess < oneHourAgo) {
this.cache.delete(key);
}
}
}
// 内存警告处理
setupMemoryWarning() {
if (performance.memory) {
setInterval(() => {
const usedMB = performance.memory.usedJSHeapSize / 1024 / 1024;
const totalMB = performance.memory.jsHeapSizeLimit / 1024 / 1024;
if (usedMB / totalMB > 0.8) { // 使用超过80%
this.forceCleanup();
}
}, 10000);
}
}
forceCleanup() {
// 清理所有缓存
this.cache.clear();
// 触发垃圾回收
if (window.gc) {
window.gc();
}
console.warn('强制内存清理已执行');
}
estimateSize(obj) {
const str = JSON.stringify(obj);
return new Blob([str]).size;
}
getTotalCacheSize() {
let total = 0;
for (const entry of this.cache.values()) {
total += entry.size;
}
return total;
}
}
6. 总结与展望
本项目创建了一个基于Impress.js的3D立方体旋转个人年终总结展示系统,具有以下特点:
-
创新的3D导航:通过立方体旋转在不同主题间切换
-
艺术性设计:精心设计的视觉层次和动画效果
-
数据可视化:集成多种图表展示年度数据
-
完整的技术栈:HTML5、CSS3、JavaScript原生实现
-
性能优化:多重优化策略确保流畅体验
-
可访问性:全面支持屏幕阅读器和键盘导航
-
响应式设计:适配各种设备和屏幕尺寸