APPLE 同款 SVG宫格弹跳轮播怎么做的?手写+编辑器两种方法都🉑

38 阅读9分钟

2025-11-21 13-23-22.2025-11-21 13_23_46.gif 有时候刷到那种又克制又有趣的宫格动效——
一整面卡片矩阵缓缓上下滑动,每停顿一下,又像被一只“无形的手”轻轻按了一下再弹回去。

你大脑里又会自动弹出一句:

嗯,这味儿还是有点 Apple。优雅,实在是优雅。

这种效果常见在 Apple 官网、iOS 小组件、精致 landing page:
不是“暴力左右轮播”,而是一整块宫格在视野里轻轻起伏、带着弹性感切换信息

今天这篇就来拆一段实际在用的 “宫格弹跳” SVG 动画
看看它是怎么只靠几条 <animateTransform>,做出这种 Apple 风格的“宫格轮播”。

本文代码基于一个现成的 SVG 编辑器组件做练习,只做技术拆解与学习。


一、先说效果:它到底在动什么?

用一句话描述这个“宫格弹跳”:

一整块 3 列 × N 行的卡片网格,沿着 Y 轴上下移动,
在关键位置会轻微缩放(scale 到 0.7 再回 1),
形成一种 “滑到某一屏 → 被按一下 → 再滑走” 的节奏感。

视觉感觉上就像:

  • 一面由 App 卡片组成的长墙,缓慢往上移动
  • 每当某一行来到黄金视野区域,就会被轻轻“按扁”一下再弹回
  • 整个动画全程循环,看起来像一个 有节奏呼吸的长版宫格轮播

而这一切,都是用一段 SVG + SMIL 完成的。


二、整体结构:一个 <g> 扛起整面宫格

先看一下代码的骨架(我把无关属性都略掉了,只保留动效相关结构):

<svg
  viewBox="-0 0 0 0"
  width="100%"
  style="display:block; overflow:hidden;">
  
  <rect x="0" y="0" width="0" height="0" fill="#000000" />

  <!-- 整面宫格都包在这个 g 里 -->
  <g transform="translate(0 -50)">
    <!-- 这里是一大堆 foreignObject,每个就是一张小卡片 -->
    <foreignObject x="-30" y="0"   width="0" height="0">...</foreignObject>
    <foreignObject x="0"   y="0"   width="0" height="0">...</foreignObject>
    <foreignObject x="30"  y="0"   width="0" height="0">...</foreignObject>

    <foreignObject x="-30" y="50"  width="0" height="0">...</foreignObject>
    <foreignObject x="0"   y="50"  width="0" height="0">...</foreignObject>
    <foreignObject x="30"  y="50"  width="0" height="0">...</foreignObject>

    <!-- 下面还有很多行:y=100,150,200,250,300 ... -->
    
    <!-- 关键的平移动画 -->
    <animateTransform
      type="translate"
      attributeName="transform"
      begin="0s"
      dur="8s"
      repeatCount="indefinite"
      calcMode="spline"
      values="0 -50; 0 -50; 0 -35; 0 -35; 0 -70; 0 -70; ...; 0 -250; 0 -50"
      keyTimes="0; 0.125; 0.1625; ...; 0.999999; 1"
      keySplines="0.42 0 0.58 1; ...; 0 0 1 1" />

    <!-- 关键的缩放弹跳 -->
    <animateTransform
      type="scale"
      attributeName="transform"
      begin="0s"
      dur="8s"
      repeatCount="indefinite"
      calcMode="spline"
      additive="sum"
      values="1; 1; 0.7; 0.7; 1; 1; 0.7; 0.7; 1; ..."
      keyTimes="0; 0.125; 0.1625; ...; 0.999999; 1"
      keySplines="0.42 0 0.58 1; ...; 0 0 1 1" />
  </g>
</svg>

几个关键点:

  • 只有一个大 <g> 在动:所有小卡片都挂在这个 <g>

  • 每个卡片是一个 foreignObject,里面再嵌一层 <svg>,用 background-image 铺图

  • 真正的动画只有两条:

    • 一条控制 纵向 translate
    • 一条控制 scale 弹跳(并且是 additive="sum" 叠加在平移上)

可以理解为:

把一整面宫格丢进一个“电梯”里,
再让这个电梯沿着 Y 轴上下,
路上在不同楼层轻轻弹一下。


三、宫格是怎么排出来的?3 列 × N 行的坐标网格

中间那一长串 foreignObject,就是宫格本体。

它的排布方式非常直白:

  • x 坐标只有三个值-30, 0, 30 → 三列
  • y 坐标每次加 500, 50, 100, 150, 200, 250, 300... → 多行
  • 每个里头再放一个内嵌 <svg>,用背景图显示一张卡片。

伪代码可以理解成:

