IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的
1、引言
好久没写技术文章了,今天这篇不是原理性文章,而是为大家分享一下由笔者主导开发实施的IM即时通讯聊天系统,针对大量离线消息(包括消息漫游)导致的用户体验问题的升级改造全过程。
文章中,我将从如下几个方面进行介绍:
- 1)这款IM产品的主要业务及特点;
- 2)IM系统业务现状和痛点;
- 3)升级改造之路;
- 4)消息ACK逻辑的优化。
下述内容都是根据笔者开发IM的亲身经历总结下来的宝贵经验,干货满满,期待你的点赞。
学习交流:
- 即时通讯/推送技术开发交流5群:215477170 [推荐]
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》
本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文公众号链接是:https://mp.weixin.qq.com/s/XHdt1IpCrdmaMDxL8WKn3w,原文链接是:http://www.52im.net/thread-3036-1-1.html
2、IM开发干货系列文章
本文是系列文章中的第25篇,总目录如下:
《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》
《一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)》
《IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理》
《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》
《IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》
《IM里“附近的人”功能实现原理是什么?如何高效率地实现它?》
《IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路》
《IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质》
《IM的扫码登功能如何实现?一文搞懂主流应用的扫码登陆技术原理》
另外,如果您是IM开发初学者,强烈建议首先阅读《新手入门一篇就够:从零开发移动端IM》。
3、此IM产品的主要业务及特点
和传统互联网行业有所不同,笔者所在的公司(名字就不透露了)是一家做娱乐社交app的公司,包括小游戏、聊天、朋友圈feed等。
大家应该都有体会:游戏业务在技术上和产品形态上与电商、旅游等行业有着本质上的区别。
大部分做后端开发的朋友,都在开发接口。客户端或浏览器h5通过HTTP请求到我们后端的Controller接口,后端查数据库等返回JSON给客户端。大家都知道,HTTP协议有短连接、无状态、三次握手四次挥手等特点。而像游戏、实时通信等业务反而很不适合用HTTP协议。
原因如下:
- 1)HTTP达不到实时通信的效果,可以用客户端轮询但是太浪费资源;
- 2)三次握手四次挥手有严重的性能问题;
- 3)无状态。
比如说,两个用户通过App聊天,一方发出去的消息,对方要实时感知到消息的到来。两个人或多个人玩游戏,玩家要实时看到对方的状态,这些场景用HTTP根本不可能实现!因为HTTP只能pull(即“拉”),而聊天、游戏业务需要push(即“推”)。
4、IM系统业务现状和痛点
4.1 业务现状
笔者负责整个公司的实时聊天系统,类似与微信、QQ那样,有私聊、群聊、发消息、语音图片、红包等功能。
下面我详细介绍一下,整个聊天系统是如何运转的。
首先:为了达到实时通信的效果,我们基于Netty开发了一套长链接网关gateway(扩展阅读:《Netty干货分享:京东京麦的生产级TCP网关技术实践总结》),采用的协议是MQTT协议,客户端登录时App通过MQTT协议连接到gateway(NettyServer),然后通过MQTT协议把聊天消息push给NettyServer,NettyServer与NettyClient保持长链接,NettyClient用于处理业务逻辑(如敏感词拦截、数据校验等)处理,最后将消息push给NettyServer,再由NettyServer通过MQTT push给客户端。
其次:客户端与服务端想要正常通信,我们需要制定一套统一的协议。拿聊天举例,我们要和对方聊天,需要通过uid等信息定位到对方的Channel(Netty中的通道,相当于一条socket连接),才能将消息发送给正确的客户端,同时客户端必须通过协议中的数据(uid、groupId等),将消息显示在私聊或者群聊的会话中。
协议中主要字段如下(我们将数据编码成protobuf格式进行传输):
{
"cmd":"chat",
"time":1554964794220,
"uid":"69212694",
"clientInfo":{
"deviceId":"b3b1519c-89ec",
"deviceInfo":"MI 6X"
},
"body":{
"v":1,
"msgId":"5ab2fe83-59ec-44f0-8adc-abf26c1e1029",
"chatType":1,
"ackFlg":1,
"from":"69212694",
"to":"872472068",
"time":1554964793813,
"msg":{
"message":"聊天消息"
}
}
}
补充说明:如果你不了Protobuf格式是什么,请详读《Protobuf通信协议详解:代码演示、详细原理介绍等》。
如上json,协议主要字段包括:

