ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CSS3实现天气图标:从box-shadow到clip-path的完整方案

CSS3实现天气图标:从box-shadow到clip-path的完整方案

用CSS3实现天气图标这个想法,最早是我在某次项目迭代中被逼出来的。当时设计稿里给了一套PNG序列图,晴天、多云、阴天、雨、雪、雷、雾各自对应两三张静态位图,遇到切换天气的过渡动画就得手动拼接,图片还经常在深色模式下露白边。来回改了几轮之后,我把整套天气图标都换成了纯CSS3实现,一个class对应一种天气,代码量没增加多少,动画衔接和换肤反而都顺了。这篇东西就来聊聊我是怎么拆解和实现的,适合正在做天气组件、仪表盘或者物联网设备界面的前端同学参考,也适合想理解CSS3图形绘制思路的人读一读。

1. 为什么我最终选择CSS3而不是SVG序列图

1.1 从一次组件迭代说起

最初用SVG图标其实挺顺手的,矢量、清晰、体积小。但真正让我头疼的是“状态变化”。天气预报页面不只是显示一个静态图标,它需要在刷新时做淡入、翻转或者位移过渡,逻辑上还要根据天气代码切换图标。SVG或者PNG序列在这种场景下会有两个硬伤:一是换图时几乎所有方案都要做DOM节点替换,动画衔接只能靠opacity交叉淡化,做不到真正意义上的“同一个图标在动”;二是夜间模式、主题色换肤时,位图要么提前导出多套,要么在CSS里加滤镜做颜色修正,维护成本不低。

CSS3实现天气图标的思路不一样。图标本身不需要任何图片资源,它由你写在样式里的基础图形、伪元素和动画组成。换天气等于换class,换主题色等于改几个CSS变量,动画可以直接挂在图形元素上。虽然CSS3能绘制的形状有边界,但天气图标恰好是它最适合的场景:几何化、符号化、动态效果要求不高、视觉识别度高。

1.2 CSS3天气图标的边界:能画什么、不能画什么

先说结论:CSS3几乎能覆盖所有常见天气图标,但在写之前你要清楚它的能力边界。

能做的包括:圆形(太阳、月亮、雨滴)、圆角矩形组合(云朵、雾条)、多边形裁剪(闪电)、线条和渐变(雪花、雨线、太阳光线)。这些都是在普通前端项目里会被大量使用的基础图形能力。不能做的,或者说不适合做的,是写实风格、带复杂渐变光影的图标,以及需要精细化物理仿真的粒子雨雪场景。CSS3的动画适合做符号层面的示意,不适合追求“像照片一样”的视觉效果。

只画静态天气符号其实用不到多少技巧,真正的价值在于“一套体系”。我给自己定的目标是:一个图标容器,不引入额外的图片,由若干零件类组合而成。晴天是一个太阳,多云是太阳加一朵云,雷阵雨是云加闪电加雨滴,这样组合逻辑跟后端下发的天气代码也更容易对应。

1.3 图标“零件化”设计思路:一次coding,到处拼装

我先把所有天气状态拆成7个基础零件:太阳、月亮、云朵、雨滴、雪花、闪电、雾条。然后这些零件就是“CSS组件”,可以自由组合。

这样做的好处很直接:

  • HTML结构稳定,切换天气时只需要替换容器上的class,不需要重建内部结构;
  • 零件样式复用,比如多云和阴天都用了云朵,只是云朵大小、透明度不同;
  • 动画复用,雨滴动画可以直接挂给雷阵雨里的雨滴层;
  • 主题换肤方便,所有颜色集中在顶层的CSS变量里。

实现上,我会给每个图标一个统一的容器尺寸,零件绝对定位在容器内。下面这个结构是整套体系的骨架:

<div class="weather-icon" aria-label="晴天" role="img"> <span class="w-sun"></span> </div>

如果换成多云:

