让界面在滚动过程中实现滚动到哪一栏,上面的导航栏随动效果,
1.第一步搭建tabcontrol栏,封装到components里
<template>
<div class="tab-control">
<template v-for="(item, index) in titles" :key="item">
<div class="tab-control-item"
:class="{ active: index === currentIndex }"
@click="itemClick(index)"
>
<span>{{ item }}</span>
</div>
</template>
</div>
</template>
<script>
export default {
props: {
titles: {
type: Array,
default: () => []
}
},
data() {
return {
currentIndex: 0
}
},
emits: ["tabItemClick"],
methods: {
itemClick(index) {
this.currentIndex = index
this.$emit("tabItemClick", index)
}
}
}
</script>
利用optionApi里的props、data、emits、methods,接收掉用自身的detail组件的数据,也就是上面的六个大标题,
再把点击事件绑定到tabItemClick,emit出去,detail接收到就可以在自己的界面进行滚动处理
2.在detail组件构建
首先,搭建细节界面,detail中建议cpns文件夹,存放各个模块的组件,全部抽取封装,降低耦合度
对应的,引用8个组件
<div class="main" v-if="mainPart" v-memo="[mainPart]">
<detail-swipe :swipe-data="mainPart.topModule.housePicture.housePics"/>
<detail-infos name="描述" :ref="getSectionRef" :top-infos="mainPart.topModule"/>
<detail-facility name="设施" :ref="getSectionRef" :house-facility="mainPart.dynamicModule.facilityModule.houseFacility"/>
<detail-landlord name="房东" :ref="getSectionRef" :landlord="mainPart.dynamicModule.landlordModule"/>
<detail-comment name="评论" :ref="getSectionRef" :comment="mainPart.dynamicModule.commentModule"/>
<detail-notice name="须知" :ref="getSectionRef" :order-rules="mainPart.dynamicModule.rulesModule.orderRules"/>
<detail-map name="周边" :ref="getSectionRef" :position="mainPart.dynamicModule.positionModule"/>
<detail-intro :price-intro="mainPart.introductionModule"/>
</div>
当然,对于这几个类似的组件,都是一个标题,一个内容块,一个尾部,也可也抽取封装到components中,实现复用
<template>
<div class="section">
<div class="header">
<h2 class="title">{{ title }}</h2>
</div>
<div class="content">
<slot>
<h3>我是默认内容</h3>
</slot>
</div>
<div class="footer" v-if="moreText.length">
<!-- 鸡巴毛 -->
<span class="more">{{ moreText }}</span>
<span class="more">附加内容:{{ addUpInfo }}</span>
<van-icon name="arrow" />
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: "我就是默认标题"
},
moreText: {
type: String,
default: ""
},
addUpInfo: {
type: String,
default: ""
}
})
</script>
3.封装hook,改为可以监听$el元素的通用滚动监听钩子
根据hook钩子中封装过的现成useScrool函数,监听界面滚动效果,这里笔者给出了此前做出的3中不同的版本,分别可以监听window和el元素:
export default function useScroll(elRef) {
const isReachBottom = ref(false)
const clientHeight = ref(0)
const scrollTop = ref(0)
const scrollHeight = ref(0)
#smallCoderWhite1 初始自己实现版本
防抖节流
const scrollListenerHandler = () => {
clientHeight.value = document.documentElement.clientHeight
scrollTop.value = document.documentElement.scrollTop
scrollHeight.value = document.documentElement.scrollHeight
// console.log("1:", clientH, "2:", scrollT, "sum:", (clientH + scrollT), "3:", scrollH)
if (clientHeight + scrollTop >= scrollHeight) {
// console.log("到头了,load!!")
// homeStore.fetchHouseListData()
// callBackFn()
// 2第二种方案
console.log("到头了,要重新获取数据!第二种方法!")
isReachBottom.value = true;
}
}
const scrollListenerHandler = throttle(() => {
clientHeight.value = document.documentElement.clientHeight
scrollTop.value = document.documentElement.scrollTop
scrollHeight.value = document.documentElement.scrollHeight
// console.log("1:", clientH, "2:", scrollT, "sum:", (clientH + scrollT), "3:", scrollH)
console.log("监听到滚动了!!")
if (clientHeight.value+ scrollTop.value >= scrollHeight.value) {
// console.log("到头了,load!!")
// callBackFn()
// 2第二种方案
console.log("滚到到位置地下了,要重新获取数据!第二种方法!")
isReachBottom.value = true;
}
},1000)
#smallCoderWhite2,
未加入对元素的通用功能前:
const scrollListenerHandler = throttle(() => {
clientHeight.value = document.documentElement.clientHeight
scrollTop.value = document.documentElement.scrollTop
scrollHeight.value = document.documentElement.scrollHeight
console.log("1:", clientHeight.value, "2:", scrollTop.value,"sum:",clientHeight.value+scrollTop.value, "----3:", scrollHeight.value)
if (clientHeight.value + scrollTop.value >= scrollHeight.value-1) {
console.log("滚动到底部了")
isReachBottom.value = true
}
}, 10)
onMounted(() => {
window.addEventListener("scroll", scrollListenerHandler)
})
onUnmounted(() => {
window.removeEventListener("scroll", scrollListenerHandler)
})
#smallCoderwhite3 :
加入了对元素的通用监听功能之后,
let el = window
const scrollListenerHandler = throttle(() => {
if (el === window) {
clientHeight.value = document.documentElement.clientHeight
scrollTop.value = document.documentElement.scrollTop
scrollHeight.value = document.documentElement.scrollHeight
} else {
clientHeight.value = el.clientHeight
scrollTop.value = el.scrollTop
scrollHeight.value = el.scrollHeight
}
if (clientHeight.value + scrollTop.value >= scrollHeight.value) {
console.log("滚动到底部了")
isReachBottom.value = true
}
}, 50)
onMounted(() => {
if (elRef) el = elRef.value
el.addEventListener("scroll", scrollListenerHandler)
})
onUnmounted(() => {
el.removeEventListener("scroll", scrollListenerHandler)
})
return { isReachBottom, clientHeight, scrollTop, scrollHeight }
}
可以看到 ,主要加入了对el的判断、onMoutnted生命周期钩子的判断类型,大体思路不变
首先,口可以控制滚动值大于300,就让它显示出来,
// tabControl相关的操作
const detailRef = ref()
const { scrollTop } = useScroll(detailRef)
const showTabControl = computed(() => {
return scrollTop.value >= 300
})
随后对getSectionRef中取到的那个数值,
const getSectionRef = (value) => {
//拿到value那个元素的基于name的属性名的值,就是基属性名取得属性名的值
const name = value.$el.getAttribute("name")
//把元素取到,然后赋给当前这个元素这个名字的那个数字的元素
sectionEls.value[name] = value.$el
}
const tabClick = (index) => {
//sectionELs.value可能看不懂,其实就是响应式的值本身!!!,响应式数据本身通过第index个渠道这个元素
const key = Object.keys(sectionEls.value)[index]
//通过这个key就是这个元素,在sectionEls中拿到这个元素本身
const el = sectionEls.value[key]
let instance = el.offsetTop
if (index !== 0) {
instance = instance - 44
}
detailRef.value.scrollTo({
top: instance,
behavior: "smooth"
})
}
再根据tabclick获取当前的元素,并让界面滚动到应该滚动的位置
const getSectionRef = (value) => {
const name = value.$el.getAttribute("name")
sectionEls.value[name] = value.$el
}
const tabClick = (index) => {
const key = Object.keys(sectionEls.value)[index]
const el = sectionEls.value[key]
let instance = el.offsetTop
if (index !== 0) {
instance = instance - 44
}
detailRef.value.scrollTo({
top: instance,
behavior: "smooth"
})
}
这里完整的源码放出来:
<template>
<div class="detail top-page" ref="detailRef">
<tab-control
v-if="showTabControl"
class="tabs"
:titles="names"
@tabItemClick="tabClick"
/>
<van-nav-bar
title="房屋详情"
left-text="旅途"
left-arrow
@click-left="onClickLeft"
/>
<div class="main" v-if="mainPart" v-memo="[mainPart]">
<detail-swipe :swipe-data="mainPart.topModule.housePicture.housePics"/>
<detail-infos name="描述" :ref="getSectionRef" :top-infos="mainPart.topModule"/>
<detail-facility name="设施" :ref="getSectionRef" :house-facility="mainPart.dynamicModule.facilityModule.houseFacility"/>
<detail-landlord name="房东" :ref="getSectionRef" :landlord="mainPart.dynamicModule.landlordModule"/>
<detail-comment name="评论" :ref="getSectionRef" :comment="mainPart.dynamicModule.commentModule"/>
<detail-notice name="须知" :ref="getSectionRef" :order-rules="mainPart.dynamicModule.rulesModule.orderRules"/>
<detail-map name="周边" :ref="getSectionRef" :position="mainPart.dynamicModule.positionModule"/>
<detail-intro :price-intro="mainPart.introductionModule"/>
</div>
<div class="footer">
<img src="@/assets/img/detail/icon_ensure.png" alt="">
<div class="text">弘源旅途, 永无止境!</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getDetailInfos } from "@/services"
import TabControl from "@/components/tab-control/tab-control.vue"
import DetailSwipe from "./cpns/detail_01-swipe.vue"
import DetailInfos from "./cpns/detail_02-infos.vue"
import DetailFacility from "./cpns/detail_03-facility.vue"
import DetailLandlord from "./cpns/detail_04-landlord.vue"
import DetailComment from "./cpns/detail_05-comment.vue"
import DetailNotice from "./cpns/detail_06-notice.vue"
import DetailMap from "./cpns/detail_07-map.vue"
import DetailIntro from "./cpns/detail_08-intro.vue"
import useScroll from '@/hooks/useScroll'
const router = useRouter()
const route = useRoute()
const houseId = route.params.id
// 发送网络请求获取数据
const detailInfos = ref({})
const mainPart = computed(() => detailInfos.value.mainPart)
getDetailInfos(houseId).then(res => {
detailInfos.value = res.data
})
// 监听返回按钮的点击
const onClickLeft = () => {
router.back()
}
// tabControl相关的操作
const detailRef = ref()
const { scrollTop } = useScroll(detailRef)
const showTabControl = computed(() => {
return scrollTop.value >= 300
})
// const landlordRef = ref()
// const sectionEls = []
// const getSectionRef = (value) => {
// sectionEls.push(value.$el)
// }
const sectionEls = ref({})
const names = computed(() => {
return Object.keys(sectionEls.value)
})
const getSectionRef = (value) => {
const name = value.$el.getAttribute("name")
sectionEls.value[name] = value.$el
}
const tabClick = (index) => {
const key = Object.keys(sectionEls.value)[index]
const el = sectionEls.value[key]
let instance = el.offsetTop
if (index !== 0) {
instance = instance - 44
}
detailRef.value.scrollTo({
top: instance,
behavior: "smooth"
})
}
</script>
<style lang="less" scoped>
.tabs {
position: fixed;
z-index: 9;
left: 0;
right: 0;
top: 0;
}
.footer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 120px;
img {
width: 123px;
}
.text {
margin-top: 12px;
font-size: 12px;
color: #7688a7;
}
}
</style>
4.summary
最麻烦的js逻辑段落就是tabcontrol滚动控制,在结合取到的元素让界面滚动到该区的位置,Object.keys操作和各种酷炫的操作确实比较费时间,比较绕