vue实现聊天框自动滚动

这篇具有很好参考价值的文章主要介绍了vue实现聊天框自动滚动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

需求   

        1、聊天数据实时更新渲染到页面
        2、页面高度随聊天数据增加而增加
        3、竖向滚动
        4、当用户输入聊天内容或者接口返回聊天内容渲染在页面后,自动滚动到底部
        5、提供点击事件操控滚动条上下翻动

环境依赖

        vue:@vue/cli 5.0.8

        taro:v3.4.1

实现方案

方案一:元素设置锚点,使用scrollIntoView() 方法滑动

         Element 接口的 scrollIntoView()  方法会滚动元素的父容器,使被调用 scrollIntoView()  的元素对用户可见

        1、语法
        element.scrollIntoView(); // 等同于 element.scrollIntoView(true)
        element.scrollIntoView(alignToTop); // alignToTop为Boolean 型参数,true/false
        element.scrollIntoView(scrollIntoViewOptions); // Object 型参数
        2、参数
     (1)alignToTop(可选)
        类型:Boolean

        如果为true,元素的顶端将和其所在滚动区的可视区域的顶端对齐。对应的 scrollIntoViewOptions: {block: “start”, inline: “nearest”}。该参数的默认值为true。
        如果为false,元素的底端将和其所在滚动区的可视区域的底端对齐。对应的scrollIntoViewOptions: {block: “end”, inline: “nearest”}。
      (2)scrollIntoViewOptions (可选)
        类型:对象          

        behavior 【可选】
                定义动画的过渡效果,取值为 auto/smooth。默认为 “auto”。
        block 【可选】
                定义垂直方向的对齐, 取值为 start/center/end/nearest 。默认为 “start”。
        inline 【可选】
                定义水平方向的对齐, 取值为 start/center/end/nearest。默认为 “nearest”。
       代码实现如下:

<template>
  <view class="main" id="main">
    <!--  scroll-y:允许纵向滚动   默认: false | 给scroll-view一个固定高度 |  scroll-into-view: 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 -->
    <scroll-view class="mainbody" id="mainbody" scroll-with-animation :scroll-y="true" :scroll-into-view="scrollId" style="height:960px;" :enhanced=true scrollIntoViewAlignment="center"
                 @scrolltoupper="upper" @scrolltolower="lower" @scroll="scroll" :scrollWithAnimation="true">
      <view v-for="(item, index) in contentTypeit.arr" v-bind:key="index"
            :class="['info',  'content-questionBlock']">
        <view :class="['content']" :id="item.id">{{ item.content
          }}
        </view>
      </view>
      <view @click="sendMsg" id="sendMsg"></view>
      <view @click="pageUp" id="pageUp" style="visibility: hidden;"></view>
      <view @click="pageDown" id="pageDown" style="visibility: hidden;"></view>

    </scroll-view>

  </view>
</template>

<script>
import { ref, reactive, toRaw } from 'vue'

