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>