2022-11-04 19:39:47 +08:00

166 lines
4.2 KiB
TypeScript

import { EventEmitter } from 'events';
interface ScrollViewerOptions {
container: HTMLDivElement;
target: HTMLDivElement;
zoom: number;
correctionScrollSize?: {
width: number;
height: number;
};
}
export class ScrollViewer extends EventEmitter {
private container: HTMLDivElement;
private target: HTMLDivElement;
private zoom = 1;
private scrollLeft = 0;
private scrollTop = 0;
private scrollHeight = 0;
private scrollWidth = 0;
private width = 0;
private height = 0;
private translateXCorrectionValue = 0;
private translateYCorrectionValue = 0;
private correctionScrollSize = {
width: 0,
height: 0,
};
private resizeObserver = new ResizeObserver(() => {
this.setSize();
this.setScrollSize();
});
constructor(options: ScrollViewerOptions) {
super();
this.container = options.container;
this.target = options.target;
this.zoom = options.zoom;
if (this.correctionScrollSize) {
this.correctionScrollSize = {
...this.correctionScrollSize,
...options.correctionScrollSize,
};
}
this.container.addEventListener('wheel', this.wheelHandler, false);
this.setSize();
this.setScrollSize();
this.resizeObserver.observe(this.container);
this.resizeObserver.observe(this.target);
}
public destroy() {
this.resizeObserver.disconnect();
this.container.removeEventListener('wheel', this.wheelHandler, false);
this.removeAllListeners();
}
public setZoom(zoom: number) {
this.zoom = zoom;
this.setScrollSize();
}
public scrollTo({ left, top }: { left?: number; top?: number }) {
if (typeof left !== 'undefined') {
this.scrollLeft = left;
}
if (typeof top !== 'undefined') {
this.scrollTop = top;
}
const translateX = -this.scrollLeft + this.translateXCorrectionValue;
const translateY = -this.scrollTop + this.translateYCorrectionValue;
this.target.style.transform = `translate(${translateX}px, ${translateY}px)`;
}
private wheelHandler = (event: WheelEvent) => {
const { deltaX, deltaY, currentTarget } = event;
if (currentTarget !== this.container) return;
let top: number | undefined;
if (this.scrollHeight > this.height) {
top = this.scrollTop + this.getPos(deltaY, this.scrollTop, this.scrollHeight, this.height);
}
let left: number | undefined;
if (this.scrollWidth > this.width) {
left = this.scrollLeft + this.getPos(deltaX, this.scrollLeft, this.scrollWidth, this.width);
}
this.scrollTo({ left, top });
this.emit('scroll', {
scrollLeft: this.scrollLeft,
scrollTop: this.scrollTop,
scrollHeight: this.scrollHeight,
scrollWidth: this.scrollWidth,
});
};
private getPos(delta: number, scrollPos: number, scrollSize: number, size: number) {
let pos = 0;
if (delta < 0) {
if (scrollPos > 0) {
pos = Math.max(delta, -scrollPos);
}
} else {
const leftPos = scrollSize - size - scrollPos;
if (leftPos > 0) {
pos = Math.min(delta, leftPos);
}
}
return pos;
}
private setScrollSize = () => {
const targetRect = this.target.getBoundingClientRect();
this.scrollWidth = targetRect.width * this.zoom + this.correctionScrollSize.width;
const targetMarginTop = Number(this.target.style.marginTop) || 0;
this.scrollHeight = (targetRect.height + targetMarginTop) * this.zoom + this.correctionScrollSize.height;
let left: number | undefined;
let top: number | undefined;
if (this.scrollWidth < this.width) {
left = 0;
this.translateXCorrectionValue = 0;
} else {
this.translateXCorrectionValue = (this.scrollWidth - this.width) / 2;
}
if (this.scrollHeight < this.height) {
top = 0;
this.translateYCorrectionValue = 0;
} else {
this.translateYCorrectionValue = (this.scrollHeight - this.height) / 2;
}
this.scrollTo({
left,
top,
});
this.emit('scroll', {
scrollLeft: this.scrollLeft,
scrollTop: this.scrollTop,
scrollHeight: this.scrollHeight,
scrollWidth: this.scrollWidth,
});
};
private setSize = () => {
const { width, height } = this.container.getBoundingClientRect();
this.width = width;
this.height = height;
};
}