使用方式:
<footer v-fixed="{
fixedPosition: 'bottom', // bottom浮动吸底、top浮动吸顶
immediate: true, // 初始化页面是否启用浮动
fixedStyle: { // 浮动后节点样式
'border-radius': '5px 5px 0 0',
'background-color': 'rgba(90, 90, 90, 0.4)'
}
}">
<el-button @click="back">返回</el-button>
<el-button type="primary">确定</el-button>
</footer>
源码:
/**
*
* description: dom吸顶、吸底
*
*/
// 获取上个存在滚动条的节点
function getScrollDom(el) {
let dom = el
while (!(dom.scrollHeight > dom.clientHeight) && dom) {
dom = dom.parentElement
}
return dom
}
// 监听节点是否完全处于视口内
class intersectionObserver {
immediate = false
intersectionObserver = null
scrollDom = null
fixedDom = null
constructor(scrollDom, fixedDom, { immediate }, revert, fixed) {
this.scrollDom = scrollDom;
this.fixedDom = fixedDom;
this.immediate = immediate;
this.intersectionObserver = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
if (this.immediate) revert()
this.immediate = true
return
};
if (this.immediate) {
fixed()
}
}, {
root: document,
rootMargin: '0px',
threshold: [0, 1] // 交叉阈值,这里设置为0和1,表示从完全不交叉到完全交叉的整个过程都会触发回调
});
}
observe() {
this.intersectionObserver.observe(this.fixedDom)
}
disconnect() {
this.intersectionObserver.disconnect(this.fixedDom)
}
}
export default {
inserted: (el, binding, vnode) => {
/**
* description: 指令参数说明
* @param fixedPosition bottom下浮动、top上浮动
* @param fixedStyle 浮动后附加样式
* @param immediate 是否页面加载就启用浮动,默认false
* @return {}
*
*/
const { fixedPosition, fixedStyle, immediate } = { fixedPosition: 'bottom', immediate: false, ...binding.value }
const scrollDom = getScrollDom(el)
const markNode = document.createElement('div')
markNode.style.setProperty('transform', `translateY(${el.offsetHeight / 4}px)`)
el.insertAdjacentElement('beforebegin', markNode)
// 记录节点原有样式
const oldStyle = [
'transition',
'zIndex',
'position',
'margin',
'width',
'left',
'right',
[fixedPosition],
...Object.keys(fixedStyle)
].reduce((old, val) => ({
...old,
[val]: el.style.getPropertyPriority(val)
}), {})
const domObserver = new intersectionObserver(scrollDom, markNode, { immediate }, function () {
// 样式还原
Object.entries(oldStyle).forEach(([k, v]) => {
el.style.setProperty(k, v)
})
el.scrollIntoView({ behavior: 'instant', block: 'end' })
}, function () {
const { left, right } = el.getBoundingClientRect()
el.style.setProperty('transition', 'all 300ms ease-out')
el.style.setProperty('z-index', 999)
el.style.setProperty('position', 'fixed')
el.style.setProperty(fixedPosition, '0px')
el.style.setProperty('left', left + 'px')
el.style.setProperty('right', (scrollDom.offsetWidth - right) + 'px')
el.style.setProperty('margin', '0px')
el.style.setProperty('width', 'auto')
// 添加浮动后样式
Object.entries(fixedStyle).forEach(([k, v]) => {
el.style.setProperty(k, v)
})
})
domObserver.observe()
}
}
备注:主要使用技术是IntersectionObserver监听节点是否处于视口,css position:fixed 进行吸底、吸顶浮动。代码有升级空间,可改为不使用fixed浮动,节点固定位置改为可滚动窗口的顶部或底部;