CSS 动画实现星球环绕效果
大家好,我是 97 年的前端小鲜肉绿生,日常对接设计师奇怪的想法。今天为大家带来一个实用的前端小技巧。在做某 H5 活动页时,设计师山田出了一个行星环绕运动的效果图 ,五颗球需要围绕倾斜的轨道进行旋转运动。JavaScript 可以画很多复杂的动画,各种星球类的实现网上有很多,那么如何用 CSS 实现这个效果呢?
CSS 实现效果:
再认识 CSS Transform
在开始之前,先回顾一些 CSS Transform 的知识点~
Transform
坐标系
- X 轴:屏幕左上角为原点,水平方向为 X 轴
- Y 轴:屏幕左上角为原点,垂直方向为 Y 轴
- Z 轴:屏幕左上角为原点,垂直电脑的轴为 Z 轴,可以理解为指向我们的轴
transform 参数的执行顺序
transform 中传入的效果是有先后执行顺序的(效果上看,可以理解为后传入的先执行,但实际计算是以矩阵:matrix 的方式去算的),且转换会改变坐标轴。如下图,skew、scale、rotate... 本质上都是用 matrix 实现的,只不过 rotate 这种形式更容易让人上手。
matrix 方法有两种:
1、matrix() 3x3 矩阵
2、matrix3d() 4x4 矩阵
举个例子:
transform: rotate(30deg); // matrix(cos30°,sin30°,-sin30°,cos30°,0,0); transform: matrix(0.866025,0.500000,-0.500000,0.866025,0,0);
这两种方式的表现结果是一致的,但如果要用纯 matrix 实现 rotate,你需要手动计算各种 sin,cos 值。再举两个简单的例子,帮助快速理解 transform 的计算(执行)顺序:
先执行 scaleX(0.5) 把正方形变成了长方形,再执行 rotateZ(45deg) 把元素顺时针旋转 45 度,得到的是一个倾斜的长方形:
.test_transform { width:100px; height:100px; background-color: #c685d9; transform: rotate(45deg) scaleX(0.5); }
先执行顺时针旋转 45 度,再压缩 X 轴,得到了一个菱形:
.test_transform_2 { ... transform: scaleX(0.5) rotate(45deg); }
rotate
简单的旋转,rotate(45deg) 其实就是 rotateZ(45deg)。
scale
缩放,如 scaleY(0.6)
scale() 仅适用于在欧几里德平面(二维平面)上的变换。如果需要进行空间中的缩放,必须使用 scale3D() 。下面介绍如何用 CSS 实现一个简单的单球环绕效果:
实现单球环绕效果
Step1 - 基础样式
<div className='wrap'> <div className='planet'> <div className='ball' /> </div> </div>
.wrap { display: flex; background-image: linear-gradient(180deg, #020205 0%, #170f39 51%, #35247a 95%); width: 600px; height: 600px; align-items: center; justify-content: center; } .planet { position: absolute; border: 2px solid #fff; transform-style: preserve-3d; width: 200px; height: 200px; } .ball { width: 50px; height: 50px; position: absolute; border-radius: 50%; background-color: yellowgreen; }
Step2 - 让圆形穿过轨道
.ball { // ... left: calc(50% - 25px); top: -25px; }
为什么需要这一步?假使给这个方形加上 border-radius: 50% 转为一个圆形,目前图中所处的点才是串在圆形轨道上的,正方形四个角的点对应的半径会大于圆形的半径。
Step3 - 旋转轨道
.planet { transform: rotateZ(45deg); } .ball { transform: rotateZ(-45deg); // 中和轨道的旋转 }
Step4 - 压缩轨道 Y 轴,形成 3D 效果
注意先后顺序,需要先旋转再压缩,否则会变成一个倾斜的长方形
.planet { transform: scaleY(0.5) rotateZ(45deg); } .ball { // 中和轨道的 scaleY 压缩,2 * 0.5 = 1 恢复原状,注意传入顺序,和 .planet 的 transform 是相反的,就像连续上了几个不同的锁,打开时要用和上锁相反的顺序去解 transform: rotateZ(-45deg) scaleY(2); }
Step5 - 把轨道变成椭圆形
.planet { border-radius: 50%; }
Step6 - 让轨道转起来
上面的步骤已经把原来的图形变成了一个类似轨道和星球的图形了,只要遵循上述关于 rotateZ 和 scaleY 的中和规律,就能让轨道转起来,且保持球体的样式不被压缩:
// 公转动画 @keyframes planet-rotate { 0% { transform: scaleY(0.5) rotate(0); } 100% { transform: scaleY(0.5) rotate(360deg); } } // 自转动画 @keyframes self-rotate { 0% { transform: rotate(0) scaleY(2); } 100% { transform: rotate(-360deg) scaleY(2); } } .planet { animation: planet-rotate 20s linear infinite; } .ball { animation: self-rotate 20s linear infinite; }
Step7 - 让轨道产生倾斜角度
依旧利用 transform 的执行顺序,只要在最后再执行一个 rotate(Z),就能让整个平面产生倾斜感
@keyframes planet-rotate { 0% { transform: rotate(45deg) scaleY(0.5) rotate(0); } 100% { transform: rotate(45deg) scaleY(0.5) rotate(360deg); } } @keyframes self-rotate { 0% { transform: rotate(0) scaleY(2) rotate(-45deg); } 100% { transform: rotate(-360deg) scaleY(2) rotate(-45deg); } }
实现多球环绕效果
因为一个轨道容器最多只能保证四个球是在圆形轨道上运动的,如果要实现大于 4 个球的运动,其实只要重叠多个轨道 + 球的平面,但只展示一个轨道(border)即可。
运动模型
独立运动个体 = 单球体 + 单球体所在轨道(父元素)
多球环绕 = 独立运动个体 * N 重叠在同一位置,并仅展示最底层球体所在轨道,其余轨道隐藏,最后对每个独立运动个体进行初始旋转位置的偏移
实现步骤
下面以 5 个球的场景为例,介绍如何实现多球环绕的效果,为了编写方便使用了 React + Sass:
1. 编写基本 DOM 结构与样式
Jsx
const dataSource = [ { name: '山田', }, ]; const renderCircleBoxItem = (name: string) => { return ( <div className={styles.circleBoxItem}> <div className={styles.ball} /> <div className={styles.name}>{name}</div> </div> ); }; <div className={styles.circleBoxWrap}> { dataSource.map((item, key) => ( <div key={key} className={styles.circleBox}>{renderCircleBoxItem(item.name)}</div> )) } </div>
Sass
@function getPlanetRotate($rotateValue) { @return rotate(45deg) scaleY(0.5) rotate(#{$rotateValue}); } @keyframes planet-rotate { 0% { transform: getPlanetRotate(0deg); } 100% { transform: getPlanetRotate(360deg); } } @function getSelfRotate($rotateValue) { @return rotate(#{$rotateValue}) scaleY(2) rotate(-45deg) scale(1)) translateX(50px); } @keyframes self-rotate { 0% { transform: getSelfRotate(0deg); } 100% { transform: getSelfRotate(-360deg); } } .circleBox { $planet-rotate-speed: 30s; width: 648px; height: 648px; position: absolute; transform-style: preserve-3d; border-radius: 50%; .circleBoxItem { position: absolute; display: flex; flex-direction: row; align-items: center; width: 200px; top: -30px; left: calc(50% - #{p2r(100)}); .ball { width: 60px; height: 60px; border-radius: 50%; overflow: hidden; border: 6px solid #fff; background-color: #6d45ca; margin-right: p2r(20); } .name { } } &:nth-child(1) { border: p2r(2) solid #fff; animation: planet-rotate $planet-ratate-speed linear infinite; .circleBoxItem { animation: self-rotate $planet-ratate-speed linear infinite; } } }
2. 处理轨道偏移
为了让球体们产生偏移,需要对每个 独立运动个体 的初始旋转位置产生偏移计算,对轨道的 keyframes 进行改写:
Sass
:root { --planet-rotate-step: 72deg; } @function getPlanetRotate($rotateValue) { @return rotate(45deg) scaleY(0.5) rotate(#{$rotateValue}); } @keyframes planet-rotate-1 { 0% { transform: getPlanetRotate(0deg); } 100% { transform: getPlanetRotate(360deg); } } @keyframes planet-rotate-2 { 0% { transform: getPlanetRotate(calc(0deg + var(--planet-rotate-step) * 1)); } 100% { transform: getPlanetRotate(calc(360deg + var(--planet-rotate-step) * 1)); } } @keyframes planet-rotate-3 { 0% { transform: getPlanetRotate(calc(0deg + var(--planet-rotate-step) * 2)); } 100% { transform: getPlanetRotate(calc(360deg + var(--planet-rotate-step) * 2)); } }
3. 处理球体运动
球体需要针对轨道的旋转路径进行位置修正,编写球体的 keyframes:
@function getSelfRotate($rotateValue) { @return rotate(#{$rotateValue}) scaleY(2) rotate(-45deg) scale(1) translateX(50px); } @keyframes self-rotate-1 { 0% { transform: getSelfRotate(0deg); } 100% { transform: getSelfRotate(-360deg); } } @keyframes self-rotate-2 { 0% { transform: getSelfRotate(calc(0deg - var(--planet-rotate-step) * 1)); } 100% { transform: getSelfRotate(calc(-360deg - var(--planet-rotate-step) * 1)); } } @keyframes self-rotate-3 { 0% { transform: getSelfRotate(calc(0deg - var(--planet-rotate-step) * 2)); } 100% { transform: getSelfRotate(calc(-360deg - var(--planet-rotate-step) * 2)); } }
4. Animations 语句编写
调整元素的 animation:
.circleBox { &:nth-child(1) { border: p2r(2) solid #fff; animation: planet-rotate-1 $planet-rotate-speed linear infinite; .circleBoxItem { animation: self-rotate-1 $planet-rotate-speed linear infinite; } } &:nth-child(2) { animation: planet-rotate-2 $planet-rotate-speed linear infinite; .circleBoxItem { animation: self-rotate-2 $planet-rotate-speed linear infinite; } } &:nth-child(3) { animation: planet-rotate-3 $planet-rotate-speed linear infinite; .circleBoxItem { animation: self-rotate-3 $planet-rotate-speed linear infinite; } } }
5. 搭配 CSS 变量自动计算球的间距
真实场景需要根据传入的数据个数自动处理球的间距(偏移距离),这时可以用 JS 动态计算并修改刚才的 CSS 变量来完美解决:
const number = dataSource.length; const step = 360 / number; document.documentElement.style.setProperty('--planet-rotate-step', `${step}deg`);
一个星球环绕的 CSS 动画就完成啦,感谢大家观看,我们下期再会~
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论