大屏数据可视化 - 边框红绿呼吸灯实现详解

在电力监控大屏、安防监控大屏等场景中,通常需要通过视觉效果直观地展示系统状态。本文介绍一种基于 CSS 伪元素 + CSS 滤镜 + 错位动画实现的红绿呼吸灯效果,正常状态显示绿色静态边框,有报警事件发生时自动切换为红色脉动呼吸效果,代码基于 Vue 3 + SCSS 实现。


一、需求背景

在一些监控大屏中,我们需要:

  1. 正常状态:显示绿色边框,静态展示,系统运行正常
  2. 报警状态:自动切换为红色,并且呈现呼吸脉动效果,强烈提醒运维人员注意
  3. 动态切换:根据后台数据实时更新状态,报警解除后自动切回绿色

本文实现的效果是:大屏左右两侧弧形装饰边框,正常状态为绿色静态,存在未处理报警时变为红色脉动呼吸灯效果。


二、实现思路

我们采用以下巧妙的设计思路:

步骤 技术方案
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");
      }
    }

设计要点:

  1. 增量音效触发:只有当新增报警电站时才播放音效,避免每次轮询都重复播放
  2. 类名切换 :通过 classList.remove/add 切换不同的样式类,CSS 动画自动生效
  3. 轮询更新:每 2 秒从后端获取最新状态,保证实时性
  4. 健壮性处理:判断元素是否存在再操作,避免 DOM 查询不到时报错

五、性能优化建议

  1. 伪元素无额外 DOM:使用伪元素不需要额外创建 DOM 节点,性能开销极小
  2. GPU 硬件加速 :CSS opacity 动画由 GPU 硬件加速,不会触发重排,性能很好
  3. 避免 JS 动画 :纯 CSS 动画比 JavaScript 控制 setInterval 修改透明度性能好很多
  4. 合理轮询间隔:状态更新不需要太高频率,2-3 秒轮询一次足够,减少后端压力
  5. 按需触发音效:只有新增报警时才播放音效,避免频繁播放打扰用户

六、常见问题解答

Q1:为什么用两个伪元素而不是一个?

A :单个伪元素从 opacity 0 → 1 → 0 也能实现呼吸效果,但两个错开相位会让过渡更自然。当第一个元素透明度从 1 降到 0 时,第二个元素从 0 升到 1,两者叠加总亮度变化更平滑,不会有明显的"亮-暗-亮"跳跃感。

Q2:CSS filter 兼容性如何?

A:现代浏览器(Chrome、Firefox、Edge、Safari)都完美支持 CSS filter,大屏展示一般都是在现代化浏览器上运行,不需要兼容 IE,所以可以放心使用。

Q3:为什么不直接准备两张不同颜色的图片?

A

  1. 减少资源:只需要一张绿色原图,减少图片资源数量,减小打包体积
  2. 易于调整 :如果需要调整颜色,直接修改滤镜的 hue-rotate 参数即可,不需要设计师重新出图
  3. 效果更丰富:滤镜叠加上透明度动画,本身就能产生比两张静态图片更丰富的呼吸效果

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 组件通信,需要通过 emitprovide/inject,会更繁琐。在大屏这种单页面应用中,直接操作 DOM 完全可行,不会有维护问题。


七、总结

本文介绍了一种大屏边框红绿呼吸灯的实现方案:

  • 纯 CSS 实现动画:GPU 硬件加速,性能优秀,帧率稳定
  • 动态类名切换:JavaScript 只需切换类名,CSS 自动处理动画
  • 资源极度精简:一张绿色原图搞定红绿双色,滤镜染色节省设计资源
  • 高度可配置:呼吸速度、颜色都可以通过修改 CSS 参数快速调整
  • 实战验证:已在项目中实际使用,效果出色
相关推荐
ydyd202604211 小时前
固定资产管理软件是什么?从功能到价值全文讲解
信息可视化·数据挖掘·数据分析
Hyyy1 小时前
很多Desktop都在上的Computer Use是什么
前端·llm
慢功夫1 小时前
开篇:VS Code 为什么不是一个普通 React App
前端·visual studio code
你挚爱的强哥1 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
huashengzsj2 小时前
Shopify个人建站如何搭建独立站:从零开始的完整指南
前端·网络·人工智能
程序猿小泓2 小时前
从 Claude Code 学 Agent Harness:一个前端工程师的 AI Agent 学习笔记
前端·人工智能·学习
柒和远方3 小时前
从审计日志到可下载证据包:事务型 Outbox、租约 fencing 与保留水位
前端·后端·架构
minglie13 小时前
npm ali-oss库依赖冲突
前端·npm
CryptoPP4 小时前
BSE股票K线数据接入实战:从接口调用到前端图表展示
大数据·前端·网络·人工智能·websocket·网络协议