需求背景
长表单校验时,需要提示对应问题,并定位到具体位置,提高用户交互体验。
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 方法进行滚动操作
vue 更新数据是异步的,数据变化时,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
Ios 兼容问题
排查、验证问题:
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>
复制代码
② 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>
复制代码
③ 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>
复制代码
④ 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>
复制代码
结论:scroll-view 存在兼容问题,非布局代码因素
锚点不准确问题
Element.getBoundingClientRect()
Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
js 的 onscroll、scrollTop、scrollHeight
- onscroll: 元素的滚动条滚动时触发的事件
- scrollTop: 元素滚动条内的顶部隐藏部分的高度
- scrollHeight: 元素滚动条内的内容高度
锚点计算公式:y = elRect.top - mainRect.top + scrollTop
1. 布局元素定高
2. 布局设置 padding 撑开高度
① margin 会导致内容塌陷,锚点计算不准
② 布局 wrap 设置 padding
更好的方法
// 链接到 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 测试无兼容问题