1、先看效果
2、以hooks方法处理,方便复制使用,见代码
Good.vue文件
<script setup lang="ts" name="goods">import {onMounted, ref, nextTick} from "vue";import useProductScroll from "@/utils/hooks/useProductScroll.ts";import loading from '@/assets/images/loading.gif'import categoryHolder from './images/category-holder.png'import productHolder from './images/product-holder.png'const productList = ref<any[]>([])const refSide = ref()const refMain = ref()const {mark, setMark, setRefSide, setRefMain} = useProductScroll()onMounted(() => {// 模拟productList的数据const tmpList = Array(15).fill(1).map((v, i) => {return {name: `人气热卖${i}`,pList: Array(5).fill(1).map((v2, i2) => {return {name: `名字${i}-${i2}`}})}})setTimeout(async () => {productList.value = tmpListawait nextTick()setRefSide(refSide.value)setRefMain(refMain.value)setMark(tmpList[0].name)}, 200)})</script><template><div class="product-limit"><ul class="category-box" ref="refSide"><li v-for="v in productList" :key="v" :class="{'active': v.name===mark}" @click="() => setMark(v.name)" :mark="v.name"><div><van-image :loading-icon="loading" :src="categoryHolder" lazy-load /></div><span>{{v.name}}</span></li><li></li></ul><div class='right-content'><div class="search-box"><van-search shape="round" placeholder="请输入商品关键词" /></div><ul class="com-product-list" ref="refMain"><li v-for="v in productList" :key="v" :mark="v.name"><div class="c-category-name">{{v.name}}</div><div class="c-detail-box" v-for="v2 in v.pList" :key="v2"><div class="img-show"><van-image :loading-icon="loading" :src="productHolder" lazy-load /></div><div class="product-tro"><p class="c-name">{{v2.name}}</p><p class="c-cut-money"><span>立省11.00元起</span></p><div class="c-money"><div class="c-money-detail">¥ <i>5.60</i>起 <span>¥19.62</span></div><span class="c-btn-size">选规格</span></div></div></div></li><li class="product-holder"></li></ul></div></div>
</template>
useProductScroll.ts文件
import {ref} from "vue";function getDomStyle(dom: HTMLElement, style: any) {return window.getComputedStyle(dom, null)[style];
}const catchMainMarkDom: {[mark: string]: {dom: HTMLElement,top: number}
} = {}
const catchSideMarkDom: {[mark: string]: {dom: HTMLElement,}
} = {}
const catchMainMarkTop: number[] = []
const sideDomView: {[key: string]: number
} = {}
let timer:any = null
let io:any = nullexport default function useProductScroll() {const mark = ref('')const refSide = ref()const refMain = ref()const setRefSide = (d: HTMLElement) => {if (getDomStyle(d, 'position') === 'static') {d.style.position = 'relative'}refSide.value = d// 判断是否在可视区域io = new IntersectionObserver(entries => {for (let i=0; i<entries.length; i++) {const dom = entries[i].targetconst m = dom.getAttribute('mark')sideDomView[m as string] = entries[i].intersectionRatio}});// 缓存const children: any = d.childrenfor (let i=0; i<children.length; i++) {const dom = children[i]const mName = dom.getAttribute('mark')if (mName) {io.observe(dom);catchSideMarkDom[mName] = {dom: dom,}}}}const setRefMain = (d: HTMLElement) => {if (getDomStyle(d, 'position') === 'static') {d.style.position = 'relative'}refMain.value = d// 缓存const children: any = d.childrenfor (let i=0; i<children.length; i++) {const dom = children[i]const mName = dom.getAttribute('mark')if (mName) {catchMainMarkDom[mName] = {dom: dom,top: dom.offsetTop}catchMainMarkTop.unshift(dom.offsetTop)}}// 绑定refMain.value.addEventListener('scroll', bindMainScroll)}const setMark = (str: string) => {mark.value = strconst dom = catchMainMarkDom[str]['dom']dom.scrollIntoView({behavior: "smooth"});}const bindMainScroll = (e: any) => {clearTimeout(timer)const scrollTop = e.target.scrollToptimer = setTimeout(() => {// 判断top值和scroll比较,获取最近的top值,获取新的mark值let newTop = 0for (let i=0; i<catchMainMarkTop.length; i++) {if (scrollTop >= catchMainMarkTop[i]) {newTop = catchMainMarkTop[i]break;}}let markName = ''// 通过newTop值,获取新的mark名称for (let mName in catchMainMarkDom) {if (catchMainMarkDom[mName]['top'] === newTop) {markName = mNamebreak}}if (mark.value === markName) return;mark.value = markNameconst isView = sideDomView[markName] > 0if (!isView) {catchSideMarkDom[markName]['dom'].scrollIntoView({behavior: "smooth"});}}, 200)}const unbind = () => {refMain.value.removeEventListener('scroll', bindMainScroll)io?.disconnect()io = null}return {mark,setMark,setRefSide,setRefMain,unbind}
}
3、使用,hooks抛出了5个方法,作用分别是:
mark:标示字符,用于判断分类
setMark:当分类点击时,传入mark值
setRefSide:传入dom元素,分类的scroll元素
setRefMain:传入dom元素,商品的scroll元素
unbind:组件卸载时调用
4、使用规则
a、用原生滚动
b、需要在页面渲染后使用
c、依次调用setRefSide,setRefMain,setMark
d、在分类列表和产品列表,scroll元素的子元素,需要绑定mark标示,用于匹配