Skip to content

空间转换与动画(三) ​

本页对应 空间转换与动画,共 7 段视频,约 54 分钟。

学习目标 ​

  1. 能够使用 @keyframes、animation 及 steps() 完成逐帧精灵动画,并通过调整持续时间控制动画播放速度。
  2. 能够分别通过盒子位移和 background-position 控制背景图滚动,实现角色从左向右移动并停在终点。
  3. 能够在同一元素上使用逗号分隔的多组动画,组合“逐帧奔跑”和“整体位移”效果,并正确编写复合动画属性。
  4. 能够搭建“全民出游”页面布局:设置 html/body/容器 高度与全屏背景,使用定位和 transform 实现居中,使用 Flex 布局完成底部小图标排列。
  5. 能够实现并排查云彩、广告、热气球、小图标和长颈鹿动画,包括动画延时错开、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/ 目录下的实际文件名替换。

常见错误 ​

  1. 现象:高屏下背景图下方出现空白,cover 看似没生效。
    原因: html、body 或外层容器高度链没打通,百分比高度失效。
    修法: 先补 html, body { height: 100%; },再检查全屏容器高度和 background-size: cover 是否落在正确元素上。

  2. 现象:三朵云、热气球或底部小图标同时动,节奏很整齐。
    原因: 多个元素复用了同一动画,但没有错开延迟或时长。
    修法: 保留同一 keyframes,给不同元素设置不同 animation-delay,必要时微调 duration。

  3. 现象:文字/标题动画一开始缩放,位置就偏了,不再居中。
    原因: 元素原本靠 transform: translate(-50%, -50%) 居中,关键帧里只写 scale(),把原 transform 覆盖了。
    修法: 关键帧内组合写 translate(...) scale(...);或用外层盒子负责定位,内层图片只做缩放。

  4. 现象:同一个盒子既要跑精灵图又要整体移动,结果只生效一个动画。
    原因: 写成多条 animation 声明,后写的覆盖前写的;或复合写法中漏写某一组。
    修法: 用逗号合并多组动画,逐组检查名称、时长、曲线、延迟、方向、填充模式,别拆成互相覆盖的多行。

  5. 现象:精灵图跑动时有滑动、残影,不像逐帧。
    原因: 速度曲线仍是默认补间,背景位置在帧与帧之间连续过渡。
    修法: 改用 steps(帧数),并核对总位移、单帧宽度和步数是否匹配。

  6. 现象:长颈鹿绕身体中心转,或旋转后头朝下。
    原因: 没改旋转原点,默认按元素中心旋转。
    修法: 先设 transform-origin 到钉住的位置,如左下角,再写旋转关键帧并调角度范围。

  7. 现象:路/装饰元素盖住中间文字。
    原因: 多个定位元素未显式设置层级,后出现的元素默认压在前面元素上。
    修法: 给需要突出的文字或元素设置合适的 z-index,不要靠 DOM 顺序碰运气。

练习 ​

  1. 背景图排查: 页面在矮屏正常、高屏下方空白。现有代码只给 body 设了 height: 100% 和背景图。请写出最小修改,并说明为什么只改 body 不够。

  2. 云彩错峰: 三朵云已共用 cloud 动画,但现在同时开始、同时往复。只允许改 CSS,让它们分别延迟 0s / 0.4s / 0.6s,并保持左右往复。

  3. 文字动画修复: 中间标题用绝对定位加 translate(-50%, -50%) 居中,加入缩放动画后位置偏移。请给出两种修复方案:一种改关键帧,一种改结构。

  4. 精灵图逐帧: 背景精灵图总位移为 -1680px,共 12 帧,当前动画有平滑滑动。请指出需要改动的动画属性,并说明 steps() 参数应如何核对。

  5. 多组动画合并: 一个盒子需要同时执行“精灵图逐帧跑动”和“整体向右移动并停在终点”。请写出合并声明时应检查的 5 个点,并说明为什么不能分成两条独立 animation 声明。

原视频对照 ​

#视频时长
1案例精灵-204:11
2案例精灵-309:25
3全名出游-1-布局17:18
4全名出游-2-云彩及广告动画08:54
5全名出游-3-热气球与小图标动画03:37
6全名出游-4-长颈鹿动画03:09
7今日小结08:21

小结 ​

  1. 综合案例先判断“谁在动”:盒子动、图片动,还是背景位置动,写法会完全不同。
  2. 百分比高度必须先打通 html → body → 容器 这条链,否则全屏背景和 cover 都不稳。
  3. 多元素共用动画时,优先复用 keyframes,用延迟和时长做错峰,效果更自然。
  4. 同一元素多组动画必须合并声明,逐组检查参数,避免后写覆盖前写。
  5. transform 是整体属性,关键帧里要保留已有位移,否则缩放、旋转会把居中位移覆盖掉。
  6. 精灵图逐帧必须用 steps(),补间动画会暴露滑动和残影。
  7. 定位元素层级冲突要显式用 z-index 解决,不能依赖 DOM 顺序。
  8. 案例写法可以不同,但交付时要保证需求准确、覆盖关系清楚、后续可维护。