scroll-view踩坑记录

最近在使用uniapp开发一个群聊的小程序,需要加载聊天历史的功能,因此使用scroll-view实现,但这个过程可谓是非常坎坷。

作为一个后端开发,本来就对前端的什么渲染,promise不太懂,所以总是在代码的执行时序上出问题,比如nextTick的使用时机,因此也犯了很多低级错误。

与传统的在底部加载更多不同,聊天历史需要在容器顶部追加数据,这就导致了一个问题,数据加载完成后会跳到容器顶部,我期望的是它能停留在当前位置,解决方案有以下

基本结构
 <scroll-view
      scroll-anchoring
      :scroll-top="amend"
      @scrolltoupper="scrolltoupper"
      :scroll-into-view="tagLocation"
      :upper-threshold="upperThreshold"
      class="scroll-view"
      scroll-y
      id="scroll-view"
    >
      <view class="flex flex-col-reverse" id="chat-content">
         <view v-for="(item, index) in chatRecord" :key="index" :id="`item-${item.id}`">    
         </view>
      </view>
 </scroll-view>

1. 给每个消息标签添加id,当数据渲染完成后,使用scroll-into-view定位,但这会导致页面闪烁,原因是数据渲染后先跳到容器顶部,然后又拉到scroll-into-view标记的id位置,页面渲染了两次。

2. 在请求数据前,记录内容盒子高度,数据渲染后,再查看内容盒子高度,然后两者相减,就是scroll-top的值,但效果和1一样,甚至不如1

3. 滚动容器上下反转,内容上下反转,如果有滚动条,再左右镜像。这种做法相当于传统的底部加载更多功能,数据渲染到容器的底部,数据渲染不会导致当前位置改变,但这种做法也有问题,鼠标滚轮是反向的,对pc用户不友好。

4. 官方给出了使用css overflow-anchor:auto;的方案。一开始使用了,发现不生效,于是乎没注意它,后来又详细了解了overflow-anchor:auto;这个功能,发现它在web端确实有用,既然官方提到了它,说明它在小程序是适用的,肯定是我的用法不对。如下

  • ① 使用overflow-anchor:auto;在完成数据渲染后,不要再使用定位,也就是不要使用scroll-top和scroll-into-view属性,否则又回到了1、2的情况
  • ② 当内容滑到了容器顶部,也就是滑到不能再往上滑了,在完成数据渲染后,会跳到容器顶部,overflow-anchor:auto;不生效,原因未知。
  • ③ 新数据追加到数组的首部无效的话,可以试试将新数据添加到数组的尾部,这样不会改变数组旧数据的索引。然后内容盒子添加display: flex; 和flex-direction: column-reverse; 让数据从下到上展示(当然接口的数据也应该反过来)

通过测试,如果避免以上情况,数据可以做到无感加载,类似于微信加载历史一样。所以要解决的问题是,如何做到内容滑到顶部之前把数据渲染出来,或者如何防止内容滑到顶部。

当你滑动微信记录时,你会发现,微信的滑动被限速了,猜测是为了给加载数据提供时间。但是小程序好像没有能限制滑动速度的方法。此路不通

我们无法避免内容被滑到顶部,特别在小程序里,加载历史是网络请求,时间无法预估。所以应该在数据渲染之前,判断当前是否滑到顶部,如果滑到顶部,向下微调1个像素,这样渲染后就不会跳到容器顶部了,

 //局部代码------------------------------------------------

//data.top表示容器视口顶部位于屏幕的y坐标
const query = uni.createSelectorQuery().in(instance.proxy);
 const all = Promise.all([
      new Promise((resolve) => {
        query 
          .select('#scroll-view')
          .boundingClientRect((data) => {
            resolve(data.top)
          })
          .exec()
      }),
      new Promise((resolve) => {
        query 
          .select('#chat-content')
          .boundingClientRect((data) => {
            resolve(data.top)
          })
          .exec()
      }),
    ])

