所有代码
<!--
吸顶组件
用法: <StickTop>
<div>李白</div>
</StickTop>
<StickTop :offsetTop="30">
<div>李白</div>
</StickTop>
属性
offsetTop Number类型 距离顶部的距离(px) 默认为0
-->
<template>
<div>
<div ref="point" style="z-index: 2;" :style="fixedStyle">
<slot></slot>
</div>
<div v-show="affix" :style="placeholderStyle"></div>
</div>
</template>
<script>
/*
在滚动过程中计算获取元素在没有fixed定位的时候的距离顶部的距离 (iview是这么做的)
*/
function getOffset(element) {
const rect = element.getBoundingClientRect()
// 这两行代码不兼容IE (兼容IE的方法是 document.documentElement.scrollTop / scrollLeft)
const scrollTop = window.pageYOffset
const scrollLeft = window.pageXOffset
/*
相加后的结果是 初始位置 在滚动过程中他将是一个固定值
*/
return {
top: rect.top + scrollTop,
left: rect.left + scrollLeft
}
}
export default {
props: {
offsetTop: {
type: Number,
default: 0
}
},
data() {
return {
affix: false, // 吸顶状态
fixedStyle: {}, // 吸顶元素的样式
placeholderStyle: {} // 占位容器
}
},
methods: {
handleScroll() {
// getOffset方法是从iview抄来的
// https://gitee.com/view-design/ViewUI/blob/master/src/components/affix/affix.vue
let rect = getOffset(this.$el) // 根元素距离顶部和左侧的距离 主要是想获取初始值
if (rect.top - this.offsetTop < window.pageYOffset) {
this.fixedStyle = {
position: 'fixed',
top: `${this.offsetTop}px`,
width: `${this.$el.offsetWidth}px`
}
this.placeholderStyle = { height: this.$refs.point.clientHeight + 'px' }
this.affix = true
} else {
this.fixedStyle = null
this.placeholderStyle = {}
this.affix = false
}
}
},
mounted() {
addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
removeEventListener('scroll', this.handleScroll)
}
}
</script>
最初的想法
模板篇
- 组件内部有个插槽,插槽里的内容就是需要吸顶的元素
- 组件内还应有个占位的容器,容器高度为吸顶的元素的高度
思想篇
没有思想的时候创造思想, 直接打开已有的效果查看,
分别查看了 iview 和 antd
iview
antd
一个叫'图钉',一个叫 '固钉',, 还是固钉比较贴切(请忽略我起的名字............)
- 在滚动浏览器滚动条的时候通过F12检查元素发现,当目标元素(希望固定的元素)到达期望位置(距离顶部的距离)时,会向目标元素的父元素加上
fixed
定位 - 在滚动过程中控制
slot
的父元素的css属性即可,即 组件内的data中的fixedStyle
, - 顺便定义一下吸顶状态
affix
(变量命名参考了 antd 和 iview ) placeholderStyle
控制当元素吸顶后占位容器的样式
开始实现
监听滚动
参考了 iview 的源码, 直接在 mounted
中监听 scroll
事件, 在 beforeDestroy
中取消监听
期初我在 mounted
中获取元素的初始位置,在自己测试的时候发现,当已经处于 fixed
定位的时候,刷新浏览器,会产生奇怪的 bug (就不描述了),虽然经过一番测试后,在 mounted
中监听事件执行前加上了 window.scrollTop({ top: 0 })
, 能够解决这个问题,但是解决方式太诡异了,本人受不了...
最后还是参考源码,在滚动过程中实时获取元素位置 就是 getOffset
函数。element.getBoundingClientRect()
函数可以获取元素的宽高距离等属性,下图
顺便提一下--来自MDN: 如果是标准盒子模型,元素的尺寸等于width/height + padding + border-width的总和。如果box-sizing: border-box,元素的的尺寸等于 width/height。
这里以使用 top
举例, getOffset()
函数就干了一件事,获取目标元素在浏览器滚动过程中距离文档顶部的距离(不是浏览器顶部), 再说一遍 目标元素距离文档顶部的位置
接下来一切的逻辑都在 handleScroll
函数里,(函数命名也参考了源码)
通过比较目标元素的目标位置与浏览器滚动条滚动过的距离,设置样式即可
彩蛋
最开始的时候没有使用 addEventListener
,而使用了window.onscroll = () => {}
导致同时使用两次的时候,由于 window.onscroll
被复写而出现 bug , 后来我终于步入了正轨......