- 前端
- 文档
- 教程
【免费下载链接】You-need-to-know-css
💄CSS tricks for web developers~
本技巧出自 You-need-to-know-css 仓库 "Transition & Animations" 分类下的 circular smooth 演示(英文文档 circular-smooth.md,中文文档 zh-cn/circular-smooth.md)。它不依赖 SVG、Canvas 或 JavaScript,仅用
animation+transform的链式rotate()/translate()组合,就让多个头像元素绕固定圆心公转,同时保持自身朝向始终不变;再借助负值animation-delay让多个元素等间距错峰,配合animation-play-state悬停暂停与气泡提示完成交互。读完本文你将掌握:用translate()替代transform-origin实现"绕任意点旋转"的底层原理、环形轨道动画的关键帧写法,以及多元素等分轨道的数学计算与参数调优方法。
效果与适用场景
该演示的最终效果是:在一个 300px × 300px 的圆形区域内,Vue.js 的 Logo 静止居中,周围 5 张开发者头像(Evan You、Damian Dulisz、defcc、Edd Yerburgh、Sarah Drasner)以 7.5 秒为周期沿圆周匀速环绕,且任意时刻 5 个头像都均匀分布在圆环上(彼此间隔 72°);鼠标悬停到圆形区域时,环绕动画立即暂停,每个头像上方弹出对应的姓名气泡,移出后动画继续。
这类效果非常适合以下场景:
- 用户/成员头像墙:团队成员或粉丝头像绕主视觉旋转,形成"众星拱月"的动感氛围;
- 环形菜单 / 仪表盘:选项沿圆弧均匀排布并缓慢旋转,悬停时定格查看;
- 加载指示器 / 动效装饰:多元素沿轨道平滑运动,比单一旋转的 spinner 更有层次感;
- 任何"元素绕固定圆心公转且不自转"的需求:例如行星轨道演示、卫星环绕动效。
前置知识:四条 CSS 基础能力
在进入代码之前,先明确该技巧依赖的四项 CSS 能力(原文档在开头列出了它们作为背景知识):
| 能力 | 作用 | 在本示例中的具体用法 |
|---|---|---|
animation | 定义动画的名称、时长、循环次数与缓动函数 | animation: circular-smooth-spin 7.5s infinite linear |
transform | 对元素做旋转、平移、缩放等几何变换 | 链式rotate() translateY() rotate()组合 |
transition | 在属性值变化时平滑过渡 | 气泡的opacity与transform: scale()渐变 |
animation-delay | 延迟动画开始时间,支持负值 | -1.5s / -3s / -4.5s / -6s实现等间距错峰 |
这四条全部是 CSS 的长期稳定特性,也是仓库中 index.html 的 keywords 元数据所声明的技术栈;具体浏览器兼容情况见本文末尾的"浏览器支持"小节。
核心原理:用三层 transform 组合代替 transform-origin
原文档引用了 James Anderson 的一句关键论断:
"Transform-origin is just a syntactic sugar. In fact, you can always use translate() instead."(transform-origin 只是一个语法糖,事实上你总是可以用 translate() 来代替它。)
这句话正是整个技巧的理论起点:transform-origin只是告诉浏览器"绕元素上的哪个点做变换",而任何"绕任意点旋转"的变换,都可以用"先平移到该点 → 旋转 → 平移回去"三步等价表达。本示例反其道而行之——不设置任何transform-origin(保持默认的 50% 50%),而是把旋转和平移写进一条transform链里。
关键帧拆解:公转与自转的抵消
环形轨道的全部秘密都在这段@keyframes中(来自 circular-smooth.md):
@keyframes circular-smooth-spin { from { transform: rotate(0turn) translateY(-124px) rotate(1turn); } to { opacity: 1; transform: rotate(1turn) translateY(-124px) rotate(0turn); } }需要特别注意:rotate(0turn)与rotate(1turn)在几何上都是恒等变换(360° 旋转回到原位),所以from和to两个端点的最终视觉效果完全相同,都是translateY(-124px)。但浏览器在插值动画时,会按位置逐个插值三个函数:
- 第一层
rotate(0turn) → rotate(1turn):角度从 0° 连续增长到 360°,驱动元素完成一整圈公转; - 中间
translateY(-124px):恒定不变,定义公转轨道的半径(124px); - 第三层
rotate(1turn) → rotate(0turn):角度从 360° 反向减到 0°,恰好抵消第一层旋转带来的"自转",使头像在环绕过程中始终保持竖直朝上,不会跟着公转一起翻转。
写成数学形式,任意时刻 t 的变换矩阵近似为R(360°·t) · T(0, -124px) · R(-360°·t):先沿本地 Y 轴负方向平移 124px,再让整个坐标系绕元素自身的变换原点旋转 θ,最后反向旋转 -θ 抵消朝向。于是元素的中心点以"自身原始位置"为圆心、124px 为半径做圆周运动,而元素的自身朝向始终不变。
这就是为什么原文档强调"transform-origin 只是语法糖"——把"绕远处一点旋转"翻译成"平移 + 旋转 + 反向旋转"的链式写法后,连transform-origin都不需要设置。
圆心与半径如何确定
轨道参数由元素定位和translateY共同决定:
.path容器为 300px × 300px 的圆(border-radius: 50%),内部用display: flex+margin: auto让.logo(52px)精确居中,Logo 中心即轨道圆心;.avatar尺寸 50px,绝对定位在top: 124px; left: 124px——容器中心坐标为 (150, 150),头像中心坐标为 (149, 149),两者几乎重合(124 + 25 = 149,与 150 只差 1px,可视为居中);translateY(-124px)使头像沿本地 Y 轴向上平移 124px——这就是公转半径。头像最外缘距圆心约 124 + 25 = 149px,恰好贴合 150px 半径的容器内壁,视觉上正好沿圆形边框内侧环绕。
完整示例:五头像绕 Logo 平滑环绕
把原文档 vuep 演示中的<style>部分完整提取出来,就是这份可直接复制的核心 CSS:
main { width: 100%; height: 529px; display: flex; flex-direction: column; align-items: center; } .path { width: 300px; height: 300px; border-radius: 50%; margin: 100px auto; position: relative; display: flex; border: 1px solid #b4a078; } .logo { width: 52px; margin: auto; background: #FFF; } .avatar { width: 50px; position: absolute; top: 124px; left: 124px; animation: circular-smooth-spin 7.5s infinite linear; animation-play-state: running; } .avatar > span { font-weight: 500; position: absolute; white-space: nowrap; top: -50%; left: 50%; padding: 3px 12px; opacity: 0; transform: scale(0); transition: opacity, transform .8s; transform-origin: 0 bottom; } .avatar > img { width: inherit; border-radius: 50%; overflow: hidden; } .path:hover .avatar { animation-play-state: paused; } .path:hover .avatar > span { opacity: 1; transform: scale(1); transition: opacity, transform .8s; } .avatar:nth-of-type(2) { animation-delay: -1.5s; } .avatar:nth-of-type(3) { animation-delay: -3s; } .avatar:nth-of-type(4) { animation-delay: -4.5s; } .avatar:nth-of-type(5) { animation-delay: -6s; } @keyframes circular-smooth-spin { from { transform: rotate(0turn) translateY(-124px) rotate(1turn); } to { opacity: 1; transform: rotate(1turn) translateY(-124px) rotate(0turn); } }对应的 HTML 结构(头像图片来自仓库 static 目录下的真实资源,如 static/vue-logo.png、static/Evan.jpeg、static/Dulisz.jpeg、static/defcc.png、static/Yerburgh.png、static/Drasner.jpeg):
<main class="main"> <div class="path"> <img class="logo" src="./static/vue-logo.png" /> <div class="avatar"> <span>Evan You</span> <img src="./static/Evan.jpeg"/> </div> <div class="avatar"> <span>Damian Dulisz</span> <img src="./static/Dulisz.jpeg"/> </div> <div class="avatar"> <span>defcc</span> <img src="./static/defcc.png"/> </div> <div class="avatar"> <span>Edd Yerburgh</span> <img src="./static/Yerburgh.png"/> </div> <div class="avatar"> <span>Sarah Drasner</span> <img src="./static/Drasner.jpeg"/> </div> </div> </main>几个值得注意的实现细节:
.avatar > img { width: inherit; }让头像图片继承父级.avatar的 50px 宽度,配合border-radius: 50%+overflow: hidden切成圆形;- 动画简写
circular-smooth-spin 7.5s infinite linear的含义依次为:动画名、时长 7.5s、无限循环、匀速linear。linear缓动是后面负值错峰能均匀排布的前提——只有匀速运动,负延迟对应的相位才严格按比例分布; - 关键帧
to里的opacity: 1并不参与头像自身的透明度动画(头像一直是不透明状态),属于演示中的冗余声明,不影响轨道效果。
在本仓库中,该演示是通过 docsify + vuep 交互式组件呈现的:文档内先写<vuep template="#circular-smooth_tlp"></vuep>,再用<script v-pre type="text/x-template" id="circular-smooth_tlp">包裹 CSS、HTML 模板与(空的)Vue script,index.html 加载 libs/vuep.min.js 与 libs/vue.min.js 后即可实时渲染。仓库的 docsify 配置见 libs/index.js,侧边栏索引见 _sidebar.md(该技巧被收录在 "Transition & Animations" 分组下)。如需本地查看全部演示,可参照 README.md 的 Quick start 用 docsify 启动后打开本地 3000 端口预览。
错峰排布:负值 animation-delay 的妙用
5 个头像想要在圆环上等间距分布,最直接的想法是给每个头像不同的延迟让它们"先后出发"。但如果使用正值延迟,页面加载后的前几秒会先看到头像一个接一个地启动,随后才趋于均匀。原文档采用的是更优雅的负值延迟:
.avatar:nth-of-type(2) { animation-delay: -1.5s; } .avatar:nth-of-type(3) { animation-delay: -3s; } .avatar:nth-of-type(4) { animation-delay: -4.5s; } .avatar:nth-of-type(5) { animation-delay: -6s; }负值animation-delay的语义是"动画仿佛已经运行了这么长时间":浏览器会让元素直接从时间轴上的对应位置开始播放,而不是真的等待。这带来两个好处:
- 页面加载瞬间就呈现完整的均匀排布,无需等待动画"跑起来";
- 所有头像从第一帧起就保持固定相位差,视觉上永远是一个稳定的五边形环绕队形。
相位间隔的计算非常简单:总周期 7.5s ÷ 5 个头像 = 1.5s,即每个头像错开 1.5s,正好对应 360° ÷ 5 = 72°。加上未设延迟的第 1 个头像(0°),5 个头像的相位依次为 0°、72°、144°、216°、288°,均匀铺满整圈。若你的场景有 n 个元素,周期 T,则延迟步长取T / n即可;若希望元素沿轨道反向排布或改变起始角度,可以改用负的translateY方向或调整第一层rotate的初值。
交互增强:悬停暂停与姓名气泡
环形动画通常需要交互配合,原文档用两条规则实现了"悬停定格 + 气泡提示":
.path:hover .avatar { animation-play-state: paused; } .path:hover .avatar > span { opacity: 1; transform: scale(1); transition: opacity, transform .8s; }暂停轨道:animation-play-state: paused让悬停瞬间冻结所有头像的当前相位。注意选择器是.path:hover .avatar——只要鼠标进入圆形区域,整组头像同时定格,而不是只暂停被悬停的那一个,这样所有气泡都能稳定展示。.avatar上显式声明的animation-play-state: running与默认值一致,属于可省略的保险写法。
气泡提示:每个.avatar > span默认opacity: 0; transform: scale(0),悬停时变为opacity: 1; transform: scale(1),配合transition: opacity, transform .8s在 0.8s 内完成淡入与放大。气泡的transform-origin: 0 bottom表示缩放的原点位于气泡自身的左下角,因此它从"贴在头像上方"的位置向上展开,视觉更自然。
这里有一个值得注意的简写细节:transition: opacity, transform .8s;按 CSStransition简写的解析规则,会被拆成两条过渡——opacity(未指定时长,默认 0s)与transform .8s。也就是说这种写法下透明度是瞬时切换、只有缩放有 0.8s 渐变。如果希望淡入和放大同步进行,应写成transition: opacity .8s, transform .8s;。原文档示例保留了前者,实际使用时可以根据预期效果选择。
关键参数速查表
| 参数 / 属性 | 取值 | 作用与调优建议 |
|---|---|---|
animation | circular-smooth-spin 7.5s infinite linear | 动画名 + 时长 + 无限循环 + 匀速。时长决定环绕快慢,周期越长越从容;务必保持linear,否则负值延迟的相位不再等距 |
animation-delay | -1.5s / -3s / -4.5s / -6s | 负值错峰,步长 = 周期 ÷ 元素数(本例 7.5 ÷ 5 = 1.5s),对应 72° 相位差 |
animation-play-state | running / paused | 悬停时暂停轨道动画,让气泡可读 |
transform | rotate(0/1turn) translateY(-124px) rotate(1/0turn) | 三层链式:公转角度 + 轨道半径 + 自转抵消;translateY的 124px 即轨道半径 |
.avatar定位 | top: 124px; left: 124px | 让头像中心与容器圆心重合,是"圆心位置"的来源 |
气泡transition | opacity, transform .8s | 注意简写语法:opacity 默认 0s、transform 0.8s;需要同步渐变时写成opacity .8s, transform .8s |
气泡transform-origin | 0 bottom | 气泡从自身左下角为原点展开,贴合"从头像上方弹出"的语义 |
常见调优方向:想扩大/缩小轨道,改translateY数值(并同步调整头像初始定位使圆心重合);想改变环绕速度,改animation时长;想增减元素数量,按周期 ÷ 数量重算延迟步长;想实现"公转 + 自转"并存(如行星自转效果),只需去掉第三层反向rotate或将其改为非全额角度。
浏览器支持
该技巧依赖三项能力:CSS Animations(animation与@keyframes)、2D Transforms(rotate/translate)、CSS Transitions(气泡过渡)。原文档在"Browser Support"小节通过三个 caniuse 内嵌面板(分别对应css-animation、transforms2d、css-transitions)给出了参考:这三者属于现代浏览器的长期稳定特性,覆盖 Chrome、Firefox、Safari、Edge 的近期版本。由于本文示例还用到了nth-of-type、flex等布局选择器与属性,落地到具体项目时,建议结合目标用户群体的浏览器版本范围做一次 caniuse 核查,并视需要为老版本浏览器补充前缀或降级方案(例如动画暂停可退化为"隐藏气泡")。
小结
沿圆形轨道平滑环绕动画的核心可以浓缩为三点:
- 轨道即变换链:
rotate(θ) translateY(r) rotate(-θ)三层组合,用纯transform实现"绕远处圆心公转 + 自身朝向不变",完全绕开transform-origin——这正是 James Anderson 所说"transform-origin 只是语法糖"的实践注脚; - 负值延迟实现错峰:
animation-delay: -T/n让多个元素在页面加载瞬间即呈现等间距分布,配合linear缓动保证相位严格均匀; - hover 暂停 + 气泡:
animation-play-state: paused定格轨道,transition+transform-origin: 0 bottom让气泡从头像上方优雅展开。
该技巧与仓库中同属 "Transition & Animations" 分组的其他动效互补使用效果更佳:无缝滚动背景见 smooth.md,弹性过渡见 elastic.md,自由落体弹跳见 bounce.md。掌握"变换链 + 负延迟"这一组合拳后,你可以自由扩展出卫星环绕、多轨行星系统、环形菜单等更丰富的 CSS 动效。
- 前端
- 文档
- 教程
【免费下载链接】You-need-to-know-css
💄CSS tricks for web developers~
相关推荐
环形文字实战:用 SVG textPath 与 CSS 动画实现 You-need-to-know-css 的圆形文字特效
环形文字实战:用 SVG textPath 与 CSS 动画实现 You need to know css 的圆形文字特效 本文围绕 You need to k
前端文档教程用 border-radius 绘制圆、半圆与椭圆:You-need-to-know-css 形状技巧实战
用 border radius 绘制圆、半圆与椭圆:You need to know css 形状技巧实战 border radius 绝不只是"圆角"这么简单
前端文档教程You-need-to-know-css 实战:用 SVG textPath 与 CSS 动画实现可交互环形文字
You need to know css 实战:用 SVG textPath 与 CSS 动画实现可交互环形文字 环形文字(Circular Text)是一种让
前端文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考