//滚动容器的top值是恒定不变的,内容盒子的top随着滑动而改变,
//当内容盒子滑到顶部时,二者相等,滚到顶部时overflow-anchor:auto;失效。
//所以在渲染数据前,手动往下滚动1个像素微调一下,
//也就是 :scroll-top = amend 这里的随机数不可去掉,等到微调完毕再渲染数据
 all.then((topArr) => {
        let dif = Math.abs(topArr[0] - topArr[1])
        if (dif == 0) {
          amend.value = Math.random() / 2 + 1
        }
        nextTick(() => {
//微调完毕页面刷新后再更新数据,否则不生效
          update() //向数组中加数据
         
        })
  })

其实这个方案也是不完美的,

        ①如果用户滑到顶部,但不松手,此时容器的top == 内容的top,且无法通过amend修正,也就是 :scroll-top = amend是不生效的,我试图在渲染数据时阻断用户触摸,但没找到对应的api,即使在请求数据时添加蒙层也不行,蒙层只能阻止蒙层触发后的触摸,没法切断蒙层触发之前的触摸。这种情况把触顶阈值设大点一般不会出现,除非接口特别慢。

        ②如果滑的非常快,接口请求也非常快,偶尔也会导致overflow-anchor:auto;失效, 我猜测在判断是否滑到顶部时为false,也就是if (dif == 0)时是false,但渲染时变成true了,因为滑的太快了。这里只能通过限制滑动速度解决,像微信那样,然而我没有找到这种api。退而求其次,就是尽量让一次请求的数据多一些,然后触顶事件的阈值大一些,尽量在滑到顶部之前就渲染完新的数据。也就是upper-threshold设的大点,我的upper-threshold设的动态值,会计算新渲染的数据的高度,然后乘以一个百分数,比如设的0.8,这样在滑动新数据的20%时我就开始请求下一页数据了。

const selectorQuery = (fun) => {
  const query = uni.createSelectorQuery().in(instance.proxy)
  query
    .select('#chat-content')
    .boundingClientRect((data) => {
      fun(data)
    })
    .exec()
}

//触顶事件


let just = 0 //请求数据之前的总高度
const upperThreshold = ref(200) //绑定:upper-threshold
const loadHistory = async () => {
    selectorQuery(async (data) => {
      just = data.height
//网络请求中。。。。。。。
      await queryHistory(......) //这里会把新的数据追加到列表数组里
      nextTick(() => {
        selectorQuery((data) => {
//新数据的高度 = 新数据渲染完后的总高度 - 之前的高度, 再乘以0.8是触发触顶事件的阈值
          let res = (data.height - just) * 0.8
          if (res < 1) {
            res = 1
          }
          upperThreshold.value = res
        })
      })
    })
}

最终实现的效果可以查看微信小程序:  一键财税甩单

