当前位置:   article > 正文

vue实现聊天框自动滚动_vue3里面聊天里面发消息怎么让消息一直显示最下面,页面也会滚动到最下面

vue3里面聊天里面发消息怎么让消息一直显示最下面,页面也会滚动到最下面

需求   

        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”。
       代码实现如下:

  1. <template>
  2. <view class="main" id="main">
  3. <!-- scroll-y:允许纵向滚动 默认: false | 给scroll-view一个固定高度 | scroll-into-view: 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 -->
  4. <scroll-view class="mainbody" id="mainbody" scroll-with-animation :scroll-y="true" :scroll-into-view="scrollId" style="height:960px;" :enhanced=true scrollIntoViewAlignment="center"
  5. @scrolltoupper="upper" @scrolltolower="lower" @scroll="scroll" :scrollWithAnimation="true">
  6. <view v-for="(item, index) in contentTypeit.arr" v-bind:key="index"
  7. :class="['info', 'content-questionBlock']">
  8. <view :class="['content']" :id="item.id">{{ item.content
  9. }}
  10. </view>
  11. </view>
  12. <view @click="sendMsg" id="sendMsg"></view>
  13. <view @click="pageUp" id="pageUp" style="visibility: hidden;"></view>
  14. <view @click="pageDown" id="pageDown" style="visibility: hidden;"></view>
  15. </scroll-view>
  16. </view>
  17. </template>
  18. <script>
  19. import { ref, reactive, toRaw } from 'vue'
  20. export default {
  21. setup () {
  22. const contentTypeit = reactive({
  23. arr: []
  24. })
  25. const scrollId = ref('id0') //scroll ID值
  26. const scrollCursor = ref('id0')
  27. const number = ref(0)
  28. //https://blog.csdn.net/weixin_43398820/article/details/119963930
  29. // 会话内容
  30. // 获取对话结果
  31. const sendMsg = function () {
  32. setContent( 'dfasdfsfsafdsafsafsdfsafsdfsdfdsfsafdsfsadfsafggfdhfhfjgfjhsdgdsfgasfsafdsafsagdhgfhfdhsgdsgdsgdgafsadfdsfdsfsadfhghsdfgsafdsaf')
  33. }
  34. // 设置对话内容
  35. const setContent = function (msg) {
  36. let idValue = 'id' + number.value
  37. const currentObjTypeit = {
  38. 'content': msg,
  39. 'id': idValue
  40. }
  41. let _arr = toRaw(contentTypeit.arr)
  42. let _arrTmp = _arr.concat(currentObjTypeit)
  43. contentTypeit.arr = _arrTmp
  44. number.value = number.value + 1;
  45. scrollCursor.value = idValue
  46. //https://blog.csdn.net/weixin_46511008/article/details/126629361
  47. setTimeout(() => {
  48. if (number.value !== 0) {
  49. let idValueSlide = 'id' + (number.value - 1)
  50. document.getElementById(idValueSlide).scrollIntoView({
  51. behavior: 'smooth',
  52. block: 'center',
  53. inline: 'end'
  54. })
  55. }
  56. }, 100);
  57. }
  58. const scroll = function (e) {
  59. // console.log('scroll', e)
  60. }
  61. const upper = function (e) {
  62. // console.log('upper', e)
  63. }
  64. const lower = function (e) {
  65. // console.log('lower', e)
  66. }
  67. const pageUp = function (e) {
  68. console.log(scrollCursor.value)
  69. if (scrollCursor.value === undefined || scrollCursor.value === '' || scrollCursor.value.length < 3) {
  70. return;
  71. }
  72. let scrollCursorValue = scrollCursor.value.substring(2);
  73. console.log(scrollCursorValue);
  74. if (scrollCursorValue >= 1) {
  75. scrollCursorValue = scrollCursorValue - 1;
  76. scrollCursor.value = 'id' + scrollCursorValue;
  77. }
  78. setTimeout(function(){
  79. if (document.querySelector('#'+ scrollCursor.value) === null) {
  80. return;
  81. }
  82. document.querySelector('#'+ scrollCursor.value).scrollIntoView()
  83. }, 200);
  84. }
  85. const pageDown = function (e) {
  86. console.log(scrollCursor.value)
  87. if (scrollCursor.value === undefined || scrollCursor.value === '' || scrollCursor.value.length < 3) {
  88. return;
  89. }
  90. let scrollCursorValue = scrollCursor.value.substring(2);
  91. console.log(scrollCursorValue);
  92. if (scrollCursorValue < contentTypeit.arr.length - 1) {
  93. scrollCursorValue = scrollCursorValue - (-1)
  94. scrollCursor.value = 'id' + scrollCursorValue;
  95. }
  96. if (scrollCursorValue === contentTypeit.arr.length - 1) {
  97. setTimeout(function(){
  98. if (document.querySelector('#'+ scrollCursor.value) === null) {
  99. return;
  100. }
  101. document.querySelector('#'+ scrollCursor.value).scrollIntoView(false)
  102. }, 500);
  103. } else {
  104. setTimeout(function() {
  105. if (document.querySelector('#'+ scrollCursor.value) === null) {
  106. return;
  107. }
  108. document.querySelector('#'+ scrollCursor.value).scrollIntoView({
  109. behavior: "smooth", // 平滑过渡
  110. block: "end", // 上边框与视窗顶部平齐。默认值
  111. })
  112. }, 100);
  113. }
  114. }
  115. return {
  116. contentTypeit,
  117. scrollId,
  118. lower,
  119. upper,
  120. scroll,
  121. sendMsg,
  122. pageUp,
  123. pageDown,
  124. }
  125. }
  126. }
  127. </script>
  128. <style lang="scss">
  129. .main {
  130. height: 100%;
  131. width: 100%;
  132. background-color: rgba(204, 204, 204, 0.32);
  133. overflow-x: hidden;
  134. overflow-y: auto;
  135. }
  136. .mainbody {
  137. max-width: 100%;
  138. background-size: contain;
  139. padding-bottom: 100px;
  140. }
  141. .info {
  142. display: flex;
  143. margin: 10px 3%;
  144. }
  145. .content-question {
  146. color: #0b4eb4;
  147. background-color: #ffffff;
  148. padding-left: 20px;
  149. }
  150. .content-questionBlock {
  151. align-items: center;
  152. }
  153. .content {
  154. background-color: #fff;
  155. border-radius: 16px;
  156. padding: 20px;
  157. margin-left: 20px;
  158. max-width: 82%;
  159. height: 100%;
  160. font-size: 36px;
  161. font-family: PingFangSC-Medium, PingFang SC;
  162. font-weight: 500;
  163. color: #0a0a27;
  164. line-height: 60px;
  165. word-break: break-all;
  166. }
  167. </style>

        效果调试:

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

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

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

        效果图如下:

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

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

        简单介绍:

                clientHeight:网页可见区域高

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

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

        具体说明:

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

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

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

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

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

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

 

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

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

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

         代码实现如下:

  1. <template>
  2. <view class="main" ref="scrollContainer" id="main">
  3. <!-- scroll-y:允许纵向滚动 默认: false | 给scroll-view一个固定高度 -->
  4. <scroll-view class="mainbody" id="mainbody" scroll-with-animation :scroll-y="true" style="height:960px;" :enhanced=true scrollIntoViewAlignment="center"
  5. @scrolltoupper="upper" @scrolltolower="lower" @scroll="scroll" :scrollWithAnimation="true">
  6. <view v-for="(item, index) in contentTypeit.arr" v-bind:key="index"
  7. :class="['info', 'content-questionBlock']">
  8. <view :class="['content']" :id="item.id">{{ item.content
  9. }}
  10. </view>
  11. </view>
  12. <view @click="sendMsg" id="sendMsg"></view>
  13. <view @click="pageUp" id="pageUp" style="visibility: hidden;"></view>
  14. <view @click="pageDown" id="pageDown" style="visibility: hidden;"></view>
  15. </scroll-view>
  16. </view>
  17. </template>
  18. <script>
  19. import { ref, reactive, toRaw } from 'vue'
  20. import Taro from "@tarojs/taro";
  21. export default {
  22. setup () {
  23. const contentTypeit = reactive({
  24. arr: []
  25. })
  26. const scrollId = ref('id0') //scroll ID值
  27. const scrollCursor = ref('id0')
  28. const scrollCursorStore = ref(0)
  29. // 自动 scrollTop
  30. //https://www.cnblogs.com/hmy-666/p/14717484.html 滚动原理与实现
  31. //由于插入新的消息属于创建新的元素的过程,这个过程是属于异步的,所以为了防止异步创建元素导致获取高度不准确,我们可以等待一段时间,等元素创建完毕之后再获取元素高度
  32. const scrollDownInterval = function () {
  33. let idDom = document.getElementById('mainbody')
  34. console.log("===================scrollTop,clientHeight,scrollHeight,offsetHeight", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)
  35. let currentScrollPosition = scrollCursorStore.value;
  36. Taro.nextTick(() => {
  37. console.log('scroll start...', idDom.scrollTop)
  38. let scrollInterval = setInterval(() => {
  39. if (
  40. (idDom.scrollTop === idDom.scrollHeight - idDom.offsetHeight) ||
  41. (idDom.scrollTop > idDom.scrollHeight - idDom.offsetHeight)
  42. ) {
  43. scrollCursorStore.value = idDom.scrollTop
  44. clearInterval(scrollInterval);
  45. console.log('scroll end...', idDom.scrollTop)
  46. } else {
  47. currentScrollPosition =
  48. currentScrollPosition + 100;
  49. idDom.scrollTop = currentScrollPosition;
  50. scrollCursorStore.value = idDom.scrollTop
  51. console.log('scrolling...', idDom.scrollTop)
  52. }
  53. }, 200)
  54. })
  55. }
  56. const number = ref(0)
  57. //https://blog.csdn.net/weixin_43398820/article/details/119963930
  58. // 会话内容
  59. // 获取对话结果
  60. const sendMsg = function () {
  61. setContent( 'dfasdfsfsafdsafsafsdfsafsdfsdfdsfsafdsfsadfsafggfdhfhfjgfjhsdgdsfgasfsafdsafsagdhgfhfdhsgdsgdsgdgafsadfdsfdsfsadfhghsdfgsafdsaf')
  62. }
  63. // 设置对话内容
  64. const setContent = function (msg) {
  65. let idValue = 'id' + number.value
  66. const currentObjTypeit = {
  67. 'content': msg,
  68. 'id': idValue
  69. }
  70. let _arr = toRaw(contentTypeit.arr)
  71. let _arrTmp = _arr.concat(currentObjTypeit)
  72. contentTypeit.arr = _arrTmp
  73. number.value = number.value + 1;
  74. scrollCursor.value = idValue
  75. //https://blog.csdn.net/weixin_46511008/article/details/126629361
  76. scrollDownInterval();
  77. }
  78. const scroll = function (e) {
  79. // console.log('scroll', e)
  80. }
  81. const upper = function (e) {
  82. // console.log('upper', e)
  83. }
  84. const lower = function (e) {
  85. // console.log('lower', e)
  86. }
  87. const pageUp = function (e) {
  88. let idDom = document.getElementById('mainbody')
  89. console.log("===================", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)
  90. let currentScrollPosition = scrollCursorStore.value;
  91. scrollCursorStore.value = scrollCursorStore.value - 400
  92. if (scrollCursorStore.value < 0) {
  93. scrollCursorStore.value = 0;
  94. }
  95. Taro.nextTick(() => {
  96. console.log('scroll start...', idDom.scrollTop)
  97. let scrollInterval = setInterval(() => {
  98. if (
  99. (idDom.scrollTop === scrollCursorStore.value) ||
  100. (idDom.scrollTop < scrollCursorStore.value)
  101. ) {
  102. clearInterval(scrollInterval);
  103. console.log('scroll end...', idDom.scrollTop)
  104. } else {
  105. currentScrollPosition =
  106. currentScrollPosition - 50;
  107. idDom.scrollTop = currentScrollPosition;
  108. console.log('scrolling...', idDom.scrollTop)
  109. }
  110. }, 100)
  111. })
  112. }
  113. const pageDown = function (e) {
  114. let idDom = document.getElementById('mainbody')
  115. console.log("===================", idDom.scrollTop, idDom.clientHeight, idDom.scrollHeight, idDom.offsetHeight)
  116. let currentScrollPosition = scrollCursorStore.value;
  117. scrollCursorStore.value = scrollCursorStore.value + 400
  118. if (scrollCursorStore.value > (idDom.scrollHeight - idDom.offsetHeight )) {
  119. scrollCursorStore.value = idDom.scrollHeight - idDom.offsetHeight;
  120. }
  121. Taro.nextTick(() => {
  122. console.log('scroll start...', idDom.scrollTop)
  123. let scrollInterval = setInterval(() => {
  124. if (
  125. (idDom.scrollTop === scrollCursorStore.value) ||
  126. (idDom.scrollTop > scrollCursorStore.value)
  127. ) {
  128. clearInterval(scrollInterval);
  129. console.log('scroll end...', idDom.scrollTop)
  130. } else {
  131. currentScrollPosition =
  132. currentScrollPosition - (-50);
  133. idDom.scrollTop = currentScrollPosition;
  134. console.log('scrolling...', idDom.scrollTop)
  135. }
  136. }, 100)
  137. })
  138. }
  139. return {
  140. contentTypeit,
  141. scrollId,
  142. lower,
  143. upper,
  144. scroll,
  145. sendMsg,
  146. pageUp,
  147. pageDown,
  148. }
  149. }
  150. }
  151. </script>
  152. <style lang="scss">
  153. .main {
  154. height: 100%;
  155. width: 100%;
  156. background-color: rgba(204, 204, 204, 0.32);
  157. overflow-x: hidden;
  158. overflow-y: auto;
  159. }
  160. .mainbody {
  161. max-width: 100%;
  162. background-size: contain;
  163. padding-bottom: 100px;
  164. }
  165. .info {
  166. display: flex;
  167. margin: 10px 3%;
  168. }
  169. .content-question {
  170. color: #0b4eb4;
  171. background-color: #ffffff;
  172. padding-left: 20px;
  173. }
  174. .content-questionBlock {
  175. align-items: center;
  176. }
  177. .content {
  178. background-color: #fff;
  179. border-radius: 16px;
  180. padding: 20px;
  181. margin-left: 20px;
  182. max-width: 82%;
  183. height: 100%;
  184. font-size: 36px;
  185. font-family: PingFangSC-Medium, PingFang SC;
  186. font-weight: 500;
  187. color: #0a0a27;
  188. line-height: 60px;
  189. word-break: break-all;
  190. }
  191. </style>

        效果调试:

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

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

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

        效果图如下:

建议

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

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

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

参考链接:

        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 文档

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/355532
推荐阅读
相关标签
  

闽ICP备14008679号