最近天气好热,热的懒得打字,中午吃点西瓜补补脑(bushi)🔥🔥
好了回到正题,今天说一下电梯导航。这个场景不复杂,我这次说一个比较简单的实现方法。
正题开始
什么是电梯导航?
坐过电梯的小伙伴都知道,电梯内的按钮对应不同楼层,点击相应按钮就可以到达相应楼层,我们要实现的组件也是这个原理,其实很多App都有类似场景,最典型的就是通讯录列表了,我们做的跟这个也就类似,不过今天我们主要实现pc端里的电梯导航。
布局
电梯导航通常是由内容区和导航区组成,导航区是相对屏幕固定的,即fixed布局。
页面结构代码如下
<div id="elavatorContainer">
<div
v-for="item in list"
:key="item.id"
:data-key="item.id"
:class="['elavatorItem']"
>
<p>{{ item.value }}</p>
</div>
<div id="navigation">
<p
v-for="item in list"
:key="item.id"
class="navigationItem"
@click="scrollTo(item.id)"
>
{{ item.value }}
</p>
</div>
</div>
CSS代码如下
#elavatorContainer {
height: 100vh;
overflow: auto;
background: #eee;
.elavatorItem {
height: 800px;
margin: 20px 80px 20px 20px;
overflow: auto;
background: #cfcfcf;
display: flex;
justify-content: center;
align-items: center;
}
#navigation {
position: fixed;
right: 25px;
top: 50%;
transform: translateY(-50%);
background: #d8d8d8;
padding: 5px;
border-radius: 10px;
.navigationItem {
cursor: pointer;
transition: all 0.2s ease-in-out;
border-radius: 5px;
&:hover {
background: #fff;
}
}
}
}
这里就是基础的布局方案,不再过多赘述。
手动导航定位
我们需要实现点击对应导航按钮,滚动条自动滚动到对应的楼层,就像是坐电梯按对应楼层的场景~
function scrollTo(id) {
const el = document.querySelector(`.elavatorItem:nth-child(${id})`);
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
4行代码搞定,看起来是是不是很简单?我们只需要拿到对应的楼层id,然后通过id可以获取到对应楼层的元素引用,然后通过el.scrollIntoView方法就可以自动定位到对应楼层了。
behavior:滚动类型(smooth平滑滚动)
block:定位位置(center居中展示)
这是我们手动定位的操作场景,那如果是直接操作滚动条呢?我们如何知道当前楼层对应的按钮索引呢?(怎么实现楼层按钮高亮)
自动感应楼层变化
一般对于感知dom变化,我们最好可以先想想浏览器原生的一些API(MutationObserver和IntersectionObserver)是否可以实现,我认为这些API的功能还是很强大的,可以少写很多js代码。
既然是感应,那一定有一个感应标识,这里我们可以根据楼层元素和楼元素的交叉情况来判定。因为交叉意味着显隐状态发生了变化,拿到这个关键信息,也就达到我们最初的目的了。
直接上代码
function createInsectionOberser() {
const observer = new IntersectionObserver(
(entires) => {
entires.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.dataset.key;
const navigationItem = document.querySelector(
`.navigationItem:nth-of-type(${id})`,
);
navigationItems.value.map((item) => {
item.style.background = '#d8d8d8';
});
navigationItem.style.background = '#fff';
}
});
},
{
threshold: 1,
root: document.querySelector('#elavatorContainer'),
},
);
const items = Array.from(document.querySelectorAll('.elavatorItem'));
items.map((item) => observer.observe(item));
}
我们获取所有的楼层元素,并为每一个楼层元素都添加上一个观察器。当楼层和父元素发生交叉的时候,取出当前楼层属性上绑定的key作为楼层标识,注意这里的key要和电梯按钮列表上绑定的key要一致,因为我们要通过楼层的key找到该层对应的电梯按钮的key。
在修改电梯按钮高亮样式前先全部初始化之前的样式样式,保证每一次的更新只有一个楼层按钮被高亮。
做完这些后,电梯导航也就实现完成了,最后别忘了在组件加载完毕时将观察方法注册上去哦~
onMounted(() => {
navigationItems.value = Array.from(
document.querySelectorAll('.navigationItem'),
);
createInsectionOberser();
});
好了,这就是电梯导航的全部内容,是不是很简单?只要能灵活运用浏览器的观察器API,可以实现很多看起来很复杂的前端场景(其实一点也不复杂)
附上完整代码
<template>
<div id="elavatorContainer">
<div
v-for="item in list"
:key="item.id"
:data-key="item.id"
:class="['elavatorItem']"
>
<p>{{ item.value }}</p>
</div>
<div id="navigation">
<p
v-for="item in list"
:key="item.id"
class="navigationItem"
@click="scrollTo(item.id)"
>
{{ item.value }}
</p>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const list = ref(
Array.from({ length: 10 }, (_, i) => ({
id: i + 1,
value: `第${i + 1}层`,
})),
);
const navigationItems = ref([]);
function scrollTo(id) {
const el = document.querySelector(`.elavatorItem:nth-child(${id})`);
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
function createInsectionOberser() {
const observer = new IntersectionObserver(
(entires) => {
entires.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.dataset.key;
const navigationItem = document.querySelector(`.navigationItem:nth-of-type(${id})`);
navigationItems.value.map((item) => {
item.style.background = '#d8d8d8';
});
navigationItem.style.background = '#fff';
}
});
},
{
threshold: 1,
root: document.querySelector('#elavatorContainer'),
},
);
const items = Array.from(document.querySelectorAll('.elavatorItem'));
items.map((item) => observer.observe(item));
}
onMounted(() => {
navigationItems.value = Array.from(
document.querySelectorAll('.navigationItem'),
);
createInsectionOberser();
});
</script>
<style lang="scss" scoped>
#elavatorContainer {
height: 100vh;
overflow: auto;
background: #eee;
.elavatorItem {
height: 800px;
margin: 20px 80px 20px 20px;
overflow: auto;
background: #cfcfcf;
display: flex;
justify-content: center;
align-items: center;
}
#navigation {
position: fixed;
right: 25px;
top: 50%;
transform: translateY(-50%);
background: #d8d8d8;
padding: 5px;
border-radius: 10px;
.navigationItem {
cursor: pointer;
transition: all 0.2s ease-in-out;
border-radius: 5px;
&:hover {
background: #fff;
}
}
}
}
</style>
就用了99行代码,非标题党哈🤣