用纯CSS打造小球跳跃台阶动画
CSS动画不仅能够实现平滑的过渡效果,还可以通过关键帧与变换的组合,再现物理世界中复杂的运动轨迹。小球跳跃台阶是一个经典的前端练习,它要求我们精确控制球体在水平与垂直方向上的位移,使每一步跳跃都符合视觉惯性。本文将从原理到完整实现,带你一步步用纯CSS完成这个动画。
动画原理分析
小球从地面开始,依次跳上每一级台阶。整个过程可以分解为若干个“跳跃-停顿”的循环。每一个循环中,小球先向上(负Y方向)再向下(正Y方向)运动,同时水平方向(X方向)向前移动一个台阶的宽度。纯CSS实现这个效果最直接的方法是使用@keyframes定义每一阶段的关键帧,并利用animation-timing-function控制加速与减速。
为了让跳跃显得自然,上升阶段使用ease-out(由快变慢),下落阶段使用ease-in(由慢变快)。如果希望动画更简洁,也可以在整个跳跃过程中使用cubic-bezier()自定义贝塞尔曲线。台阶本身通过固定宽高的矩形元素绝对定位排列而成。
HTML结构设计
我们需要一个容器包裹小球和台阶。台阶使用多个<div>元素,每个代表一级台阶,通过定位调整它们的左偏移和顶偏移,形成楼梯形状。小球也是一个<div>,应用动画。
以下是一个包含三级台阶的示例结构:
<div class="scene"> <div class="ball"></div> <div class="step" style="left:0px; top:200px;"></div> <div class="step" style="left:100px; top:150px;"></div> <div class="step" style="left:200px; top:100px;"></div> <div class="ground"></div> </div>
这里.scene作为场景容器,.ball是球,每个.step通过内联样式设置位置,.ground表示地面。实际项目中建议将样式放在CSS中管理。
CSS样式与动画
1. 场景与基础元素
设置场景的宽度、高度,以及相对定位以便子元素绝对定位。台阶使用固定宽高(例如宽100px、高50px),背景色区分。球体为圆形,尺寸适中。
.scene {
position: relative;
width: 400px;
height: 300px;
border: 1px solid #ccc;
overflow: hidden;
margin: 50px auto;
}
.step {
position: absolute;
width: 100px;
height: 50px;
background: #8B4513;
border-radius: 3px;
}
.ground {
position: absolute;
bottom: 0;
height: 10px;
width: 100%;
background: #333;
}
.ball {
position: absolute;
width: 40px;
height: 40px;
background: radial-gradient(circle at 30% 30%, #f00, #900);
border-radius: 50%;
bottom: 10px; /* 站在地面上 */
left: 0;
animation: jumpStairs 3s ease-in-out infinite;
}注意:地面高度10px,球体底部对齐地面,所以bottom:10px使球刚好接触地面。台阶的top值需要根据台阶高度和地面位置计算。例如第一级台阶地面高度300px,台阶高50px,顶部应在250px位置,但为了符合视觉,我们将台阶顶部向上偏移:第一级台阶底部在200px,顶部在150px;第二级底部在150px,顶部在100px;第三级底部在100px,顶部在50px。上述HTML中我用了top:200px、150px、100px,实际台阶顶部是top + 50px,所以球跳落时需落在台阶顶部。
更清晰的做法:直接设定台阶的底部位置,通过bottom属性。这里为简化,使用top手动对齐。
2. 关键帧动画
球需要完成三次跳跃,从起点到第一级台阶,再到第二级,最后到第三级。每一段跳跃包含水平移动和垂直移动。我们将总时间分为三个等份(每段约1秒),每段内再细分上升和下落。
关键帧设计如下:
- 0%:起始位置 (left: 0, bottom: 10) 站在地面。
- 25%:跳跃最高点,同时水平移动一半距离,垂直上升最高。例如跳到第一级台阶,最高点垂直坐标是台阶顶部之上,然后下落。
- 33%左右:落在第一级台阶上 (left: 100, bottom: 100?) 。
- 之后重复类似模式。
实际编写时,更精确的方法是使用多个关键帧节点,或者借助steps()函数让位移离散化。但为了更好的视觉效果,这里用分段关键帧:
@keyframes jumpStairs {
0% {
left: 0;
bottom: 10px;
}
16% {
left: 50px;
bottom: 80px; /* 第一跳到达最高点底部升至80px (地面之上) */
}
33% {
left: 100px;
bottom: 60px; /* 落在第一级台阶顶部 (台阶底部60px) */
}
50% {
left: 150px;
bottom: 110px; /* 第二跳最高点 */
}
66% {
left: 200px;
bottom: 110px; /* 落在第二级台阶顶部 (台阶底部110px) */
}
83% {
left: 250px;
bottom: 160px; /* 第三跳最高点 */
}
100% {
left: 300px;
bottom: 160px; /* 落在第三级台阶顶部 */
}
}注意:这里的bottom值需要与台阶的实际位置匹配。台阶1:left 0, bottom 60px? 实际上我们之前用top:200px,台阶高度50px,所以底部在250px位置,但由于场景高度300px,底部在300-250 = 50px?更直观的是将台阶底部坐标定义为场景底部到台阶底部距离。例如第一级台阶顶部距离地面50px(因为台阶高50px,底部在地面上方50px?混乱了)。
建议统一坐标系:使用bottom属性表示元素底部距离场景底部的距离。地面高度10px,所以球在地面时bottom:10px。第一级台阶:如果台阶顶部距离地面50px(即台阶底部距离地面100px),因为台阶高50px,所以bottom:100px。第二级台阶底部bottom:150px,第三级bottom:200px。球落在台阶上时,它的底部应等于台阶底部,即bottom与台阶相同。跳跃最高点需要比目标位置高一些(例如高30px)。
修改HTML结构:
<div class="scene"> <div class="ball"></div> <div class="step step1"></div> <div class="step step2"></div> <div class="step step3"></div> <div class="ground"></div> </div>
.step1 { left: 100px; bottom: 100px; }
.step2 { left: 200px; bottom: 150px; }
.step3 { left: 300px; bottom: 200px; }
.ball {
position: absolute;
width: 40px; height: 40px;
background: red;
border-radius: 50%;
left: 0;
bottom: 10px;
animation: jumpStairs 4s ease-in-out infinite;
}更新关键帧,使得球在对应时间落到台阶上:
@keyframes jumpStairs {
0% { left: 0px; bottom: 10px; }
20% { left: 50px; bottom: 80px; } /* 最高点1 */
33% { left: 100px; bottom: 100px; } /* 落在台阶1 */
46% { left: 150px; bottom: 130px; } /* 最高点2 */
60% { left: 200px; bottom: 150px; } /* 落在台阶2 */
73% { left: 250px; bottom: 180px; } /* 最高点3 */
87% { left: 300px; bottom: 200px; } /* 落在台阶3 */
100% { left: 300px; bottom: 200px; } /* 保持 */
}注意:最高点时bottom要比目标台阶底部高出约20-30px(模拟跳跃高度)。落入台阶后bottom与台阶bottom一致。球体直径40px,台阶高度50px,所以视觉上球会有一部分悬空,但可接受。
为了让跳跃更真实,可以在每段跳跃中使用animation-timing-function为cubic-bezier(0.33, 0, 0.66, 1),但这里整体使用ease-in-out已经能获得类似抛物线效果。
完整示例代码
下面提供一份可以直接运行的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>小球跳跃台阶 - 纯CSS</title>
<style>
.scene {
position: relative;
width: 500px;
height: 300px;
margin: 50px auto;
border: 1px solid #ccc;
overflow: hidden;
background: #f5f5f5;
}
.ground {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 10px;
background: #333;
}
.step {
position: absolute;
width: 100px;
height: 50px;
background: #b5651d;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.step1 { left: 50px; bottom: 100px; }
.step2 { left: 200px; bottom: 150px; }
.step3 { left: 350px; bottom: 200px; }
.ball {
position: absolute;
width: 40px;
height: 40px;
background: radial-gradient(circle at 35% 35%, #ff4a4a, #c00);
border-radius: 50%;
left: 0;
bottom: 10px;
animation: jumpStairs 4s ease-in-out infinite;
z-index: 10;
}
@keyframes jumpStairs {
0% { left: 0px; bottom: 10px; }
20% { left: 25px; bottom: 80px; }
33% { left: 50px; bottom: 100px; }
46% { left: 125px; bottom: 130px; }
60% { left: 200px; bottom: 150px; }
73% { left: 275px; bottom: 180px; }
87% { left: 350px; bottom: 200px; }
100% { left: 350px; bottom: 200px; }
}
</style>
</head>
<body>
<div class="scene">
<div class="ground"></div>
<div class="step step1"></div>
<div class="step step2"></div>
<div class="step step3"></div>
<div class="ball"></div>
</div>
</body>
</html>进一步优化与扩展
1. 添加阴影:在球下方加一个椭圆形阴影,随着球高度变化改变阴影大小与不透明度,增强立体感。
2. 调整步态:使用cubic-bezier()单独控制每次跳跃的加速阶段,比如animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1)模拟更自然的弹跳。
3. 无限循环与暂停:添加animation-play-state,鼠标悬停时暂停。
4. 响应式:通过vw或百分比单位让动画适应不同屏幕。
5. 动态台阶数量:可以预留更多台阶,但关键帧随之增加,可借助SCSS或Less生成。
总结
纯CSS实现小球跳跃台阶的动画,核心在于精确控制left和bottom的数值,并通过关键帧分解每一阶段的位移。虽然代码量不算大,但需要对坐标和时序有清晰的理解。通过本示例,你可以举一反三,将其应用到加载提示、游戏角色动作等场景中。