最近项目中有些业务场景需要用到粘性布局,组件库使用的是element,查看了文档发现element组件库并没有粘性布局的组件,又不能引入其他库,网上找了许多,只发现一篇Vue Affix组件不过使用之后并不能达到我需要的效果,所以做了修改。
思路就是利用css的position:fixed。当元素位置移除对应视窗时则为元素添加position:fixed,回到视窗内时取消fixed。 有几个需要注意的地方
在组件挂载时启动对元素位置的监听,以及scroll滚轮的监听 window.addEventListener("scroll", this.handleScroll, true); // 监听滚动条 window.addEventListener("resize", this.handleScroll, true);// 监听视窗大小每次滚动页面时都会触发指定函数,在指定函数中做对元素fixed状态的处理 2. 在页面关闭时也需要卸载对应的监听
beforeDestroy() { window.removeEventListener("scroll", this.handleScroll); window.removeEventListener("resize", this.handleScroll); },这段代码有一个需要注意的地方,粘性布局应该是默认相对与整个window的定位,在不设置节点得情况下应该是固钉在浏览器顶部。我这边因为省事是直接取得包裹节点得位置,结果就是默认固定在affixFather元素中。 可以自行修改,当前也只是粗糙版本,只能提供吸顶。
<template> <div ref="affixFather" :style="wrapStyle"> <div :class="{'fixed':affixed}" :style="styles"> <slot></slot> </div> </div> </template> <script> export default { props: { offsetTop: { type: Number, default: 0, }, onAffix: { type: Function, default() {}, }, boundary: { type: String, default: "", }, }, data() { return { affixed: false, styles: {}, affixedClientHeight: 0, fatherWidth: "", fatherDomOffsetTop:'', boundaryOffsetTop:'', wrapStyle:{} }; }, methods: { getScroll(w, top) { // 获取元素对比window的距离。 let ret = w[`page${top ? "Y" : "X"}Offset`]; const method = `scroll${top ? "Top" : "Left"}`; if (typeof ret !== "number") { const d = w.document; // ie6,7,8 standard mode ret = d.documentElement[method]; if (typeof ret !== "number") { // quirks mode ret = d.body[method]; } } return ret; }, getOffset(element) { const rect = element.getBoundingClientRect(); const body = document.body; const clientTop = element.clientTop || body.clientTop || 0; const clientLeft = element.clientLeft || body.clientLeft || 0; // const clientHeight = element.clientHeight || 0; const scrollTop = this.getScroll(window, true); const scrollLeft = this.getScroll(window); return { top: rect.bottom + scrollTop - clientTop - this.affixedClientHeight, left: rect.left + scrollLeft - clientLeft, bottom: rect.top, }; }, handleScroll() { var _that = this; const elementOffset = _that.getOffset(this.$el); // const elOffsetTop = _that.$el.getBoundingClientRect().top; // 当前元素至顶部距离 let isAffixTop = _that.fatherDomOffsetTop + _that.offsetTop; // 需要固钉的距离 // 获取动态视窗距离 ,若没绑定特殊节点 if (!_that.boundary) { if (!_that.affixed && elOffsetTop < isAffixTop) { // 当滚动条移动使元素要离开视窗时,固钉状态生效 _that.affixed = true; _that.styles = { // 设置top距离等于初始距离加上传入offset距离 top: `${isAffixTop}px`, left: `${elementOffset.left}px`, width: `${_that.$el.clientWidth}px`, zIndex: 1, }; // 固钉事件触发传递 _that.onAffix(_that.affixed); }else if(_that.affixed && elOffsetTop > isAffixTop){ // 滚动条返回最初距离时 固钉状态取消,若不取消则元素会一直保持在顶部不会返回初始位置 _that.affixed = false _that.styles = {} _that.onAffix(_that.affixed); } } else if (_that.boundary) { // 绑定指定父节点,则高度使用父节点的初始高度 const boundaryOffsetTop = _that.boundaryOffsetTop + _that.offsetTop if (!_that.affixed && elOffsetTop < _that.boundaryOffsetTop) { // 若绑定特殊固钉节点 _that.affixed = true; _that.styles = { top: `${boundaryOffsetTop}px`, left: `${elementOffset.left}px`, width: `${_that.$el.clientWidth}px`, zIndex: 1, }; // 固钉事件触发传递 _that.onAffix(_that.affixed); }else if (_that.affixed && elOffsetTop > _that.boundaryOffsetTop){ _that.affixed = false _that.styles = {} // 固钉事件触发传递 _that.onAffix(_that.affixed); } } }, getWidth() { this.fatherWidth = `${this.$refs.affixFather.clientWidth}px`; }, }, computed: { offsets() { if (this.boundary) { return 0; } return this.offsetTop; }, }, mounted() { this.getWidth(); this.affixedClientHeight = this.$el.children[0].clientHeight; // 挂载时获取自身节点至视窗顶部高度 this.fatherDomOffsetTop = this.$el.parentNode.getBoundingClientRect().top; if(this.boundary){ // 若有指定父节点则获取父节点至视窗顶部高度 this.boundaryOffsetTop = document.getElementById(this.boundary).getBoundingClientRect().top; } window.addEventListener("scroll", this.handleScroll, true); // 监听滚动条 window.addEventListener("resize", this.handleScroll, true); window.onresize = () => { // 监听浏览器视窗大小 return (() => { // 获取动态宽度自适应 this.getWidth(); })(); }; }, beforeDestroy() { window.removeEventListener("scroll", this.handleScroll); window.removeEventListener("resize", this.handleScroll); }, }; </script> <style > .fixed { position: fixed; } </style>