平移动画是网页中最常见的动画形式之一,比如轮播图滚动、悬浮提示滑入、加载图标来回移动等。实现平移动画的核心组合就是animation与transform:transform负责让元素产生位移,animation负责控制位移的时间节奏。这篇文章会详细讲解两者如何配合使用,以及实际开发中需要注意的性能与细节问题。

为什么平移要用transform而不是top/left
很多初学者实现位移时习惯直接修改元素的top、left或margin属性,这种做法虽然也能动起来,但性能很差。原因是top、left、margin属于影响布局的属性,每次修改都会触发浏览器的回流(reflow)和重绘(repaint),浏览器需要重新计算整个页面的布局,代价高昂。
而transform属性完全不同,它只影响元素的视觉呈现,不参与布局计算。浏览器渲染时会将元素提升到独立的合成层(compositing layer),由GPU直接处理,主线程的压力大大降低。因此在动画频繁的场景下,使用transform: translate()的动画帧率明显更高,尤其在低端移动设备上差异非常明显。
translate支持两个参数,分别是X轴和Y轴的位移距离,示例如下:
.box {
/* 向右移动100像素,向下移动50像素 */
transform: translate(100px, 50px);
}
/* 也可以只沿一个轴移动 */
.box-x {
transform: translateX(200px);
}
.box-y {
transform: translateY(-30px);
}需要注意,位移是相对于元素自身当前位置的,正值表示向右或向下,负值表示向左或向上。此外还有translate3d(x, y, z)和translateZ(),即使Z值为0,也能强制开启GPU加速,后面性能优化部分会详细说明。
用animation定义完整的平移动画流程
transform只定义了元素在哪里,要让元素自动动起来,还需要@keyframes规则描述动画的各个关键状态,再通过animation属性把关键帧绑定到元素上。一个最简单的从左向右平移的动画如下:
@keyframes slide {
from {
transform: translateX(0);
}
to {
transform: translateX(300px);
}
}
.box {
width: 100px;
height: 100px;
background: #4a90d9;
animation: slide 2s ease-in-out infinite alternate;
}上面代码中,from表示动画起点(等同于0%),to表示终点(等同于100%)。animation的简写属性依次是动画名称、持续时间、缓动函数、延迟、播放次数和方向。infinite表示无限循环,alternate表示往返播放,两者组合就能实现元素来回移动的效果,不需要额外写反向的关键帧。
如果需要更复杂的路径,比如先向右再向下,可以使用百分比定义多个关键帧节点:
@keyframes movePath {
0% { transform: translate(0, 0); }
50% { transform: translate(300px, 0); }
100% { transform: translate(300px, 200px); }
}
.box {
animation: movePath 3s linear forwards;
}这里的forwards表示动画结束后停留在最后一帧的位置,如果不加,元素会瞬间跳回起点。常见的取值还有backwards(在延迟期间应用第一帧样式)和both(同时具备前后两种行为)。另外要特别提醒一点:关键帧中只写transform属性,不要在from和to中混入width、left等其他属性,否则动画过程中会不断触发重排,性能会显著下降。
缓动函数与进阶技巧
缓动函数决定了动画的速度曲线,直接影响动画的观感。linear是匀速运动,机械感较强;ease-in是先慢后快,适合元素离开屏幕;ease-out是先快后慢,适合元素进入屏幕,看起来更自然。如果想要更有弹性的效果,可以使用cubic-bezier自定义曲线,甚至做出回弹:
.box {
/* x1 y1 x2 y2,超出0-1范围的y值会产生回弹效果 */
animation: slide 1.2s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards;
}除了位移方向和速度,还有一些实用技巧值得掌握。第一是分阶段动画:利用animation-delay给多个元素设置不同的延迟,就能做出依次滑入的列表效果:
.item {
animation: slideIn 0.5s ease-out both;
}
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }
.item:nth-child(4) { animation-delay: 0.3s; }第二是性能优化:给使用动画的元素额外加上will-change: transform,可以让浏览器提前为该元素创建独立的合成层,减少动画开始时的卡顿。也可以使用translateZ(0)这样的写法强制触发硬件加速。但要注意will-change不能滥用,页面中同时存在几十个带此属性的元素反而会增加内存占用,只在确实有性能问题的地方使用即可。
第三是动画结束的交互处理:如果需要在动画播完后执行某些逻辑,比如移除元素或触发下一段动画,可以监听animationend事件:
<div class="box" id="box"></div>
<script>
const box = document.getElementById('box');
box.addEventListener('animationend', function () {
console.log('平移动画播放完毕');
box.classList.remove('active');
});
</script>常见问题与避坑建议
第一个常见的坑是transform被覆盖。如果一个元素同时有多个规则设置transform,后面的声明会整体覆盖前面的,而不是叠加。比如悬停时放大、动画时平移,两个transform会互相冲突。解决办法是用translate3d、scale写在同一个transform里,或者把平移放在外层容器上、缩放放在内层元素上,分层处理互不干扰。
第二个坑是百分比单位的参照物问题。translateX(50%)中的百分比是相对于元素自身宽度,而不是父容器宽度。想让一个100像素宽的元素从父容器右侧完全滑入,需要知道元素自身尺寸后再换算,或者直接用JavaScript计算具体像素值。这一点和left: 50%的参照逻辑完全不同,初学者非常容易混淆。
第三个坑是无限循环动画的衔接。如果关键帧的0%和100%状态不一致,配合infinite播放时会在衔接处产生跳变。除非刻意追求这种效果,否则建议保持首尾帧一致,或者使用alternate方向让动画自然往返。
最后总结一下核心要点:平移动画优先使用transform: translate而不是布局属性;用@keyframes描述状态、animation控制播放;根据场景选择合适的缓动函数与填充模式;性能敏感时配合will-change开启合成层加速。掌握这些内容,基本可以覆盖日常开发中绝大多数平移动画的需求,再结合opacity还能进一步扩展出各种丝滑的入场出场效果。
CSS animationtransform平移动画修改时间:2026-08-31 18:46:36