实现图文消息的正确加载

标签: 消息 正确 加载 | 发表时间:2021-02-19 10:36 | 作者:神奇的程序员
出处:https://juejin.cn/frontend

前言

昨天,在我的开源项目chat-system中查看聊天记录时,发现消息中如果有图片滚动条的位置就会算错,导致最后一条消息定位不准确。

经过一番排查后,终于解决了这个问题,本文就跟大家分享下我的解决方案与思路,欢迎各位感兴趣的开发者阅读本文。

问题分析

如下图所示,我们点开一个聊天窗口,最后一条消息是图片,滚动条位置计算有误,没有触底,导致图片没有显示完全,在上拉加载历史消息时也是因为图片导致的滚动条位置计算失误,没有正确定位到上次浏览的消息位置。

滚动条触底分析

我们来看下触底时滚动条位置计算的实现代码:

       nextTick().then(() => {
      let scrollHeight = 0;
      if (messagesContainer.value == null) return;
      // 获取消息容器滚动区域高度
      scrollHeight = messagesContainer.value.scrollHeight;
      // 当前滚动条在底部或者当前消息为发送端所发送的则修改滚动条位置
      if (isBottomOut.value || data.isSendMessages.value) {
        // 新消息渲染完成,修改滚动条位置
        messagesContainer.value.scrollTop = scrollHeight;
      }
    });
复制代码

如上述代码所示,我们在nextTick回调中获取了消息容器的滚动区域高度,然后修改滚动条位置为滚动区域高度,这样滚动条就会触底了,逻辑上没问题,而且在纯文字的消息中是正常的。

那么,问题可能出在获取消息容器高度时,没有获取正确,于是我尝试了下将 scrollHeight改为 99999,这样它的滚动条就肯定在底部了。

然而,并没有我预想的那样顺利,改成 99999后,滚动条的位置依然是错的。

那么,我想问题应该是 nextTick()后滚动条确实到底部了,但是此时图片还没有加载完,图片加载完成后滚动条位置就又变了。

此时,我们就找到了问题,那么我们就可以得到下述解决思路:

  • 获取页面内的所有聊天图片
  • 遍历获取到的图片
  • 每一张图片加载完成后就获取可滚动容器的高度,然后修改滚动条位置

滚动条触顶分析

触顶加载数据时,也是因为图片的缘故,导致了滚动条位置计算失误,一开始我选择沿用触底的时的方案,等img加载完成后获取滚动容器的高度,然后用当前消息容器高度 - 上一次保存的消息容器高度,这样就能计算出上一次浏览消息时的滚动条位置。

按照上述思路实现后,滚动条的位置依然是错的,经过一番调试后,发现每次触顶时,dom都会重新加载,自然已经加载过的图片还会重新加载一次,滚动条的位置自然也就算错了。

经过一番思考后,我想到了一个解决方案,既然等图片加载完行不通,那我就用定时器吧。

  • nextTick()后,等待150ms,然后获取消息容器的可滚动高度.
  • 计算滚动条的位置
  • 修改滚动条位置

实现代码

接下来,我们来看下具体的实现代码。

滚动条触底

滚动条触底时的部分代码如下所示,完整代码请移步: messageParsing.ts

     nextTick().then(() => {
    const scrollHeight = 0;
    // 获取页面内所有的聊天图片
    const previewablePanel = document.getElementsByClassName("previewable");
    if (messagesContainer.value == null) return;
    for (let i = 0; i < previewablePanel.length; i++) {
      const item = previewablePanel.item(i) as HTMLImageElement;
      item.onload = () => {
          if (messagesContainer.value == null) return;
          // 置底滚动条
          bottomScrollBar(
            scrollHeight,
            messagesContainer as Ref,
            isBottomOut,
            msgListPanelHeight,
            isFirstLoading
          );
        };
    }
  });
复制代码
   const bottomScrollBar = (
  scrollHeight: number,
  messagesContainer: Ref,
  isBottomOut: Ref<boolean>,
  msgListPanelHeight: Ref<number>,
  isFirstLoading: Ref<boolean>
) => {
  const data = initData();
  // 显示消息内容
  data.msgShowStatus.value = "";
  // 获取容器高度
  scrollHeight = messagesContainer.value.scrollHeight;
  // 当前滚动条在底部或者当前消息为发送端所发送的则修改滚动条位置
  if (isBottomOut.value || data.isSendMessages.value) {
    // 新消息渲染完成,修改滚动条位置
    messagesContainer.value.scrollTop = scrollHeight;
    // 更新消息记录容器高度
    msgListPanelHeight.value = scrollHeight;
    // 修改组件第一次加载状态为false
    isFirstLoading.value = false;
    // 修改消息发送端状态为false
    data.isSendMessages.value = false;
  }
};

复制代码

滚动条触顶

滚动条触顶时的部分代码如下所示,完整代码请移步: messageParsing.ts

       nextTick().then(() => {
      // 隐藏消息内容
      data.msgShowStatus.value = "hidden";

      if (data.pageNo.value > 20) {
        // 数据加载超过20条,加载时间改为400ms
        loadingTime = 400;
      }

      setTimeout(() => {
        if (messagesContainer.value == null) return;
        scrollHeight = messagesContainer.value.scrollHeight;
        // 加载历史消息,修改滚动条位置:当前消息记录容器高度 - 消息记录容器高度
        messagesContainer.value.scrollTop =
          scrollHeight - msgListPanelHeight.value;
        // 一条消息渲染完成,待渲染消息总条数自减
        msgTotals.value--;
        // 判断消息是否渲染完成
        if (msgTotals.value === 0) {
          // 显示消息内容
          data.msgShowStatus.value = "";
          // 关闭加载动画
          isLoading.value = false;
          // 加载历史消息完成,更新消息记录容器高度
          msgListPanelHeight.value = scrollHeight;
        }
      }, loadingTime);
    });
