在电力监控大屏、安防监控大屏等场景中,通常需要通过视觉效果直观地展示系统状态。本文介绍一种基于 CSS 伪元素 + CSS 滤镜 + 错位动画实现的红绿呼吸灯效果,正常状态显示绿色静态边框,有报警事件发生时自动切换为红色脉动呼吸效果,代码基于 Vue 3 + SCSS 实现。
一、需求背景
在一些监控大屏中,我们需要:
- 正常状态:显示绿色边框,静态展示,系统运行正常
- 报警状态:自动切换为红色,并且呈现呼吸脉动效果,强烈提醒运维人员注意
- 动态切换:根据后台数据实时更新状态,报警解除后自动切回绿色
本文实现的效果是:大屏左右两侧弧形装饰边框,正常状态为绿色静态,存在未处理报警时变为红色脉动呼吸灯效果。
二、实现思路
我们采用以下巧妙的设计思路:
| 步骤 | 技术方案 |
|---|---|
| 1. 基础图片 | 使用一张绿色装饰边框图片作为基础 |
| 2. 双色处理 | 通过 CSS filter 将同一张图片分别滤色为不同色调 |
| 3. 伪元素层叠 | 利用两个伪元素(::before、::after)分别叠加滤色后的图片 |
| 4. 错位动画 | 两个伪元素分别使用相反相位的透明度动画,错开 50%,形成自然的呼吸效果 |
| 5. 动态切换 | JavaScript 通过 DOM API 切换类名,根据后台报警状态决定是否启用动画 |
关键点:只需要一张绿色原图,通过 CSS 滤镜就能转换成红色,不需要设计师出两张不同颜色的图片,节省设计资源和包体积。
三、完整代码实现
3.1 模板结构
html
<template>
<div class="dashboard-container">
<!-- 顶部和底部装饰 -->
<div class="top-bottom">
<img :src="top" alt="" />
</div>
<div class="top-bottom">
<img :src="bottom" alt="" />
</div>
<!-- 左右弧形边框 -->
<div class="arc" /> <!-- 右侧弧形边框 -->
<div class="arc-left" /> <!-- 左侧弧形边框 -->
<!-- 内容区域 -->
<div class="banner" />
<div class="search" />
<div class="map-container dashboard-panel">
<MainMap />
</div>
</div>
</template>
3.2 SCSS 样式定义
scss
.dashboard-container {
// 呼吸灯动画 - 两个元素错开相位
@keyframes fade-in-out {
0%, 100% { opacity: 1; } // 开始和结束完全不透明
50% { opacity: 0; } // 中间完全透明
}
@keyframes fade-out-in {
0%, 100% { opacity: 0; } // 开始和结束完全透明
50% { opacity: 1; } // 中间完全不透明
}
position: relative;
width: 100%;
height: 100%;
// 正常状态 - 绿色静态(右侧)
.arc {
position: absolute;
top: -7%;
right: 0;
z-index: 11;
height: 116%;
aspect-ratio: 331 / 1377;
pointer-events: none;
background: url("@/assets/dashboard/index/right.png") no-repeat center center / cover;
}
// 报警状态 - 红色呼吸灯(右侧)
.arc-red {
position: absolute;
top: -7%;
right: 0;
z-index: 11;
height: 116%;
aspect-ratio: 331 / 1377;
pointer-events: none;
// 两个伪元素分别使用不同滤色和不同相位动画
&::before,
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: "";
background: url("@/assets/dashboard/index/right.png") no-repeat center center / cover;
opacity: 0.8;
}
// 第一个伪元素:红色调,fade-in-out 动画
&::before {
filter: grayscale(100%) brightness(0.5) sepia(1) hue-rotate(-50deg) saturate(5);
animation: fade-in-out 3s ease-in-out infinite;
}
// 第二个伪元素:偏黄/绿色调,fade-out-in 动画(相位相反)
&::after {
filter: grayscale(100%) brightness(0.5) sepia(1) hue-rotate(100deg) saturate(5);
animation: fade-out-in 3s ease-in-out infinite;
}
}
// 正常状态 - 绿色静态(左侧)
.arc-left {
position: absolute;
top: -7%;
left: 0;
z-index: 11;
height: 116%;
aspect-ratio: 331 / 1377;
pointer-events: none;
background: url("@/assets/dashboard/index/left.png") no-repeat center center / cover;
}
// 报警状态 - 红色呼吸灯(左侧)
.arc-left-red {
position: absolute;
top: -7%;
left: 0;
z-index: 11;
height: 116%;
aspect-ratio: 331 / 1377;
pointer-events: none;
&::before,
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: "";
background: url("@/assets/dashboard/index/left.png") no-repeat center center / cover;
opacity: 0.8;
}
&::before {
filter: grayscale(100%) brightness(0.5) sepia(1) hue-rotate(-50deg) saturate(5);
animation: fade-in-out 3s ease-in-out infinite;
}
&::after {
filter: grayscale(100%) brightness(0.5) sepia(1) hue-rotate(100deg) saturate(5);
animation: fade-out-in 3s ease-in-out infinite;
}
}
// 停止动画类(可选,用于需要暂停的场景)
.arc.no-animation::before,
.arc.no-animation::after,
.arc-left.no-animation::before,
.arc-left.no-animation::after {
animation: none;
}
}
3.3 CSS 滤镜链原理解析
scss
filter: grayscale(100%) brightness(0.5) sepia(1) hue-rotate(-50deg) saturate(5);
这一串滤镜组合的作用逐一分解:
| 滤镜参数 | 作用 |
|---|---|
grayscale(100%) |
将图片转为灰度,去掉原始绿色,为后续染色做准备 |
brightness(0.5) |
降低亮度,让颜色更容易被后续滤镜改变色相 |
sepia(1) |
添加褐色(棕黄色)基调,这是变色的基础 |
hue-rotate(-50deg) |
色相旋转,把颜色旋转到红色区域 |
hue-rotate(100deg) |
另一个伪元素旋转到偏绿/黄色区域,与第一个形成色差 |
saturate(5) |
大幅提高饱和度,让颜色更鲜艳 |
通过这种方式,一张绿色原图就能产生红色呼吸效果,不需要额外的图片资源。
3.4 错位动画为什么更自然
scss
@keyframes fade-in-out {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
@keyframes fade-out-in {
0%, 100% { opacity: 0; }
50% { opacity: 1; }
}
这种错位设计:
- 第一个伪元素:亮 → 暗 → 亮
- 第二个伪元素:暗 → 亮 → 暗
两个叠加之后,总亮度从高→低→高平滑变化,不会有单个元素从完全亮到完全暗的生硬感,呼吸效果更自然。
动画周期设置为 3s,不快不慢,适合大屏展示的节奏感。如果觉得太快或太慢,直接修改这个数值即可。
四、JavaScript 动态切换逻辑
根据需求动态切换类名:
typescript
// 存在需求 → 切换到红色呼吸灯
if (你的判断条件) {
// 通过类名切换触发呼吸动画
const arcElements = document.querySelector(".arc");
const arcElementsLeft = document.querySelector(".arc-left");
if (arcElements && arcElementsLeft) {
arcElements.classList.remove("arc");
arcElementsLeft.classList.remove("arc-left");
arcElements.classList.add("arc-red");
arcElementsLeft.classList.add("arc-left-red");
}
} else {
// 无报警 → 切回绿色静态
const arcElementsLeftRed = document.querySelector(".arc-left-red");
const arcElementsRed = document.querySelector(".arc-red");
if (arcElementsRed && arcElementsLeftRed) {
arcElementsRed.classList.remove("arc-red");
arcElementsLeftRed.classList.remove("arc-left-red");
arcElementsRed.classList.add("arc");
arcElementsLeftRed.classList.add("arc-left");
}
}
设计要点:
- 增量音效触发:只有当新增报警电站时才播放音效,避免每次轮询都重复播放
- 类名切换 :通过
classList.remove/add切换不同的样式类,CSS 动画自动生效 - 轮询更新:每 2 秒从后端获取最新状态,保证实时性
- 健壮性处理:判断元素是否存在再操作,避免 DOM 查询不到时报错
五、性能优化建议
- 伪元素无额外 DOM:使用伪元素不需要额外创建 DOM 节点,性能开销极小
- GPU 硬件加速 :CSS
opacity动画由 GPU 硬件加速,不会触发重排,性能很好 - 避免 JS 动画 :纯 CSS 动画比 JavaScript 控制
setInterval修改透明度性能好很多 - 合理轮询间隔:状态更新不需要太高频率,2-3 秒轮询一次足够,减少后端压力
- 按需触发音效:只有新增报警时才播放音效,避免频繁播放打扰用户
六、常见问题解答
Q1:为什么用两个伪元素而不是一个?
A :单个伪元素从 opacity 0 → 1 → 0 也能实现呼吸效果,但两个错开相位会让过渡更自然。当第一个元素透明度从 1 降到 0 时,第二个元素从 0 升到 1,两者叠加总亮度变化更平滑,不会有明显的"亮-暗-亮"跳跃感。
Q2:CSS filter 兼容性如何?
A:现代浏览器(Chrome、Firefox、Edge、Safari)都完美支持 CSS filter,大屏展示一般都是在现代化浏览器上运行,不需要兼容 IE,所以可以放心使用。
Q3:为什么不直接准备两张不同颜色的图片?
A:
- 减少资源:只需要一张绿色原图,减少图片资源数量,减小打包体积
- 易于调整 :如果需要调整颜色,直接修改滤镜的
hue-rotate参数即可,不需要设计师重新出图 - 效果更丰富:滤镜叠加上透明度动画,本身就能产生比两张静态图片更丰富的呼吸效果
Q4:如何调整呼吸速度?
A:直接修改动画的 duration 值:
scss
animation: fade-in-out 2s ease-in-out infinite; // 更快
animation: fade-in-out 4s ease-in-out infinite; // 更慢
Q5:为什么使用 document.querySelector 而不是 Vue ref?
A :因为弧形边框元素定义在父组件 index.vue 中,而状态轮询逻辑在子组件 map.vue 中,通过 DOM API 直接获取元素是最简单直接的方式。如果使用 Vue 组件通信,需要通过 emit 或 provide/inject,会更繁琐。在大屏这种单页面应用中,直接操作 DOM 完全可行,不会有维护问题。
七、总结
本文介绍了一种大屏边框红绿呼吸灯的实现方案:
- 纯 CSS 实现动画:GPU 硬件加速,性能优秀,帧率稳定
- 动态类名切换:JavaScript 只需切换类名,CSS 自动处理动画
- 资源极度精简:一张绿色原图搞定红绿双色,滤镜染色节省设计资源
- 高度可配置:呼吸速度、颜色都可以通过修改 CSS 参数快速调整
- 实战验证:已在项目中实际使用,效果出色