如果客户端不在线,我们服务端需要把发送的消息存储在离线消息表中,等下次对方客户端上线,服务端NettyServer通过长链接把离线消息push给客户端。
4.2 业务痛点
随着业务蓬勃发展,用户的不断增多,用户创建的群、加入的群和好友不断增多和聊天活跃度的上升,某些用户不在线期间,产生大量的离线消息(尤其是针对群聊,离线消息特别多)。
等下次客户端上线时,服务端会给客户端强推全部的离线消息,导致客户端卡死在登录后的首页。并且产品提出的需求,要扩大群成员的人数(由之前的百人群扩展到千人群、万人群等)。
这样一来,某些客户端登录后必定会因为大量离线消息而卡死,用户体验极为不好。
和客户端的同事一起分析了一下原因:
- 1)用户登录,服务端通过循环分批下发所有离线消息,数据量较大;
- 2)客户端登录后进入首页,需要加载的数据不光有离线消息,还有其他初始化数据;
- 3)不同价位的客户端处理数据能力有限,处理聊天消息时,需要把消息存储到本地数据库,并且刷新UI界面,回复给服务端ack消息,整个过程很耗性能。
(庆幸的是,在线消息目前没有性能问题)。
所以针对上述问题,结合产品对IM系统的远大规划,我们服务端决定优化离线消息(稍微吐槽一下,客户端处理能力不够,为什么要服务端做优化?服务端的性能远没达到瓶颈。。。)。
5、升级改造之路
值得庆幸的是,笔者100%参与这次系统优化的全部过程,包括技术选型、方案制定和最后的代码编写。在此期间,笔者思考出多种方案,然后和服务端、客户端同事一起讨论,最后定下来一套稳定的方案。
5.1 方案一(被pass掉的一个方案)
【问题症状】:
客户端登录卡顿的主要原因是,服务端会强推大量离线消息给客户端,客户端收到离线消息后会回复服务端ack,然后将消息存储到本地数据库、刷新UI等。客户端反馈,即使客户端采用异步方式也会有比较严重的性能问题。
【于是我想】:
为什么客户端收到消息后还没有将数据存储到数据库就回复给服务端ack?很有可能存储失败,这本身不合理,这是其一。其二,服务端强推导致客户端卡死,不关心客户端的处理能力,不合理。
【伪代码如下】:
int max = 100;
//从新库读
while(max > 0) {
List<OfflineMsgInfo> offlineMsgListNew = shardChatOfflineMsgDao.getByToUid(uid, 20);
if(CollectionUtils.isEmpty(offlineMsgListNew)) {
break;
}
handleOfflineMsg(uid, offlineMsgListNew, checkOnlineWhenSendingOfflineMsg);
max--;
}
【初步方案】:
既然强推不合理,我们可以换一种方式,根据客户端不同机型的处理能力的不同,服务端采用不同的速度下发。
我们可以把整个过程当成一种生产者消费者模型,服务端是消息生产者,客户端是消息消费者。客户端收到消息,将消息存储在本地数据库,刷新UI界面后,再向服务端发送ack消息,服务端收到客户端的ack消息后,再推送下一批消息。
这么一来,消息下发速度完全根据客户端的处理能力,分批下发。但这种方式仍然属于推方式。
【悲剧结果】:
然而,理想很丰满,现实却很骨感。
针对这个方案,客户端提出一些问题:
- 1)虽然这种方案,客户端不会卡死,但是如果当前用户的离线消息特别多,那么收到所有离线消息的时间会非常长;
- 2)客户端每次收到消息后会刷新界面,很有可能客户端会发生,界面上下乱跳的画面。
so,这个方案被否定了。。。
5.2 方案二
【我的思考】:
既然强推的数据量过大,我们是否可以做到,按需加载?客户端需要读取离线消息的时候服务端给客户端下发,不需要的时候,服务端就不下发。
【技术方案】:针对离线消息,我们做了如下方案的优化
1)我们增加了离线消息计数器的概念:保存了每个用户的每个会话,未读的消息的元数据(包括未读消息数,最近的一条未读消息、时间戳等数据),这个计数器用于客户端显示未读消息的的红色气泡。这个数据属于增量数据,只保留离线期间收到的消息元数据。
消息格式如下:
{
"sessionId1":{
"count":20,
"lastMsg":[
"最后N条消息"
],
"timestamp":1234567890
},
"sessionId2":{
}
}