复制代码

在上述代码中,定时器的时间是动态的,是因为我发现当加载的消息超过20页时,等待150ms已经拿不到正确的可滚动容器高度了,需要等待400ms。

实现效果

接下来,我们来看下最终的实现效果。

滚动条触顶

在上述实现代码中,我还做了一个优化,nextTick后我隐藏了消息内容,滚动条位置计算完成后,让消息内容再显示出来。

至于为什么要做这个优化,我通过gif图来描述下吧,我们先来看下没做优化时的触顶加载效果,如下所示:

如上图所示,未优化时加载消息会先闪一下错误位置的消息,然后才会展示正确的消息,看着很难受。

接下来,我们来看下优化后的效果,如下所示:

优化后,视觉效果相比未优化时要好上很多,虽然还是有点瑕疵,会闪烁一下,目前想不到其他解决方案了,只能先这样了,如果大家有更好的方案,可在评论区一起讨论。

滚动条触底

滚动条触底时,由于是需要等图片加载完成后修改滚动条的位置,图片未加载完成时,界面会先闪一下错误位置的消息,然后才是正确的消息。

触底时,我采用了与触顶时相同的解决方案,滚动条位置计算完成后才让聊天记录显示,实现效果如下所示:

项目地址

写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注
  • 本文首发于掘金,未经许可禁止转载

相关 [消息 正确 加载] 推荐:

实现图文消息的正确加载

- - 掘金 前端
昨天,在我的开源项目chat-system中查看聊天记录时,发现消息中如果有图片滚动条的位置就会算错,导致最后一条消息定位不准确. 经过一番排查后,终于解决了这个问题,本文就跟大家分享下我的解决方案与思路,欢迎各位感兴趣的开发者阅读本文. 如下图所示,我们点开一个聊天窗口,最后一条消息是图片,滚动条位置计算有误,没有触底,导致图片没有显示完全,在上拉加载历史消息时也是因为图片导致的滚动条位置计算失误,没有正确定位到上次浏览的消息位置.

如何正确控制springboot中bean的加载顺序总结

- - SegmentFault 最新的文章
1.为什么需要控制加载顺序. springboot遵从约定大于配置的原则,极大程度的解决了配置繁琐的问题. 在此基础上,又提供了spi机制,用 spring.factories可以完成一个小组件的自动装配功能. 在一般业务场景,可能你不大关心一个bean是如何被注册进spring容器的. 只需要把需要注册进容器的bean声明为 @Component即可,spring会自动扫描到这个Bean完成初始化并加载到spring上下文容器.

消息两则

- 藏书人 - 李志官方博客
1,经过深思熟虑,我放弃了十月份做个人小巡演的计划,全心全意投入跨年音乐会的准备工作. 如不出意外,12月31日南京见. 2,如果不出意外,第六张专辑会在十一之前发布. 经过深思熟虑,我决定不做实体,直接放到官网提供下载,能者多劳,愿者给钱. 3,当然对我而言,意外是常态.

正确理解ThreadLocal

- - Java - 编程语言 - ITeye博客
转自: http://www.iteye.com/topic/103804. 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的. 另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本.

周一消息树

- 水御龙神 - 1416 教室
每一个光鲜的封面,都饱含美术编辑的”血泪“和杂志主编的“阴谋”——今天的消息树让我们将掀开封面往里瞅瞅. 最新一期的美国新闻周刊封面,实在让人有点儿难以置信. 优雅的戴安娜王妃突然出现在二十一世纪的街头,旁边是她的儿媳妇Kate,但仔细看,她却不是当年的王妃,变老了,变丑了——这是新闻周刊编辑们想象中的一个五十岁的女人的样子.

正确重置MySQL密码

- xxg - 火丁笔记
谁都不想弄丢家门钥匙,但不管多么小心,时间长了,这样的事情总会发生几次. MySQL密码也是一样,把它写在文档上不太安全,记在脑子里又难免会忘记. 如果你忘记了MySQL密码,如何重置它呢. 首先停止MySQL服务,然后使用skip-grant-tables参数启动它:. 此时无需授权就可以进入到MySQL命令行,使用SQL重置MySQL密码:.

正确使用银行卡

- - 雨中发呆
请大家看清楚了,是网上银行汇款. 不是银行柜台上汇!柜台上的手续费比网上银行贵的. 但有一个例外,邮政储蓄要收0.5%. 工行:0.9%,最低0.9元/笔,最高45元/笔. 农行:0.4% ,最低1元/笔,最高20元(柜台手续费是0.5%,最高50元).  跨省:如果对方是银行卡:转账金额的0.06% (也就是万分之六),最低1元/笔,最高12元/笔.

对象的消息模型

- loudly - 酷壳 - CoolShell.cn
[ ———— 感谢 Todd 同学 投递本文,原文链接 ———— ]. 话题从下面这段C++程序说起,你认为它可以顺利执行吗. 试试的确可以顺利运行输出hello world,奇怪吗. 其实并不奇怪,根据C++对象模型,类的非虚方法并不会存在于对象内存布局中,实际上编译器是把Hello方法转化成了类似这样的全局函数:.