需求效果图
我们先看看对应的需求效果图
完整代码在文末,笔者的github仓库中
给el-table吸顶
给一些dom元素吸顶
实现过程
思路分析
- 因为
css
中的sticky
兼容性不是特别好 - 所以我们使用
fixed
搭配滚动,去动态控制dom元素是否固定定位 - 首先,吸顶效果一般是整页滚动条 【就是以html或body作为根元素滚动条】
- 因为使用的是Vue的框架,所以我们把html和body都设置成定高禁止滚动
- 把滚动的元素更改为
#app
如下:
css
html,
body {
margin: 0;
width: 100vw;
height: 100vh;
background-color: #fff;
}
#app {
color: #363636;
overflow-y: auto;
}
- 所以我们就可以给根元素
#app
绑定滚动事件,并在滚动事件的句柄中,去看看 用于计算距离的dom元素 距离页面视口顶部距离是多少 - 当距离小于等于0的时候,我们就给 用于吸顶固定定位的元素 加上固定定位的相关样式
- 这里要提一下,三个关键词:
1. 用于计算距离的dom元素
2. 用于吸顶固定定位的元素
3. 固定定位的相关样式
图示:用于计算距离的dom元素、用于吸顶固定定位的元素、和样式
- 在#app容器中滚动,我们需要首先看看距离顶部视口的距离,再给吸顶元素添加固定定位的样式
- 如下图:
- 至于固定定位的相关样式,则是可以通过传递一个类名去控制
- 因为有时候,我们除了添加 position: fixed; 之外,还要去添加其他的样式(如本文第二张图,吸顶的时候,要给吸顶元素添加一个阴影和底部的边框)
再加一个层级高度控制,注意吸顶元素的遮挡问题
- 某些情况下,我们需要给吸顶的元素抬高层级
- 否则会挡住一些组件,所以还需要再指定一个层级(有默认值)不让其挡住一些组件
如下效果图:
代码步骤
引入、使用、注册
主入口文件引入:
js
// main.js
import install from './directives' // 引入并使用自定义指令
app.use(install)
directives文件夹
js
// directives文件夹下的index暴露的出口文件
// 引入各个自定义指令
import stick from "./stick/index";
// 自定义指令对象,用于遍历注册
const directives = {
stick,
}
// 批量注册指令并暴露到main.js中去便于注册use
export default {
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key])
})
}
}
编写自定义指令v-stick代码
directives/stick/index.js
js
// 单独拎出来一个css文件,用于维护吸顶元素的样式
import './index.css'
// 计算距离顶部高度的元素,如el-table就是表格最外层容器元素以class为例
let calcDom = null
// 需要吸顶的元素,如el-table需要吸顶的就是表头元素
let stickDom = null
// 指定吸顶固定定位的样式类名
let className = ''
// 指定默认层级,防止层级太高遮挡住别的层
let zIndex = 1000
export default {
mounted(el, binding) {
const { calcDomClass, stickDomClass, fixedName, zInd } = binding.value
if (!calcDomClass || !stickDomClass || !fixedName) throw Error('need >= 3 params')
calcDom = document.querySelector(calcDomClass)
stickDom = document.querySelector(stickDomClass)
className = fixedName
zInd ? zIndex = zInd : null
setFixed()
},
unmounted(el, binding) {
removeFixed()
}
}
// 绑定监听滚动事件
const setFixed = () => {
// 这里以挂载元素#app为滚动容器,body或html也是一个意思
const scrollWrap = document.querySelector('#app')
scrollWrap?.addEventListener('scroll', handle)
}
// 解绑监听滚动事件
const removeFixed = () => {
// 这里以挂载元素#app为滚动容器,body或html也是一个意思
const scrollWrap = document.querySelector('#app')
scrollWrap?.removeEventListener('scroll', handle)
calcDom = null
stickDom = null
className = ''
zIndex = 1000
}
const handle = (e) => {
console.log(147)
let topDistance = calcDom?.getBoundingClientRect().top || 0
// 距离小于等于0,不在可视区域内时,添加固定定位吸顶
if (topDistance <= 0) {
add(stickDom)
}
// 距离大于0,在可视区域内时,取消固定定位吸顶
else {
remove(stickDom)
}
}
const add = (stickDom) => {
if (stickDom) {
if (stickDom.style.position != 'fixed') {
stickDom.classList.add(className)
stickDom.style.zIndex = zIndex // 层级单独处理
}
}
}
const remove = (stickDom) => {
if (stickDom) {
if (stickDom.classList.contains(className)) {
stickDom.classList.remove(className)
stickDom.style.zIndex = 'initial' // 层级单独处理
}
}
}
directives/stick/index.css
css
/* 固定el-table添加的样式 */
.fixedElTable {
position: fixed;
top: 0;
}
/* 固定H1元素添加的样式 */
.fixedH1Dom {
position: fixed;
top: 0;
box-shadow: rgba(113, 100, 100, 0.42) 0px 6px 20px;
border-bottom: 3px solid #79BBFF;
box-sizing: border-box;
}
el-table使用示例
html
<template>
<h2 v-for="i in 10" :key="i">吸顶效果</h2>
<el-table v-stick="{
calcDomClass: '.el-table',
stickDomClass: '.el-table__header-wrapper',
fixedName: 'fixedElTable',
}" :data="tableData" border style="width: 100%" :header-cell-style="{ background: '#999', color: '#000' }">
<el-table-column prop="name" label="姓名" width="180" />
<el-table-column prop="age" label="年龄" width="180" />
<el-table-column prop="home" label="家乡" />
</el-table>
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
interface row {
name: String,
age: Number,
home: String
}
const tableData = ref([] as row[])
const initData = () => {
for (let i = 0; i < 50; i++) {
tableData.value.push({
name: '孙悟空' + i,
age: 500 + i,
home: '花果山' + i
})
}
}
initData()
</script>
普通dom元素使用示例
html
<template>
<h2 v-for="i in 10" :key="i">吸顶效果{{ i }}</h2>
<div class="useForCalc">
<h1 class="useForStick" v-stick="{
calcDomClass: '.useForCalc',
stickDomClass: '.useForStick',
fixedName: 'fixedH1Dom',
zInd: 10000
}">--------吸顶元素-------- <button @click="showLevel">吸顶时点击</button> </h1>
</div>
<h2 v-for="i in 40" :key="i">吸顶效果{{ i }}</h2>
</template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
const showLevel = () => {
ElMessage({
message: 'Congrats, this is a success message.',
type: 'success',
offset: 28
})
}
</script>
<style>
h1 {
width: 480px;
background-color: #999;
display: flex;
}
button {
padding: 6px 12px;
}
</style>
仓库代码地址
完整示例,在笔者的github仓库代码里面(仓库后续会更新一些功能,欢迎不吝star)
可以自行加上lodash的节流做滚动,当然加不加取决于滚动吸顶效果是否炒鸡丝滑