使用自定义事件Directive实现吸顶/吸低效果(vue3+ts+directive+eventListener)
自定义事件部分:
import type { DirectiveBinding, Directive } from 'vue'
type Position = 'top' | 'bottom'
interface DirectiveBindingValue {
position?: Position; // 定位方式 吸顶/吸底
height?: number; // 自身高度(仅定位前有遮挡,且需要展示完整高度时传递)
clientY?: number; // 偏移距离
}
// 超出视图则固定定位
const affix: Directive = {
mounted(el, binding: DirectiveBinding) {
// 监听的容器
const container = document.getElementsByClassName('view-container')[0]
if (!container) { return }
// 传参(可不传) 例如:v-affix="{position:'top',height:36,clientY:23}"
const bindingValue: DirectiveBindingValue = binding.value ?? {}
const clientY = bindingValue.clientY ?? 0
const height = bindingValue.height ?? 0
const position = bindingValue.position ?? 'top'
// 定位样式
const style = `position: fixed;${position}: ${clientY}px;z-index:3`
// 变化时的方法(默认为吸顶)
let positionFun = () => {
// 获取元素的位置信息
const t = el.getBoundingClientRect()
// 元素超出顶部时,定位
if (t.top < clientY || t.top < (clientY + height)) {
el.setAttribute('style', style)
} else {
el.setAttribute('style', '')
}
}
// 固定方式为吸底时
if (position === 'bottom') {
positionFun = () => {
const t = el.getBoundingClientRect()
const windowHeight = window.innerHeight
// 元素距离底部的距离小于指定偏移高度时,手动定位
if (t.bottom + clientY > windowHeight || t.bottom + clientY + height > windowHeight) {
el.setAttribute('style', style)
} else {
el.setAttribute('style', '')
}
}
}
// 滚动监听
container.addEventListener('scroll', () => {
el.setAttribute('style', '')
positionFun()
})
},
// 销毁前移除事件监听
unmounted() {
// 监听的容器
const container = document.getElementsByClassName('view-container')[0]
if (!container) { return }
container.removeEventListener('scroll', () => { })
}
}
export default affix
入口文件引入:
import affix from '@/directives/affix'
import { createApp } from 'vue'
const app = createApp({})
app.directive('affix', affix)
组件中使用:
<template>
<div class="header"></div>
<div class="view-container">
<!-- 有遮挡的顶部 -->
<div v-affix="{ position: 'top', height: 50, clientY: 50 }" class="menu-bar">吸顶</div>
<div class="content" />
<!-- 无遮挡的底部 -->
<div v-affix="{ position: 'bottom', clientY: 0 }" class="menu-bar">吸底</div>
</div>
</template>
<style scoped>
.header {
width: 100%;
height: 58px;
background: #fff;
}
.view-container {
border: 1px solid;
padding: 100px 0 100px 0;
}
.menu-bar {
width: 100%;
height: 50px;
background: rgb(133, 101, 101);
font-size: 20px;
color: #fff;
}
.content {
width: 100%;
height: 2000px;
}</style>
效果(顶部初始位置,吸顶和吸底效果,低部初始位置):