怎样用JavaScript实现简单的动画效果
在网页开发中,动画效果可以提升用户的交互体验,让页面更生动。JavaScript不需要依赖复杂的第三方库,就可以实现基础的动画效果,比如元素的位移、透明度变化、尺寸变化等。下面我们通过几个实际案例,讲解如何使用原生JavaScript实现简单的动画。
一、动画实现的核心原理
JavaScript实现动画的核心逻辑是通过定时器,每隔一段时间修改元素的样式属性,当修改的次数足够多、间隔足够短时,人眼就会感知到连续的动画效果。常用的定时器有两个:
setInterval:按照指定的时间间隔重复执行回调函数,适合持续运行的动画requestAnimationFrame:浏览器专门为动画提供的API,会在浏览器下一次重绘之前执行回调,动画流畅度更高,性能更好,是更推荐的动画实现方式
二、实现元素水平位移动画
我们先来实现一个最简单的动画:让一个方块的div元素从左到右水平移动。首先准备基础的HTML结构,包含一个用于展示动画的容器和方块元素:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript位移动画</title>
<style>
#container {
width: 500px;
height: 100px;
border: 1px solid #ccc;
position: relative;
}
#box {
width: 50px;
height: 50px;
background-color: #409eff;
position: absolute;
left: 0;
top: 25px;
}
</style>
</head>
<body>
<div id="container">
<div id="box"></div>
</div>
<button id="startBtn">开始动画</button>
<script>
// 获取DOM元素
const box = document.getElementById('box');
const startBtn = document.getElementById('startBtn');
let animationId = null; // 存储动画ID用于停止
let currentLeft = 0; // 当前元素的left值
// 点击按钮开始动画
startBtn.addEventListener('click', () => {
// 如果动画已经在运行,先停止之前的动画
if (animationId) {
cancelAnimationFrame(animationId);
}
currentLeft = 0; // 重置初始位置
// 动画执行函数
function animate() {
currentLeft += 2; // 每次移动2px
box.style.left = currentLeft + 'px'; // 更新元素位置
// 如果还没移动到容器最右侧,继续下一帧动画
if (currentLeft < 450) {
animationId = requestAnimationFrame(animate);
} else {
animationId = null; // 动画结束,清空ID
}
}
// 启动动画
animationId = requestAnimationFrame(animate);
});
</script>
</body>
</html>上面的代码中,我们使用requestAnimationFrame来驱动动画:每次回调执行时,给方块的left值增加2px,然后判断是否到达终点,如果没到就继续注册下一帧的动画。相比setInterval,这种方式会根据浏览器的刷新率自动调整执行频率,不会出现掉帧的情况。
三、实现多属性同步变化的动画
除了位移,我们还可以同时修改多个样式属性,比如让元素在移动的同时,透明度逐渐降低,尺寸逐渐变大。下面是扩展后的代码示例:
// 获取元素
const box = document.getElementById('box');
const startBtn = document.getElementById('startBtn');
let animationId = null;
// 初始属性值
let currentLeft = 0;
let currentOpacity = 1;
let currentSize = 50;
startBtn.addEventListener('click', () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
// 重置所有属性
currentLeft = 0;
currentOpacity = 1;
currentSize = 50;
box.style.left = '0px';
box.style.opacity = '1';
box.style.width = '50px';
box.style.height = '50px';
function animate() {
// 位移变化
currentLeft += 2;
// 透明度变化,每次减少0.02
currentOpacity -= 0.02;
// 尺寸变化,每次增加1px
currentSize += 1;
// 更新样式
box.style.left = currentLeft + 'px';
box.style.opacity = currentOpacity;
box.style.width = currentSize + 'px';
box.style.height = currentSize + 'px';
// 判断动画是否结束:移动到最右侧或者透明度降到0
if (currentLeft < 450 && currentOpacity > 0) {
animationId = requestAnimationFrame(animate);
} else {
animationId = null;
}
}
animationId = requestAnimationFrame(animate);
});这个例子中我们同时操作了left、opacity、width、height四个属性,每个属性按照自己的变化速率同步更新,就能实现复合的动画效果。
四、封装通用的动画函数
如果多个元素都需要动画效果,我们可以把动画逻辑封装成一个通用函数,传入目标元素、要变化的属性、目标值和变化速度,就可以复用动画逻辑:
/**
* 通用动画函数
* @param {HTMLElement} element - 要执行动画的元素
* @param {Object} targetAttrs - 目标属性对象,比如 { left: 450, opacity: 0 }
* @param {number} speed - 变化速度,值越大动画越快
* @param {Function} callback - 动画结束后的回调函数
*/
function animateElement(element, targetAttrs, speed = 2, callback) {
// 存储每个属性的当前值
const currentValues = {};
// 初始化当前值
for (let key in targetAttrs) {
if (key === 'opacity') {
currentValues[key] = parseFloat(getComputedStyle(element)[key]) || 1;
} else {
// 处理px单位的属性,去掉px转成数字
const currentVal = getComputedStyle(element)[key];
currentValues[key] = parseFloat(currentVal) || 0;
}
}
let animationId = null;
function step() {
let isAllDone = true; // 标记是否所有属性都到达目标值
for (let key in targetAttrs) {
const target = targetAttrs[key];
const current = currentValues[key];
// 计算当前值和目标值的差
const diff = target - current;
// 如果差值很小,直接设置到目标值,避免无限循环
if (Math.abs(diff) < speed) {
currentValues[key] = target;
if (key === 'opacity') {
element.style[key] = target;
} else {
element.style[key] = target + 'px';
}
} else {
// 按照速度更新当前值
currentValues[key] += diff > 0 ? speed : -speed;
if (key === 'opacity') {
element.style[key] = currentValues[key];
} else {
element.style[key] = currentValues[key] + 'px';
}
isAllDone = false; // 还有属性没到终点
}
}
// 如果所有属性都到达目标,结束动画,执行回调
if (isAllDone) {
cancelAnimationFrame(animationId);
if (typeof callback === 'function') {
callback();
}
} else {
animationId = requestAnimationFrame(step);
}
}
animationId = requestAnimationFrame(step);
}
// 使用示例
const box = document.getElementById('box');
const startBtn = document.getElementById('startBtn');
startBtn.addEventListener('click', () => {
// 让box移动到left=450,opacity=0.3,宽度变为100px
animateElement(
box,
{ left: 450, opacity: 0.3, width: 100 },
3, // 速度为3
() => {
console.log('动画执行结束');
}
);
});这个通用函数可以适配大部分基础动画场景,不需要每次都重复写动画逻辑,只需要传入对应的参数就可以实现不同的动画效果。
五、注意事项
- 动画执行前最好先清除之前可能存在的动画定时器,避免多个动画同时运行导致样式冲突
- 修改样式时要对应正确的单位,比如位移、尺寸一般带
px,透明度不需要单位 - 如果是需要循环播放的动画,可以在动画结束后重新初始化属性值,再次启动动画即可
- 对于复杂的动画序列,可以结合Promise封装,让动画按顺序执行,逻辑更清晰