资讯动态

大屏告警列表无缝滚动实践:liMarquee参数调优与数据刷新方案

发布时间:2026/10/14 7:16:47 来源:尧图企业网站定制
上个月帮一个数据运营中心的大屏做改版左侧要挂一块“实时业务告警”列表。需求提得很朴素数据每隔一分钟刷一次告警信息按时间倒序在固定区域里平滑地往上滚底部无缝衔接不能有白屏闪烁鼠标放上去要暂停移开继续滚。我一开始觉得这活儿不难拿CSS动画写了个marquee效果结果在真机上被连着打回三次第一次是滚到末尾直接跳到开头闪了一下第二次是鼠标悬停暂停后恢复时位置对不上第三次是数据更新时列表重排整个滚动区域像抽风一样。后来换成了liMarquee这个jQuery插件才把这些破事一一理顺。这篇文章就讲讲我在大屏项目里用liMarquee做列表上下滚动的完整思路包括选型理由、参数调优、数据刷新下的重绘方案以及几个容易踩的坑。适合正在做可视化大屏、后台监控面板或者想给页面加一个平滑滚动列表的前端朋友参考。1. 为什么大屏列表滚动要交给liMarquee而不是自己写1.1 自研滚动方案里最容易翻车的三个地方大屏列表滚动的需求拆开看其实就是三件事让内容连续地动、动到结尾不要断、用户交互时能停能走。听起来简单自己做一遍就发现问题了。第一用setInterval配合transform: translateY来做位移量基本靠猜。每次移动的像素间隔一长滚动看起来一卡一卡的特别像劣质PPT动画。你减小位移间隔CPU占用又上来了数据一多大屏自带的那台瘦客户端直接风扇狂转。第二无缝衔接的问题。很多人第一反应是把列表复制一份放在后面等滚动到一半时瞬间把位置归零。这个思路本身没错但复制内容的时机、首尾元素的间距、最后一个item的margin是否会导致偏移全是要单独处理的细节。稍微没对齐滚动连接处就会出现一条白缝或者跳一下。第三鼠标悬停暂停。自己写的时候得记录当前动画状态、暂停时的偏移量、恢复后的起点一不留神状态就错乱了。更不用说数据刷新时列表项增删跟当前的滚动位置发生冲突处理起来非常难受。所以我的结论是除非你的需求只是“静态文字慢慢往上飘”否则别自己折腾。找一个成熟的滚动插件把状态管理交给它自己专注在数据和样式上这才是大屏项目该有的效率。1.2 liMarquee的定位和同类方案对比liMarquee是一个基于jQuery的无缝滚动插件支持上下左右四个方向核心能力正好覆盖大屏列表的需求。它本质上是把jQuery的动画能力封装成了模块化的滚动逻辑你只需要给一个ul容器配置好方向它自己完成克隆、位移、循环、悬停暂停这一整套动作。我当时也对比了几个方案列了个表供参考方案优点缺点适合场景原生CSS marquee标签零依赖写法简单样式控制弱交互差现代浏览器兼容性一般临时占位、纯文本滚动条自己写setInterval translateY可控性最高无缝衔接、暂停恢复、数据更新全要自己处理只有滚动、没有交互和变动的极简场景liMarquee开箱即用支持方向/速度/悬停/拖拽体积小依赖jQuery复杂定制需要读源码改大屏列表、告警滚动、跑马灯横幅vue-seamless-scroll等Vue方案与Vue生态契合组件化非Vue项目没法用大屏改版时迁移成本高Vue技术栈的新项目这里必须承认jQuery现在听起来有点“过时”但当你接手一个两三年历史的管理后台大屏项目里面到处是jQuery写的图表联动刻意引入一大套新框架反而风险更大。liMarquee在这个生态里就是最顺手的零件拿过来十几分钟就能跑通不值得为了“技术先进”去自找麻烦。1.3 选型边界什么场景不适合liMarquee再说说反例免得有人拿着这个插件硬套。如果你的列表项高度不固定有的内容撑成两行有的只有一行而且没法通过CSS强制截断那liMarquee的滚动节奏就会忽快忽慢因为它是按整体容器高度来算位移的不是逐条按内容高度算的。这种情况更适合做“分页轮播”而不是“连续滚动”。另外如果列表数据量极大比如几千上万条同时又要求瞬时定位到某一条这种场景也应该用虚拟滚动方案。liMarquee会把内容克隆一份来制造无缝循环数据量过大时DOM数量和动画开销都会上涨不如表格组件的虚拟列表来得干净。说白了liMarquee最适合的是“中短列表、固定高度、定期刷新、视觉上平滑滚动”这块位置而大屏上的告警列表、设备状态列表、数据流水列表正好全都长这样。2. liMarquee的滚动原理与最小接入示例2.1 你看到的“无缝”是怎么来的不搞懂原理用起来心里就没底。liMarquee实现无缝滚动的核心是“克隆”它会在你传入的列表末尾复制出一份同样的内容滚动方向朝上时原本的列表滚出视野克隆体紧随其后等克隆体完全占据可视区域时一瞬间把容器位置拉回起点。因为前后两份内容视觉上完全一样人眼根本察觉不到这个重置动作。这里有个关键点克隆节点时liMarquee会尽量保持列表结构的完整性但你如果给li加了不规则的margin、padding或者边框克隆出来的视觉差异会在重置瞬间被放大看起来就会“抖”一下。我后续会详细说怎么处理。另外需要注意这个插件不是把每条li单独做动画而是对整体滚动容器做位移。你可以理解成把一条很长的胶片拉进镜头里镜头大小就是你的可视区域胶片上的图案是循环印制的所以什么时候都能无缝接上。2.2 五分钟接入的最小示例先看最基础的用法。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title大屏告警列表/title style #alertBoard { width: 480px; height: 360px; border: 1px solid #1d5c8f; background: #0a1a2b; overflow: hidden; } #alertBoard ul { margin: 0; padding: 0; list-style: none; } #alertBoard li { height: 60px; line-height: 60px; padding: 0 16px; color: #d4eaff; font-size: 16px; box-sizing: border-box; } /style /head body div idalertBoard ul classalert-list li【高】CPU使用率超过85%/li li【中】服务响应时间超过300ms/li li【低】磁盘使用率达到70%/li li【高】订单支付失败率飙升至5%/li li【中】数据库连接池占用超过80%/li li【低】缓存命中率降至90%以下/li /ul /div script srchttps://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js/script script srchttps://cdn.bootcdn.net/ajax/libs/liMarquee/1.0.0/jquery.liMarquee.min.js/script script $(.alert-list).liMarquee({ direction: up, hoverstop: true }); /script /body /html这个示例里方向是“up”也就是向上滚动符合大屏告警列表的时间倒序习惯。hoverstop: true让鼠标悬停时暂停移开后继续滚动。这里要提醒一个细节liMarquee的调用必须发生在jQuery引入之后且一定要等到DOM就绪再做初始化。稳妥的做法是把初始化代码放在$(function(){ ... })里或者放在页面底部。我见过不少人直接把初始化写在head里结果拿到的是空的列表滚动自然没反应。2.3 初始化返回值与实例方法的利用liMarquee调用之后通常会返回一个对象里面带有pause、resume之类的方法用来手动控制滚动的暂停和恢复。这一点在大屏交互里特别有用。比如在告警列表场景里我希望用户点击某一条告警时滚动停下来弹出详情关闭详情后恢复滚动。初始化的返回值就可以干这件事var marqueeInstance $(.alert-list).liMarquee({ direction: up, hoverstop: true }); // 点击告警条目时暂停 $(.alert-list).on(click, li, function () { if (marqueeInstance marqueeInstance.pause) { marqueeInstance.pause(); } // 展示详情弹窗... }); // 关闭详情后恢复 function closeDetail() { if (marqueeInstance marqueeInstance.resume) { marqueeInstance.resume(); } }不同版本的liMarquee实例方法命名可能有差异我见过有人用destroy做彻底销毁也有人用stop做临时暂停。接入时先看一眼你们下载版本的源码把方法名核对清楚再写业务逻辑。还有一个小技巧如果页面上有多个列表最好把实例统一存在一个数组里方便后续统一暂停、恢复或销毁。别每次都重新$(.xxx).liMarquee()再开一个新实例旧实例的动画和定时器如果没有被清理不仅白耗性能还会跟新实例打架。3. 大屏场景下的参数调优从能用变成好用3.1 核心参数逐项解读liMarquee提供了不少配置参数但大屏场景真正需要调的就那么几个。我把常用参数整理成一张表方便快速定位参数作用大屏场景推荐值direction滚动方向up/down/left/rightup列表向上滚动最常用scrollamt每次滚动的像素距离40-60太大容易晕太小显得慢scrollDelay两次滚动之间的间隔300-500ms配合动画时长控制节奏duration单次滚动动画的时长600-1000ms太长显得拖沓rate是否按速度优先计算true这样duration会自动适配scrollamthoverstop鼠标悬停时暂停true大屏演示时经常需要指哪看哪drag是否允许鼠标拖拽滚动false大屏场景基本用不上runshows是否自动执行滚动true默认就是自动的这些参数的背后逻辑是scrollamt决定“每一步走多远”scrollDelay决定“走完一步歇多久”duration决定“这一步走得是快是慢”。三者配合起来你就能控制滚动是“匀速缓行”还是“一步一停”。大屏告警列表建议匀速缓行看起来更专业视觉压力也小。如果你的列表区域比较长滚动速度可以适当加快一点但不能只调scrollDelay因为滚动间隔一变整个列表的“脚步感”会变得很生硬。正确做法是同时加大scrollamt让每一步跨得更大。3.2 配合CSS让滚动效果更自然这可能是很多人会忽略的一块。liMarquee解决的是“滚动逻辑”但“滚动好不好看”很大程度上取决于CSS。首先外层容器一定要设固定高度并且加overflow: hidden。liMarquee的循环逻辑依赖可视区域高度如果外层容器高度被内容撑开滚动区域就会乱套。我习惯在外层再写一层遮罩效果用CSS的mask-image或者伪元素做上下渐隐让列表滚动到边界时不会突然消失而是慢慢淡出非常像大屏显示器的质感。#alertBoard { overflow: hidden; -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 15%, #000 85%, transparent 100%); mask-image: linear-gradient(to bottom, transparent 0%, #000 15%, #000 85%, transparent 100%); }注意mask-image会对整个滚动区域生效如果你的设计稿里要求列表只在下边缘渐隐可以把渐隐范围调小一点。实测下来这个样式对大屏整体观感提升非常明显尤其是深色背景下列表和屏幕边界会柔和很多。另外一个容易忽略的点是列表li的高度要尽可能统一。大屏界面通常分辨率高、可视面积大li的文本如果超出高度被换行整个列表的视觉节奏就会乱。建议给li设置固定高度然后让文本超出部分做省略号处理确保每条的高度一致。3.3 多分辨率和多方向滚动的适配技巧大屏项目几乎绕不开分辨率适配。同一套页面可能跑在1080p的显示器上也可能是2K甚至4K的一体机上。liMarquee的滚动参数是像素值不能直接响应分辨率变化。我的做法是在初始化之前先根据实际屏幕尺寸计算一个缩放系数把scrollamt和scrollDelay按比例调整。比如基准分辨率是19201080实际是25601440缩放系数是1.33那scrollamt就从40调到53左右。这样不同分辨率下滚动速度的观感保持一致比例上不会出现一台机器飞快、一台机器龟速的情况。另外方向参数如果要做成可配置可以直接用>function renderAlertList(list) { var $board $(#alertBoard); // 先清掉旧内容和旧实例 $board.empty(); // 建立新的滚动列表 var $ul $(ul classalert-list/ul); list.forEach(function (item) { $ul.append(li classalert-item item.title /li); }); $board.append($ul); $ul.liMarquee({ direction: up, scrollamt: 50, scrollDelay: 400, duration: 800, rate: true, hoverstop: true }); }用$board.empty()而不是只清空ul的子元素目的就是让旧的滚动定时器和动画上下文跟着DOM一起被移除新实例从零开始。这是我在实际项目里踩了两次坑之后总结出来的不要试图在旧实例上做“更新数据并延续动画”因为插件内部对克隆结构和滚动位置有很强的状态依赖一旦数据变了状态就废了。如果你用的是带WebSocket的实时大屏建议在收到第一条数据之前不要急着初始化滚动列表。先用一个“加载中”状态占位等数据真正到了再渲染这样避免了空列表初始化后不断重绘的尴尬。4.3 增量刷新尽量复用DOM全量刷新虽然稳但每分钟来一次全量刷新列表会每隔60秒“归零”一次视觉上像系统卡了一下。所以周期轮询场景我推荐增量刷新。增量刷新的核心思路是对比新旧数据只对变化的条目做操作。比如新数据比旧数据多了两条就在列表头部插入两条新li少了某条数据就把对应的li移除。function patchAlertList(newList, key) { var $ul $(.alert-list); var $items $ul.find(li); var existingKeys []; $items.each(function () { existingKeys.push($(this).data(key)); }); // 找出新增的条目 var newItems newList.filter(function (item) { return existingKeys.indexOf(item[key]) -1; }); // 新的向上插入 newItems.forEach(function (item) { var $li $(li/li) .data(key, item[key]) .text(item.title); $ul.prepend($li); }); // 找出已删除的条目 $items.each(function () { var key $(this).data(key); var stillExists newList.some(function (item) { return item[key] key; }); if (!stillExists) { $(this).remove(); } }); }这样操作之后滚动位置不会重置整个列表的滚动是连续的视觉上看起来就像数据在流动中自然更新大屏演示的效果会好很多。不过要说明一点这个增量刷新方案需要数据源保证同一个数据项的key是稳定的比如告警ID、设备ID。如果数据源给不出稳定ID判断“哪条是新的、哪条是旧的”就会出问题这时候宁愿退回全量刷新。4.4 避免数据刷新时的滚动跳动就算用了增量刷新数据插入和删除的那一刻列表的滚动高度和相对位置还是会发生细微变化。为了尽量减少跳动感我还有一个习惯在更新数据的同时暂停动画等DOM操作完成后再恢复动画。function updateWithPause(list) { if (marqueeInstance marqueeInstance.pause) { marqueeInstance.pause(); } setTimeout(function () { patchAlertList(list, id); if (marqueeInstance marqueeInstance.resume) { marqueeInstance.resume(); } }, 50); }这个50ms的延时是刻意留出来的目的是让DOM操作先完成浏览器完成一次渲染再恢复滚动避免恢复瞬间去读一个尚未稳定的高度。实测下来这个方案能去掉绝大部分“跳动感”。如果你更新频率非常高比如每秒一次那这个延时也可以再短一点但不宜省略。5. 真实项目里踩过的坑与优化建议5.1 坑一多个滚动列表同时运行时掉帧大屏上通常不只一个列表。我那个项目里左上是告警列表右下是设备状态列表中间还有两块跑马灯横幅总共有四个liMarquee实例在同时滚动。一开始参数全都按最顺滑的标准来配结果在给客户演示用的那台旧一体机上掉帧掉到怀疑人生。后来分析原因每个liMarquee实例内部都维护着独立的动画计时循环四个实例同时跑CPU和主线程负担直接叠加。解决办法是降低同时滚动的“烈度”非核心区域的列表把scrollDelay从350ms调到500msscrollamt从50降到35同时让中间两块跑马灯只在有数据变化时才滚动平时保持静止。这样整体观感反而更沉稳不掉帧也不抢焦点。5.2 坑二hoverstop悬停暂停偶尔失灵hoverstop: true应该是所有交互需求都想要的功能但它有一个隐藏问题如果鼠标恰好停在li内部的某个子元素上有些版本的插件对mouseenter/mouseleave事件的处理会出现状态不一致。现象就是鼠标明明已经移开列表还是停着不动。排查之后我发现问题出在li内部还包了一个span图标span的hover事件和li的hover事件互相干扰。最直接的修复方式有两步一是给li内部的子元素加pointer-events: none让鼠标事件全部落在li上二是确认监听事件绑定在ul上而不是每个li上减少事件绑定的数量。.alert-list li * { pointer-events: none; }这样一行CSS就能让hover行为稳定很多。如果你的列表里确实需要点击按钮之类的交互元素那就别用hoverstop改用手动pause/resume来控制更加可控。5.3 坑三切到后台再回来滚动位置错位了大屏项目有个特殊场景演示人员可能中途切到别的窗口去查资料切回来之后发现滚动列表要么停住了要么位置跳了一段。原因是浏览器为了省电会对后台页面的定时器和动画做节流。切回来时定时器回调被批量执行或丢弃动画进度就跟实际位置对不上了。我的解法是监听页面的可见性变化在页面隐藏时主动暂停所有滚动实例页面恢复可见后再重新初始化或恢复滚动。这个操作成本很低但对稳定性的提升非常明显。document.addEventListener(visibilitychange, function () { if (document.hidden) { // 统一暂停所有滚动 allMarqueeInstances.forEach(function (instance) { instance.pause instance.pause(); }); } else { allMarqueeInstances.forEach(function (instance) { instance.resume instance.resume(); }); } });5.4 几个提升观感的小细节最后说几个我在大屏项目里反复用的小技巧不一定都是liMarquee本身的但组合起来效果很好。列表区域的背景色建议用深色配浅色文字这样滚动时不管克隆体从哪里接上色差几乎感知不到。如果项目是浅色主题滚动条区域的边界感会强很多最好给外层容器加一个与背景同色的边框或阴影来收边。li的文本内容不要带超长数字或英文串大屏字体放大之后一长串不换行的数字会把布局撑破。建议在渲染时统一格式化文本该换行换行该截断截断。还有滚动动画的曲线尽量保持线性。liMarquee默认的动画方式比较偏线性这在大屏场景反而是对的因为弹跳、缓动这类效果在频繁循环滚动的列表上会显得很不专业。我个人的体会是liMarquee这种老牌jQuery插件虽然年轻前端不太愿意碰但它在解决“滚动”这一类具体问题上依然很扎实。尤其是大屏这类对稳定性和可控性要求高的场景与其纠结技术栈新旧不如把手头工具的参数和边界吃透。把上面的配置和坑都过一遍之后我后来再做任何大屏列表滚动基本半小时内就能交付一个效果稳定、观感顺滑的版本。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