uni-app 导航锚点踩坑

需求背景

长表单校验时,需要提示对应问题,并定位到具体位置,提高用户交互体验。

image.png

scroll-view 导航锚点

1. scroll-view 包裹表单

<template>
  <scroll-view :scroll-y="true" :scroll-into-view="navIndex">
    <!-- 表单 -->
  </scroll-view>
</template>
复制代码

2. 设置锚点

<template>
  <view id="nav1">锚点1</view>
</template>
复制代码

3. 定位到锚点

this.navIndex = "nav1";
复制代码

存在问题

第一次定位后,滚动滚动条,无法定位到锚点位置

猜想可能是无法定位同一锚点位置,并尝试使用 vue 调试渲染问题三件套:

  • key
  • nextTick
  • setTimeout

解决了定位问题,定位前,先把原锚点清除,再重新设置锚点

this.navIndex = null; this.$nextTick(() => { this.navIndex = 'nav1'; });
复制代码

问题分析

为什么重新设置锚点要放在 $nextTick 里?

翻看 uni-app 源码,找到 scroll-view 组件:

# src/core/view/components/scroll-view/index.vue
复制代码

组件内部是通过 watch scrollIntoView 数据变更,执行 _scrollIntoViewChanged 方法进行滚动操作

image.png

vue 更新数据是异步的,数据变化时,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。

Ios 兼容问题

image.png

排查、验证问题:

scroll-view 在设置 scroll-y 允许允许纵向滚动,在 ios 机型下会改变布局,会形成一个新的布局区域,position: fixed 只相对于 scroll-view 区域

设置两个 div 来验证,div1,div2 设置 fixed 定位,宽度 100vw

  • div1,height: 44px,背景蓝色

  • div2, height: 100vh,背景红色

① scroll-view 定高,div1 在外,div2 在内

<template>
  <view class="wrapper">
    <!-- 顶部白色区域 -->
    <view
      style="position: fixed;width: 100vw;height: 44px;z-index: 1;background: blue;"
    ></view>

    <scroll-view style="height: 500px" :scroll-y="true">
      <!-- 红色区域 -->
      <view
        style="position: fixed;width: 100vw;height: 100vh;z-index: 1;background: red;"
      ></view>
    </scroll-view>
  </view>
</template>
复制代码

image.png

② scroll-view 不定高,div1 在外,div2 在内

<template>
  <view class="wrapper">
    <!-- 顶部白色区域 -->
    <view
      style="position: fixed;width: 100vw;height: 44px;z-index: 1;background: blue;"
    ></view>

    <scroll-view :scroll-y="true">
      <!-- 红色区域 -->
      <view
        style="position: fixed;width: 100vw;height: 100vh;z-index: 1;background: red;"
      ></view>
    </scroll-view>
  </view>
</template>
复制代码

image.png

③ scroll-view 定高,div1,div2 都在内。div1 在 dvi2 前

<template>
  <view class="wrapper">
    <scroll-view style="height: 500px" :scroll-y="true">
      <!-- 顶部白色区域 -->
      <view
        style="position: fixed;width: 100vw;height: 44px;z-index: 1;background: blue;"
      ></view>

      <!-- 红色区域 -->
      <view
        style="position: fixed;width: 100vw;height: 100vh;z-index: 1;background: red;"
      ></view>
    </scroll-view>
  </view>
</template>
复制代码

image.png

④ scroll-view 定高,div1,div2 都在内。div1 在 dvi2 后

<template>
  <view class="wrapper">
    <scroll-view style="height: 500px" :scroll-y="true">
      <!-- 红色区域 -->
      <view
        style="position: fixed;width: 100vw;height: 100vh;z-index: 1;background: red;"
      ></view>

      <!-- 顶部白色区域 -->
      <view
        style="position: fixed;width: 100vw;height: 44px;z-index: 1;background: blue;"
      ></view>
    </scroll-view>
  </view>
</template>
复制代码

image.png

结论:scroll-view 存在兼容问题,非布局代码因素

锚点不准确问题

image.png

Element.getBoundingClientRect()

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。

js 的 onscroll、scrollTop、scrollHeight

  • onscroll: 元素的滚动条滚动时触发的事件
  • scrollTop: 元素滚动条内的顶部隐藏部分的高度
  • scrollHeight: 元素滚动条内的内容高度

锚点计算公式:y = elRect.top - mainRect.top + scrollTop

1. 布局元素定高

2. 布局设置 padding 撑开高度

① margin 会导致内容塌陷,锚点计算不准

image.png

② 布局 wrap 设置 padding

image.png

更好的方法

uni-app 节点信息

//  链接到 id 位置
//  @param{\*}inside 目标元素
//  @param{_}outside 滚动元素
exportconstlinkToId = (inside, outside) => {
  uni
    .createSelectorQuery()
    .select(`${inside}`)
    .boundingClientRect((data) => {
      uni
        .createSelectorQuery()
        .select(`${outside}`)
        .boundingClientRect((res) => {
          uni.pageScrollTo({
            duration: 99, // 过渡时间
            scrollTop: data.top - res.top,
          });
        })
        .exec();
    })
    .exec();
};
复制代码

优点:兼容好,使用简单,安卓、ios、h5 测试无兼容问题

猜你喜欢

转载自juejin.im/post/7055591552474677256