CSS 和 JavaScript 两种方式实现 div 右移 1000px 动画
Category(分类): CSS Status: 已整理
本文演示如何让一个 div 向右移动 1000px,并比较 CSS 动画和 JavaScript 动画的实现方式。
需要先区分几个概念:
animation可以在样式应用后按关键帧自动播放,通常不需要用户事件触发;transition依赖计算样式发生变化,通常需要通过类名、伪类或 JavaScript 改变状态;transform描述元素的平移、旋转或缩放方式,本身不负责随时间播放;- JavaScript 可以通过定时器或
requestAnimationFrame按时间计算动画进度。
一、使用 CSS animation 实现
1. 完整示例
<div class="box"></div>
.box {
width: 100px;
height: 100px;
background-color: skyblue;
animation: move-right 1s linear forwards;
}
@keyframes move-right {
from {
transform: translateX(0);
}
to {
transform: translateX(1000px);
}
}
2. 代码说明
animation: move-right 1s linear forwards;
各部分含义如下:
move-right:要使用的@keyframes动画名称;1s:动画持续时间;linear:匀速运动;forwards:动画结束后保留最后一个关键帧的样式。
如果省略 forwards,动画结束后通常会恢复到动画开始前的样式。animation 默认播放一次,因此这个示例会在元素应用样式后自动向右移动一次。
也可以使用百分比书写关键帧:
@keyframes move-right {
0% {
transform: translateX(0);
}
100% {
transform: translateX(1000px);
}
}
animation 的优点是写法简洁、由浏览器管理时间轴,并且可以通过 animation-iteration-count、animation-direction、animation-play-state 等属性实现循环、反向和暂停。
.box {
animation: move-right 1s ease-in-out 0s 2 alternate;
}
上面的写法表示播放两次,并在正向和反向之间交替运行。
二、使用 CSS transition 实现
transition 需要先存在一个状态变化。它不是自动播放的关键帧动画,而是在属性的计算值发生变化时,让元素从起始值平滑过渡到结束值。
<button class="start-button" type="button">开始移动</button>
<div class="transition-box"></div>
.transition-box {
width: 100px;
height: 100px;
background-color: skyblue;
transform: translateX(0);
transition: transform 1s linear;
}
.transition-box.active {
transform: translateX(1000px);
}
const button = document.querySelector('.start-button')
const box = document.querySelector('.transition-box')
button.addEventListener('click', () => {
box.classList.add('active')
})
这里的触发条件不是 transition 自己提供的,而是 JavaScript 给元素添加了 active 类,使 transform 的计算值从 translateX(0) 变为 translateX(1000px)。
也可以使用 :hover 触发:
.transition-box:hover {
transform: translateX(1000px);
}
animation 和 transition 的区别
| 对比项 | animation | transition |
|---|---|---|
| 是否需要状态变化 | 通常不需要,可以自动播放 | 需要计算样式发生变化 |
| 是否使用关键帧 | 使用 @keyframes | 只有起始值和结束值 |
| 是否支持循环 | 支持 | 没有独立的循环次数设置 |
| 是否支持暂停 | 支持 animation-play-state | 没有专用播放状态属性 |
| 适合场景 | 加载动画、循环动画、复杂时间轴 | hover、focus、展开收起等状态变化 |
三、使用 JavaScript 定时器实现
原始示例使用 setInterval 每隔一段时间改变元素的 left 值。下面保留这种实现思路,并修正最终位置会超过 1000px 的问题:
<div class="js-box"></div>
const box = document.querySelector('.js-box')
box.style.width = '100px'
box.style.height = '100px'
box.style.backgroundColor = 'red'
box.style.position = 'relative'
box.style.left = '0px'
const target = 1000
const step = 10
const id = setInterval(() => {
const current = parseFloat(box.style.left) || 0
const next = Math.min(current + step, target)
box.style.left = `${next}px`
if (next >= target) {
clearInterval(id)
}
}, 10)
原示例中的问题
原来的判断方式类似:
if (parseInt(box.style.left) == 1000) {
clearInterval(id)
}
const left = parseInt(box.style.left)
box.style.left = left + 10 + 'px'
当 left 已经等于 1000 时,虽然调用了 clearInterval,但后面的代码仍会继续执行,最终把位置设置为 1010px。使用 Math.min() 可以确保最终位置不会超过目标值。
另外,建议:
- 使用
parseFloat(value, 10)或明确处理空值; - 使用
>=而不是只判断==; - 在达到目标位置后先设置准确值,再清除定时器;
- 不要假设
setInterval(..., 10)一定每 10ms 执行一次。
setInterval 方案的局限
setInterval 的执行时间会受到主线程任务、浏览器调度和页面可见性的影响,因此:
- 回调不一定严格每 10ms 执行;
- 动画总时长不一定准确为 1 秒;
- 固定步长可能造成卡顿或速度不稳定;
- 频繁修改定位属性可能引起布局或绘制更新。
这个方案适合演示 JavaScript 修改样式的基本过程,但不适合作为精确的通用动画方案。
四、使用 requestAnimationFrame 实现 JavaScript 动画
对于 JavaScript 驱动的视觉动画,通常可以使用 requestAnimationFrame。它会请求浏览器在下一次绘制前执行回调,并根据真实经过的时间计算动画进度:
<div class="raf-box"></div>
.raf-box {
width: 100px;
height: 100px;
background-color: seagreen;
}
const box = document.querySelector('.raf-box')
const duration = 1000
const distance = 1000
const start = performance.now()
function animate(timestamp) {
const elapsed = timestamp - start
const progress = Math.min(elapsed / duration, 1)
box.style.transform = `translateX(${distance * progress}px)`
if (progress < 1) {
requestAnimationFrame(animate)
}
}
requestAnimationFrame(animate)
这个版本的特点是:
- 根据实际经过时间计算位置;
- 不依赖固定执行次数;
- 使用
transform,与 CSS 示例的动画属性一致; - 达到
1时自动停止,不会超过1000px。
requestAnimationFrame 通常比使用固定间隔的 setInterval 更适合视觉动画,但它并不能保证回调中的所有代码都高性能。复杂计算、强制同步布局和大量 DOM 操作仍然可能造成卡顿。
五、left 和 transform 的区别
原来的 CSS 示例使用:
transform: translateX(1000px);
JavaScript 示例使用:
box.style.left = '1000px'
两种方式并不是完全等价的:
transform: translateX()
- 通常只改变元素的视觉位置;
- 通常不会改变普通流中其他元素的布局位置;
- 适合位移、旋转和缩放动画;
- 可能创建层叠上下文和新的定位包含块;
- 实际性能仍需结合浏览器和页面场景验证。
left
- 需要元素处于定位状态,例如
position: relative; - 修改的是定位偏移;
- 相对定位元素原本占据的布局空间通常仍然保留;
- 频繁修改时可能引起布局和绘制更新;
- 适合确实需要使用定位偏移的场景。
如果文章要公平比较 CSS 和 JavaScript 两种动画实现,建议两者都使用 transform,只比较“由 CSS 时间轴控制”和“由 JavaScript 时间计算控制”的区别。
六、如何选择实现方式?
适合使用 CSS animation 的情况
- 页面加载后的自动动画;
- 无限循环的加载指示器;
- 关键帧较明确的复杂动画;
- 不需要 JavaScript 参与计算的视觉效果。
适合使用 CSS transition 的情况
- hover、focus 等状态变化;
- 菜单展开和收起;
- 按钮反馈;
- 简单的位移、颜色、透明度和尺寸变化。
适合使用 JavaScript 的情况
- 动画依赖鼠标、触摸或实时数据;
- 需要物理效果、碰撞检测或复杂计算;
- 动画过程需要由业务逻辑精确控制;
- 需要根据滚动距离或其他外部状态实时更新。
如果只是让元素右移 1000px,通常优先使用 CSS animation 或 transition。只有在动画过程需要 JavaScript 实时参与时,才考虑使用 JavaScript 实现。
七、减少动态效果与性能注意事项
部分用户会在系统中启用“减少动态效果”。可以使用媒体查询降低动画效果:
@media (prefers-reduced-motion: reduce) {
.box,
.transition-box,
.raf-box {
animation: none;
transition: none;
}
}
不要仅凭 transform 或 left 的名称绝对判断性能。实际情况还会受到元素大小、阴影、滤镜、页面其他任务、浏览器实现和设备性能影响,应通过浏览器 Performance 工具验证。
总结
animation可以在样式应用后自动播放,不一定需要用户事件触发;transition需要属性的计算值发生变化;transform描述变换方式,本身不负责按时间播放;- JavaScript 可以使用
setInterval实现简单示例,但定时器时间不精确; requestAnimationFrame更适合 JavaScript 驱动的视觉动画;- 使用
Math.min()可以避免动画最终超过目标位置; - CSS 与 JavaScript 示例最好使用相同的属性和时间函数,避免比较条件不一致;
- 简单动画优先使用 CSS,复杂交互和实时计算再使用 JavaScript。