ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FlowingLight:基于Canvas的数据大屏流光动效插件设计与接入

FlowingLight:基于Canvas的数据大屏流光动效插件设计与接入 做可视化数据大屏这几年我最大的感受是图表好写动效难调。尤其是领导或客户走近大屏的那一刻如果页面全是干巴巴的柱状图和折线图哪怕数据再准确观感上总觉得少了一口气。后来我在自己的大屏项目里沉淀了一个叫 FlowingLight 的流光动效插件专门解决“大屏看起来不够活”的问题。它能给页面边框加上流动光带、给地图上的链路加上粒子流光也能直接把 ECharts 的曲线坐标拿过来当路径让数据真的“流”起来。FlowingLight 的定位很明确不替换 ECharts、不侵入业务代码它只在一层独立的 Canvas 上做“光效叠加”。免费、开源、配置简单体积也很小适合正在做可视化数据大屏、运维监控大屏、展厅指挥大屏的前端开发和可视化工程师。这篇文章我打算把它的设计思路、接入方法和我在项目里踩过的坑一次说清楚照着抄能省不少时间。1. 先想明白大屏上的流光到底在表达什么1.1 大屏是“观看型”界面不是“操作型”界面做后台管理系统时用户盯着一个表格半天不动界面安静是对的因为眼睛要长时间停留在数据上。数据大屏完全反过来。它的使用场景基本是人在几米外扫一眼几秒钟内要感受到“这个系统是活的、数据在跑、状态正常”。这种观看模式下静态图表的信息传递效率其实是偏低的人的视觉天然更容易被运动物体吸引。所以好的数据大屏都会在关键位置放一点动效。不是动画越多越好而是要在边框、链路、流动方向这些地方做有引导性的运动。“流光”就是其中最常用的一种一条光沿着边界或者路径不断流动让人第一眼就知道“这段线路正在传输数据”或“这个模块正处于运行状态”。FlowingLight 要做的就是把这类效果从“每次用手搓Canvas代码”变成“填配置就行”。1.2 FlowingLight 要解决的三个具体问题先说我为什么决定把流光效果单独抽成一个插件。在此之前我做过不少大屏发现大家反复在问三件事页面层次感弱所有版块都是矩形边框看不出主次。数据链路关系看不出来比如两条飞线从 A 点到 B 点静态线根本没法表达“正在传输”。大屏上线后没有“运行感”领导站着看了一分钟觉得系统像截图。这些问题用流光都能解决。给核心版块加一圈流动光边视觉层级马上出来了给链路加粒子光流方向感和动态感也出来了。FlowingLight 把这些能力统一成一套 API内部处理路径计算、粒子调度、性能优化业务侧只需要告诉它“光往哪儿走”。下面这张对比应该更直观表现手段能表达的信息维护成本静态边框区域划分最低静态飞线连接关系低闪烁装饰“这里有东西”中ECharts 自带 effect数据流向中FlowingLight 流光流向、活跃度、层级感低2. FlowingLight 的核心设计一条光两种走法2.1 整体架构只做一个独立 Canvas 动效层FlowingLight 的第一个设计原则是不污染业务代码。它不往 DOM 里塞一堆 div也不去改 ECharts 的 option而是在大屏容器上覆盖一个透明的 Canvas 层。所有流光都在这个 Canvas 上绘制和底下已有的图表完全解耦。这样有几个实际好处。一是上线和回滚都方便出问题把 Canvas 层隐藏掉业务不受任何影响。二是多个图表之间可以共享同一条流光路径比如一个跨模块的链路关系不需要分别写在两个图表的 option 里。Canvas 层本身是透明的CSS 里设一行pointer-events: none就不会挡住 ECharts 的 tooltip 和缩放操作。我遇到过不少开发同学觉得“多一个 Canvas 层会不会太重”。其实不会。一个全屏 Canvas 2D 的绘制开销完全可控流光的粒子数量又不是无上限增长后面我会专门讲性能参数怎么调。2.2 路径抽象一段坐标数组就够用FlowingLight 对路径的抽象非常简化核心就一种数据结构有序坐标点数组。矩形边框提供rect快捷生成折线、曲线、飞线这些场景直接传points数组就行。// 路径的本质就是这些点 const path { type: polygon, points: [ { x: 100, y: 80 }, { x: 500, y: 80 }, { x: 500, y: 300 }, { x: 100, y: 300 } ] };你可能觉得这没什么技术含量但正因为路径被简化为“点数组”它才能和 ECharts 无缝衔接。ECharts 内部所有线、折线、地图边界最终渲染时都会变成一个个的坐标点。拿到这些点交给 FlowingLight就等于把手绘图表的路径原样复制到光效层上两边天然对齐。2.3 流光粒子的运动原理流光效果看起来复杂本质上是若干小光点在一条路径上连续移动。每个光点有一个参数 t表示它走了整条路径的百分之多少。帧循环把 t 不断累加再根据 t 在路径上插值出当前坐标画出来就是一个移动的光点。当多个光点按不同间隔分布在同一条路径上并且每个光点尾部拖着渐变色拖尾时人眼就会把它拼接成“一条流动的光带”。这个原理不神秘很多游戏引擎里的轨迹效果也是这么做的。FlowingLight 只是把“路径插值”“光晕渐变”“拖尾长度”这些步骤打包好用配置项暴露出来。核心参数有三个count控制光点数量length控制拖尾长度speed控制流动速度。调这三项效果差异非常明显。比如监控大屏的链路要给人“数据正在稳定传输”的感觉speed 取 1.2 左右比较合适如果是告警高亮可以临时把 speed 拉到 3 以上人眼立刻会被吸引。2.4 为什么选择 Canvas 2D而不是 CSS 或 SVG这个选择我纠结过一段时间。最开始做流光边框时用 CSS 动画也能实现但 CSS 只能绕矩形或圆形走遇到折线、地图边界、ECharts 任意曲线就无能为力了。SVG 可以用 stroke-dashoffset 做虚线流动效果也不错但复杂路径多的时候 DOM 节点暴增性能风险大还不好做粒子光晕。最后选 Canvas 2D是因为它在路径复杂度和性能之间最平衡。Canvas 没有节点数量压力几十条路径几百个粒子一帧就能画完光晕可以通过shadowBlur或渐变模拟效果足够平滑。FlowingLight 内部还用了一个小技巧把每条路径上的粒子位置预先算好缓存到数组里只有路径变化时才重新采样这样运行时循环里只做增量和绘制性能开销很小。3. 最快速的接入姿势把 FlowingLight 装进你的大屏3.1 引入方式npm 和 script 标签都能用FlowingLight 的构建产物是单文件 JS编译后大约 10KB 左右没有运行时依赖。如果你的项目是 webpack、vite 这类工程化环境可以直接作为模块引入。如果是一个老项目或者想快速做 Demo直接下载dist/flowing-light.min.js用 script 标签挂到页面上也有全局变量可用。import FlowingLight from flowing-light;script src./dist/flowing-light.min.js/script script // 此时全局上会有 FlowingLight 构造函数 const light new FlowingLight({ ... }); /script我个人的习惯是刚开始做效果验证时用 script 标签确认完效果再改成模块引入。这样不会把临时调试代码混进工程依赖里。3.2 最简示例给大屏套一个流光边框最常见的需求就是给大屏页面加一个外边框流光。FlowingLight 里可以直接用type: rect生成圆角矩形路径几行代码就能跑起来。const light new FlowingLight({ container: document.getElementById(screen), paths: [ { type: rect, x: 10, y: 10, width: 600, height: 400, radius: 8, color: #00e5ff, count: 10, length: 60, speed: 1.2, width: 2, blur: 6, loop: true } ] }); light.start();这里container是承载大屏的容器流光 Canvas 会自动铺满容器。radius: 8让边框四个角变成圆角流光在拐角处会自动做平滑过渡。width是光带的线条宽度blur是光晕强度。第一次跑通后建议只调这三个视觉参数先别贪多找到感觉再继续加内容。3.3 进阶效果把流光接到 ECharts 曲线和地图飞线上如果说边框流光只是开胃菜那和 ECharts 结合的部分才是正餐。ECharts 官方其实自带 lines 图系列的 effect 效果很多地图飞线的流光就是用它实现的。但它的问题是效果绑定在某个 series 上、参数不够细致、而且自带的拖尾效果在复杂线路上偶尔会出现渲染闪烁。FlowingLight 的做法是从 ECharts 实例上把图形坐标读出来再作为自定义路径交给流光层绘制。这样 ECharts 图表只管数据展示光效层单独加工互不干扰。我自己一般这样取 ECharts 的路径坐标const chart echarts.init(document.getElementById(chart)); // 正常 setOption 渲染图表 chart.setOption(option); // 从 ECharts 内部取渲染后的折线坐标 const displayList chart.getZr().storage.getDisplayList(); const rect document.getElementById(chart).getBoundingClientRect(); const points []; displayList.forEach((el) { if (el.type polyline || el.type line) { const shape el.shape; if (shape shape.points) { shape.points.forEach((p) { points.push({ x: p[0] - rect.left, y: p[1] - rect.top }); }); } } }); const light new FlowingLight({ container: document.getElementById(chart), paths: [ { type: polygon, points, color: #76ff03, count: 12, length: 40, speed: 1.5, width: 2, blur: 5, loop: true } ] }); light.start();这段代码里有个关键点ECharts 的坐标是相对于 canvas 内部的而 FlowingLight 的路径坐标是相对于 container 的所以要做一次rect.left和rect.top的换算。大多数流光错位问题最后查下来都是这一步漏了。地图飞线的做法类似。用 ECharts 的 lines 系列先渲染飞线再从显示列表里把线段的坐标点读出来。FlowingLight 会自动把不闭合的路径首尾相接在两条端点之间循环流动。3.4 配置项速查表我把常用配置项整理成了表格调试时对照着改会快很多。配置项类型默认值说明containerHTMLElement必填承载光效的容器pathsArray[]路径配置数组typestringpolygonrect 或 polygonpointsArray[]多边形路径的坐标点x/y/width/heightnumber-rect 路径专用radiusnumber0圆角大小colorstring#00e5ff流光颜色countnumber10同路径上的光点数量lengthnumber40拖尾长度单位像素speednumber1流动速度倍率widthnumber2光带线条宽度blurnumber4光晕模糊半径loopbooleantrue是否循环流动3.5 两种使用模式装饰型 vs 数据联动型用了一段时间之后我习惯把 FlowingLight 的方案分成两种。一种是“装饰型”光只在大屏固定位置流动比如边框、标题分隔线、背景线路纯视觉增强和业务数据关系不大。另一种是“数据联动型”光必须跟着数据状态变化比如告警时某条光变红、数值异常时流光加速、链路切换时路径重绘。装饰型接入很简单初始化一次就行。数据联动型需要你额外注意状态变化时怎么更新插件。FlowingLight 提供了updatePath(pathIndex, newConfig)和removePath(pathIndex)方法业务层只需要在数据变更时调用对应方法不需要整个插件重建。这个设计帮我避了很多坑尤其是大屏自动轮询数据时频繁销毁重建 Canvas 会带来明显的卡顿。4. 实操实录我在监控大屏里接入 FlowingLight 的完整过程4.1 需求梳理哪些区域值得加流光有一次做一个数据中心监控大屏客户要求屏幕必须是“一眼就能看出系统在运行”。接到需求后我没有急着写代码而是先在稿子上圈了四个位置最外层整体边框、中间的网络链路图、两侧的实时指标牌、底部的滚动告警条。这四个位置分别承担“整体氛围”“数据流转”“重点展示”“告警提醒”四种角色。圈完之后我给每个位置定了流光参数外边框用蓝色速度慢一点只求氛围网络链路用绿色速度中等表达数据流动指标牌的边框用橙色只在数值变化时短暂闪烁告警条直接在出现告警时用红色流光高亮。需求定了参数自然就清楚了。4.2 落地步骤拆解整个接入过程我分五步走这也是我建议你复用的流程先搭一个独立测试页面尺寸和真实大屏一致用假数据调 FlowingLight 的参数。把调好的配置写进一个独立的lightConfig.js文件和大屏业务代码分开。在业务大屏初始化完成后调用 FlowingLight 的初始化方法。联调数据联动逻辑比如告警流光的触发和复位。切到低端显卡的机器上实测帧率针对卡顿做参数降级。这五步里最花时间的是第一步。因为大屏实际部署的屏幕经常是拼接屏分辨率和开发机的比例不一样流光坐标如果依赖硬编码换一台机器很可能就歪了。所以我强烈建议所有坐标都基于 container 的宽高按比例计算FlowingLight 内部虽然做了容器尺寸变化的重绘处理但业务侧传进去的 points 最好是相对比例坐标而不是绝对像素。4.3 联调时最容易被忽略的细节联调阶段我踩过一个很典型的坑ECharts 图表在一次渲染后内部显示列表的顺序并不是按照 setOption 时 series 的书写顺序来的。所以最初读取坐标时用forEach直接遍历显示列表会把多条折线的点混在一起流光路径完全乱掉。后面我改成按 seriesName 过滤图形元素只读取目标 series 对应的点路径才恢复正常。这里给你一个比较可靠的思路取坐标前先console.log(displayList)看元素结构确认好过滤条件再写代码别凭直觉猜。另一个细节是设备像素比。大屏开发机的 devicePixelRatio 经常是 1但高清拼接屏可能是 2 甚至更高。FlowingLight 初始化时会读取window.devicePixelRatio做画布缩放如果你是在 iframe 里嵌入的最好手动传一下dpr参数否则流光在某种屏幕上会发虚或者变粗。4.4 效果验收怎么判断流光“到位了”我判断流光效果是否达标的办法比较朴素把大屏切到静态截图再切回动态页面如果动态版能让人明显感觉“这些数据是活的”就说明氛围到位了。如果切换前后差别不大那就继续调速度或者增加粒子数。还有一条验收标准容易被忽略——距离。如果大屏实际观看距离是三米以上流光宽度和拖尾长度建议适当加大因为站在远处看2 像素的光带几乎等于没有。我在监控大屏项目里最终把外边框流光宽度提到了 3、拖尾长度拉到了 80效果才明显。5. 踩坑记录流光效果常见问题与排查办法5.1 常见问题速查表先给一个速查表你遇到对应现象可以直接在这里找答案。现象可能原因处理办法流光不动没调用 start()或容器不可见时初始化确认调用 start()容器可见后再初始化光效非常模糊shadowBlur 太大把 blur 控制在 2 到 8 之间边框流光拐角断裂路径点没有闭合polygon 模式自动闭合rect 模式检查 radius流光错位坐标系换算漏了确认 points 是相对 container 的坐标tooltip 被挡住Canvas 拦截了鼠标事件给光效 Canvas 设 pointer-events: none切回标签页掉帧浏览器暂停了 rAF监听 visibilitychange 后重置时间戳多条路径颜色错乱全局变量被修改每个 FlowingLight 实例使用独立状态5.2 三个我印象最深的偏门问题第一个是“标签页切回来之后流光突然快进”。原因是浏览器在后台会暂停requestAnimationFrame切回来时积压的帧时间全被一次性执行导致粒子瞬移一大段。FlowingLight 内部在处理帧率时用了增量时间限制把单帧位移钳制在一个安全范围切回来之后看起来会稍微平滑一些。这里也提醒大家如果自己手写动画一定不要用固定的 step方式累加粒子位置最好基于上一帧的实际时间差来计算。第二个是“圆角矩形边框的流光在四个角出现叠影”。原因是我最初实现时把圆角路径分成了四段圆弧和四条直线粒子在分段拼接处会短暂重复绘制。解决办法是路径采样时不按段独立计算而是把整条路径展开成一个连续的采样表粒子每次只按总进度去采样。这样不管路径是不是圆角、有多少折点流动都是连续的。第三个是“页面用 transform 缩放后流光错位”。很多大屏为了适配不同分辨率的屏幕会对整个页面做 scale 缩放。Canvas 同时被缩放后光效的坐标和宽度都会被拉伸容易出现光带变形。FlowingLight 的对策是监听容器的尺寸变化在检测到缩放后重新执行一次路径采样你可以把这个事件周期性地连上页面 resize 流程。5.3 性能敏感型大屏怎么进一步优化如果你的大屏同时跑多个图表、数据轮询、还有视频流那流光这层动效也要学会做减法。以下几个优化点是我实测有效的单条路径粒子数控制在 20 以内通常 8 到 12 个就够了。全屏区域只放一两个大面积流光小模块之间不要互相抢视觉重心。把blur调小或关掉shadowBlur 在低端设备上是最耗性能的绘制操作之一。动态数据频繁更新时增加一个手动控制开关数据轮询期间暂停流光轮询结束后再恢复。大屏页面在同一个标签页里切换路由或隐藏某些模块时记得调用light.destroy()释放 Canvas避免内存持续上涨。6. 不是所有大屏都适合加流光怎么判断6.1 适合用 FlowingLight 的典型场景从我经历的项目看有三类大屏非常适合监控运维大屏系统状态本身是动态的流光能强化“实时监测”的感觉。指挥调度大屏线路上流动的光点能清楚表达事件流向和处理进度。展厅展示大屏观众需要在短时间内被吸引流光的大视觉张力很占优势。这几种大屏的共同点是页面结构稳定、信息层级分明、观众离屏较远且停留时间短。流光在这些场景起的是引导和氛围作用而不是和详细数据抢注意力。6.2 建议慎用的场景有一类大屏我不建议加过多流光就是“个人深度分析型”的数据工作台。用户需要长时间阅读表格、对比数值、查看明细这时候屏幕上一堆光带只会制造干扰。还有一种是页面本身信息密度极高的情况图表密密麻麻再加流光很容易让画面显得脏。如果项目判定真的需要我的建议是把流光局限在页面边框和标题装饰上不要动图表内部。总之克制比炫技重要。6.3 使用时的审美原则最后分享几个我坚持的原则。第一流光颜色不要超过三种最好和大屏的主色调一致常用的蓝绿系、青紫系都很安全。第二速度不要拉满默认的 1 倍速就够大部分场景用了速度太快会让人焦虑。第三流光数量宁少勿多一个页面同时只要有一到两个“视觉活跃点”整体节奏就是舒服的。给了这么多配置参数和接入方法最后还想提醒一句千万别忘了流光本身是给大屏加分的配角数据内容的准确性和清晰度才永远是第一位。动效做得再漂亮数据逻辑讲不清楚大屏照样没有灵魂。我个人使用 FlowingLight 的习惯是每个项目先花 30 分钟在独立页面把光效参数调熟再花 10 分钟接入业务代码这样后期返工最少。试试这个顺序你会发现让大屏“活”起来没那么复杂。
返回列表