<!-- 第 1 行 -->
<foreignObject x="-30" y="0">   [卡片1] </foreignObject>
<foreignObject x="0"   y="0">   [卡片2] </foreignObject>
<foreignObject x="30"  y="0">   [卡片3] </foreignObject>

<!-- 第 2 行 -->
<foreignObject x="-30" y="50">  [卡片4] </foreignObject>
<foreignObject x="0"   y="50">  [卡片5] </foreignObject>
<foreignObject x="30"  y="50">  [卡片6] </foreignObject>

<!-- 第 3 行 -->
<foreignObject x="-30" y="100"> [卡片7] </foreignObject>
...

每个格子内部长这样(简化版):

<foreignObject x="-30" y="0" width="0" height="0">
  <svg
    width="0"
    height="0"
    style="
      display:block;
      background-image:url('你的图片地址');
      background-position:center center;
      background-size:cover;
      background-repeat:no-repeat;">
  </svg>
</foreignObject>

你要做自己的宫格:

  • 只需要把 background-image:url("") 换成自己的图片
  • 每个格子可以理解为一个 App 图标 / 分类卡片 / 产品卡片

这层完全不动,只是跟着外层 <g> 一起上下移动 + 缩放。


四、纵向平移:translate 把宫格当整块内容往上“推”

第一个 animateTransform 控制的是纵向位移:

<animateTransform
  type="translate"
  attributeName="transform"
  begin="0s"
  dur="8s"
  repeatCount="indefinite"
  calcMode="spline"
  values="0 -50; 0 -50; 0 -35; 0 -35; 0 -70; 0 -70; 0 -100; ...; 0 -250; 0 -50"
  keyTimes="0; 0.1250; 0.1625; ...; 0.999999; 1"
  keySplines="0.42 0 0.58 1; ...; 0 0 1 1" />

这里有三个点很有意思:

1)values 不是简单“从 A 到 B”

而是很多个分段:

  • 0 -50
  • 0 -35
  • 0 -70
  • 0 -100
  • 0 -70
  • 0 -105
  • 0 -150
  • 0 -105
  • 0 -140
  • 0 -200
  • 0 -140
  • 0 -175
  • 0 -250
  • 0 -50

这意味着:

宫格不会“线性一路向上”,
而是像电梯那样:在不同楼层短暂停顿、再加速、再停顿、再继续往上。

视觉上就会出现:

  • 有时缓慢上移一点点
  • 有时突然位移更多像跳一格
  • 有时又回弹一些,再继续往上
  • 最后从某个最高点瞬间回起点(实现循环)

这正是 Apple 那种网页里常见的“有层次的内容切换”的感觉,而不是简单 banner 滑动。

2)keyTimes 精细控制节点节奏

keyTimes 是一组 0~1 之间的比例,表示:

每一个 values 对应在整段动画时间轴上的位置。

比如简化理解:

  • 0:起点(0%)
  • 0.125:12.5% 位置
  • 0.25:25% 位置
  • ……
  • 1:终点(100%)

通过这种方式,作者可以非常精细地控制:

  • 每一段位移花多长时间
  • 哪些位置停留时间更长
  • 哪一段“跳得更急”

3)贝塞尔曲线:keySplines="0.42 0 0.58 1"

calcMode="spline" + keySplines="0.42 0 0.58 1" 这组合,其实就是 SVG 里的 cubic-bezier:

cubic-bezier(0.42, 0, 0.58, 1)  ≈ ease-in-out

效果是:

  • 起步慢一点
  • 中间稍微加速
  • 结束时再慢一下刹车

配合上那一堆不是很大的位移值,整块宫格动起来就变成:

没有“广告位滑动”的廉价感,
更像一组内容被轻轻推上来的“过场动画”。


五、弹跳感从哪来?第二条 scale 动画是关键

接下来这条 animateTransform 就是“弹跳感”的来源:

<animateTransform
  type="scale"
  attributeName="transform"
  begin="0s"
  dur="8s"
  repeatCount="indefinite"
  calcMode="spline"
  additive="sum"
  values="1; 1; 0.7; 0.7; 1; 1; 0.7; 0.7; 1; 1; ..."
  keyTimes="0; 0.1250; 0.1625; 0.2125; 0.25; ..."
  keySplines="0.42 0 0.58 1; ...; 0 0 1 1" />

几个关键信息:

1)additive="sum":叠加在前面的 translate 上

type="scale"additive="sum" 表示:

这个缩放不是单独存在的,而是和前面的 translate 叠加。

因此:

  • 纵向平移负责“把宫格整体上下推进视野”
  • 这个 scale 负责“在特定节点把整块宫格压扁/弹回”

2)values:1 → 0.7 → 1 的周期

values

1; 1; 0.7; 0.7; 1; 1; 0.7; 0.7; 1; ...

