看了jquery的animate动画函数,自己也用js写了一个,感觉还不错!
'# 看了jQuery的animate动画函数,自己也用JS写了一个,感觉还不错!
一、背景与问题
在前端开发中,动画效果是提升用户体验的重要手段。jQuery的animate函数因其简单易用而广受欢迎,但其底层实现原理和性能表现值得深入研究。本文将从零开始实现一个功能完备的动画系统,重点分析其工作原理、实现细节和工程实践。
二、基本原理
1. 动画核心机制
动画的本质是通过持续更新元素的CSS属性值,制造视觉上的渐变效果。核心要素包括:
- 属性插值:计算属性从初始值到目标值的中间值
- 时间控制:使用requestAnimationFrame实现流畅动画
- 缓动函数:模拟真实物理运动效果
- 状态管理:跟踪动画的运行状态和当前值
2. 动画系统架构
[用户调用] -> 动画队列管理 -> 动画执行器 -> 属性插值器 -> DOM更新三、环境准备
# 假设使用Node.js环境
npm init -y
npm install --save-dev typescript ts-node// tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "ESNext",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"lib": ["DOM", "ESNext"],
"outDir": "./dist",
"rootDir": "."
},
"include": ["src/**/*"]
}四、核心实现
1. 基础动画函数
// src/animation.ts
type AnimationOptions = {
duration: number;
easing?: (t: number) => number;
complete?: () => void;
};
function animate(element: HTMLElement, properties: Record<string, number>, options: AnimationOptions = {}) {
const { duration = 500, easing = linear, complete } = options;
const startTime = performance.now();
const startValues = getComputedStyle(element);
// 提取需要动画的属性
const animatedProps = Object.entries(properties).filter(([prop]) => {
const value = parseFloat(startValues[prop] || 0);
return !isNaN(value);
}).reduce((acc, [prop, target]) => {
acc[prop] = target;
return acc;
}, {} as Record<string, number>);
const requestAnimationFrameId = requestAnimationFrame(() => {
const now = performance.now();
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
// 计算当前值
const currentValues = Object.entries(animatedProps).reduce((acc, [prop, target]) => {
const start = parseFloat(startValues[prop] || 0);
const eased = easing(progress);
acc[prop] = start + (target - start) * eased;
return acc;
}, {} as Record<string, number>);
// 更新DOM
Object.entries(currentValues).forEach(([prop, value]) => {
element.style.setProperty(prop, `${value}px`);
});
// 动画未完成时继续
if (progress < 1) {
requestAnimationFrameId = requestAnimationFrame(() => {
// 递归调用
});
} else {
complete && complete();
}
});
}2. 缓动函数实现
// src/easing.ts
export const linear = (t: number) => t;
export const easeIn = (t: number) => t * t;
export const easeOut = (t: number) => 1 - Math.pow(1 - t, 2);
export const easeInOut = (t: number) =>
t < 0.5 ? 2 * Math.pow(t, 2) : 1 - Math.pow(1 - t, 2);3. 动画队列管理
// src/animationQueue.ts
class AnimationQueue {
private queue: Array<{ element: HTMLElement; properties: Record<string, number>; options: AnimationOptions }> = [];
private isRunning = false;
public add(element: HTMLElement, properties: Record<string, number>, options: AnimationOptions = {}) {
this.queue.push({ element, properties, options });
}
public run() {
if (!this.isRunning && this.queue.length > 0) {
this.isRunning = true;
const { element, properties, options } = this.queue.shift()!;
animate(element, properties, options);
// 动画完成后继续执行队列
this.isRunning = false;
this.run();
}
}
}五、完整案例
1. 点击按钮实现元素动画
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<style>
#box {
width: 100px;
height: 100px;
background-color: red;
transition: all 0.3s ease-in-out;
}
</style>
</head>
<body>
<button id="animateBtn">开始动画</button>
<div id="box"></div>
<script type="module">
import { animate, linear, easeInOut } from './src/animation.js';
import { AnimationQueue } from './src/animationQueue.js';
const queue = new AnimationQueue();
document.getElementById('animateBtn')!.addEventListener('click', () => {
queue.add(
document.getElementById('box')!,
{
width: 200,
height: 200,
backgroundColor: 'blue'
},
{
duration: 1000,
easing: easeInOut
}
);
queue.run();
});
</script>
</body>
</html>2. 动画队列示例
// src/animationQueue.ts
// 已包含在上文代码中
// 使用示例
queue.add(
document.getElementById('box')!,
{ opacity: 0.5 },
{ duration: 500 }
);
queue.add(
document.getElementById('box')!,
{ transform: 'translateX(100px)' },
{ duration: 500, easing: easeIn }
);
queue.run();六、源码解析
1. 动画执行流程
- 初始化阶段:获取元素当前样式值
- 计算阶段:通过缓动函数计算当前进度
- 更新阶段:将计算后的值写入CSS
- 循环阶段:通过requestAnimationFrame持续执行
2. 关键代码分析
// 动画执行器
const requestAnimationFrameId = requestAnimationFrame(() => {
const now = performance.now();
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
// 计算当前值
const currentValues = Object.entries(animatedProps).reduce((acc, [prop, target]) => {
const start = parseFloat(startValues[prop] || 0);
const eased = easing(progress);
acc[prop] = start + (target - start) * eased;
return acc;
}, {} as Record<string, number>);
// 更新DOM
Object.entries(currentValues).forEach(([prop, value]) => {
element.style.setProperty(prop, `${value}px`);
});
// 动画未完成时继续
if (progress < 1) {
requestAnimationFrameId = requestAnimationFrame(() => {
// 递归调用
});
} else {
complete && complete();
}
});七、进阶使用
1. 复合动画实现
function createCompositeAnimation(
elements: HTMLElement[],
properties: Record<string, number>[],
options: AnimationOptions
) {
const queue = new AnimationQueue();
elements.forEach((element, index) => {
queue.add(
element,
properties[index],
options
);
});
queue.run();
}2. 动画监听器
function addAnimationListener(element: HTMLElement, callback: () => void) {
element.addEventListener('transitionend', () => {
callback();
});
}八、性能与工程实践
1. 性能优化策略
- 避免频繁DOM操作:通过批量更新策略
- 使用CSS变量:
@property规范支持 - 硬件加速:使用
transform和opacity属性 - 内存管理:及时移除动画监听器
2. 异常处理
function safeAnimate(element: HTMLElement, properties: Record<string, number>, options: AnimationOptions = {}) {
try {
// 动画逻辑
} catch (error) {
console.error('动画执行异常:', error);
// 清理资源
}
}3. 安全考虑
- 属性验证:过滤非标准CSS属性
- XSS防护:避免直接拼接CSS值
- 类型校验:使用TypeScript保证类型安全
九、常见问题与踩坑
1. 常见错误
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 动画卡顿 | 使用setInterval导致 | 改用requestAnimationFrame |
| 动画不流畅 | 缓动函数实现错误 | 使用标准缓动函数 |
| 动画重复执行 | 未清除动画队列 | 在动画完成时清空队列 |
| 属性未更新 | CSS属性名拼写错误 | 使用getComputedStyle校验 |
2. 常见陷阱
- CSS属性类型不一致:
width和height需保持相同单位 - 浏览器兼容性:
requestAnimationFrame在IE中不支持 - 动画冲突:多个动画同时执行时的处理
十、最佳实践
1. 推荐方案
- 使用CSS变量:
--size: 100px;便于动态控制 - 分离动画逻辑:创建独立的动画库模块
- 使用TypeScript:强类型保证安全
- 添加动画队列:确保动画顺序执行
- 添加动画状态:支持暂停、重置、清除
2. 代码规范
- 命名规范:
animate,easing,requestAnimationFrame - 类型定义:使用
Record<string, number>表示属性 - 模块划分:分离动画核心、缓动函数、队列管理
十一、总结
通过实现一个完整的动画系统,我们深入理解了动画的底层原理和实现细节。在实际开发中,这种自定义动画方案适用于:
- 需要精细控制动画行为的场景
- 要求性能优化的高性能动画
- 需要自定义缓动函数的特殊需求
但需要注意避免:
- 在简单场景中过度设计
- 在需要兼容IE的项目中使用
- 在处理大量元素时未做性能优化
建议在使用时结合CSS transitions和JavaScript动画,形成混合使用策略。通过合理的设计和优化,可以实现既灵活又高效的动画系统,提升用户体验的同时保证代码质量。
评论已关闭