2)客户端每次登录时,服务端不推送全量离线消息,只推送离线消息计数器(这部分数据存储在redis里,并且数据量很小),这个数量用户显示在客户端消息列表的未读消息小红点上。
3)客户端拿到这些离线消息计数器数据,遍历会话列表,依次将未读消息数量累加(注意:不是覆盖,服务端保存客户端离线后的增量数据),然后通知服务端清空离线消息计数器的增量数据。
4)当客户端进入某会话后,上拉加载时,通过消息的msgId等信息发送HTTP请求给服务端,服务端再去分页查询离线消息返回给客户端。
5)客户端收到消息并保存在本地数据库后,向服务端发送ack,然后服务端删除离线消息表的离线消息。
【预期结果】:
客户端、服务端的技术人员认可这个方案。我们通过推拉结合的方式,解决了客户端加载离线消息卡顿的问题。(改造前是强推,改造后采用推拉结合的方式)
流程图如下:

【新的问题】:
方案虽然通过了,但是引发了一个新问题:即客户端消息衔接问题。
问题描述如下:客户端登录后进入会话页面,因为客户端本身就保存着历史消息,那么客户端下拉加载新消息时,到底怎么判断要加载本地历史消息?还是要请求服务端加载离线消息呢?
经过一番思考,服务端和客户端最终达成了一致的方案:
- 1)在未读消息计数器的小红点逻辑中,服务端会把每个会话的最近N条消息一起下发给客户端;
- 2)客户端进入会话时,会根据未读消息计数器的最近N条消息展示首页数据;
- 3)客户端每次下拉加载时,请求服务端,服务端按时间倒排离线消息返回当前会话最近一页离线消息,直到离线消息库中的数据全部返回给客户端;
- 4)当离线消息库中没有离线消息后,返回给客户端一个标识,客户端根据这个标识,在会话页面下一次下拉加载时不请求服务端的离线消息,直接请求本地数据库。
6、消息ACK逻辑的优化
最后,我们也对消息ack的逻辑进行了优化。
优化前:服务端采用push模型给客户端推消息,不论是在线消息还是离线消息,ack的逻辑都一样,其中还用到了kafka、redis等中间件,流程很复杂(我在这里就不详细展开介绍ack的具体流程了,反正不合理)。
离线消息和在线消息不同的是,我们不存储在线消息,而离线消息会有一个单独的库存储。完全没必要用在线消息的ack逻辑去处理离线消息,反而很不合理,不仅流程上有问题,也浪费kafka、redis等中间件性能。
优化后:我们和客户端决定在每次下拉加载离线消息时,将收到的上一批离线消息的msgId或消息偏移量等信息发送给服务端,服务端直接根据msgId删除离线库中已经发送给客户端的离线消息,再返回给客户端下一批离线消息。
另外:我们还增加了消息漫游功能,用户切换手机登录后仍然可以查到历史消息,这部分内容我就不展开详细介绍给大家了。
7、设计优化方案时的文档截图(仅供参考)
下面是优化的方案文档截图,请大家参考。