大概就是一段一段的:

正常大小 → 缩到 0.7 → 停一下 → 回到 1

在视觉上就是:

  • 上滑到某个“分屏位置”
  • 宫格突然“被按一下” → 缩小一点
  • 再弹回原始大小 → 像是确认“对齐了一屏内容”

跟刚才 translate 的 keyTimes 一组合起来,就是:

  • 某个 Y 值段:宫格刚好停住
  • 同一时间段:scale 从 1 → 0.7 → 1
  • 于是你看到的就是 “滑到一个位置 → 咚一下 → 再溜走” 的弹跳感

这非常符合 Apple、GQ、奈雪这类品牌喜欢用的那种 “有质量感的卡片运动”


六、如果你想做自己的宫格弹跳,可以改这些地方

基于上面的结构,如果你要在自己项目里做类似效果,大致可以改 4 个点:

1. 替换成你自己的图片

找到每个小格的这段:

<svg
  style="
    display:block;
    background-image:url('https://你的图.jpg');
    background-position:center;
    background-size:cover;
    background-repeat:no-repeat;">
</svg>

把 URL 换成你的图片地址即可。

  • 建议所有图尺寸一致、风格统一
  • 做产品宫格、栏目宫格、内容卡片都可以

2. 改宫格密度和间距

通过 foreignObjectx / y 来改:

  • 想更紧凑:把 x 改近一点,比如 -25, 0, 25y 间距改小
  • 想更松弛:把行距、列距都拉开一点

3. 调整节奏:更佛系 or 更蹦迪

节奏由这几项决定:

  • dur="8s":一轮多长时间
  • values(translate):切换到哪些 Y 值
  • keyTimes:每段停留占总时长的比例
  • values(scale):是否要弹得更狠(比如缩放到 0.6)

例如:

  • 做“禅系 / 画册感”:

    • dur 改成 12s 或 16s
    • 减少弹跳次数(少一些 0.7)
  • 做“节日活动页”:

    • dur 改成 6s
    • 把缩放做得更明显,比如 1 → 0.6 → 1

4. 控制循环方式

目前的写法是:

  • repeatCount="indefinite":无限循环
  • 最后一个值从 0 -250 回到 0 -50:相当于瞬间 teleport 到初始位置,继续下一轮

如果你想只播一次,可以:

  • 去掉 repeatCount 或改成 1
  • 结尾 values 停在你想要的位置即可

七、不想手写的话:E2 编辑器里有现成的“宫格弹跳”组件

如果你不想自己去排整块宫格、算 keyTimes、调贝塞尔曲线,也有一个更省事的方式:用现成组件。

比如在 E2 SVG 编辑器 里,就有一个已经封装好的交互组件,名字就叫:

「宫格弹跳」

image.png 这个组件的特性大致是:

  • 基于纯 SVG 动画(无需 JS),适配公众号 / H5 / Web

  • 宫格排布已经设计好,包含多行多列的图片网格

  • translate + scale 的弹跳节奏已调好,默认就是类似 Apple 风格的“轻盈起伏”

  • 只需要:

    • 上传自己的多张图片(尺寸统一更好)
    • 调整一些基础参数(节奏、时长、整体缩放等)
    • 一键导出 SVG 代码,直接嵌入到公众号或网页

更关键的是:

  • 这个「宫格弹跳」组件是免费使用的

  • 整个 E2 编辑器是专门做 SVG 互动动效的,有大量类似:

    • 轮播 / 宫格 / 滚动动效
    • 点击掉落 / 刮刮乐 / 烟花特效 / “我是谁”猜影子组件
    • 适合运营人、设计师、开发者做高互动图文

对前端来说也很友好:

  • 你可以先在 E2 里把“动效结构”预制好
  • 导出 SVG 后,再在自己的项目里做二次封装、懒加载、按需插入
  • 既保留了可视化编辑的效率,又可以保留代码层面的可控性

小结

这类 “宫格弹跳” Apple 风格轮播,本质上做的事情并不复杂:

  1. 把所有小卡片,用 foreignObject 排成 3 列 × N 行网格
  2. 整体包在一个 <g>
  3. 用一条 translate 动画控制纵向位移,模拟滑屏/切屏
  4. 用一条 scale 动画叠加在上面,制造“弹跳感”
  5. 通过 keyTimes + keySplines 精细控制节奏,让宫格“会呼吸”

拆开来看全是很朴素的 SVG 技巧,
组合在一起,就能做出那种:

“好像没怎么动,但越看越顺眼”的 Apple 味宫格轮播。

如果你也在折腾 SVG / 公众号 / H5 的互动动效,可以试着先用一两个宫格开始玩起,
搞明白一次 translate + scale 的组合关系之后,后面很多“高级感动效”其实都是同一套套路延伸。