export default {
  setup () {
    const contentTypeit = reactive({
      arr: []
    })
    const scrollId = ref('id0') //scroll ID值
    const scrollCursor = ref('id0')

    const number = ref(0)
    //https://blog.csdn.net/weixin_43398820/article/details/119963930

    // 会话内容
    // 获取对话结果
    const sendMsg = function () {
      setContent( 'dfasdfsfsafdsafsafsdfsafsdfsdfdsfsafdsfsadfsafggfdhfhfjgfjhsdgdsfgasfsafdsafsagdhgfhfdhsgdsgdsgdgafsadfdsfdsfsadfhghsdfgsafdsaf')
    }
    // 设置对话内容
    const setContent = function (msg) {
      let idValue = 'id' + number.value
      const currentObjTypeit = {
        'content': msg,
        'id': idValue
      }

      let _arr = toRaw(contentTypeit.arr)
      let _arrTmp = _arr.concat(currentObjTypeit)
      contentTypeit.arr = _arrTmp

      number.value = number.value + 1;
      scrollCursor.value = idValue
      //https://blog.csdn.net/weixin_46511008/article/details/126629361
      setTimeout(() => {
        if (number.value !== 0) {
          let idValueSlide = 'id' + (number.value - 1)
          document.getElementById(idValueSlide).scrollIntoView({
            behavior: 'smooth',
            block: 'center',
            inline: 'end'
          })
        }
      }, 100);

    }

    const scroll = function (e) {
      // console.log('scroll', e)
    }
    const upper = function (e) {
      // console.log('upper', e)
    }
    const lower = function (e) {
      // console.log('lower', e)
    }

    const pageUp = function (e) {
      console.log(scrollCursor.value)
      if (scrollCursor.value === undefined || scrollCursor.value === '' || scrollCursor.value.length < 3) {
        return;
      }

      let scrollCursorValue = scrollCursor.value.substring(2);
      console.log(scrollCursorValue);
      if (scrollCursorValue >= 1) {
        scrollCursorValue = scrollCursorValue - 1;
        scrollCursor.value = 'id' + scrollCursorValue;
      }
      setTimeout(function(){
        if (document.querySelector('#'+ scrollCursor.value) === null) {
          return;
        }
        document.querySelector('#'+ scrollCursor.value).scrollIntoView()
      }, 200);

    }
    const pageDown = function (e) {
      console.log(scrollCursor.value)
      if (scrollCursor.value === undefined || scrollCursor.value === '' || scrollCursor.value.length < 3) {
        return;
      }
      let scrollCursorValue = scrollCursor.value.substring(2);
      console.log(scrollCursorValue);
      if (scrollCursorValue < contentTypeit.arr.length - 1) {
        scrollCursorValue = scrollCursorValue -  (-1)
        scrollCursor.value = 'id' +  scrollCursorValue;
      }
      if (scrollCursorValue === contentTypeit.arr.length - 1) {
        setTimeout(function(){
          if (document.querySelector('#'+ scrollCursor.value) === null) {
            return;
          }
          document.querySelector('#'+ scrollCursor.value).scrollIntoView(false)
        }, 500);
      } else {
        setTimeout(function() {
          if (document.querySelector('#'+ scrollCursor.value) === null) {
            return;
          }
          document.querySelector('#'+ scrollCursor.value).scrollIntoView({
            behavior: "smooth", // 平滑过渡
            block: "end", // 上边框与视窗顶部平齐。默认值
          })
        }, 100);
      }
    }

    return {
      contentTypeit,
      scrollId,
      lower,
      upper,
      scroll,
      sendMsg,
      pageUp,
      pageDown,
    }
  }
}
</script>

<style lang="scss">
.main {
  height: 100%;
  width: 100%;
  background-color: rgba(204, 204, 204, 0.32);
  overflow-x: hidden;
  overflow-y: auto;
}

.mainbody {
  max-width: 100%;
  background-size: contain;
  padding-bottom: 100px;
}

.info {
  display: flex;
  margin: 10px 3%;
}
.content-question {
  color: #0b4eb4;
  background-color: #ffffff;
  padding-left: 20px;
}


.content-questionBlock {
  align-items: center;
}

.content {
  background-color: #fff;
  border-radius: 16px;
  padding: 20px;
  margin-left: 20px;
  max-width: 82%;
  height: 100%;
  font-size: 36px;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #0a0a27;
  line-height: 60px;
  word-break: break-all;
}
</style>

        效果调试:

      (1)打开浏览器,按下F12进入调试模式;

      (2)在console窗口,多次调用document.getElementById('sendMsg').click(),使得对话内容超出界面高度,可观察到自动滚动效果;

       (3)在console窗口,调用document.getElementById('pageUp').click(),若没有滚动,可调整代码或者调用多次(取决于scrollIntoView()的参数),可观察到向上滚动;接着调用document.getElementById('pageDown').click(),可观察到向下滚动。

        效果图如下:vue实现聊天框自动滚动

 方案二: 更改scrollTop取值,进行滚动        

        首先我们需要了解 clientHeightoffsetHeightscrollHeightscrollTop 的概念

        简单介绍:

                clientHeight:网页可见区域高

                offsetHeight:网页可见区域高(包括边线的高)

                scrollHeight:网页正文全文高
                scrollTop:网页被卷去的高

        具体说明:

       (1)clientHeight:包括padding 但不包括 border、水平滚动条、margin的元素的高度。对于inline的元素来说这个属性一直是0,单位px,为只读元素。

        简单来说就是——盒子的原始高度,具体可参考下图:

vue实现聊天框自动滚动

      (2)offsetHeight:包括padding、border、水平滚动条,但不包括margin的元素的高度。对于inline的元素来说这个属性一直是0,单位px,为只读元素。

        简单来说就是——盒子的原始高度+padding+border+滚动条,具体可参考下图:

vue实现聊天框自动滚动

       (3)scrollHeight 这个只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。

        简单来说就是——盒子里面包含的内容的真实高度,具体可参考下图:

 vue实现聊天框自动滚动

       (4)scrollTop: 代表在有滚动条时,滚动条向下滚动的距离也就是元素顶部被遮住部分的高度。在没有滚动条时 scrollTop==0 恒成立。单位px,可读可设置。

        MDN解释:一个元素的 scrollTop 值是这个元素的内容顶部(被卷起来的)到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那它的 scrollTop 值为0,具体可参考下图:

vue实现聊天框自动滚动

         实现算法:卷起的高度(scrollTop) = 总的内容高度(scrollHeight) - 聊天区域盒子大小 (offsetHeight);

         代码实现如下:

<template>
  <view class="main" ref="scrollContainer" id="main">
    <!--  scroll-y:允许纵向滚动   默认: false | 给scroll-view一个固定高度 -->
    <scroll-view class="mainbody" id="mainbody" scroll-with-animation :scroll-y="true"  style="height:960px;" :enhanced=true scrollIntoViewAlignment="center"
                 @scrolltoupper="upper" @scrolltolower="lower" @scroll="scroll" :scrollWithAnimation="true">
      <view v-for="(item, index) in contentTypeit.arr" v-bind:key="index"
            :class="['info',  'content-questionBlock']">
        <view :class="['content']" :id="item.id">{{ item.content
          }}
        </view>
      </view>
      <view @click="sendMsg" id="sendMsg"></view>
      <view @click="pageUp" id="pageUp" style="visibility: hidden;"></view>
      <view @click="pageDown" id="pageDown" style="visibility: hidden;"></view>

    </scroll-view>

  </view>
</template>

<script>
import { ref, reactive, toRaw } from 'vue'
import Taro from "@tarojs/taro";

export default {
  setup () {
    const contentTypeit = reactive({
      arr: []
    })
    const scrollId = ref('id0') //scroll ID值
    const scrollCursor = ref('id0')

    const scrollCursorStore = ref(0)
    // 自动 scrollTop
    //https://www.cnblogs.com/hmy-666/p/14717484.html  滚动原理与实现
    //由于插入新的消息属于创建新的元素的过程,这个过程是属于异步的,所以为了防止异步创建元素导致获取高度不准确,我们可以等待一段时间,等元素创建完毕之后再获取元素高度
    const scrollDownInterval = function () {
      let idDom = document.getElementById('mainbody')
      console.log("===================scrollTop,clientHeight,scrollHeight,offsetHeight", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)
      let currentScrollPosition = scrollCursorStore.value;
      Taro.nextTick(() => {
        console.log('scroll start...', idDom.scrollTop)
        let scrollInterval = setInterval(() => {
          if (
            (idDom.scrollTop === idDom.scrollHeight - idDom.offsetHeight) ||
            (idDom.scrollTop > idDom.scrollHeight - idDom.offsetHeight)
          ) {
            scrollCursorStore.value = idDom.scrollTop
            clearInterval(scrollInterval);
            console.log('scroll end...', idDom.scrollTop)
          } else {
            currentScrollPosition =
              currentScrollPosition + 100;
            idDom.scrollTop = currentScrollPosition;
            scrollCursorStore.value = idDom.scrollTop
            console.log('scrolling...', idDom.scrollTop)
          }
        }, 200)
      })
    }

    const number = ref(0)
    //https://blog.csdn.net/weixin_43398820/article/details/119963930

    // 会话内容
    // 获取对话结果
    const sendMsg = function () {
      setContent( 'dfasdfsfsafdsafsafsdfsafsdfsdfdsfsafdsfsadfsafggfdhfhfjgfjhsdgdsfgasfsafdsafsagdhgfhfdhsgdsgdsgdgafsadfdsfdsfsadfhghsdfgsafdsaf')
    }
    // 设置对话内容
    const setContent = function (msg) {
      let idValue = 'id' + number.value
      const currentObjTypeit = {
        'content': msg,
        'id': idValue
      }

      let _arr = toRaw(contentTypeit.arr)
      let _arrTmp = _arr.concat(currentObjTypeit)
      contentTypeit.arr = _arrTmp

      number.value = number.value + 1;
      scrollCursor.value = idValue
      //https://blog.csdn.net/weixin_46511008/article/details/126629361
      scrollDownInterval();

    }

    const scroll = function (e) {
      // console.log('scroll', e)
    }
    const upper = function (e) {
      // console.log('upper', e)
    }
    const lower = function (e) {
      // console.log('lower', e)
    }

    const pageUp = function (e) {
      let idDom = document.getElementById('mainbody')
      console.log("===================", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)
      let currentScrollPosition = scrollCursorStore.value;
      scrollCursorStore.value = scrollCursorStore.value - 400
      if (scrollCursorStore.value < 0) {
        scrollCursorStore.value = 0;
      }
      Taro.nextTick(() => {
        console.log('scroll start...', idDom.scrollTop)
        let scrollInterval = setInterval(() => {
          if (
            (idDom.scrollTop === scrollCursorStore.value) ||
            (idDom.scrollTop < scrollCursorStore.value)
          ) {
            clearInterval(scrollInterval);
            console.log('scroll end...', idDom.scrollTop)
          } else {
            currentScrollPosition =
              currentScrollPosition - 50;
            idDom.scrollTop = currentScrollPosition;
            console.log('scrolling...', idDom.scrollTop)
          }
        }, 100)
      })
    }
    const pageDown = function (e) {
      let idDom = document.getElementById('mainbody')
      console.log("===================", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)
      let currentScrollPosition = scrollCursorStore.value;
      scrollCursorStore.value = scrollCursorStore.value + 400
      if (scrollCursorStore.value > (idDom.scrollHeight - idDom.offsetHeight )) {
        scrollCursorStore.value =  idDom.scrollHeight - idDom.offsetHeight;
      }
      Taro.nextTick(() => {
        console.log('scroll start...', idDom.scrollTop)
        let scrollInterval = setInterval(() => {
          if (
            (idDom.scrollTop === scrollCursorStore.value) ||
            (idDom.scrollTop > scrollCursorStore.value)
          ) {

            clearInterval(scrollInterval);
            console.log('scroll end...', idDom.scrollTop)
          } else {
            currentScrollPosition =
              currentScrollPosition - (-50);
            idDom.scrollTop = currentScrollPosition;
            console.log('scrolling...', idDom.scrollTop)
          }
        }, 100)
      })
    }

    return {
      contentTypeit,
      scrollId,
      lower,
      upper,
      scroll,
      sendMsg,
      pageUp,
      pageDown,
    }
  }
}
</script>