附录:更多IM开发相关文章汇总
[1] IM开发热门文章:
《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》
《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》
《现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障》
《小白必读:闲话HTTP短连接中的Session和Token》
《IM开发基础知识补课:正确理解前置HTTP SSO单点登录接口的原理》
《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》
《开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀》
《QQ音乐团队分享:Android中的图片压缩技术详解(上篇)》
《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》
《腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》
《腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)》
《腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)》
《如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源》
《基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?》
《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)》
《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)》
《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》
《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》
《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》
《适合新手:从零开发一个IM服务端(基于Netty,有完整源码)》
《适合新手:手把手教你用Go快速搭建高性能、可扩展的IM系统(有源码)》
《IM里“附近的人”功能实现原理是什么?如何高效率地实现它?》
《IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路》
《IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质》
《IM“扫一扫”功能很好做?看看微信“扫一扫识物”的完整技术实现》
《IM的扫码登录功能如何实现?一文搞懂主流应用的扫码登录技术原理》
《IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)》
《IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略》
《IM消息ID技术专题(四):深度解密美团的分布式ID生成算法》
《IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现》
《IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总》
《IM开发干货分享:我是如何解决大量离线聊天消息导致客户端卡顿的》
>> 更多同类文章 ……
[2] IM群聊相关的技术文章:
《IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?》
《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》
《[技术脑洞] 如果把14亿中国人拉到一个微信群里技术上能实现吗?》
《阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处》
>> 更多同类文章 ……
[3] 有关IM架构设计的文章:
《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》
《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》
《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》
《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》
《WhatsApp技术实践分享:32人工程团队创造的技术神话》
《王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等》
《IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?》
《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》
《知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》
《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》
《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》
《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》
《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》
《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》
《阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》
《社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等》
《社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进》
《社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节》
《社交软件红包技术解密(四):微信红包系统是如何应对高并发的》
《社交软件红包技术解密(五):微信红包系统是如何实现高可用性的》
《社交软件红包技术解密(六):微信红包系统的存储层架构演进实践》
《社交软件红包技术解密(七):支付宝红包的海量高并发技术实践》
《社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等》
《社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践》
《即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?》
《即时通讯新手入门:快速理解RPC技术——基本概念、原理和用途》
《多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了》
《从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》
《从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》
《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》
《瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)》
《阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处》
《从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践》
《IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!》
>> 更多同类文章 ……
(本文同步发布于:http://www.52im.net/thread-3036-1-1.html)
IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的的更多相关文章
- Android ListView只加载当前屏幕内的图片(解决list滑动时加载卡顿)
最近在做ListView分页显示,其中包括图片 和文字(先下载解析文字内容,再异步加载图片)发现每次点击下一页后,文字内容加载完毕,马上向下滑动,由于这时后台在用线程池异步下载图片,我每页有20条,也 ...
- 解决duilib使用zip换肤卡顿的问题:修改duilib并使用资源文件换肤
转载请说明原出处,谢谢~~ 今天在做单子是,客户要求做换肤功能,为此我专门写了一个换肤函数,并且把各种皮肤资源压缩为各个zip文件来换肤.但是客户反映程序运行缓慢,我测试后发现的确明显可以看出慢了不少 ...
- 解决MyEclipse启动慢,使用卡顿问题
卡顿原因: 1.启动的服务和插件过多,导致启动和运行缓慢,电脑配置较差的直接会卡死没有响应 2.软件运行内存设置不足,导致没有足够的空间运行软件,致使软件卡顿 解决方法: windows --> ...
- 解决duilib使用zip换肤卡顿的问题(附将资源集成到程序中的操作方法)
转载请说明原出处,谢谢~~ 今天在做单子是.客户要求做换肤功能,为此我专门写了一个换肤函数,而且把各种皮肤资源压缩为各个zip文件来换肤.可是客户反映程序执行缓慢,我測试后发现的确明显能够看出慢了不少 ...
- 解决Mac下AndroidStudio内容时卡顿
Mac下AndroidStudio在写代码的时候出现卡顿,小圆圈会一直转,此时我们应该检查下AndroidStudio的内存使用情况了. 1.点击左上角 AndroidStudio -- Prefer ...
- 【Bugly安卓开发干货分享】Android APP 快速 Pad 化实现
项目背景 采用最新版本手机 APP(之后称为 MyApp)代码,实现其 Pad 化,为平板和大屏手机用户提供更好的体验.为实现 MyApp 的 Pad 化工作,需要我们首先来了解一下 MyApp 项目 ...
- Windows10 磁盘活动时间百分之百导致系统卡顿解决方法
最近电脑边的特别慢,打开任务管理器发现是磁盘活动时间时不时的就会变成100%.起初是以为硬盘出问题了,后来网上查了一下才发现很多人都遇到过这个问题,其原因就是Windows的SuperFetch和家庭 ...
- 解决无法安装cnpm,cnpm卡顿问题
# 注册模块镜像 npm set registry https://registry.npm.taobao.org # node-gyp 编译依赖的 node 源码镜像 npm set disturl ...
- 解决windows 1903 alt + tab 切换卡顿
右击此电脑图标 选择管理 服务和应用程序 服务 禁用system interface foundation service
- 【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog
本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/581c2c46bef1702a2db3ae53 Dev Club 是一个交流移动 ...
随机推荐
- ROS入门21讲(7)
十二.launch启动文件的使用方法 1.launch文件:通过XML文件实现多节点的配置和启动(可自动启动ROS Master) 2.Launch文件语法: <launch> <n ...
- Docker Compose容器编排--项目五
一.Docker Compose概念 Docker Compose (可简称Compose)是一个定义与运行复杂应用程序的 Docker 工具,是 Docker 官方 编排(Orchestration ...
- Power BI新卡片更改显示单位
Power BI 不知道什么时候发布了新卡片,照现在官方来说,该视觉对象目前还属于预览版,但已经可以正常使用了,对比旧的卡片,显示效果个人觉得会友好一些,详见官方说明:创建"新"卡 ...
- 洛谷:P5707 【深基2.例12】上学迟到 (纯净的顺序结构)
本文纯作者吃饱了没事干写的,仅供奇特思路参考和娱乐 最近尝试找一个体量精良的刷题平台重新提升一下自己的编程能力,所以选择了洛谷. 题目描述 学校和 yyy 的家之间的距离为 s 米,而 yyy 以 v ...
- 网站免费https加密教程
为网站实现HTTPS加密可以大大提高网站的安全性和用户信任度.以下是一个详细的免费HTTPS加密教程: 一.选择免费SSL证书提供商 JoySSL:这是目前国内为数不多的国产CA服务商打造的自主品牌S ...
- node-npm发布包-package.json中bin的用法
前言 用过angular-cli,create-react-app这些脚手架的朋友们,不知道你们有没有好奇过,为什么安装这些脚手架后,可以使用类似ng generate之类的命令.小弟研究了以下,原来 ...
- linux bash shell 中的单引号和双引号
摘抄自:ABS_CN 当要引用一个变量的值时,一般推荐使用双引号.使用双引号除了变量名[2]前缀($).后引符(`)和转义符(\)外,会使shell不再解释引号中其它所有的特殊字符.[3] 用双引号时 ...
- docker-compose之配置docker-compose.yml
当前有三种版本的Compose配置文件格式: Version 1 旧版格式,通过省略YAML的根配置项version来指定. 未声明版本的Compose配置文件都被视为V1版,所有的服务都作为根选项在 ...
- offline RL · PbRL | LiRE:构造 A>B>C 的 RLT 列表,得到更多 preference 数据
论文标题:Listwise Reward Estimation for Offline Preference-based Reinforcement Learning,ICML 2024. arxiv ...
- Vue.js 文本交替滚动
1.前言 当一段文本需要单行显示,但是又限于容器宽度无法完全展示时,我们需要对其滚动展示,所以就有了这个插件,如图: 2.封装思路 使用js模拟循环滚动的动画,容器宽度固定且超出隐藏,文本元素禁止换行 ...