Vue實現電梯樣式錨點導航效果流程詳解
目錄
- 1、目標效果
- 2、原理
- 3、源代碼
1、目標效果
最近喝了不少的咖啡、奶茶,有一個效果我倒是挺好奇怎么實現的:
(1)點擊左側分類菜單,右側滾動到該分類區域
(2)右側滑動屏幕,左側顯示當前所處的分類區域
這種功能會出現在商城項目中或者分類數量較多的項目中,專業名稱稱電梯導航
目標效果:
(1)點擊左側的分類,右側滑動到指定區域
(2)滾動右側區域,左邊分類顯示當前所處的分類區域
2、原理
(1)這要用到原生js關于偏移量和位置相關的api,這些api建立在你的布局是定位的基礎上,父親相對定位,左邊分類和右邊商品都是絕對定位
(2)左邊分類要與右側商品模塊數量一一相等(數量和位置都要對應相等),否則實現不了電梯導航效果
(3)點擊左側分類,右側跳轉到對應模塊;這用到了window.scrollTo(水平方向距離,豎直方向距離)
(4)右側滑動,左側發生相應的變化,這要用到滾動事件,vue中使用滾動事件需要再onMounted()生命周期注冊一下滾動事件
onMounted(() => { window.addEventListener("scroll", handleScroll);})
(5)如何判斷滾動到什么程度左側才顯示對應的模塊?
- dom.offsetTop:每個dom元素有該屬性,表示距離頂部窗口的距離
- document.documentElement.scrollTop:表示頁面滾動的距離
- document.documentElement.scrollTop >=dom.offsetTop:顯示對應的模塊,可以通過遍歷商品模塊數組,拿到對應的索引,然后設置左邊分類對應的dom為激活狀態
(6) 出現一個問題:window.scrollTo()將模塊滾動至某一位置 與頁面滾動事件 發生了沖突,此時可以添加一個互斥變量isLock,等window.scrollTo()滾動結束之后,再放開鎖
// 獲取選中的dom元素 const typeItemDom = shop.value[val] // 開鎖 isLock.value = true // 第一個參數為水平方向,第二個參數為縱軸方向 window.scrollTo(0, typeItemDom.offsetTop) setTimeout(() => {//關鎖isLock.value = false }, 0)
(7)為什么放開鎖要在setTimeout里面?根據js事件循環機制,同步任務(主線程代碼、new Promise里面的代碼)執行速度快于異步任務(setTimeout、setInterval、ajax、promise.then里面的任務),這樣才能確保鎖是在window.scrollTo() 執行完畢之后才打開的
3、源代碼
App.vue
<template> <div> <ClassifyByVue></ClassifyByVue> </div></template><script setup>// import ClassifyByJs from "./components/ClassifyByJS.vue";import ClassifyByVue from "./components/ClassifyByVue.vue";</script><style>* { padding: 0; margin: 0;}</style>
ClassifyByVue.vue
<template> <div><div> <div :class="{ active: currentIndex == index }" v-for="(item, index) in types"@click="changeType(index)">{{ item }} </div></div><div @scroll="handleScroll"> <div v-for="(item, index) in shops" :key="index" ref="shop"><div>{{ item.category }}</div><div v-for="(i, j) in item.data" :key="j"> <div><img src="/vite.svg" /> </div> <div><div>{{ i.name }}</div><div>{{ i.type }}</div><div>{{ i.desc }}</div><div>購買</div> </div></div> </div></div> </div></template><script setup>import { ref, onMounted, onBeforeUnmount, nextTick } from "vue"let isLock = ref(false)// 分類let types = ref([ "人氣Top", "爆款套餐", "大師咖啡", "小黑杯", "中國茶咖", "生椰家族", "厚乳拿鐵", "絲絨拿鐵", "生酪拿鐵", "經典拿鐵",])// 商品let shops = ref([ {category: "人氣Top",data: [ {name: "冰吸生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "摸魚生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "茉莉花香拿鐵",type: "咖啡",desc: "咖啡" }, {name: "絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "小甘橘美式",type: "咖啡",desc: "咖啡" },] }, {category: "爆款套餐",data: [ {name: "2杯套餐",type: "咖啡",desc: "咖啡" }, {name: "3杯套餐",type: "咖啡",desc: "咖啡" }, {name: "4杯套餐",type: "咖啡",desc: "咖啡" }, {name: "5杯套餐",type: "咖啡",desc: "咖啡" }, {name: "不喝咖啡套餐",type: "咖啡",desc: "咖啡" }, {name: "必喝套餐",type: "咖啡",desc: "咖啡" },] }, {category: "大師咖啡",data: [ {name: "美式",type: "咖啡",desc: "咖啡" }, {name: "加濃美式",type: "咖啡",desc: "咖啡" }, {name: "橙C美式",type: "咖啡",desc: "咖啡" }, {name: "澳瑞白",type: "咖啡",desc: "咖啡" }, {name: "卡布奇諾",type: "咖啡",desc: "咖啡" }, {name: "瑪奇朵",type: "咖啡",desc: "咖啡" },] }, {category: "小黑杯",data: [ {name: "云南小柑橘",type: "咖啡",desc: "咖啡" }, {name: "廣東小柑橘",type: "咖啡",desc: "咖啡" }, {name: "廣西小柑橘",type: "咖啡",desc: "咖啡" }, {name: "福建小柑橘",type: "咖啡",desc: "咖啡" }, {name: "湖南小柑橘",type: "咖啡",desc: "咖啡" }, {name: "江西小柑橘",type: "咖啡",desc: "咖啡" },] }, {category: "中國茶咖",data: [ {name: "碧螺知春拿鐵",type: "咖啡",desc: "咖啡" }, {name: "茉莉花香拿鐵",type: "咖啡",desc: "咖啡" }, {name: "菊花香拿鐵",type: "咖啡",desc: "咖啡" }, {name: "梅花香拿鐵",type: "咖啡",desc: "咖啡" }, {name: "蘭花香拿鐵",type: "咖啡",desc: "咖啡" }, {name: "玫瑰花香拿鐵",type: "咖啡",desc: "咖啡" },] }, {category: "生椰家族",data: [ {name: "冰吸生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "摸魚生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "椰云拿鐵",type: "咖啡",desc: "咖啡" }, {name: "絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "隕石拿鐵",type: "咖啡",desc: "咖啡" },] }, {category: "厚乳拿鐵",data: [ {name: "厚乳拿鐵",type: "咖啡",desc: "咖啡" }, {name: "生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "茉莉花香拿鐵",type: "咖啡",desc: "咖啡" }, {name: "椰云拿鐵",type: "咖啡",desc: "咖啡" }, {name: "絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "海鹽拿鐵",type: "咖啡",desc: "咖啡" },] }, {category: "絲絨拿鐵",data: [ {name: "絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "生椰絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "黑糖絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "椰云絲絨拿鐵",type: "咖啡",desc: "咖啡" }, {name: "香草絲絨拿鐵",type: "咖啡",desc: "咖啡" }] }, {category: "生酪拿鐵",data: [ {name: "生酪拿鐵",type: "咖啡",desc: "咖啡" }, {name: "綠豆拿鐵",type: "咖啡",desc: "咖啡" }, {name: "紅豆拿鐵",type: "咖啡",desc: "咖啡" }, {name: "黑豆拿鐵",type: "咖啡",desc: "咖啡" }, {name: "黃豆拿鐵",type: "咖啡",desc: "咖啡" }] }, {category: "經典拿鐵",data: [ {name: "拿鐵",type: "咖啡",desc: "咖啡" }, {name: "隕石拿鐵",type: "咖啡",desc: "咖啡" }, {name: "焦糖拿鐵",type: "咖啡",desc: "咖啡" }, {name: "生椰拿鐵",type: "咖啡",desc: "咖啡" }, {name: "美式",type: "咖啡",desc: "咖啡" }] },])// 獲取右側商品的ref實例let shop = ref(null)// 用來表示當前選中處于激活狀態的分類的索引let currentIndex = ref(0)// 切換類型const changeType = val => { currentIndex.value = val // 獲取選中的dom元素 const typeItemDom = shop.value[val] // 開鎖 isLock.value = true // 第一個參數為水平方向,第二個參數為縱軸方向 window.scrollTo(0, typeItemDom.offsetTop) setTimeout(() => {//關鎖isLock.value = false }, 0)}// 監聽頁面滾動const handleScroll = () => { // 鎖關了滾動事件才有效 if (!isLock.value) {types.value.forEach((item, index) => { // console.dir(shop.value[index]); const shopItemDom = shop.value[index] // 每個模塊距離頂部的距離 const offsetTop = shopItemDom.offsetTop // 頁面滾動的距離 const scrollTop = document.documentElement.scrollTop if (scrollTop >= offsetTop) {// 給左邊分類設置激活的效果currentIndex.value = index }}) }}onMounted(() => { window.addEventListener("scroll", handleScroll);})onBeforeUnmount(() => { window.removeEventListener("scroll", handleScroll);})</script><style scoped lang="less">.classify { display: flex; position: relative; .left {display: flex;flex-direction: column;align-items: center;position: fixed;left: 0;top: 0;bottom: 0;width: 92px;overflow-y: scroll;border-right: 1px solid #C1C2C4;.item { display: flex; justify-content: center; align-items: center; width: 67px; height: 29px; font-size: 15px; font-family: PingFang SC-Semibold, PingFang SC; font-weight: 600; color: #333333;}.active { color: #00B1FF;}.item:not(:last-child) { margin-bottom: 25px;} } .right {flex: 1;position: absolute;top: 0;right: 17px;overflow-y: scroll;.title { font-size: 18px; margin-bottom: 5px;}.item { display: flex; justify-content: space-between; margin-bottom: 17px; width: 246px; height: 73px; .photo {width: 58px;height: 58px;img { width: 100%; height: 100%; border-radius: 12px; border: 1px solid gray;} } .info {display: flex;flex-direction: column;position: relative;width: 171px;height: 73px;box-shadow: 0px 1px 0px 0px rgba(221, 221, 221, 1);.name { padding-left: 0; font-size: 17px; font-weight: 600; color: #333333;}.type,.desc { font-size: 14px; font-weight: 400; color: #999999;}.buy { display: flex; align-items: center; justify-content: center; position: absolute; right: 0; top: 17px; width: 67px; height: 29px; background: #E7E8EA; border-radius: 21px; font-size: 15px; font-family: PingFang SC-Semibold, PingFang SC; font-weight: 600; color: #05AFFA;} }} }}</style>
到此這篇關于Vue實現電梯樣式錨點導航效果流程詳解的文章就介紹到這了,更多相關Vue電梯錨點導航內容請搜索以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持!
