水球效果是CSS动效里非常经典的一个案例,它用到的技术点并不多,核心就是border-radius的奇技淫巧加上animation旋转动画,再叠一点点图层思维。很多天气类页面、加载进度组件都喜欢用这种效果,一个半透明的球体里水面上下起伏,看起来既灵动又高级。这篇文章会从零开始,带你把一个逼真的水球完整做出来,并且讲清楚每一步背后的原理,而不是让你抄完代码就跑。

水球的结构:先搭好三层骨架
先把结构想清楚再动手写代码,这是做复杂CSS效果的第一原则。一个标准的水球由三层组成:最外层是球形容器,负责提供圆形边界和背景色;中间层是水体,也就是我们看到的那个会晃动的蓝色部分;最内层可以放文字内容,比如百分比数字。三层各司其职,互不干扰。
球形容器本身很简单,设置宽高相等再加上border-radius: 50%就能得到一个圆。但要注意,容器需要设置overflow: hidden,这一点非常关键,因为后面水体的波浪是通过旋转一个巨大的方块实现的,如果不裁剪,波浪会溢出到球体外面,整个效果就穿帮了。很多人第一次做水球失败,问题就出在漏了这一行。
文字层建议放在容器内但水体层之外,用绝对定位居中。如果直接放在水体里面,波浪旋转时文字会跟着转,那就不是水球而是滚筒洗衣机了。基础结构代码如下:
<div class="ball"> <div class="water"></div> <div class="percent">75%</div> </div>
.ball {
position: relative;
width: 240px;
height: 240px;
border-radius: 50%;
overflow: hidden; /* 必须裁剪,防止波浪溢出 */
background: rgba(255, 255, 255, 0.05);
border: 4px solid rgba(255, 255, 255, 0.2);
}
.percent {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 36px;
color: #fff;
z-index: 3;
}核心技巧:用border-radius画出水面弧线
水球最精髓的部分在于波浪形状的生成。这里用到的是border-radius一个不太常见的用法:当四个角的半径值设得非常大甚至超过100%时,元素的两个角会被极度拉伸,形成两条向内凹陷的弧线,看起来就像水面被张力拉弯的效果。
具体来说,我们给水体元素设置一个宽度远大于球体直径的尺寸,然后用border-radius: 40% 40% 0 0 / 100% 100% 0 0这样的写法。斜杠前面是水平半径,后面是垂直半径。垂直半径设为100%意味着左上和右上两个角的圆弧会被拉成一个很深的碗状曲线。当这个巨大的水体元素在球内旋转时,这条弧线扫过的区域就成了起伏的波纹。原理示意图不好画,直接看代码更容易理解:
.water {
position: absolute;
width: 400px; /* 远大于球直径 */
height: 400px;
left: -80px; /* 让水体大致居中于球体 */
top: 35%; /* 控制水位高度,水位越低数值越大 */
background: rgba(45, 132, 235, 0.8);
border-radius: 42% 46% 0 0 / 100% 100% 0 0;
animation: spin 8s linear infinite;
z-index: 1;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}这里有个容易踩的坑:旋转中心。默认的transform-origin是元素中心,水体元素比球大,如果中心对不上,旋转起来会左右摇摆,虽然有时候这种摇摆反而更像水在晃,但如果你想控制得精确一些,就要仔细计算left和top的偏移,让水体中心恰好落在球心附近。
另外top这个属性实际上就是你的水位控制器。想让水球显示75%的水量,就把水体的上边缘放到球体高度的25%左右位置。如果要做进度展示,用JavaScript动态修改这个值即可,纯展示场景直接写死也没问题。
多层波纹叠加:让水感真实的关键一步
单层波浪的效果其实一眼假,因为真实的水面不会只有一道波。解决方法很简单,再复制一层水体,两层同时旋转但速度不同、透明度不同,叠加之后水感立刻上一个台阶。这利用的是视觉残留原理:两道相位不同的波交叉运动,看起来就像水面在不断涌动。
第二层的参数建议这样调:动画时间设为第一层的一点几倍,比如第一层8秒、第二层10秒,转速差会让两层波浪周期性地靠近又分开;透明度降到0.5左右,颜色可以稍微调浅一点,形成深浅对比。第二层的z-index要低于第一层,让主波盖在辅波上面。代码示例如下:
.water:nth-child(2) 或单独写一个类:
.water-back {
position: absolute;
width: 420px;
height: 420px;
left: -90px;
top: 38%;
background: rgba(80, 160, 255, 0.5);
border-radius: 45% 48% 0 0 / 100% 100% 0 0;
animation: spin 10s linear infinite;
z-index: 0;
}两层弧线的border-radius数值最好也稍微错开一点,比如一层42%、另一层45%,这样两条弧线的曲率不同,交叉时形态变化更丰富。如果两层数值完全一致,波峰会长时间重叠,看起来反而不自然。这些细节单看不起眼,叠加起来就是逼真和廉价之间的差距。
性能方面提醒一句:旋转动画走的是GPU合成通道,两个元素的动画开销几乎可以忽略。但如果你的页面上有十几个水球同时转,建议给水体加上will-change: transform提示浏览器提前创建合成层,避免掉帧。
细节收尾:光照、高光与水面反光
做完波浪只是及格线,逼真感的最后一步是光影。现实中的球体表面有高光和暗部,纯平色块的水球会显得像贴图。处理办法是在球体上再叠加两个绝对定位的圆形元素:一个白色半透明的小椭圆放在左上方模拟高光,配合轻微的模糊滤镜;一个黑色半透明的大半圆放在右下方模拟暗部。
.ball::before {
content: "";
position: absolute;
top: 12%;
left: 16%;
width: 40px;
height: 22px;
background: rgba(255, 255, 255, 0.55);
border-radius: 50%;
filter: blur(4px);
transform: rotate(-25deg);
z-index: 4;
}
.ball::after {
content: "";
position: absolute;
bottom: -30%;
right: -30%;
width: 70%;
height: 70%;
background: radial-gradient(rgba(0, 0, 0, 0.35), transparent 65%);
border-radius: 50%;
z-index: 4;
}注意这两个伪元素的z-index要高于水体,否则光会被水盖住。高光的位置也有讲究,默认光源来自左上方,所以高光放左上、暗部放右下,符合大多数人的视觉习惯。如果你想做玻璃质感,还可以给球形容器加一个box-shadow: inset内阴影,边缘压暗一点,玻璃球的味道就出来了。
最后总结一下整体思路:圆形容器裁剪提供边界,超大水体元素借助border-radius的深弧线在旋转中生成波纹,多层波纹用速度差和透明度差制造涌动感,光影层负责收尾提升质感。整个效果纯CSS实现,没有一行JavaScript,把这套思路吃透之后,你可以很轻松地改造成水位进度条、天气图标甚至各种液态按钮,本质上都是同一套技术的变体。
CSS水球效果CSS波纹动画border-radius修改时间:2026-09-07 09:28:46