主题
空间转换与动画(三)
本页对应 空间转换与动画,共 7 段视频,约 54 分钟。
学习目标
- 能够使用
@keyframes、animation及steps()完成逐帧精灵动画,并通过调整持续时间控制动画播放速度。 - 能够分别通过盒子位移和
background-position控制背景图滚动,实现角色从左向右移动并停在终点。 - 能够在同一元素上使用逗号分隔的多组动画,组合“逐帧奔跑”和“整体位移”效果,并正确编写复合动画属性。
- 能够搭建“全民出游”页面布局:设置
html/body/容器高度与全屏背景,使用定位和transform实现居中,使用 Flex 布局完成底部小图标排列。 - 能够实现并排查云彩、广告、热气球、小图标和长颈鹿动画,包括动画延时错开、
transform复合写法避免覆盖、transform-origin设置旋转原点及alternate往复运动。
知识点
案例目标与整体结构

本节做一个“全名出游”的综合小案例:背景图铺满屏幕,云彩在页面中轻微往复移动,中间文字做一次“缩小—放大—回弹”的动画。它把背景图、绝对定位、关键帧动画和调试思路串在一起。
为什么需要:单看每个属性都不难,但综合案例里容易出现“背景铺不满”“云彩位置乱跑”“文字动画把居中覆盖掉”等问题,需要把属性之间的关系想清楚。
怎么想:先搭背景,再放装饰元素,最后给元素加动画;动画元素优先用绝对定位脱离文档流。
怎么做:按笔记中的 HTML 结构和 CSS 顺序写。
易错点:不要一上来就写动画,先把静态布局调稳,否则动画一动就找不到问题来源。
背景铺满:html 和 body 都要有高度
为什么需要:默认状态下 HTML 和 body 的高度是 0,直接给 body 设置 background-size: cover 时,背景图没有足够高度可铺,缩放会不成功。
怎么想:百分比高度是相对父级计算的。html 要相对视口拿到高度,body 再相对 html 拿到高度,最后背景才能铺满。
怎么做:给 html 和 body 都设置 height: 100%,再写背景图、居中、cover。
css
/* 大背景 */
/* 默认状态HTML和body的高度是0,所以导致cover缩放背景图不成功 */
html {
height: 100%;
}
body {
height: 100%;
background: url(../images/f1_1.jpg) no-repeat center 0 / cover;
/* background-size: cover; */
}易错点:只给 body 设 height: 100% 往往无效,因为它的父级 html 没有高度;另外 cover 是覆盖填满,可能裁切图片,contain 是完整包含,可能留白,本案例更适合 cover。
云彩位置和动画
为什么需要:三朵云要叠在背景上,并且位置相对页面统一,不能靠普通文档流一个个挤下来。
怎么想:让三张云图都绝对定位,统一用 left: 50% 找到水平基准,再用不同的 margin-left 左右错开,用 top 控制高低。动画用 alternate 做往复,避免从终点瞬间跳回起点。
怎么做:HTML 用一个 .cloud 包住三张图,CSS 给 .cloud img 统一绝对定位,再分别设置偏移和动画延迟。
html
<!-- 云 -->
<div class="cloud">
<img src="./images/yun1.png" alt="">
<img src="./images/yun2.png" alt="">
<img src="./images/yun3.png" alt="">
</div>css
/* 云 */
.cloud img {
position: absolute;
left: 50%;
}
.cloud img:nth-child(1) {
margin-left: -250px;
top: 20px;
animation: cloud 1s infinite alternate linear;
}
.cloud img:nth-child(2) {
margin-left: 400px;
top: 100px;
animation: cloud 1s infinite alternate linear 0.4s;
}
.cloud img:nth-child(3) {
margin-left: -550px;
top: 200px;
animation: cloud 1s infinite alternate linear 0.6s;
}
@keyframes cloud {
100% {
transform: translate(20px);
}
}易错点:animation 复合写法里两个时间值,第一个是持续时间,第二个是延迟时间,顺序不能反;三朵云如果延迟都一样,会同步移动,看起来像整体在抖,要用 0.4s、0.6s 错开节奏。
文字动画
为什么需要:中间文字需要先居中,再做一次有节奏的缩放,形成“默认 → 小 → 大 → 小 → 默认”的广告式回弹。
怎么想:先用 left: 50%; top: 50%; transform: translate(-50%, -50%); 把文字真正居中,再用关键帧控制 scale。关键点是:每个关键帧都要保留 translate(-50%, -50%),否则后面的 scale 会把前面的位移覆盖掉。
怎么做:给 .text img 设置绝对定位和动画,在 @keyframes text 中每个百分比都写完整的 translate(...) scale(...)。
html
<!-- 文字 -->
<div class="text">
<img src="./images/font1.png" alt="">
</div>css
/* 文字 */
.text img {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
animation: text 1s;
}
/* 默认 → 小 → 大 → 小 → 默认 */
@keyframes text {
0% {
transform: translate(-50%, -50%) scale(1);
}
20% {
transform: translate(-50%, -50%) scale(0.1);
}
40% {
transform: translate(-50%, -50%) scale(1.4);
}
70% {
transform: translate(-50%, -50%) scale(0.8);
}
100% {
transform: translate(-50%, -50%) scale(1);
}
}调试过程:转写稿里提到,文字动画写完后刷新,发现它没有保持居中,而是在原地缩放。原因是初始状态已经用 transform 做了位移,关键帧里如果只写 scale,新的 transform 会覆盖旧的位移。解决方式不是绕开 transform,而是在关键帧里把位移和缩放复合写全,所以笔记代码每个关键帧都带着 translate(-50%, -50%)。
易错点:同一个元素的 transform 后写会覆盖前写;关键帧百分比要连贯,否则会出现突兀跳变;动画名称要与 @keyframes 名称一致。
代码示例
示例一:全屏背景 + 云彩动画 + 文字缩放动画
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>全民出游</title>
<style>
* { margin: 0; padding: 0; }
/* 大背景:html 和 body 默认高度是 0,必须都设 100% 才能撑满 */
html { height: 100%; }
body {
height: 100%;
background: url(./images/f1_1.jpg) no-repeat center 0 / cover;
}
/* 云 */
.cloud img {
position: absolute;
left: 50%;
}
.cloud img:nth-child(1) {
margin-left: -250px;
top: 20px;
animation: cloud 1s infinite alternate linear;
}
.cloud img:nth-child(2) {
margin-left: 400px;
top: 100px;
animation: cloud 1s infinite alternate linear 0.4s;
}
.cloud img:nth-child(3) {
margin-left: -550px;
top: 200px;
animation: cloud 1s infinite alternate linear 0.6s;
}
@keyframes cloud {
100% { transform: translate(20px); }
}
/* 文字:默认 → 小 → 大 → 小 → 默认 */
.text img {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
animation: text 1s;
}
@keyframes text {
0% { transform: translate(-50%, -50%) scale(1); }
20% { transform: translate(-50%, -50%) scale(0.1); }
40% { transform: translate(-50%, -50%) scale(1.4); }
70% { transform: translate(-50%, -50%) scale(0.8); }
100% { transform: translate(-50%, -50%) scale(1); }
}
</style>
</head>
<body>
<!-- 云 -->
<div class="cloud">
<img src="./images/yun1.png" alt="">
<img src="./images/yun2.png" alt="">
<img src="./images/yun3.png" alt="">
</div>
<!-- 文字 -->
<div class="text">
<img src="./images/font1.png" alt="">
</div>
</body>
</html>关键行与易错点
html { height:100% }+body { height:100% }:html、body默认高度是 0,只给body设百分比高度会“相对父元素”失效,导致cover缩放背景图失败、下方留白。background: ... center 0 / cover:复合写法里第一个值是左右位置、第二个是上下位置,/后面才是background-size;cover是覆盖(可能显示不全),contain是包含(可能有空白)。left:50%+margin-left:-250px:云朵全部以中线为基准,再用负 margin 左右错开,比直接写死left值更好调。animation: cloud 1s infinite alternate linear 0.4s:简写里第一个时间是持续时间,最后一个时间是延迟,顺序不能乱;alternate做往复,三朵云用不同延迟岔开,不会同步。- 最大的坑在文字动画:
.text img的初始化里已经用了transform: translate(-50%,-50%),而@keyframes text的每一帧都必须把这个位移重新带上。只写scale()会把上一处transform整体覆盖掉,元素就会从“居中”变回“左上角为原点”做缩放,位置直接跑偏。
示例二:同一个盒子做多组动画(逐帧跑 + 整体位移)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>精灵奔跑</title>
<style>
* { margin: 0; padding: 0; }
.box {
position: absolute;
left: 0;
top: 100px;
width: 140px;
height: 140px;
/* 精灵图作为背景图,不是 img 标签 */
background: url(./images/bg.png) no-repeat;
/* 一个盒子两组动画,逗号隔开,各管各的 */
animation: run 0.8s steps(12) infinite, box 3s forwards;
}
/* 第一组:改背景图位置,逐帧切换 */
@keyframes run {
0% { background-position: 0 0; }
100% { background-position: -1680px 0; }
}
/* 第二组:控制盒子整体往前走,跑完停在终点 */
@keyframes box {
0% { left: 0; }
100% { left: 800px; }
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>关键行与易错点
- 精灵图用
background-image而不是<img>,所以“动”的不是元素的transform,而是background-position:水平值从0走到-1680px(总宽),图就一格格往左推。 background-position: -1680px 0两个值之间是空格,没有逗号;写成逗号会被当成两组背景声明而失效。steps(12)才是逐帧:linear/ease是补间动画,会在两格图之间“滑过去”,中间空白露出来;steps(格子数)是浏览器不补中间值,一帧一帧跳。animation: run 0.8s steps(12) infinite, box 3s forwards:同一个盒子可以写多组动画,用逗号分隔,各自的时长、循环、填充模式互不影响;这里第一组无限循环负责“腿动”,第二组只跑一次负责“人往前走”。forwards让box动画结束后停在最后一帧(left:800px)。注意它和infinite是冲突的——无限循环永远不结束,forwards也就无从生效。- 想省事也可以像讲课时那样拆成两个盒子(图片管图片、盒子管盒子)各写各的动画,殊途同归,核心是需求实现。
示例三:热气球浮动 + 小图标复用动画 + 长颈鹿绕原点摇摆
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>摇摆与浮动</title>
<style>
* { margin: 0; padding: 0; }
html, body { height: 100%; }
body { position: relative; }
/* 热气球:上下浮动 */
.hot {
position: absolute;
left: 600px;
top: 300px;
width: 120px; /* 只控制宽度,高度自动等比缩放 */
animation: hot 1s infinite alternate linear;
}
@keyframes hot {
0% { transform: translateY(0); }
100% { transform: translateY(-80px); }
}
/* 底部四个小图标:直接复用 hot 动画,改时长 + 加延迟岔开 */
.box {
position: absolute;
left: 50%;
bottom: 30px;
transform: translateX(-50%); /* 只做左右居中 */
display: flex;
justify-content: space-around;
align-items: flex-end;
width: 600px;
}
.box img {
width: 80px;
animation: hot 0.8s infinite alternate linear;
}
.box img:nth-child(2) { animation-delay: 0.2s; }
.box img:nth-child(3) { animation-delay: 0.4s; }
.box img:nth-child(4) { animation-delay: 0.6s; }
/* 长颈鹿:钉子钉在左下角,绕这个点左右摇 */
.deer {
position: absolute;
left: 80px;
bottom: 40px;
transform-origin: left bottom;
animation: deer 1s infinite alternate linear;
}
@keyframes deer {
0% { transform: rotate(-45deg); } /* 逆时针偏过去 */
100% { transform: rotate(10deg); } /* 顺时针偏回来 */
}
</style>
</head>
<body>
<img class="hot" src="./images/hot.png" alt="">
<img class="deer" src="./images/deer.png" alt="">
<div class="box">
<img src="./images/icon1.png" alt="">
<img src="./images/icon2.png" alt="">
<img src="./images/icon3.png" alt="">
<img src="./images/icon4.png" alt="">
</div>
</body>
</html>关键行与易错点
transform-origin: left bottom:长颈鹿的“图钉”必须先钉在左下角,否则默认绕元素中心转,会出现“头朝下原地打转”的怪效果。定了原点再写rotate才是摇摆。- 摇摆在
-45deg → 10deg之间alternate往复,两端度数不对称是因为角度再大会把鹿头挡住,数值按视觉效果自己调。 .hot只写width不写height:图片宽高会自动等比缩放,避免手动算比例变形。- 小图标没有重写动画,直接复用
hot的关键帧,只改时长(0.8s)并给后三个加animation-delay,同一动画就能错开节奏;注意第一个没加延迟,所以和热气球节奏最接近。 .box的居中只需水平:left:50%+translateX(-50%),不要照抄标题那套top:50% + translateY(-50%),否则灰色盒子会跑到屏幕中间而不是贴底。align-items: flex-end让四个图标底部对齐,配合justify-content: space-around一行搞定,不需要给每个图标单独定位。- 素材路径请按自己本地
images/目录下的实际文件名替换。
常见错误
现象:高屏下背景图下方出现空白,
cover看似没生效。
原因:html、body或外层容器高度链没打通,百分比高度失效。
修法: 先补html, body { height: 100%; },再检查全屏容器高度和background-size: cover是否落在正确元素上。现象:三朵云、热气球或底部小图标同时动,节奏很整齐。
原因: 多个元素复用了同一动画,但没有错开延迟或时长。
修法: 保留同一keyframes,给不同元素设置不同animation-delay,必要时微调duration。现象:文字/标题动画一开始缩放,位置就偏了,不再居中。
原因: 元素原本靠transform: translate(-50%, -50%)居中,关键帧里只写scale(),把原transform覆盖了。
修法: 关键帧内组合写translate(...) scale(...);或用外层盒子负责定位,内层图片只做缩放。现象:同一个盒子既要跑精灵图又要整体移动,结果只生效一个动画。
原因: 写成多条animation声明,后写的覆盖前写的;或复合写法中漏写某一组。
修法: 用逗号合并多组动画,逐组检查名称、时长、曲线、延迟、方向、填充模式,别拆成互相覆盖的多行。现象:精灵图跑动时有滑动、残影,不像逐帧。
原因: 速度曲线仍是默认补间,背景位置在帧与帧之间连续过渡。
修法: 改用steps(帧数),并核对总位移、单帧宽度和步数是否匹配。现象:长颈鹿绕身体中心转,或旋转后头朝下。
原因: 没改旋转原点,默认按元素中心旋转。
修法: 先设transform-origin到钉住的位置,如左下角,再写旋转关键帧并调角度范围。现象:路/装饰元素盖住中间文字。
原因: 多个定位元素未显式设置层级,后出现的元素默认压在前面元素上。
修法: 给需要突出的文字或元素设置合适的z-index,不要靠 DOM 顺序碰运气。
练习
背景图排查: 页面在矮屏正常、高屏下方空白。现有代码只给
body设了height: 100%和背景图。请写出最小修改,并说明为什么只改body不够。云彩错峰: 三朵云已共用
cloud动画,但现在同时开始、同时往复。只允许改 CSS,让它们分别延迟0s / 0.4s / 0.6s,并保持左右往复。文字动画修复: 中间标题用绝对定位加
translate(-50%, -50%)居中,加入缩放动画后位置偏移。请给出两种修复方案:一种改关键帧,一种改结构。精灵图逐帧: 背景精灵图总位移为
-1680px,共 12 帧,当前动画有平滑滑动。请指出需要改动的动画属性,并说明steps()参数应如何核对。多组动画合并: 一个盒子需要同时执行“精灵图逐帧跑动”和“整体向右移动并停在终点”。请写出合并声明时应检查的 5 个点,并说明为什么不能分成两条独立
animation声明。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 案例精灵-2 | 04:11 |
| 2 | 案例精灵-3 | 09:25 |
| 3 | 全名出游-1-布局 | 17:18 |
| 4 | 全名出游-2-云彩及广告动画 | 08:54 |
| 5 | 全名出游-3-热气球与小图标动画 | 03:37 |
| 6 | 全名出游-4-长颈鹿动画 | 03:09 |
| 7 | 今日小结 | 08:21 |
小结
- 综合案例先判断“谁在动”:盒子动、图片动,还是背景位置动,写法会完全不同。
- 百分比高度必须先打通
html → body → 容器这条链,否则全屏背景和cover都不稳。 - 多元素共用动画时,优先复用
keyframes,用延迟和时长做错峰,效果更自然。 - 同一元素多组动画必须合并声明,逐组检查参数,避免后写覆盖前写。
transform是整体属性,关键帧里要保留已有位移,否则缩放、旋转会把居中位移覆盖掉。- 精灵图逐帧必须用
steps(),补间动画会暴露滑动和残影。 - 定位元素层级冲突要显式用
z-index解决,不能依赖 DOM 顺序。 - 案例写法可以不同,但交付时要保证需求准确、覆盖关系清楚、后续可维护。