自定义滚动条组件(VUE)
前言
实际开发的业务有这样的场景:需要对浏览器滚动条进行定制化的更改,比如滚动条的颜色、样式等。
谷歌浏览器中提供::-webkit-scrollbar属性可对滚动条样式进行简单的更改。如果需要进行更复杂的,例如滚动条宽度等的更改,就需要模拟浏览器的滚动条自己封装定制组件。
废话不多说直接上代码
HTML部分
<!-- showScroll 控制滚动条显示与否 -->
<div class="scrollBox-work" v-if="showScroll">
<!-- scrollBox-track-work 定义轨道 -->
<div class="scrollBox-track-work">
<!-- scrollBox-thumb-work 定义滑轮 -->
<div class="scrollBox-thumb-work"></div>
</div>
</div>
js部分
export default {
name: "ScrollBar",
props: {
targetEle: {
type: HTMLDivElement,
},
},
data() {
return {
// 注销事件对象
removeEvent: null,
showScroll: false,
// ref方式调用时保存的滚动元素dom
refTargetEle: null
};
},
mounted() {
// 页面尺寸改变时,需要重新获取滚动
window.addEventListener("resize", this.resizeFun);
},
beforeDestroy() {
// 组件注销时,移除监听事件
this.removeEvent ? this.removeEvent() : null;
window.removeEventListener("resize", this.resizeFun);
},
methods: {
// 主逻辑部分
addScrollEvent(targetEle = this.targetEle) {
if (!this.targetEle) this.refTargetEle = targetEle
if (targetEle) {
const targetEleWidth = targetEle.clientWidth;
const scrollWidth = targetEle.scrollWidth;
this.showScroll = targetEleWidth >= scrollWidth ? false : true;
this.$nextTick(function () {
if (this.showScroll) {
const scrollEle = document.getElementsByClassName("scrollBox-thumb-work")[0];
const scrollTrackEle = document.getElementsByClassName(
"scrollBox-track-work"
)[0];
// 轨道宽度
const trackWidth = scrollTrackEle.clientWidth;
// 滑轨宽度
const thumbWidth = scrollEle.clientWidth;
const defaultScrollLeft = scrollWidth - targetEleWidth;
// 鼠标拖拽移动
// 初始页面位置
let startX = null;
// 滑轨初始位置
let startLeft = null;
// start
scrollEle.addEventListener("mousedown", startEvent);
targetEle.addEventListener("scroll", scrollEvent);
// event
function startEvent(e) {
e.preventDefault();
startX = e.pageX;
startLeft = scrollEle.offsetLeft;
document.addEventListener("mousemove", moveEvent);
document.addEventListener("mouseup", stopEvent);
}
function moveEvent(e) {
// 鼠标移动距离
const deltaY = e.pageX - startX;
// 滑轨新的位置
const newLeft = Math.max(
0,
Math.min(trackWidth - thumbWidth, startLeft + deltaY)
);
// 滚动元素位置
const scrollLeft =
(newLeft / (trackWidth - thumbWidth)) * (scrollWidth - targetEleWidth);
targetEle.scrollLeft = scrollLeft;
scrollEle.style.left = newLeft + "px";
}
function stopEvent(e) {
document.removeEventListener("mousemove", moveEvent);
document.removeEventListener("mouseup", stopEvent);
}
function scrollEvent(e) {
const { scrollLeft, clientWidth, scrollWidth } = targetEle;
const thumbLeft =
(scrollLeft / (scrollWidth - clientWidth)) * (trackWidth - thumbWidth);
scrollEle.style.left = thumbLeft + "px";
}
// 鼠标滚轮
// 注册滚动事件处理程序
if ("onmousewheel" in targetEle) {
targetEle.onmousewheel = handleMouseWheel; // Chrome/Safari/Opera等非IE浏览器
} else if ("DOMMouseScroll" in targetEle) {
targetEle.addEventListerner("DOMMouseScroll", handleMouseWheel, false); // Firefox浏览器
} else {
targetEle.addEventListener("MozMousePixelScroll", handleMouseWheel, false); // IE浏览器
}
function handleMouseWheel(e) {
if (e.deltaX !== 0) {
e.preventDefault();
e = e || window.event; // 兼容不同浏览器
let deltaX = Math.max(-1, Math.min(1, e.wheelDeltaX || -e.deltaX)); // 计算滚动量
let startLeft = scrollEle.offsetLeft;
let speed = (defaultScrollLeft > 200 ? defaultScrollLeft : 200) / 200;
const newLeft = Math.max(
0,
Math.min(trackWidth - thumbWidth, startLeft - speed * deltaX)
);
// 滚动元素位置
const scrollLeft =
(newLeft / (trackWidth - thumbWidth)) * defaultScrollLeft;
targetEle.scrollLeft = scrollLeft;
}
}
// 注销事件
function removeEvent() {
document.removeEventListener("mousemove", moveEvent);
document.removeEventListener("mouseup", stopEvent);
scrollEle.removeEventListener("mousedown", startEvent);
targetEle.removeEventListener("scroll", scrollEvent);
if ("onmousewheel" in targetEle) {
targetEle.onmousewheel = null; // Chrome/Safari/Opera等非IE浏览器
} else if ("DOMMouseScroll" in targetEle) {
targetEle.removeEventListener("DOMMouseScroll", handleMouseWheel); // Firefox浏览器
} else {
targetEle.removeEventListener("MozMousePixelScroll", handleMouseWheel); // IE浏览器
}
}
this.removeEvent = removeEvent;
}
});
}
},
resizeFun(e) {
this.targetEle ? this.addScrollEvent() : this.addScrollEvent(this.refTargetEle);
},
},
};
css部分
.scrollBox-work {
width: 100%;
justify-content: center;
align-items: center;
display: flex;
transition: 0.1s all;
margin-top: 20px;
.scrollBox-track-work {
position: relative;
width: 76px;
height: 6px;
background-color: #f2f3f5;
border-radius: 3px;
.scrollBox-thumb-work {
width: 47px;
height: 6px;
background-color: #3270ff;
border-radius: 3px;
position: absolute;
left: 0;
}
}
}
注意事项
- targetEle 为滚动条关联的滚动元素,采用props传入方式,或者refs调用滚动条组件addScrollEvent方法建立联系。
- (newLeft / (trackWidth - thumbWidth)) * (scrollWidth - targetEleWidth); 等比例获取滚动元素滚动距离。
实战演示
以一个步骤条组件为例,与滚动条组件实现关联
<div class="c-steps">
...代码省略
</div>
<ScrollBar ref="scrollBar" />
import ScrollBar from '../ScrollBar/ScrollBar.vue'
export default {
components: { ScrollBar },
mounted() {
this.$refs.scrollBar?.addScrollEvent(this.getTargetEle())
window.addEventListener('resize', this.resizeFun)
},
methods: {
getTargetEle() {
return document.getElementsByClassName("c-steps")[0]
},
resizeFun() {
this.$refs.scrollBar?.addScrollEvent(this.getTargetEle())
},
},
};
注:ref调用优势为,滚动元素数据为异步加载形式,可以在异步回调函数中,进行滚动的关联绑定。(后续优化方案:使用MutationObserver 构造函数,监听滚动元素尺寸改变,重新调用addScrollEvent方法。)
最后附上实现效果