首先记录几个资料的url
知乎的消息 Dropdown 是如何做到当消息滚动到头的时候阻止页面滚动的?
How to prevent page scrolling when scrolling a DIV element?
[代码]
JS滚轮事件(mousewheel/DOMMouseScroll)了解
先附上几段代码吧
目录
事件监听绑定与解绑
const on = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
const off = (function() {
if (document.removeEventListener) {
return function(element, event, handler) {
if (element && event) {
element.removeEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event) {
element.detachEvent('on' + event, handler);
}
};
}
})();
事件监听
// 监听滚动事件
/**
* 监听滚动中 -- 元素滚动时执行,
* js改变滚动位置会触发
* 每次滚动会事中触发多次
* 不能滚动时不会触发
*/
on(wrap, 'scroll', handleScroll);
/**
* 监听滚动前 -- 滚动鼠标滚轮时执行
* js改变滚动位置不会触发
* 每次滚动在事前触发一次
* 不能滚动时也会触发
*/
.on(wrap, 'wheel', handleWheel);
数据对象
{
wrapOwn: {
scrollTop: 0,
scrollLeft: 0,
scrollHeight: 1,
scrollWidth: 1,
clientHeight: 1,
clientWidth:1
},
wrap: wrap, // 滚动监听对象
}
初始化数据
wrapOwn.clientHeight = wrap.clientHeight;
wrapOwn.clientWidth = wrap.clientWidth;
wrapOwn.scrollHeight = wrap.scrollHeight;
wrapOwn.scrollWidth = wrap.scrollWidth;
事件回调方法
handleScroll(e){
wrapOwn.scrollTop = wrap.scrollTop;
wrapOwn.scrollLeft = wrap.scrollLeft;
}
// 滚动鼠标滚轮时回调,确定是否达到极限然后阻止滚动
handleWheel(e) {
// console.log('scrollTop=',wrapOwn.scrollTop, 'deltaY=', e.deltaY, 'scrollHeight=',wrapOwn.scrollHeight, 'clientHeight=', wrapOwn.clientHeight);
// console.log('scrollLeft=',wrapOwn.scrollLeft, 'deltaX=', e.deltaX, 'scrollWidth=',wrapOwn.scrollWidth, 'clientHeight=', wrapOwn.clientWidth);
// e.deltaY e.deltaX === 0 , 表示在当前方向上没有位移
// 垂直滚动到顶部
if(wrapOwn.scrollTop===0 && e.deltaY < 0){
// console.log('垂直滚动到顶部');
e.preventDefault();
return;
}
// 垂直滚动到底部
if(wrapOwn.clientHeight + wrapOwn.scrollTop === wrapOwn.scrollHeight && e.deltaY > 0){
// console.log('垂直滚动到底部');
e.preventDefault();
return;
}
// 水平滚动到左侧
if(wrapOwn.scrollLeft===0 && e.deltaX < 0){
// console.log('水平滚动到左侧');
e.preventDefault();
return;
}
// 水平滚动到右侧
if(wrapOwn.clientWidth + wrapOwn.scrollLeft === wrapOwn.scrollWidth && e.deltaX > 0){
// console.log('水平滚动到右侧');
e.preventDefault();
return;
}
}
各种计算属性
// 因为使用的是百分比,所以都要乘以100
moveY(){
return ((wrapOwn.scrollTop * 100) / wrapOwn.clientHeight) || 0;
},
moveX(){
return ((wrapOwn.scrollLeft * 100) / wrapOwn.clientWidth) || 0;
},
heightPercentage(){
return (wrapOwn.clientHeight * 100 / wrapOwn.scrollHeight);
},
// 垂直滚动条的高度百分比,只有小于100才显示
sizeHeight(){
return (heightPercentage < 100) ? (heightPercentage + '%') : undefined;
},
widthPercentage(){
return (wrapOwn.clientWidth * 100 / wrapOwn.scrollWidth);
},
// 水平滚动条的宽度百分比,只有小于100才显示
sizeWidth(){
return (widthPercentage < 100) ? (widthPercentage + '%') : undefined;
}
设置滚动条
<Bar
v-if="sizeWidth"
:move="moveX"
:size="sizeWidth"></Bar>
<Bar
v-if="sizeHeight"
vertical
:move="moveY"
:size="sizeHeight"></Bar>
Bar.vue
<template>
<div class="yii-scrollbar--bar"
:class="['is-' + bar.key, {'is-allows': isAllows}]"
@mousedown="clickBarHandler">
<div class="yii-scrollbar--inner"
ref="inner"
@mousedown="clickInnerHandler"
:style="[{
'transform':translate,
'msTransform':translate,
'webkitTransform':translate
}, sizeStyle]">
</div>
</div>
</template>
<script>
import Utils from "utils";
export default {
name: 'Bar',
props: {
vertical: Boolean, // 是否垂直,true表示垂直
size: String, // 百分比或者空
move: Number // 滚动条滚动距离
},
computed: {
// 滚动条参数
bar() {
return this.BAR_MAP[this.vertical ? 'vertical' : 'horizontal'];
},
// 视口
wrap() {
return this.$parent.wrap;
},
// 偏移位置
translate() {
return `translate${this.bar.axis}(${ this.move }%)`;
},
// 滚动条大小样式
sizeStyle() {
const style = {};
style[this.bar.size] = this.size;
return style;
},
isAllows(){
return this.$parent.allows;
}
},
data(){
return {
// 滚动条参数
BAR_MAP: {
vertical: {
offset: 'offsetHeight',
scroll: 'scrollTop',
scrollSize: 'scrollHeight',
size: 'height',
key: 'vertical',
axis: 'Y',
client: 'clientY',
direction: 'top'
},
horizontal: {
offset: 'offsetWidth',
scroll: 'scrollLeft',
scrollSize: 'scrollWidth',
size: 'width',
key: 'horizontal',
axis: 'X',
client: 'clientX',
direction: 'left'
}
},
// 鼠标是否按下
cursorDown: false
};
},
methods: {
// 滑块点击,实现拖拽效果
clickInnerHandler(e) {
this.startDrag(e);
// 滑块点击右侧/下侧长度 = 滑块size - (滑块点击位置 - 滑块左侧/上面位置)
this[this.bar.axis] = (e.currentTarget[this.bar.offset] -
(e[this.bar.client] - e.currentTarget.getBoundingClientRect()[this.bar.direction]));
},
// 轨道点击,实现在轨道点击后页面滚动
clickBarHandler(e) {
// 点击位置相对轨道左侧/上面的水平/垂直距离
const offset = Math.abs(e.target.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]);
// 滑块宽度/高度的一半,即中心点
const innerHalf = (this.$refs.inner[this.bar.offset] / 2);
// 滑块相对移动比例 = (点击轨道相对位置 - 滑块中心点) / 轨道宽度
// 减去一个滑块的一半,为了使点击后滑块的中心在所点击的位置
const innerPositionPercentage = ((offset - innerHalf) / e.target[this.bar.offset]);
// 视口滚动条要滚动的距离
this.wrap[this.bar.scroll] = (innerPositionPercentage * this.wrap[this.bar.scrollSize]);
},
// 开始拖拽
startDrag(e) {
e.stopImmediatePropagation();
this.cursorDown = true;
Utils.dom.on(document, 'mousemove', this.mouseMoveDocumentHandler);
Utils.dom.on(document, 'mouseup', this.mouseUpDocumentHandler);
// 禁止选择
document.onselectstart = () => false;
},
// 鼠标移动回调
mouseMoveDocumentHandler(e) {
if (this.cursorDown === false) return;
const prevPage = this[this.bar.axis];
if (!prevPage) return;
// 移动后相对左侧/上侧的偏移 = 点击点的位置 - 轨道左侧/上侧的位置
const offset = ((this.$el.getBoundingClientRect()[this.bar.direction] - e[this.bar.client]) * -1);
// 滑块点击位置到左侧/上侧的距离
const innerClickPosition = (this.$refs.inner[this.bar.offset] - prevPage);
// 滑块偏移轨道比例
// 相对轨道偏移值=移动后偏移值 - 点击位置左侧/上侧size
const innerPositionPercentage = ((offset - innerClickPosition) / this.$el[this.bar.offset]);
// 视口滚动条偏移值
this.wrap[this.bar.scroll] = innerPositionPercentage * this.wrap[this.bar.scrollSize];
},
// 鼠标抬起回调
mouseUpDocumentHandler(e) {
this.cursorDown = false;
this[this.bar.axis] = 0;
Utils.dom.off(document, 'mousemove', this.mouseMoveDocumentHandler);
Utils.dom.off(document, 'mouseup');
document.onselectstart = null;
},
destroyed(){
// console.log("method destroyed");
Utils.dom.off(document, 'mouseup', this.mouseUpDocumentHandler);
}
}
}
</script>
样式postcss
<style type="postcss">
:root{
--color-gray40: #576166;
--scrollbar-background-color: color(var(--color-gray40) a(50%));
--scrollbar-hover-background-color: color(var(--color-gray40) a(60%));
--scrollbar-bar-hover-background-color: color(var(--color-gray40) a(30%));
}
@n yii {
@b scrollbar{
overflow: hidden;
position: relative;
&:hover,
&:active,
&:focus {
.yii-scrollbar-bar {
opacity: 1;
transition: opacity 340ms ease-out;
}
}
@e wrap{
overflow: scroll;
@when native{
overflow: auto;
/*&::-webkit-scrollbar {
width: 16px;
height: 16px;
background-color: #F5F5F5;
}*/
/*定义滚动条轨道 内阴影+圆角*/
/*&::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
border-radius: 10px;
background-color: #F5F5F5;
}*/
/*定义滑块 内阴影+圆角*/
/*&::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: #555;
}*/
}
}
@e inner{
position: relative;
display: block;
width: 0;
heigh: 0;
cursor: pointer;
border-radius: inherit;
background-color: var(--scrollbar-background-color);
transition: .3s background-color;
&:hover {
background-color: var(--scrollbar-hover-background-color);
}
}
@e bar{
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
transition: opacity 120ms ease-out;
@when vertical {
width: 6px;
top: 2px;
> div {
width: 100%;
}
}
@when horizontal {
height: 6px;
left: 2px;
> div {
height: 100%;
}
}
@when allows{
opacity: 1;
}
&:hover {
background-color: var(--scrollbar-bar-hover-background-color);
}
}
}
}
</style>