<think>好的,我现在需要帮助用户了解uni-app或小程序中scroll-view组件的scroll-into-view属性的使用方法及常见问题。根据用户提供的四个引用内容,我需要整理出结构清晰的回答,并生成相关问题。 首先,我要先理解scroll-into-view的作用。根据引用内容,它用于滚动到指定id的子元素位置。使用方法的话,用户提供的示例代码中,比如在scroll-view标签中设置:scroll-into-view="rightIndex",而子元素需要设置对应的id如:id="right${index}"。这说明需要将scroll-into-view属性绑定到一个变量,该变量对应目标元素的id值。这应该是基本使用步骤的第一点。 然后,常见问题部分。引用里提到了几个问题:比如元素定位不准确,这时候引用2提到可以在外层包裹view,并在顶部添加空的定位view,这样可以让元素的顶部显示出来。这可能是因为scroll-into-view默认将元素顶部对齐容器顶部,但如果有布局问题,导致定位不准,所以需要这种hack方法。 另一个问题是scroll-view高度设置不正确导致无法滚动。引用3指出,scroll-view的高度必须小于子元素总高度才能滚动,否则无法触发滚动。因此需要检查样式,确保scroll-view有固定高度且可以滚动(比如设置scroll-y为true,并且高度足够但不超过内容高度)。 还有动态绑定id的问题,比如引用4提到id必须对应存在的子元素id,如果动态生成时id没有正确生成或绑定,可能导致不生效。同时,引用3中的例子是因为错误地将scroll-view的高度设得和子元素一样高,导致无法滚动,所以需要调整高度。 还有事件冲突的问题,比如@scroll事件可能影响scroll-into-view,可能需要调整触发的时机或使用其他方法。 现在需要将这些整理成步骤,先讲使用方法,再讲常见问题和解决方案。确保使用中文,结构清晰,同时引用对应的引用标记。例如,在讲定位不准的问题时,引用[^2]的方法,添加空的view来定位。 生成的相关问题可能需要包括如何动态绑定scroll-into-view、如何解决滚动失效、与UI框架结合使用的问题等。需要根据常见问题来生成,比如用户可能遇到的滚动不生效的原因,或者如何优化滚动定位的准确性。 最后,要检查LaTeX是否符合要求,但在这个问题中可能用不到数学公式,所以可能不需要处理这点。但需要确保代码块正确,引用正确标注。</think>### scroll-view组件中scroll-into-view属性的使用与常见问题 #### 一、基本使用方法 1. **属性绑定** 在`scroll-view`标签中,通过`:scroll-into-view`绑定目标元素的`id`值,动态控制滚动位置。例如: ```html <scroll-view scroll-y :scroll-into-view="targetId" scroll-with-animation> <view v-for="(item, index) in list" :id="`item${index}`"> {{ item }} </view> </scroll-view> ``` 当`targetId`设置为`item2`时,会自动滚动到第三个子元素位置[^1][^4]。 2. **子元素id格式** 子元素的`id`需为**字符串类型**,且不能以数字开头。推荐使用模板语法生成唯一标识,例如`:id="'section' + index"`[^4]。 3. **动画效果** 添加`scroll-with-animation`属性可使滚动过程更平滑[^2]。 --- #### 二、常见问题与解决方案 1. **滚动失效** - **原因1**:`scroll-view`未设置固定高度或高度超出容器限制。 **解决**:为`scroll-view`设置明确高度(如`height: 500px`),并确保其小于子元素总高度[^3]。 - **原因2**:绑定的`id`值未匹配子元素。 **解决**:检查动态生成的`id`格式,如使用`:id="'prefix' + index"`避免特殊字符。 2. **定位偏移** - **现象**:元素顶部未对齐容器顶部。 - **解决**:在目标元素外层包裹`view`,并添加空定位元素: ```html <view style="position: relative;"> <view :id="`target${index}`" style="position: absolute; top: -80rpx;"></view> <!-- 实际内容 --> </view> ``` 通过空元素调整滚动基准点[^2]。 3. **动态绑定失效** - **现象**:数据更新后滚动未触发。 - **解决**:确保绑定的`id`值在数据渲染完成后更新,可使用`$nextTick`延迟赋值。 4. **与其他组件冲突** - **现象**:与UI框架(如vant-tabs)结合时失效。 - **解决**:直接为子元素设置`id`,避免嵌套组件干扰。例如: ```html <van-tab> <view id="tab-content"></view> </van-tab> ``` --- #### 三、代码示例(优化定位) ```html <scroll-view scroll-y :scroll-into-view="activeId" scroll-with-animation style="height: 600rpx;" > <view v-for="(item, index) in list" :key="index"> <!-- 外层容器 + 空定位元素 --> <view style="position: relative;"> <view :id="`pos${index}`" style="position: absolute; top: -50rpx;"></view> <view class="content">{{ item }}</view> </view> </view> </scroll-view> ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值