22技术分享-vue3实战难点之tabControl组件滚动控制

本文介绍了如何在Vue项目中使用tabcontrol实现滚动时导航栏随动效果,通过组件封装和滚动监听功能,当用户滚动到某一栏目时,对应的导航栏会动态切换。同时,详细展示了如何在detail组件中复用和处理滚动事件,确保页面内容流畅切换。
摘要由CSDN通过智能技术生成

 让界面在滚动过程中实现滚动到哪一栏,上面的导航栏随动效果,


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操作和各种酷炫的操作确实比较费时间,比较绕

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值