CSS 动画体系(二)—— Animation关键帧动画

文章目录

  • [CSS 动画体系(二)------ Animation关键帧动画](#CSS 动画体系(二)—— Animation关键帧动画)
    • [1. Animation 动画基础](#1. Animation 动画基础)
      • [1.1 定义关键帧(@keyframes)](#1.1 定义关键帧(@keyframes))
      • [1.2 使用 animation 属性](#1.2 使用 animation 属性)
    • [2. 实战案例:跑马灯动画(Marquee)](#2. 实战案例:跑马灯动画(Marquee))
      • [2.1 跑马灯原理](#2.1 跑马灯原理)
      • [2.2 实现跑马灯](#2.2 实现跑马灯)

CSS 动画体系(二)------ Animation关键帧动画

基于关键帧(@keyframes)的时间轴动画系统。

它不是"属性变化动画"。而是自己就能跑的时间驱动动画。

1. Animation 动画基础

1.1 定义关键帧(@keyframes)

css 复制代码
@keyframes marquee-left {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

含义:从原位置 → 向左移动 50%。

还有百分比写法:

css 复制代码
@keyframes move {
  0%   { transform: translateX(0); }
  50%  { transform: translateX(100px); }
  100% { transform: translateX(0); }
}

1.2 使用 animation 属性

css 复制代码
.marquee-left {
  animation: marquee-left 30s linear infinite;
}
参数 含义
marquee-left 动画名字
30s 持续时间
linear 匀速
infinite 无限循环

2. 实战案例:跑马灯动画(Marquee)

2.1 跑马灯原理

跑马灯核心原理:

css 复制代码
/* 向左滚动动画:从 0 到 -50% */
@keyframes marquee-left {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

/* 向右滚动动画:从 -50% 到 0 */
@keyframes marquee-right {
  0% {
    transform: translateX(-50%);
  }
  100% {
    transform: translateX(0);
  }
}

跑马灯不是"滚完消失"。而是:滚动一段距离后,画面能无缝衔接。

结构一般是这样的:

javascript 复制代码
[A B C D][A B C D]

两份完全一样的内容

动画从 0%-50% 的意思

假设:

  • 整个容器宽度是 200%
  • 原始内容宽度是 100%

当你:

css 复制代码
transform: translateX(-50%);

意味着:

  • 向左移动"原始内容的一半宽度"
  • 刚好移动一份内容的长度

2.2 实现跑马灯

实现跑马灯的步骤:

(一)复制数组

虽然在上面例子的展示中,是复制一个数组一次,也就是一共有两个数组宽度,但是,真实的数据可能没有那么多,会造成跑马灯中会出现空白,所以,需要动态计算要复制的次数

typescript 复制代码
// 复制 items 数组以实现无缝循环,确保有足够的内容填满容器
const duplicatedItems = computed(() => {
  if (props.items.length === 0) return []

  // 每个项目的宽度(380px 卡片 + 16px gap)
  const itemWidth = 396
  // 估算需要的最小项目数(假设容器宽度至少 1200px,需要 2400px 内容)
  const minItemsNeeded = Math.ceil(2400 / itemWidth) // 约 6-7 个
  // 计算需要复制的次数,确保至少复制 2 次用于无缝循环
  const copiesNeeded = Math.max(2, Math.ceil(minItemsNeeded / props.items.length))

  // 一次性生成所有副本,避免数组指数膨胀
  return Array.from({ length: copiesNeeded }, () => props.items).flat()
})

1.空数组保护

javascript 复制代码
if (props.items.length === 0) return []
  • 没有数据就直接返回空数组,下面的计算都不用做。

2.估算一个"合理的内容总宽度"

javascript 复制代码
const itemWidth = 396
const minItemsNeeded = Math.ceil(2400 / itemWidth) // 约 6-7 个
  • 假设每个卡片占宽度 396px(380px 卡片 + 16px 间距)。

  • 假设想让滚动内容至少有 2400px 的长度(约等于一个宽 1200px 容器的 2 倍),滚起来才自然。

  • minItemsNeeded 就是:要达到 2400px 总长度,至少需要多少个卡片。

比如 2400 / 396 ≈ 6.06,向上取整成 7 个。

3.算出要复制多少"轮"

javascript 复制代码
const copiesNeeded = Math.max(2, Math.ceil(minItemsNeeded / props.items.length))
  • Math.max 取两者最大值,最少是复制 2 次,Math.ceil 上取整,minItemsNeeded 最小宽度,除于实际的一个数组的长度

4.return Array.from({ length: copiesNeeded }, () => props.items).flat()

其中,Array.from() 是 JavaScript 的一个静态方法。

作用是,把"类数组"或"可迭代对象"转换成真正的数组。

javascript 复制代码
Array.from(arrayLike, mapFn)

一共有两个参数,第一个参数是类数组,第二个参数是映射函数(每一项生成的时候,都执行这个函数)

先说类数组,

数组的本质是"有 length 属性的对象"

数组底层其实长这样(简化理解):

javascript 复制代码
{
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
}

所以 { length: 3 }就满足一个类数组最基本的要求

javascript 复制代码
Array.from({ length: 3 })
//结果是
[undefined, undefined, undefined]

再说 mapFn

mapFn 接收两个参数

javascript 复制代码
(value, index)

举例写法

javascript 复制代码
Array.from({ length: 5 }, (_, i) => i)
[0, 1, 2, 3, 4]
  • _就是一个占位的,箭头函数中 value 没有用,用的是 index,index 的递增的

.flat() 是数组方法,作用是把二维数组"拍平"成一维数组。

javascript 复制代码
[
  [A, B],
  [A, B],
  [A, B]
].flat()
变成
[A, B, A, B, A, B]

(二)跑马灯动画

css 复制代码
.horizontal-image-list.marquee-left .horizontal-image-list-container {
  animation: marquee-left 30s linear infinite;
}

.horizontal-image-list.marquee-right .horizontal-image-list-container {
  animation: marquee-right 30s linear infinite;
}

/* 向左滚动动画:从 0 到 -50% */
@keyframes marquee-left {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}

/* 向右滚动动画:从 -50% 到 0 */
@keyframes marquee-right {
  0% {
    transform: translateX(-50%);
  }
  100% {
    transform: translateX(0);
  }
}

(三)鼠标悬浮时,暂停跑动

css 复制代码
/* Hover 时暂停动画(仅在支持 hover 的设备上生效,如 desktop) */
@media (hover: hover) {
  .horizontal-image-list:hover .horizontal-image-list-container {
    animation-play-state: paused;
  }
}

@media 是 CSS 的一个 At-Rule(规则指令),当然了,@keyframes 也是一个规则指令

在满足某个条件时,才应用里面的样式。

@media (hover: hover)

  • 设备支持鼠标 hover 行为
  • 因为手机是没有 hover 的,如果不加入这个判断,手机可能会卡住,动画异常,交互行为怪异
javascript 复制代码
animation-play-state: paused;

animation-play-state 就是 CSS animation 的一个属性,专门用来控制动画的"播放状态"

animation-play-state 用来控制动画是"播放"还是"暂停"。

它有两个值

animation-play-state: running; /* 默认,播放 */

animation-play-state: paused; /* 暂停 */

animation 体系

animation 实际上是一个"简写属性",包含很多细分属性,比如:

属性 作用
animation-name 动画名称
animation-duration 持续时间
animation-timing-function 速度曲线
animation-iteration-count 循环次数
animation-delay 延迟
animation-direction 播放方向
animation-fill-mode 结束状态
animation-play-state 播放状态
相关推荐
T-shmily2 小时前
CSS Grid 网格布局(display: grid)全解析
前端·css
Flywith242 小时前
【每日一技】Warp Workflow 使用示例
android·前端
跟着珅聪学java2 小时前
Electron 读取 JSON 配置文件教程
前端·javascript·vue.js
GISer_Jing3 小时前
Agent技术深度解析:LLM增强智能体架构与优化
前端·人工智能·架构·aigc
難釋懷3 小时前
Redis主从-主从数据同步原理
前端·数据库·redis
a1117763 小时前
Markdown生成思维导图(html 开源)
前端·开源·html
我命由我123453 小时前
React - state、state 的简写方式、props、props 的简写方式、类式组件中的构造器与 props、函数式组件使用 props
前端·javascript·react.js·前端框架·html·html5·js
钰衡大师3 小时前
Vue 3 源码学习教程
前端·vue.js·学习
C澒3 小时前
React + TypeScript 编码规范|统一标准 & 高效维护
前端·react.js·typescript·团队开发·代码规范