1. 防抖类Animate, 使用requestAnimationFrame代替setTimeout
也可以使用节流函数, lodash有现成的防抖和节流方法
_.debounce防抖
_.throttle节流
export default class Animate {
constructor() {
this.timer = null;
}
start = (fn) => {
if (!fn) {
throw new Error('需要执行函数');
}
if (this.timer) {
this.stop();
}
this.timer = requestAnimationFrame(fn);
};
stop = () => {
if (!this.timer) {
return;
}
cancelAnimationFrame(this.timer);
this.timer = null;
};
}
2. 使用animate封装滚动方法
const animate = new Animate();
throttleScroll = e => animate.start(() => handleScroll(e));
3. 着手写滚动函数handleScroll
//滚动的函数
function handleScroll(e) {
const scrollingElement = e.target.scrollingElement;
const headerOffsetTop = header1.value.offsetTop; //header的高度
const headerOffsetHeight = header1.value.offsetHeight;
const scrollTop = scrollingElement.scrollTop;
// 如果滚动元素的scrollTop比header元素的高度+offsetTop还大, 就让nav部分悬停在顶部!!!
if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
isFixed.value = true;
} else {
isFixed.value = false;
}
}
nav的定位何时设置为fixed的原理:
当滚动后, header元素因为向上滚动,或者向下滚动而消失不见时, 就让nav悬停在顶部, 反之就让nav元素正常显示在header元素下面
实践可知:
1) 滚动元素的scrollTop === header元素的offsetTop + header元素的offsetHeight时, header元素开始消失在视野;
2) scrollTop > header元素的offsetTop + header元素的offsetHeight时, header元素继续消失在视野;
3) 反之header就是可见到它的一部分就是它的全部都在视野范围中
4. 在onMounted钩子监听scroll事件(因为dom已经渲染完成)
onMounted(() => {
//写在掉接口的里面的
nextTick(() => {
window.addEventListener('scroll', throttleScroll, false);
});
// 这里使用监听的scroll的事件,使用了防抖函数封装;
throttleScroll = e => animate.start(() => handleScroll(e));
});
5. 组件将要销毁或者将要离开此组件时解除scroll事件绑定
onBeforeUnmount(() => { // 页面即将销毁取消事件监听(相当于vue2的beforeDestroy)
//离开页面需要remove这个监听器,不然还是卡到爆。
window.removeEventListener('scroll', throttleScroll);
});
<template>
<div class="fixed-top-container">
<header class="header" :ref="header1">头部</header>
<nav class="fixed-top-nav" :ref="nav1" :class="{ isFixed }">
<div class="box" v-for="(item, index) in navData" :key="index">
{{ item.title }}
</div>
</nav>
<ul class="fixed-top-list">
<li class="list-item" v-for="(item, index) in listData" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, onDeactivated, nextTick, Ref } from 'vue'
import _ from 'lodash';
import Animate from '../../utils/animate'
const navData = reactive([
{ title: 'nav1', id: 1 },
{ title: 'nav2', id: 2 },
{ title: 'nav3', id: 3 },
{ title: 'nav4', id: 4 },
]);
const listData = reactive(Array.from({ length: 50 }, (_, i) => i + 1));
const isFixed = ref(false); //是否固定的
let throttleScroll: any = null; //定义一个截流函数的变量
const header1 = ref('header1') as Ref;
const nav1 = ref('nav1') as Ref;
const animate = new Animate()
//滚动的函数
function handleScroll(e) {
const scrollingElement = e.target.scrollingElement;
const headerOffsetTop = header1.value.offsetTop; //header的高度
const headerOffsetHeight = header1.value.offsetHeight;
const scrollTop = scrollingElement.scrollTop;
// 如果滚动元素的scrollTop比header元素的高度+offsetTop还大, 就让nav部分悬停在顶部!!!
if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
isFixed.value = true;
} else {
isFixed.value = false;
}
}
onMounted(() => {
//写在掉接口的里面的
nextTick(() => {
window.addEventListener('scroll', throttleScroll, false);
});
// 这里使用监听的scroll的事件,使用了防抖函数封装;
throttleScroll = e => animate.start(() => handleScroll(e));
});
onBeforeUnmount(() => { // 页面即将销毁取消事件监听
//离开页面需要remove这个监听器,不然还是卡到爆。
window.removeEventListener('scroll', throttleScroll);
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.fixed-top-container {
height: 100vh;
& .header {
height: 200px;
width: 100%;
background-color: #f40;
}
& .fixed-top-nav {
display: flex;
width: 100%;
background-color: #f90;
&.isFixed {
position: fixed;
left: 0;
top: 0;
z-index: 999;
}
& .box {
font-size: 14px;
height: 30px;
line-height: 30px;
color: #333;
flex: 1 1 0%;
}
}
& .fixed-top-list {
list-style: none;
& .list-item {
width: 100%;
height: 40px;
line-height: 40px;
font-size: 16px;
border-bottom: 1px solid #333;
background-color: #fff;
}
}
}
</style>