<div class="weather-icon partly" aria-label="多云" role="img"> <span class="w-sun"></span> <span class="w-cloud"></span> </div>

基础容器固定尺寸是120px,用这个尺寸来设计图形比例,后面要用它做响应式缩放。

2. 太阳和月亮:光照类图标的核心画法

2.1 太阳光线:从8条box-shadow到repeating-conic-gradient

太阳的经典画法是中心一个圆,四周放射若干条光线。我最早用box-shadow写的时候,是给一个圆形中心元素挂8条不同方向的阴影:

.w-sun-core { width: 48px; height: 48px; border-radius: 50%; background: #ffd75e; box-shadow: 0 -20px 0 -6px #ffd75e, 14px -14px 0 -6px #ffd75e, 20px 0 0 -6px #ffd75e, 14px 14px 0 -6px #ffd75e, 0 20px 0 -6px #ffd75e, -14px 14px 0 -6px #ffd75e, -20px 0 0 -6px #ffd75e, -14px -14px 0 -6px #ffd75e; }

这个方案能看懂,但有个缺点:8条阴影全部是等长等粗的,视觉上比较呆板,而且每个方向都要手写一条阴影,改起来麻烦。后来我换成了repeating-conic-gradient,一行代码就解决了:

.w-sun::before { content: ""; position: absolute; inset: -24px; border-radius: 50%; background: repeating-conic-gradient( from 0deg, #ffd75e 0deg, #ffd75e 4deg, transparent 4deg, transparent 45deg ); }

这段样式的逻辑是:在一个以太阳核心为中心的圆形区域内,从0度开始,每45度循环一次,其中前4度画太阳光线的颜色,剩余41度透明,于是形成8条光线。inset: -24px把伪元素撑大到比核心圆更大,光线就自然分布在四周。

它比我手动写的8条box-shadow清爽很多,而且调整光线粗细只需要改4deg,调整光线数量只需要把45度改成其他角度。平时我为了照顾兼容性还保留box-shadow写法,但在支持conic-gradient的环境里,这个方案确实更适合维护。

2.2 月牙形态:mask的“两圆相减”技巧

月亮比太阳麻烦一点,因为它不是完整圆形,而是月牙形。很多人第一反应是用背景色圆错位去盖住一部分,但这样一旦图标背景不是纯色,遮挡圆就会穿帮。

我采用的方案是CSS的mask,思路非常直观:先画一个完整的浅黄色圆作为月亮底色,再用一个偏心的透明圆把它“咬”掉一块,剩下的就是月牙。可以理解成两圆相减。

.w-moon { position: absolute; left: 50%; top: 50%; width: 56px; height: 56px; transform: translate(-50%, -50%); border-radius: 50%; background: #f5f1d0; -webkit-mask: radial-gradient( circle at 64% 36%, transparent 0 46%, #000 47% ); mask: radial-gradient( circle at 64% 36%, transparent 0 46%, #000 47% ); }

mask里transparent的部分不会显示,#000的部分显示背景色。我把偏心圆圆心放在太阳圆心的右上方,让左边和下边露出来,形成比较自然的月牙弧度。

这里有个经验:mask的渐变边界建议留一点过渡,比如46%到47%之间的1%过渡,否则月牙边缘会显得很生硬,像被剪刀剪的。生产环境下记得补上-webkit-前缀,虽然现代Safari已经支持无前缀写法,但保险起见我一般两个都写。

为了让月亮更有辨识度,我还会在月牙的凹面加一两个环形阴影,让表面看起来有月海的感觉。做法是在mask容器里叠加一个不透明的伪元素,用半透明的径向渐变模拟凹陷:

.w-moon::after { content: ""; position: absolute; width: 14px; height: 14px; left: 40%; top: 38%; border-radius: 50%; background: rgba(0, 0, 0, 0.08); box-shadow: 10px 14px 0 2px rgba(0, 0, 0, 0.06), -4px 24px 0 -2px rgba(0, 0, 0, 0.05); }

这样小尺寸下也能看出月亮的体积感。实际上这个伪元素会被mask一起裁剪,月海只会出现在月牙露出的部分,不会跑到背景里,这也是mask方案比遮挡法更好用的原因之一。

2.3 早晚切换动画:旋转加淡入的细节处理

天气图标不只是静态图形,用户刷新天气时的过渡动画很影响体验。我遇到的一个场景是:同一个位置,白天显示太阳,晚上显示月亮,用户翻转卡片时希望两者之间有自然的过渡。

我的做法是用一个直接子元素承载太阳,另一个承载月亮,两者的opacity和transform在父容器切换class时做过渡:

.weather-icon .w-sun, .weather-icon .w-moon { transition: opacity 0.6s ease, transform 0.6s ease; } .weather-icon.night .w-sun { opacity: 0; transform: rotate(120deg) scale(0.6); } .weather-icon.night .w-moon { opacity: 1; transform: rotate(0deg) scale(1); } .weather-icon .w-moon { opacity: 0; transform: rotate(-120deg) scale(0.6); }

这里有个容易被忽略的坑:如果只做opacity淡入淡出,太阳消失和月亮出现是“交叉叠画”,视觉上会有一小段浑浊的叠影。我加入了一个小幅度的缩放和旋转,让两个天体在空间上有交错感,会自然很多。

3. 云朵:单元素box-shadow玩出的花瓣形

3.1 云朵拆解:一个圆角矩形加两个圆

云朵是所有天气图标里最高频出现的零件,晴天、多云、阴天、雷雨全都要用到它,所以它的绘制质量直接影响整套图标的观感。

从形状上拆解,一朵卡通云可以看作三个部分:底部一条偏长的圆角矩形,左上角和右上角各有一个凸起的圆弧。最朴素的实现是用三个DOM元素拼:一个div加两个圆形伪元素。

<span class="w-cloud"> <i></i> <i></i> <i></i> </span>

但是我在设计零件时已经定了“能用伪元素就不加DOM”的原则,所以更倾向于用box-shadow把云朵压缩成一个单元素绘制。

3.2 box-shadow的spread参数怎么玩

单元素云朵的核心技巧是:先画一个圆角矩形作为云朵主体,然后用box-shadow生成两个“额外的圆”,叠在主体上面,形成云朵的蓬松轮廓。

.w-cloud { position: absolute; width: 72px; height: 22px; left: 50%; top: 50%; border-radius: 20px; background: #f8fafc; box-shadow: 16px -12px 0 -4px #f8fafc, 34px -4px 0 -8px #f8fafc; transform: translate(-50%, -45%); }

解释一下:第一个box-shadow向右边偏移16像素、向上偏移12像素,-4px是spread负值,它会让阴影比主体尺寸小一圈,再配合它没有水平偏移,视觉上就像主体右侧鼓起的一个圆包。第二个box-shadow偏移更多,-8px的负spread让它更小,作为云朵右侧的另一个凸包。

这个方案的优点是一行box-shadow就能画出云朵轮廓,缺点是阴影的“圆度”完全靠调整偏移和负spread控制,需要多调几次。我给一个经验值范围:主体高度在18到24像素之间时,第一个阴影的负spread在-4到-6px之间,第二个阴影在-6到-10px之间,出来的轮廓最自然。

如果想让云朵再立体一点,可以在主体上加一层垂直渐变,让底部更实、顶部更亮:

background: linear-gradient(to bottom, #ffffff 0%, #e2e8f0 100%);

但是这里有个坑:box-shadow继承的是background整体,渐变会跟着阴影一起被复制。如果你只想让主体渐变而阴影保持纯白,就不能直接沿用上面的写法,需要单独用一个伪元素来做渐变覆盖,或者干脆所有云朵统一纯白底色。我在实际项目里为了控制复杂度,云朵一律用纯色,阴影和主体同色,这样组合时最省心。

3.3 多云与阴天的层级关系:太阳躲到云后面

多云图标是“太阳+云朵”,但这里的太阳是小尺寸版本,而且要被云朵遮挡一部分。层级上,太阳放在云朵下面,也就是用z-index: 1给云朵,太阳不设或设成0,这样云朵自然盖住太阳的边缘。

.weather-icon.partly .w-sun { width: 28px; height: 28px; position: absolute; right: 18px; top: 12px; z-index: 0; } .weather-icon.partly .w-cloud { bottom: 14px; left: 8px; z-index: 1; }

这里我会把云朵的尺寸放大到90px左右,让云朵主体更大,视觉上像是太阳“躲”在云后,而不是云朵贴在太阳旁边。

阴天图标更简单,云朵颜色改成深灰,去掉太阳零件,云朵可以整体拉伸一点,宽度做到100px左右,显得更沉闷。这里我注意到一个视觉细节:同样一朵云,在晴天时适合偏小偏亮,在阴天时适合偏大偏暗,这样图标传达的情绪才准确。很多CSS图标教程只教你画形状,不考虑情绪区分,实际做出来用户会觉得“哪里不对劲”。

阴天云朵的实现我会复用云朵类,但覆盖颜色和尺寸:

.weather-icon.overcast .w-cloud { width: 96px; background: #cbd5e1; box-shadow: 16px -12px 0 -4px #cbd5e1, 34px -4px 0 -8px #cbd5e1; opacity: 0.9; }

不建议用filter: brightness去把白色云朵变灰,因为会在云朵边缘产生模糊光晕,显得脏;直接换颜色才是可控的做法。

4. 雨、雪和雾:用伪元素体系模拟粒子运动

4.1 雨滴粒子的错峰动画

雨滴图标其实就是云朵加上几条倾斜的雨线。云朵复用上面的样式,雨线需要单独处理。

我的雨线方案是用一个伪元素画一条小竖线,再通过box-shadow在容器里复制出多个雨滴位置,最后对整个伪元素组做上下位移和透明度动画:

.w-rain::before { content: ""; position: absolute; left: 20px; top: 56px; width: 4px; height: 14px; border-radius: 2px; background: #4aa8ff; box-shadow: 18px 16px 0 0 rgba(74, 168, 255, 0.8), 38px 28px 0 0 rgba(74, 168, 255, 0.7), 54px 10px 0 0 rgba(74, 168, 255, 0.85), 70px 22px 0 0 rgba(74, 168, 255, 0.75); animation: rainDrop 1.1s linear infinite; } @keyframes rainDrop { 0% { transform: translateY(-8px); opacity: 0.6; } 40% { opacity: 1; } 100% { transform: translateY(28px); opacity: 0; } }

从远处看,这些雨线都是在同一个容器内移动,但各自的位置固定,错落有致。你可能会觉得所有雨滴都在同一节奏下落,有一点“整齐”,但天气图标本身就是符号化表达,以可读性优先,这个效果完全够用。

要让雨滴更自然,我还有一个技巧:把雨线分成两组,一组挂在::before上,一组挂在::after上,两组动画的duration略有差异。第一组1.1秒循环,第二组1.6秒循环,这样两组雨滴的下落节奏叠加,视觉上就破除了“集体跳楼”的机械感。

.w-rain::after { content: ""; position: absolute; left: 8px; top: 60px; width: 3px; height: 10px; border-radius: 2px; background: rgba(74, 168, 255, 0.6); box-shadow: 34px 40px 0 0 rgba(74, 168, 255, 0.6), 62px 34px 0 0 rgba(74, 168, 255, 0.6); animation: rainDrop 1.6s 0.3s linear infinite; }

4.2 雪花的相对简单实现:十字旋转

雪花的图案比雨滴复杂,好在它的“雪花”形态不需要细节到真实晶体,一个六角形的符号就够用了。我选择用两个旋转45度相交的十字来模拟,再在中心加一个圆点。

具体实现上,我习惯用background的线性渐变来画两个形状:

.w-snow::before { content: ""; position: absolute; left: 10px; top: 56px; width: 18px; height: 18px; background: linear-gradient(#e7f0ff, #e7f0ff) center / 2px 100% no-repeat, linear-gradient(#e7f0ff, #e7f0ff) center / 100% 2px no-repeat; border-radius: 50%; box-shadow: 32px 18px 0 -2px #e7f0ff, 62px 12px 0 -3px #e7f0ff; animation: snowFall 2s ease-in-out infinite; }

这里用background背景渐变绘制出“十字雪花”,然后通过box-shadow复制出另外两处雪花,位置错开。动画上,我让雪花既做匀速下落,又叠加一个缓慢旋转:

@keyframes snowFall { 0% { transform: translateY(-6px) rotate(0deg); opacity: 0.7; } 50% { opacity: 1; } 100% { transform: translateY(24px) rotate(60deg); opacity: 0.3; } }

旋转幅度不需要大,20到60度之间就能让雪花看起来有漂浮感。如果旋转超过90度,十字雪花的四个方向会变回原形,视觉上会“倒回去”,容易穿帮。

4.3 雾的三层横条件动画

雾是天气图标系列里最容易做漏的一个,因为它的辨识度不高。我观察过很多天气应用,雾的图标经常做得像一坨脏云,用户根本分不清。

我自己的方案是在图标底部画三到四条不同长度的扁平横条,模拟雾气层叠的效果:

.w-fog::before, .w-fog::after { content: ""; position: absolute; left: 0; right: 0; height: 4px; border-radius: 3px; background: #b8c6d9; box-shadow: 0 14px 0 #a9bcd0; } .w-fog::before { top: 52px; opacity: 0.8; animation: fogDrift 4s ease-in-out infinite alternate; } .w-fog::after { top: 68px; width: 70%; background: #b8c6d9; box-shadow: none; opacity: 0.6; animation: fogDrift 5.5s ease-in-out infinite alternate-reverse; }

横条不需要是完整矩形,我加上border-radius让两端变成圆头,更像悬浮的雾团。动画用水平方向的小幅位移,让雾有“缓慢飘动”的感觉:

@keyframes fogDrift { from { transform: translateX(-4px); } to { transform: translateX(4px); } }

这里有个实用经验:雾条的动画位移幅度不要超过8px,否则整个图标的结构会显得松散。雾本来就是低对比度的天气,动画一旦过大,用户会觉得是“图标在滑动”而不是“雾在飘”。

4.4 为什么不用真实粒子DOM:性能预算

写雨雪动画的时候,最直接的思路可能是DOM里放十颗雨滴、二十颗雨滴,每颗都有独立的动画。我以前在一版仪表盘项目里试过,一个页面上同时出现20个大天气卡片时,雨滴动画全部独立运行,CPU在低端设备上明显吃紧。

CSS3的好处在于,一个粒子元素上的box-shadow可以复制出多个视觉粒子,而浏览器只需要为一个元素计算动画。我上面所有雨雪动画都是这种思路:同一个伪元素上的box-shadow负责视觉数量,transform动画负责整体运动。这样单个天气图标最多只需要两个动画层,页面上同时出现几十个卡片也不会卡。

这条性能预算原则很重要:在图标这类高频复用小元素上,尽量把动画收敛到单个元素的transform和opacity属性上,不要铺开成几十个独立DOM节点。

5. 雷暴与组合天气:把零件组装成完整图标

5.1 闪电的clip-path多边形裁剪

闪电形状比较特殊,天然是一个带尖角的多边形,我直接用clip-path裁剪出来,比用一堆旋转矩形拼接方便得多。

.w-lightning { position: absolute; left: 50%; top: 56px; width: 30px; height: 46px; background: #ffd75e; clip-path: polygon( 54% 0%, 100% 0%, 44% 52%, 70% 52%, 0% 100%, 24% 46%, 0% 46% ); filter: drop-shadow(0 2px 4px rgba(255, 215, 94, 0.4)); transform: translateX(-50%); }

这里clip-path的polygon参数是我反复调出来的,闪电的一个尖角朝左下方,一个朝右下方,中间有两个内收的凹槽,整体比较接近我们日常见到的闪电符号。filter: drop-shadow只给闪电本身加一点光晕,不会给容器造成大片阴影。

在雷阵雨图标里,闪电放在云朵下方的正中间,雨滴层放在闪电后面或者旁边。由于闪电颜色和云朵颜色有明暗对比,视觉中心很自然落在闪电上,雨滴只做气氛补充。

5.2 组合图标的层级管理和z-index问题

组合天气的层级管理是我最想强调的部分。很多人在写CSS图标时,习惯把零件全部凑进一个容器里,然后发现云朵挡住了太阳、雨滴跑到了云朵上面,只能加越来越多的z-index。

我的做法是把层级规则统一成一个简单的约定:天气容器内部,背景层零件在最下面,中间层是云朵,前景层是雨雪闪电。

具体到代码里:

.w-sun, .w-moon { z-index: 0; } .w-cloud { z-index: 1; } .w-rain, .w-snow, .w-lightning, .w-fog { z-index: 2; }

这样一套层级从root到叶子就固定了,组合时不需要反复调试。雷阵雨里云朵在z-index: 1,闪电和雨滴在z-index: 2,视觉上闪电会压在云朵下方边缘,雨滴会在云朵前下落,层次自然正确。

这里踩过一个坑:如果给云朵的box-shadow画了负spread的凸包,云朵的视觉边界实际比元素本体要大,但元素本体的z-index层级不包含box-shadow的排序问题,所以不会出现云朵阴影压到闪电上的情况,放心用。

5.3 如何用CSS变量统一整套图标尺寸

为了让整套图标适配不同尺寸(手机通知栏、窄侧边栏、大屏展示),我用CSS变量做整体缩放,而不是在每个零件里写死px。

基础做法是在容器上定义一个尺寸变量:

.weather-icon { --icon-size: 120px; --icon-color-rain: #4aa8ff; --icon-color-sun: #ffd75e; --icon-color-cloud: #f8fafc; width: var(--icon-size); height: var(--icon-size); position: relative; overflow: hidden; }

内部所有零件的绝对定位、宽高都基于这个变量计算。比如太阳核心原本固定48px,可以改成:

.w-sun { width: calc(var(--icon-size) * 0.4); height: calc(var(--icon-size) * 0.4); }

云朵宽度是calc(var(--icon-size) * 0.6),雨滴左偏移是calc(var(--icon-size) * 0.17)。虽然写起来比固定px繁琐一点,但之后要做小尺寸图标,只需要在父容器上设置--icon-size: 80px,整套图标就跟着缩放了,不用一个个改。

如果项目不需要特别精细的响应式,也可以偷个懒,用transform: scale()对容器做整体缩放:

.weather-icon.small { transform: scale(0.6); }

这样写最快,但有两个副作用:一是缩放后容器占位尺寸不变,需要额外用margin或者width调整占位;二是内部文字或阴影的模糊程度不会跟着等比变化,如果图标里用了filter,小尺寸时可能显得发虚。我的建议是生产环境用CSS变量方案,临时demo可以用scale。

6. 上线前必须处理的性能与兼容细节

6.1 动画性能:transform和opacity的优先级

CSS3图标动画写起来简单,但性能问题很容易在低端设备上暴露。核心原则是:动画尽可能只改变transform和opacity,不要改变top、left、width、height、filter这些属性。

top、left的动画会触发浏览器重新计算布局,然后重绘,每一帧都要做大量计算;而transform动画只触发合成器工作,GPU直接处理,开销小得多。我上面所有动画都严格遵守这个原则:云朵的位移用transform: translateX,雨滴的下落用transform: translateY,雪花的旋转用transform: rotate,没有用top/left做动画。

filter: drop-shadow虽然视觉效果好,但它在动画过程中的开销比transform高。我一般只在静态的闪电元素上用filter做光晕,不会让它参与帧动画。如果实在要在动画中用阴影,尽量用box-shadow替代filter,但box-shadow在动画中也有重绘成本,最好的做法是阴影固定不变,只动元素本身。

6.2 prefers-reduced-motion:尊重用户的系统设置

天气图标不是核心交互组件,很多人会忽略无障碍动画控制。但雨雪动画如果一直跳动,对前庭敏感或视觉障碍用户来说就是一个干扰源。

我习惯在所有动效类组件上都加上prefers-reduced-motion适配:

@media (prefers-reduced-motion: reduce) { .weather-icon *, .weather-icon *::before, .weather-icon *::after { animation: none !important; transition: none !important; } }

这样用户在系统里开启了“减少动态效果”,图标会退化为静态图形,保留基本的识别信息。加这个媒体查询的代价很低,但对可访问性的提升是实打实的。我身边有做过政务类项目的前端同事,他们把这也列入了强制检查项。

6.3 兼容性速查表:mask、conic-gradient、clip-path

CSS3绘图标用到的几个关键技术特性,在不同浏览器上的支持情况值得心里有数。我整理了一个自己的速查表:

特性现代Chrome/Edge/FirefoxSafari使用时需要关注的点
border-radius + box-shadow完全支持完全支持无,最稳的方案
repeating-conic-gradient支持14.1+支持太阳光线优先用,需测试
mask / -webkit-mask支持支持月亮建议两个前缀都写
clip-path: polygon支持15.4+支持闪电可用,注意老Safari降级
CSS变量支持支持全局定义变量时留意命名冲突
transform动画完全支持完全支持无

如果项目必须兼容Safari 14以下,我的建议是月亮别用mask,改成box-shadow的inset技巧;太阳光线用box-shadow版本,避免conic-gradient踩坑。闪电如果没有clip-path支持,可以用几个旋转过的矩形拼接,丑一点但功能可用。

另外要提醒一个细节:box-shadow和border-radius画出的云朵,在Windows和macOS的字体渲染下观感差异不大,因为不依赖字体;但如果用字符实体(比如“❄”或“☂”)来画图标,不同系统、不同字体的渲染差异会非常大,这也是我坚持不用字符法画天气图标的理由。

一点实际的工程体会

整套天气图标做下来,我最大的感受是:CSS3的真正优势不在于它能画出多复杂的图形,而在于它能把“图形零件”和“动画行为”解耦,让一套代码同时服务于视觉和交互。复用同一个云朵零件,配上不同的尺寸、颜色、动画参数,就能表达晴天、多云、阴天和雷阵雨,这在传统的位图方案里需要导出好几张图才能做到。

如果你也想在项目里用这套方案,我建议先把设计稿里的天气状态全部列出来,切成零件清单,再动手写样式。零件清单越清晰,最后的CSS越稳。像雨滴动画这类细节,不必追求完美拟真,用户看天气图标要的是“0.5秒内认出来”,清晰度优先于写实度。

最后分享一个调试小习惯:我在调整云朵和闪电这类多边形图形时,会在浏览器开发者工具里打开“显示布局边界”,同时用一个纯黑背景的测试容器预览图标。因为深色背景下很容易发现图形边缘的瑕疵和阴影穿帮,浅色背景下不容易注意到。这套CSS3天气图标我已经在两个内部项目里复用过了,每次只需要调颜色变量和局部尺寸,没有再为天气图形本身动过结构。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进