<style lang="scss">
.main {
  height: 100%;
  width: 100%;
  background-color: rgba(204, 204, 204, 0.32);
  overflow-x: hidden;
  overflow-y: auto;
}

.mainbody {
  max-width: 100%;
  background-size: contain;
  padding-bottom: 100px;
}

.info {
  display: flex;
  margin: 10px 3%;
}
.content-question {
  color: #0b4eb4;
  background-color: #ffffff;
  padding-left: 20px;
}


.content-questionBlock {
  align-items: center;
}

.content {
  background-color: #fff;
  border-radius: 16px;
  padding: 20px;
  margin-left: 20px;
  max-width: 82%;
  height: 100%;
  font-size: 36px;
  font-family: PingFangSC-Medium, PingFang SC;
  font-weight: 500;
  color: #0a0a27;
  line-height: 60px;
  word-break: break-all;
}
</style>

        效果调试:

      (1)打开浏览器,按下F12进入调试模式;

      (2)在console窗口,多次调用document.getElementById('sendMsg').click(),使得对话内容超出界面高度,可观察到自动滚动效果;

       (3)在console窗口,调用document.getElementById('pageUp').click(),可观察到向上滚动;接着调用document.getElementById('pageDown').click(),可观察到向下滚动。

        效果图如下:

vue实现聊天框自动滚动

建议

        方案一由于接口支持,滑动效果更平滑,但是翻页只能调到指定锚点,滑动步长不可控,大部分场景不能满足需求。

        方案二可以自行调整翻页的步长,按需滑动至指定高度,不过滑动动画需要自行实现,看起来卡顿感较强。

        总体来说,建议使用方案二。

参考链接:

        https://blog.csdn.net/weixin_46511008/article/details/126629361

        https://www.cnblogs.com/wq805/p/16399600.html

        https://www.cnblogs.com/hmy-666/p/14717484.html

        Taro 文档文章来源地址https://www.toymoban.com/news/detail-443915.html

