2024-09-19 06:58:17 +08:00

442 lines
14 KiB
JavaScript
Vendored

import {supportsTouch, toInt} from "./lib/util";
import * as CSS from './lib/css';
export default {
name: 'Scrollbar',
props: {
tag: {
type: String,
default: 'div'
},
className: {
type: String,
default: ''
},
enableX: {
type: Boolean,
default: false
},
enableY: {
type: Boolean,
default: true
},
hideBar: {
type: Boolean,
default: false
},
minSize: {
type: Number,
default: 20
},
touchContentBlur: {
type: Boolean,
default: true
},
},
data() {
return {
isReady: false,
scrollingX: false,
scrollingY: false,
moveingX: false,
moveingY: false,
containerWidth: null,
containerHeight: null,
contentWidth: null,
contentHeight: null,
contentOverflow: {
x: null,
y: null,
},
thumbYHeight: null,
thumbYTop: null,
thumbXWidth: null,
thumbXLeft: null,
lastScrollTop: 0,
lastScrollLeft: 0,
timeouts: {},
}
},
computed: {
containerClass() {
const classList = ['scrollbar-container'];
if (supportsTouch) {
classList.push('scrollbar-touch')
} else {
classList.push('scrollbar-desktop')
}
if (this.contentWidth > this.containerWidth && this.contentOverflow.x !== 'hidden' && this.enableX) {
classList.push('scrollbar-active-x')
}
if (this.contentHeight > this.containerHeight && this.contentOverflow.y !== 'hidden' && this.enableY) {
classList.push('scrollbar-active-y')
}
if (this.scrollingX) {
classList.push('scrollbar-scrolling-x')
}
if (this.scrollingY) {
classList.push('scrollbar-scrolling-y')
}
if (this.moveingX) {
classList.push('scrollbar-moveing-x')
}
if (this.moveingY) {
classList.push('scrollbar-moveing-y')
}
if (this.hideBar || !this.isReady) {
classList.push('scrollbar-hidebar')
}
return classList
},
contentClass({className, enableX, enableY}) {
const classList = ['scrollbar-content'];
if (className) {
classList.push(className)
}
if (!enableX) {
classList.push('scrollbar-disable-x')
}
if (!enableY) {
classList.push('scrollbar-disable-y')
}
return classList
}
},
mounted() {
this.$nextTick(() => {
this.updateBase()
});
},
updated() {
this.$nextTick(() => {
this.updateGeometry(false);
});
},
methods: {
/**
* 滚动区域信息
* @returns {{scale: number, scrollY: *, scrollE: number}}
*/
scrollInfo() {
const scroller = $A(this.$refs.content);
const wInnerH = Math.round(scroller.innerHeight());
const wScrollY = scroller.scrollTop();
const bScrollH = this.$refs.content.scrollHeight;
return {
scale: wScrollY / (bScrollH - wInnerH), //已滚动比例
scrollY: wScrollY, //滚动的距离
scrollE: bScrollH - wInnerH - wScrollY, //与底部距离
}
},
/**
* 滚动区域元素
* @returns {Vue | Element | (Vue | Element)[]}
*/
scrollElement() {
return this.$refs.content;
},
/**
* 从滚动区域获取指定元素
* @param el
* @returns {*}
*/
querySelector(el) {
return this.$refs.content && this.$refs.content.querySelector(el)
},
/**
* 更新基础信息
*/
updateBase() {
if (supportsTouch) {
return;
}
const containerStyles = CSS.get(this.$refs.container);
const contentStyles = CSS.get(this.$refs.content);
CSS.set(this.$refs.trackX, {
left: toInt(containerStyles.paddingLeft) + toInt(contentStyles.marginLeft),
right: toInt(containerStyles.paddingRight) + toInt(contentStyles.marginRight),
bottom: toInt(containerStyles.paddingBottom) + toInt(contentStyles.marginBottom),
});
CSS.set(this.$refs.trackY, {
top: toInt(containerStyles.paddingTop) + toInt(contentStyles.marginTop),
bottom: toInt(containerStyles.paddingBottom) + toInt(contentStyles.marginBottom),
right: toInt(containerStyles.paddingRight) + toInt(contentStyles.marginRight),
});
this.contentOverflow = {
x: contentStyles.overflowX,
y: contentStyles.overflowY,
}
},
/**
* 更新滚动条
* @param scrolling 是否正在滚动
*/
updateGeometry(scrolling) {
if (supportsTouch) {
return;
}
const element = this.$refs.content;
if (!element) {
return;
}
const scrollTop = Math.floor(element.scrollTop);
const rect = element.getBoundingClientRect();
this.containerWidth = Math.round(rect.width);
this.containerHeight = Math.round(rect.height);
this.contentWidth = element.scrollWidth;
this.contentHeight = element.scrollHeight;
this.thumbXWidth = Math.max(toInt((this.containerWidth * this.containerWidth) / this.contentWidth), this.minSize);
this.thumbXLeft = toInt((element.scrollLeft * (this.containerWidth - this.thumbXWidth)) / (this.contentWidth - this.containerWidth));
this.thumbYHeight = Math.max(toInt((this.containerHeight * this.containerHeight) / this.contentHeight), this.minSize);
this.thumbYTop = toInt((scrollTop * (this.containerHeight - this.thumbYHeight)) / (this.contentHeight - this.containerHeight));
CSS.set(this.$refs.thumbX, {
left: this.thumbXLeft,
width: this.thumbXWidth,
});
CSS.set(this.$refs.thumbY, {
top: this.thumbYTop,
height: this.thumbYHeight,
});
if (scrolling) {
this.scrollingX = this.lastScrollLeft !== element.scrollLeft;
this.scrollingY = this.lastScrollTop !== element.scrollTop;
this.lastScrollTop = element.scrollTop;
this.lastScrollLeft = element.scrollLeft;
this.timeouts['scroll'] && clearTimeout(this.timeouts['scroll']);
this.timeouts['scroll'] = setTimeout(() => {
this.scrollingX = false;
this.scrollingY = false;
}, 1000)
}
},
/**
* 鼠标移入事件(单次)
*/
onContainerMouseMove() {
if (this.$store?.state.windowTouch) {
return;
}
setTimeout(() => {
if (this.isReady) {
return
}
this.updateGeometry(true);
this.isReady = true
}, 300)
},
/**
* 内容区域触摸开始事件
* @param e
*/
onContentTouchStart(e) {
if (!this.touchContentBlur) {
return;
}
const focusedElement = document.activeElement;
if (focusedElement) {
focusedElement.blur();
}
},
/**
* 滚动区域滚动事件
* @param e
*/
onContentScroll(e) {
this.updateGeometry(true);
this.$emit('on-scroll', e);
this.isReady = true
},
/**
* 内容区域鼠标进入事件
*/
onContentMouseenter() {
this.updateBase();
this.updateGeometry(false);
},
/**
* 轨道区域(X)鼠标按下事件
* @param e
*/
onTrackXMouseDown(e) {
if (supportsTouch) {
return;
}
const element = this.$refs.content;
const rect = this.$refs.trackX.getBoundingClientRect();
const positionLeft = e.pageX - window.scrollX - rect.left;
const direction = positionLeft > this.thumbXLeft ? 1 : -1;
element.scrollLeft += direction * this.containerWidth;
this.updateGeometry(true);
e.stopPropagation();
},
/**
* 轨道区域(Y)鼠标按下事件
* @param e
*/
onTrackYMouseDown(e) {
if (supportsTouch) {
return;
}
const element = this.$refs.content;
const rect = this.$refs.trackY.getBoundingClientRect();
const positionTop = e.pageY - window.scrollY - rect.top;
const direction = positionTop > this.thumbYTop ? 1 : -1;
element.scrollTop += direction * this.containerHeight;
this.updateGeometry(true);
e.stopPropagation();
},
/**
* 滚动条(X)鼠标按下事件
* @param e
*/
onThumbXMouseDown(e) {
if (supportsTouch) {
return;
}
const element = this.$refs.content;
const rect = element.getBoundingClientRect();
const scrollLeft = element.scrollLeft;
const pageX = e.pageX - window.scrollX;
const mouseMoveHandler = (e) => {
const diff = e.pageX - pageX;
element.scrollLeft = scrollLeft + diff * this.contentWidth / rect.width;
};
const mouseUpHandler = () => {
this.timeouts['moveX'] = setTimeout(() => {
this.moveingX = false;
}, 100);
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
};
this.moveingX = true;
this.timeouts['moveX'] && clearTimeout(this.timeouts['moveX']);
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
e.preventDefault();
e.stopPropagation();
},
/**
* 滚动条(Y)鼠标按下事件
* @param e
*/
onThumbYMouseDown(e) {
if (supportsTouch) {
return;
}
const element = this.$refs.content;
const rect = element.getBoundingClientRect();
const scrollTop = element.scrollTop;
const pageY = e.pageY - window.scrollY;
const mouseMoveHandler = (e) => {
const diff = e.pageY - pageY;
element.scrollTop = scrollTop + diff * this.contentHeight / rect.height;
};
const mouseUpHandler = () => {
this.timeouts['moveY'] = setTimeout(() => {
this.moveingY = false;
}, 100);
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
};
this.moveingY = true;
this.timeouts['moveY'] && clearTimeout(this.timeouts['moveY']);
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
e.preventDefault();
e.stopPropagation();
}
},
render(h) {
return h('div', {
ref: 'container',
class: this.containerClass,
on: {
'~mousemove': this.onContainerMouseMove,
}
}, [
h(this.tag, {
ref: 'content',
class: this.contentClass,
on: {
touchstart: this.onContentTouchStart,
scroll: this.onContentScroll,
mouseenter: this.onContentMouseenter,
}
}, this.$slots.default),
h('div', {
ref: 'trackX',
class: 'scrollbar-track-x',
on: {
mousedown: this.onTrackXMouseDown
}
}, [
h('div', {
ref: 'thumbX',
class: 'scrollbar-thumb-x',
on: {
mousedown: this.onThumbXMouseDown
}
}),
]),
h('div', {
ref: 'trackY',
class: 'scrollbar-track-y',
on: {
mousedown: this.onTrackYMouseDown
}
}, [
h('div', {
ref: 'thumbY',
class: 'scrollbar-thumb-y',
on: {
mousedown: this.onThumbYMouseDown
}
})
]),
])
}
}