写在开头
Hello,各位小伙伴好呀!👋
今是2025年06月26日 周四夜 22:40:10,夜已深,无心睡眠。🌌
这周也已经过去一半了,工作日无什么大事发生,平平常常,每天三点一线,工作上也还行,无什么不好的事情,也没什么压力,能愉快的迎来周末的到来。😋
然后,记录一下上周末,一个不错的周末,和好友小酌一杯(上一次喝酒还是在上一次呢😗),然后还去下了两次馆子吃大餐,无图,光顾着吃了。😵
回到正题,本次要分享的是一面七彩飘扬的"旗帜"。效果如下,请诸君按需食用哈。
🎯关键原理
上来咱们先来整原理,因为呢,这种案例是一眼望过去就应该能通晓其背后原理的,起码你要做到能纯手工撸出来才行喔。😉
那么,要实现这种旗帜"飘扬"的效果,核心思路其实很简单:把旗帜拆分成多个等宽的 "竖条",让每个竖条以不同的延迟上下摆动,就能形成风吹般的"飘扬"效果了。 🌊
这里小编将用到了 CSS 的 animation-delay 属性:给每个竖条设置递增的延迟时间,比如第一个延迟 100ms,第二个 200ms,第三个 300ms……, 这样它们就会像多米诺骨牌一样依次摆动,视觉上就形成了 “风拂过旗帜” 的动态感啦~ ✨
那么,最关键代码如下:
@keyframes oscillate {
from {
transform: translateY(20px);
}
to {
transform: translateY(-20px);
}
}
.column {
/* 每个.column的延时将被动态设置 */
animation: oscillate 500ms infinite alternate ease-in-out;
}
这个 CSS 能让咱们实现一堆上下移动的列,虽然还比较丑😂。效果如下:
然后,为了旗帜的"飘扬"效果,咱们需要给它们分别添加不同的动画延时时长(animation-delay),也非常简单,如下:
<div id="flag-container">
<div class="column" style="animation-delay: 100ms"></div>
<div class="column" style="animation-delay: 200ms"></div>
<div class="column" style="animation-delay: 300ms"></div>
<div class="column" style="animation-delay: 400ms"></div>
<div class="column" style="animation-delay: 500ms"></div>
<div class="column" style="animation-delay: 600ms"></div>
<div class="column" style="animation-delay: 700ms"></div>
</div>
然后呢,就可以得到如下的效果:
应该是有那味了吧?🤔🤔🤔
🏳️🌈绘制旗帜
基础的动画咱们算是有方向了,接下来,我们要来绘制一面彩色旗帜,这说来也挺简单👌,小编第一想法就是给每个列创建一堆 div,再给不同的背景颜色,如下:
<div id="flag-container">
<div class="column" style="animation-delay: 100ms">
<div style="background-color: hsl(0, 100%, 50%);"></div>
<div style="background-color: hsl(30, 100%, 50%);"></div>
<div style="background-color: hsl(60, 100%, 50%);"></div>
<div style="background-color: hsl(120, 100%, 25%);"></div>
<div style="background-color: hsl(180, 100%, 50%);"></div>
<div style="background-color: hsl(240, 100%, 50%);"></div>
<div style="background-color: hsl(270, 100%, 50%);"></div>
</div>
<!-- ... -->
</div>
效果:
效果确实挺好。✅
但是呢,如果旗帜列数多、色条复杂,就会疯狂堆砌 DOM 节点。比如一面 8 色 15 列的旗帜,就需要 120 个 DOM 节点!这就会消耗大量的DOM节点,这显然不够优雅。❌
在 Google 开发者文档中,明确说明:
建议保持整个页面的 DOM 节点数在 1500 个以下,深度不超过 32 层,单个父节点子元素不超过 60 个。⏰
所以,这并不是一个完美方案,那么,有没有既能解决需求又比较优雅的方案呢❓
当然有啦❗咱们可以使用背景的 线性渐变(linear-gradient) 来解决。
先来看一个小案例:
对应代码:
<style>
.box1 {
width: 150px;
height: 60px;
margin-bottom: 50px;
aspect-ratio: 3 / 2;
background: linear-gradient(
to bottom,
hsl(0, 100%, 50%), /* 赤 */
hsl(30, 100%, 50%), /* 橙 */
hsl(60, 100%, 50%) /* 黄 */
);
}
.box2 {
width: 150px;
height: 60px;
aspect-ratio: 3 / 2;
background: linear-gradient(
to bottom,
hsl(0, 100%, 50%) 0% 33.3%, /* 赤 */
hsl(30, 100%, 50%) 33.3% 66.7%, /* 橙 */
hsl(60, 100%, 50%) 66.7% 100% /* 黄 */
);
}
</style>
<div class="box1"></div>
<div class="box2"></div>
-
.box1是普通渐变,颜色会平滑过渡,这就没什么好说的吧。👌 -
.box2才是重点!每个颜色都指定了起始位置和结束位置,比如hsl(0, 100%, 50%) 0% 33.3%表示:- 从 0% 开始显示赤色,到 33.3% 时仍显示赤色;
- 33.3% 处立即切换到橙色,直到 66.7%;
- 以此类推,从而实现了无过渡的实色分块。
另一种等价写法:
background: linear-gradient( to bottom, hsl(0, 100%, 50%) 0%, hsl(0, 100%, 50%) 33.3%, hsl(30, 100%, 50%) 33.3%, hsl(30, 100%, 50%) 66.7%, hsl(60, 100%, 50%) 66.7%, hsl(60, 100%, 50%) 100% );
这样一来,原本需要 N 个 DOM 节点的色条,现在只用一行 CSS 就能搞定,不仅减少了 DOM 数量,还能通过 JS 动态生成渐变字符串,适配任意颜色组合🎨,这就很灵活,完美。🥳
💯细节消除
最麻烦的核心部分已经讲完啦,剩下的就是一些细节问题了。咱们接下来重点是把能动态调控的细节都交给 JS,比如,动态创建旗帜、不同延时、不同颜色渐变、不同飘扬幅度等等,让旗帜效果更灵活可控。
1️⃣飘扬幅度
还记得最开始写的 CSS 动画吗?当时用的是硬编码的20px幅度,虽然方便,但不够灵活,它让旗帜每列都固定了一个飘扬幅度运动,现在小编想让每个列都有一个略微不一样的飘扬幅度,这样旗帜的飘扬效果估计会更好玩一点。🤔
这里咱们选择用 CSS 变量 + JS 的组合拳来实现,首先,在 CSS 中定义动画,用 var(--billow) 作为摆动幅度的变量,如:
@keyframes oscillate {
from {
transform: translateY(var(--billow));
}
to {
transform: translateY(calc(var(--billow) * -1));
}
}
然后,通过 JS 给每一列动态设置不同的 --billow 值, 比如让中间列幅度小,边缘列幅度大,模拟旗帜中心固定、边缘摆动更明显的物理特性:
for (let i = 0; i < numOfColumns; i++) {
// 动态创建旗帜
const column = document.createElement("div");
column.className = "column";
// 不同飘扬幅度
column.style.setProperty("--billow", `${(i + 1) * billow}px`);
// ...
flag.appendChild(column);
}
这样每一列的摆动幅度就有了层次感。💯 咱们也可以在页面上动态控制 billow 的变化,如:
2️⃣渐变
在之前绘制旗帜的部分,咱们手动编写了渐变的代码:
background: linear-gradient(
to bottom,
hsl(0, 100%, 50%) 0% 33.3%, /* 赤 */
hsl(30, 100%, 50%) 33.3% 66.7%, /* 橙 */
hsl(60, 100%, 50%) 66.7% 100% /* 黄 */
);
这种硬编码方式有两个小缺点:
- 颜色数量固定,想新增颜色得手动计算🧮百分比;
- 要换一种旗帜时,得重新写一套渐变逻辑。
这有点麻烦,咱们可以用 JS 写一个自动生成渐变的函数,如:
const RAINBOW_COLORS = [
"hsl(0, 100%, 50%)", // 赤
"hsl(30, 100%, 50%)", // 橙
"hsl(60, 100%, 50%)", // 黄
"hsl(120, 100%, 25%)", // 绿
"hsl(180, 100%, 50%)", // 青
"hsl(240, 100%, 50%)", // 蓝
"hsl(270, 100%, 50%)", // 紫
];
/**
* @name 生成渐变字符串
* @param {Array<string>} colors 颜色数组
* @returns {string} 渐变字符串
*/
function generateGradientString(colors) {
const numOfColors = colors.length;
// 每个颜色占据的百分比区间
const segmentHeight = 100 / numOfColors;
const gradientStops = colors.map((color, index) => {
const from = index * segmentHeight;
const to = (index + 1) * segmentHeight;
// 格式:颜色 起始% 结束%
return `${color} ${from}% ${to}%`;
});
return `linear-gradient(to bottom, ${gradientStops.join(", ")})`;
}
const gradient = generateGradientString(RAINBOW_COLORS);
console.log(gradient);
/* 输出:
* hsl(0, 100%, 50%) 0% 14.285714285714285%,
* hsl(30, 100%, 50%) 14.285714285714285% 28.57142857142857%,
* ...
*/
3️⃣间隙
有时候浏览器会因为像素取整问题,在竖条之间留下细微间隙。
解决办法是让旗帜总宽度能被竖条数整除:
const friendlyWidth = Math.round(width / numOfColumns) * numOfColumns;
// 例如:宽度200px,12列,200/12=16.666px,取整为17px×12=204px
👉完整源码
至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。