到了这里,关于vue实现聊天框自动滚动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue和node使用websocket实现数据推送,实时聊天

    需求:node做后端根据websocket,连接数据库,数据库的字段改变后,前端不用刷新页面也能更新到数据,前端也可以发送消息给后端,后端接受后把前端消息做处理再推送给前端展示 使用node ./app.js运行项目 在需要使用websocket连接的页面引入 默认如下: id为243 在数据库改为

    2024年02月15日
    浏览(52)
  • vue+element-UI实现跟随滚动条加载表格数据

    el-table当数据量大的时候,实现滚动到底部后加载数据,直接上js代码,有其他需求请各自更改  第一步、在data中定义两个数组 第二步、在数据发生改变的方法中先循环存放一部分数据用于页面显示 第三步、在mounted监听滚动事件

    2024年02月16日
    浏览(57)
  • vue 基于原生动画的自动滚动表格

    公司展示大屏需要写滚动表格,通过滚动播放数据,自己随便摸了一个基于动画的自动滚动表格 根据每行的大小和设置的每行滚动时间设置滚动位置,动态添加动画,并把数组第一项移动到最后一项,并订阅该动画结束的事件,在结束时循环执行该操作。 可自定义单元格或

    2024年02月05日
    浏览(33)
  • vue3-seamless-scroll 自动横向滚动 滚动插件

    Vue3.0 无缝滚动组件,支持Vite2.0,支持服务端打包 目前组件支持上下左右无缝滚动,单步滚动,并且支持复杂图标的无缝滚动,目前组件支持平台与 Vue3.0 支持平台一致。 npm Yarn browser list 无缝滚动列表数据,组件内部使用列表长度。 v-model 通过v-model控制动画滚动与停止,默

    2024年01月19日
    浏览(59)
  • vue自动滚动组件 可以支持鼠标滚轮操作

    vue自动滚动组件 可以支持鼠标滚轮操作

    2024年02月10日
    浏览(45)
  • vue中数据滚动显示 实现Element-UI中el-table内数据的懒加载

    工作中我们经常会用到element-ui组件库中的le-table组件来展示数据,但当table的数据源数量过大的时候统一展示可能会出现页面卡顿,且会影响用户体验,为此我们可以尝试对el-table中的数据做懒加载的效果展示: 1. 挂在阶段监听el-table的scroll滚动事件 2. 当table表格滚动条的位置

    2023年04月08日
    浏览(57)
  • Vue - 超详细实现文字上下滚动功能效果,类似网站公告文字循环翻滚、中将人员名单公布上下无限滚动效果(支持鼠标移入时悬停停止滚动、接口动态数据渲染、自由DIY样式等)

    如果您是 Vue3 项目,请访问 这篇文章。 本文 实现了在 vue 项目中,实现文本的上下无限翻滚效果,类似公告栏和获奖名单人员等(文字内容无缝向上滚动 / 支持开启和关闭鼠标移入停止滚动和鼠标离开继续滚动) 您直接复制示例代码,稍微改改样式就能用了, 如下图所示,

    2024年02月21日
    浏览(130)
  • 自动驾驶数据回传需求

    用户 用户需求 实时性要求 需回传数据 数据类型 采样周期 数据量 大小 数据回传通道 研发工程师 分析评估系统性能表现,例如智驾里程统计、接管率表现、油耗表现、 AEB 报警次数等 当天 车身底盘数据、自动驾驶系统状态数据等 结构化数据 100ms 级 小 4G/5G 处理分析问题原

    2024年02月12日
    浏览(38)
  • vue3 h5进入页面后自动滚动到底部

    背景: 在做h5项目中的聊天页面时,需求是进入页面自动滚到底部,方便用户看到最新消息(因为消息是正序排列的,最新消息自然展示在底部)。  直接上代码: 主要代码:

    2024年02月16日
    浏览(40)
  • [Unity学习]使用ScrollRect实现自动滚动到底部显示实时消息,并在拖动的时候取消自动滚动,再次手动滑到底部,又继续自动滚动

    首先需要重写ScrollRect组件: 下面通过协程实现在不滚动ScrollRect的时候,自动滚动到底部。 使用时,写下面类似代码即可: Unity原生Scroll View更改配置如下: 其中ScrollView游戏物体更改组件如下: content配置如下: 实现效果如下: 大功告成!加上对象池模式控制添加的text实例

    2024年02月16日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包