UNPKG

e-virt-table

Version:

A powerful data table based on canvas. You can use it as data grid、Microsoft Excel or Google sheets. It supports virtual scroll、cell edit etc.

460 lines 16.8 kB
class Scrollbar { constructor(ctx, type) { Object.defineProperty(this, "ctx", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "type", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "isFocus", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "trackX", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "trackY", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "trackWidth", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "trackHeight", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "splitPoints", { enumerable: true, configurable: true, writable: true, value: [] }); //分割线 Object.defineProperty(this, "barX", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "barY", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "barWidth", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "barHeight", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "distance", { enumerable: true, configurable: true, writable: true, value: 0 }); // 滚动条的长度 Object.defineProperty(this, "visibleDistance", { enumerable: true, configurable: true, writable: true, value: 0 }); //可见区域的长度 Object.defineProperty(this, "clientX", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "clientY", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "dragStart", { enumerable: true, configurable: true, writable: true, value: 0 }); // 拖拽开始的位置 Object.defineProperty(this, "isDragging", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "scroll", { enumerable: true, configurable: true, writable: true, value: 0 }); this.ctx = ctx; this.type = type; if (this.type === 'vertical') { this.scroll = this.ctx.scrollY; } else { this.scroll = this.ctx.scrollX; } } onWheel(e) { this.updateScroll(e); } onMouseDown(e) { if (!(e.target instanceof Element)) { return; } if (!this.ctx.isTarget()) { return; } // 行调整大小中不处理 if (this.ctx.stageElement.style.cursor === 'row-resize') { return true; } // 列调整大小中不处理 if (this.ctx.stageElement.style.cursor === 'col-resize') { return true; } const { offsetX, offsetY, clientX, clientY } = e; if (clientX == this.clientX && clientY == this.clientY) return; if (this.isOnScrollbar(offsetX, offsetY)) { this.clientX = clientX; this.clientY = clientY; this.isDragging = true; this.ctx.scrollerMove = true; // 滚动条移动 this.isFocus = true; this.dragStart = this.scroll; e.preventDefault(); } else if (this.isOnTrack(offsetX, offsetY)) { // 点击轨道滚动 let scroll = 0; if (this.type === 'vertical') { // 滚动条位置=(鼠标位置-滚动条的高度/2-头部的高度)/(可见区域的长度-滚动条的高度)*滚动条的长度 // 滚动到中间所以减去滚动条的高度/2 const offset = offsetY - this.ctx.header.height - this.barHeight / 2; scroll = (offset / (this.visibleDistance - this.barHeight)) * this.distance; } else { const offset = offsetX - this.barWidth / 2; scroll = (offset / (this.visibleDistance - this.barWidth)) * this.distance; } this.scroll = Math.max(0, Math.min(scroll, this.distance)); } } onMouseUp() { this.isDragging = false; this.isFocus = false; this.clientX = 0; this.clientY = 0; } onMouseMove(e) { const { offsetX, offsetY, clientX, clientY } = e; // 悬浮提示 if (this.isOnScrollbar(offsetX, offsetY) && e.target === this.ctx.canvasElement) { this.isFocus = true; } else { this.isFocus = false; } // 拖拽移动滚动条 if (clientX == this.clientX && clientY == this.clientY) return; let offset = 0; if (this.type === 'horizontal') { offset = clientX - this.clientX; } else { offset = clientY - this.clientY; } if (this.isDragging && offset !== 0) { // scroll= 开始滚动条位置+(鼠标移动的距离/可见区域的长度)*滚动条的长度 let scroll = 0; if (this.type === 'vertical') { scroll = this.dragStart + (offset / (this.visibleDistance - this.barHeight)) * this.distance; } else { scroll = this.dragStart + (offset / (this.visibleDistance - this.barWidth)) * this.distance; } this.scroll = Math.max(0, Math.min(scroll, this.distance)); } } isPointInElement(pointX, pointY, elementX, elementY, elementWidth, elementHeight) { return (pointX >= elementX && pointX <= elementX + elementWidth && pointY >= elementY && pointY <= elementY + elementHeight); } hasScrollbar() { if (this.type === 'vertical') { return this.barHeight > 0; } else if (this.type === 'horizontal') { return this.barWidth > 0; } return false; } isOnScrollbar(x, y) { return this.isPointInElement(x, y, this.barX, this.barY, this.barWidth, this.barHeight); } isOnTrack(x, y) { return this.isPointInElement(x, y, this.trackX, this.trackY, this.trackWidth, this.trackHeight); } updateScroll(e) { const deltaX = e.deltaX; const deltaY = e.deltaY; if (this.type === 'vertical' && e.shiftKey === false) { // 只有在滚动条需要滚动时才阻止默认事件 const hasScrollbar = this.hasScrollbar(); if (hasScrollbar && !((this.scroll === 0 && deltaY < 0) || (this.scroll === this.distance && deltaY > 0))) { e.preventDefault(); } this.scroll = Math.max(0, Math.min(this.scroll + deltaY, this.distance)); } else if (this.type === 'horizontal') { if (e.shiftKey) { this.scroll = Math.max(0, Math.min(this.scroll + deltaY, this.distance)); } else { this.scroll = Math.max(0, Math.min(this.scroll + deltaX, this.distance)); } } } updatedSize() { const { body, header, stageHeight, stageWidth, config: { SCROLLER_TRACK_SIZE = 0, SCROLLER_SIZE = 0 }, } = this.ctx; const visibleWidth = stageWidth; const visibleHeight = stageHeight; const headerHeight = header.height; const headerWidth = header.width; const bodyHeight = body.height; const footerHeight = this.ctx.footer.height; if (this.type === 'vertical') { this.visibleDistance = visibleHeight - SCROLLER_TRACK_SIZE - headerHeight; this.distance = bodyHeight - this.visibleDistance + footerHeight; this.trackX = visibleWidth - SCROLLER_TRACK_SIZE; this.trackY = 0; // 分割线 this.splitPoints = [this.trackX, headerHeight, this.trackX + SCROLLER_TRACK_SIZE, headerHeight]; this.trackWidth = SCROLLER_TRACK_SIZE; this.trackHeight = visibleHeight; // 滚动条的X位置=轨道的X位置+(轨道的宽度-滚动条的宽度)/2 this.barX = this.trackX - 1 + (SCROLLER_TRACK_SIZE - SCROLLER_SIZE) / 2; this.barWidth = SCROLLER_SIZE; const ratio = this.distance ? this.visibleDistance / (bodyHeight + footerHeight) : 0; let _barHeight = Math.floor(ratio * this.visibleDistance); // 最小30,超出可见区域则隐藏 if (_barHeight < 30) { _barHeight = 30; } else if (_barHeight > this.visibleDistance) { _barHeight = 0; } this.barHeight = _barHeight; this.barY = headerHeight + (this.scroll / this.distance) * (this.visibleDistance - this.barHeight); // 范围限制 this.scroll = Math.max(0, Math.min(this.scroll, this.distance)); } else { this.visibleDistance = visibleWidth - SCROLLER_TRACK_SIZE; this.distance = headerWidth - this.visibleDistance; // 分割线 this.splitPoints = [ visibleWidth - SCROLLER_TRACK_SIZE, visibleHeight - SCROLLER_TRACK_SIZE, visibleWidth - SCROLLER_TRACK_SIZE, visibleHeight, ]; this.trackX = 0; this.trackY = visibleHeight - SCROLLER_TRACK_SIZE; this.trackWidth = visibleWidth; this.trackHeight = SCROLLER_TRACK_SIZE; const ratio = this.distance ? this.visibleDistance / headerWidth : 0; let _barWidth = Math.floor(ratio * this.visibleDistance); this.barY = this.trackY - 1 + (SCROLLER_TRACK_SIZE - SCROLLER_SIZE) / 2; // 最小30,超出可见区域则隐藏 if (_barWidth < 30) { _barWidth = 30; } else if (_barWidth >= this.visibleDistance) { _barWidth = 0; } this.barWidth = _barWidth; this.barHeight = SCROLLER_SIZE; this.barX = (this.scroll / this.distance) * (this.visibleDistance - this.barWidth); // 范围限制 this.scroll = Math.max(0, Math.min(this.scroll, this.distance)); } } draw() { const { config: { SCROLLER_FOCUS_COLOR, SCROLLER_COLOR, BORDER_COLOR, SCROLLER_TRACK_COLOR }, } = this.ctx; this.updatedSize(); // 轨道 this.ctx.paint.drawRect(this.trackX, this.trackY, this.trackWidth, this.trackHeight, { borderColor: BORDER_COLOR, fillColor: SCROLLER_TRACK_COLOR, }); // 滚动条 this.ctx.paint.drawRect(this.barX, this.barY, this.barWidth, this.barHeight, { fillColor: this.isFocus || this.isDragging ? SCROLLER_FOCUS_COLOR : SCROLLER_COLOR, radius: 4, }); // 分割线范围外 if (this.splitPoints.length > 0) { this.ctx.paint.drawLine(this.splitPoints, { borderColor: BORDER_COLOR, borderWidth: 1, }); } // 悬浮状态 this.ctx.scrollerFocus = this.isFocus; } } export default class Scroller { constructor(ctx) { Object.defineProperty(this, "ctx", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "verticalScrollbar", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "horizontalScrollbar", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.ctx = ctx; this.verticalScrollbar = new Scrollbar(ctx, 'vertical'); this.horizontalScrollbar = new Scrollbar(ctx, 'horizontal'); this.ctx.on('wheel', (e) => this.onWheel(e)); this.ctx.on('mousedown', (e) => this.onMouseDown(e)); this.ctx.on('mousemove', (e) => this.onMouseMove(e)); this.ctx.on('mouseup', () => this.onMouseUp()); this.ctx.on('setScroll', (scrollX, scrollY) => { this.setScroll(scrollX, scrollY); }); this.ctx.on('setScrollX', (scrollX) => { this.setScrollX(scrollX); }); this.ctx.on('setScrollY', (scrollY) => { this.setScrollY(scrollY); }); } onWheel(e) { this.verticalScrollbar.onWheel(e); this.horizontalScrollbar.onWheel(e); this.draw(); } onMouseDown(e) { this.verticalScrollbar.onMouseDown(e); this.horizontalScrollbar.onMouseDown(e); this.draw(); } onMouseMove(e) { this.verticalScrollbar.onMouseMove(e); this.horizontalScrollbar.onMouseMove(e); this.draw(); } onMouseUp() { this.verticalScrollbar.onMouseUp(); this.horizontalScrollbar.onMouseUp(); this.ctx.scrollerMove = false; } draw() { this.verticalScrollbar.draw(); this.horizontalScrollbar.draw(); const scrollX = Math.floor(this.horizontalScrollbar.scroll); const scrollY = Math.floor(this.verticalScrollbar.scroll); // 只有滚动条发生变化才触发绘制 if (scrollX !== this.ctx.scrollX || scrollY !== this.ctx.scrollY) { this.ctx.emit('onScroll', scrollX, scrollY); if (scrollX !== this.ctx.scrollX) { this.ctx.emit('onScrollX', scrollX); } if (scrollY !== this.ctx.scrollY) { this.ctx.emit('onScrollY', scrollY); } this.ctx.scrollX = scrollX; this.ctx.scrollY = scrollY; this.ctx.emit('draw'); } } setScroll(x, y) { this.horizontalScrollbar.scroll = x; this.verticalScrollbar.scroll = y; this.ctx.emit('draw'); } setScrollX(scrollX) { this.horizontalScrollbar.scroll = scrollX; this.ctx.emit('draw'); } setScrollY(scrollY) { this.verticalScrollbar.scroll = scrollY; this.ctx.emit('draw'); } scrollToColkey(key) { const { header } = this.ctx; const cell = header.leafCellHeaders.find((cell) => cell.key === key); if (cell) { // 移动到窗口中间/2 this.setScrollX(cell.x - header.visibleWidth / 2); } } scrollToColIndex(colIndex) { const { header } = this.ctx; const cell = header.leafCellHeaders.find((cell) => cell.colIndex === colIndex); if (cell) { // 移动到窗口中间/2 if (cell.x > header.visibleWidth / 2) { this.setScrollX(cell.x - header.visibleWidth / 2); } } } scrollToRowIndex(rowIndex) { const { body, database } = this.ctx; const { top } = database.getPositionForRowIndex(rowIndex); if (top > body.visibleHeight) { this.setScrollY(top - body.visibleHeight / 2); } } scrollToRowKey(rowKey) { const { body, database } = this.ctx; const rowIndex = database.getRowIndexForRowKey(rowKey); const { top } = database.getPositionForRowIndex(rowIndex); this.setScrollY(top - body.visibleHeight / 2); } } //# sourceMappingURL=Scroller.js.map