diff --git a/src/image-viewer/index.ts b/src/image-viewer/index.ts index 0a5f6ff..e3bbe49 100644 --- a/src/image-viewer/index.ts +++ b/src/image-viewer/index.ts @@ -105,14 +105,17 @@ export default class ImageViewer extends Component { imageData.width = newWidth imageData.height = newHeight - if (pivot) { - imageData.left -= offsetWidth * ((pivot.x - imageData.left) / width) - imageData.top -= offsetHeight * ((pivot.y - imageData.top) / height) - } else { - imageData.left -= offsetWidth / 2 - imageData.top -= offsetHeight / 2 + + if (!pivot) { + pivot = { + x: width / 2 + imageData.left, + y: height / 2 + imageData.top, + } } + imageData.left -= offsetWidth * ((pivot.x - imageData.left) / width) + imageData.top -= offsetHeight * ((pivot.y - imageData.top) / height) + this.render() } /** Reset image to initial state. */ diff --git a/src/painter/index.ts b/src/painter/index.ts index 5b9374b..110cef4 100644 --- a/src/painter/index.ts +++ b/src/painter/index.ts @@ -29,7 +29,6 @@ export default class Painter extends Component { private $tools: $.$ private $canvas: $.$ private $viewport: $.$ - private viewport: HTMLDivElement private $body: $.$ private canvas: HTMLCanvasElement private ctx: CanvasRenderingContext2D @@ -56,7 +55,6 @@ export default class Painter extends Component { this.$tools = this.find('.tools') this.$canvas = this.find('.main-canvas') this.$viewport = this.find('.viewport') - this.viewport = this.$viewport.get(0) as HTMLDivElement this.$body = this.find('.body') this.canvas = this.$canvas.get(0) as HTMLCanvasElement this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D @@ -64,9 +62,6 @@ export default class Painter extends Component { this.resizeSensor = new ResizeSensor(container) this.canvasResizeSenor = new ResizeSensor(this.canvas) - this.resetViewport() - this.centerCanvas() - this.addLayer() this.activeLayer = this.layers[0] @@ -77,6 +72,9 @@ export default class Painter extends Component { this.hand = new Hand(this) this.zoom = new Zoom(this) + this.resetViewport() + this.hand.centerCanvas() + this.useTool(this.options.tool) } destroy() { @@ -102,6 +100,7 @@ export default class Painter extends Component { if (tool) { this.currentTool = tool + tool.onUse() $tools.find(c('.tool')).rmClass(c('selected')) $tools.find(`${c('.tool')}[data-tool="${name}"]`).addClass(c('selected')) } @@ -200,7 +199,7 @@ export default class Painter extends Component { const { width: viewportWidth, height: viewportHeight } = this.getViewportSize() if (canvasWidth < viewportWidth && canvasHeight < viewportHeight) { - this.centerCanvas() + this.hand.centerCanvas() } } private resetViewport = () => { @@ -216,14 +215,7 @@ export default class Painter extends Component { height, }) } - private centerCanvas() { - const { viewport } = this - const { width: viewportWidth, height: viewportHeight } = - this.getViewportSize() - viewport.scrollLeft = (viewport.scrollWidth - viewportWidth) / 2 - viewport.scrollTop = (viewport.scrollHeight - viewportHeight) / 2 - } - private getViewportSize() { + getViewportSize() { let { width, height } = this.$viewport.offset() const scrollbarSize = measuredScrollbarWidth() width -= scrollbarSize diff --git a/src/painter/style.scss b/src/painter/style.scss index a52bf28..0cf2dcf 100644 --- a/src/painter/style.scss +++ b/src/painter/style.scss @@ -61,7 +61,7 @@ } .main-canvas { - transition: width 0.3s, height 0.3s; + image-rendering: pixelated; } .theme-dark { diff --git a/src/painter/tools.ts b/src/painter/tools.ts index 5daa0ea..b911a7b 100644 --- a/src/painter/tools.ts +++ b/src/painter/tools.ts @@ -1,6 +1,7 @@ import Painter from './index' import $ from 'licia/$' import types from 'licia/types' +import Tween from 'licia/Tween' import { eventPage, eventClient } from '../share/util' interface IPivot { @@ -40,11 +41,13 @@ export class Tool { onDragEnd(e: any) { this.getXY(e) } + // eslint-disable-next-line @typescript-eslint/no-empty-function + onUse() {} // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars onClick(e: any) {} private getXY(e: any) { - const canvas = this.painter.getCanvas() - const offset = $(canvas).offset() + const { canvas, $canvas } = this + const offset = $canvas.offset() const pageX = eventPage('x', e) const pageY = eventPage('y', e) @@ -97,7 +100,8 @@ export class Pencil extends Tool { const { ctx } = this const { size } = this.options - ctx.fillStyle = 'black' + const color = 'rgb(0,0,0)' + ctx.fillStyle = color ctx.fillRect(x, y, size, size) this.painter.updateCanvas() } @@ -124,13 +128,37 @@ export class Hand extends Tool { viewport.scrollLeft = this.startScrollLeft - deltaX viewport.scrollTop = this.startScrollTop - deltaY } + centerCanvas() { + const { viewport } = this + const { width: viewportWidth, height: viewportHeight } = + this.painter.getViewportSize() + viewport.scrollLeft = (viewport.scrollWidth - viewportWidth) / 2 + viewport.scrollTop = (viewport.scrollHeight - viewportHeight) / 2 + } } export class Zoom extends Tool { + private isZooming = false + constructor(painter: Painter) { + super(painter) + + this.bindEvent() + } + onUse() { + const { $canvas } = this + + if (!$canvas.attr('style')) { + const { width, height } = $canvas.offset() + $canvas.css({ + width, + height, + }) + } + } onClick(e: any) { const offset = this.$viewport.offset() - this.zoom(e.altKey ? -0.1 : 0.1, { + this.zoom(e.altKey ? -0.3 : 0.3, { x: eventPage('x', e) - offset.left, y: eventPage('y', e) - offset.top, }) @@ -141,14 +169,97 @@ export class Zoom extends Tool { this.zoomTo((offset.width * ratio) / this.canvas.width, pivot) } zoomTo(ratio: number, pivot?: IPivot) { - const { canvas } = this + if (this.isZooming) { + return + } + this.isZooming = true + + const { canvas, viewport, $canvas } = this - const width = canvas.width * ratio - const height = canvas.height * ratio + const newWidth = canvas.width * ratio + const newHeight = canvas.height * ratio + const offset = this.$canvas.offset() + const { width, height } = offset + let { left, top } = offset + const deltaWidth = newWidth - width + const deltaHeight = newHeight - height + const { scrollLeft, scrollTop } = viewport + const viewportOffset = this.$viewport.offset() + left -= viewportOffset.left + top -= viewportOffset.top + const { scrollWidth, scrollHeight } = viewport + const marginLeft = (scrollWidth - width) / 2 + const marginTop = (scrollHeight - height) / 2 + + if (!pivot) { + pivot = { + x: width / 2 + left, + y: height / 2 + top, + } + } - this.$canvas.css({ + const { width: viewportWidth, height: viewportHeight } = + this.painter.getViewportSize() + const newMarginLeft = viewportWidth - Math.min(newWidth, 100) + const newMarginTop = viewportHeight - Math.min(newHeight, 100) + const deltaMarginLeft = newMarginLeft - marginLeft + const deltaMarginTop = newMarginTop - marginTop + + const newScrollLeft = + scrollLeft + deltaMarginLeft + deltaWidth * ((pivot.x - left) / width) + const newScrollTop = + scrollTop + deltaMarginTop + deltaHeight * ((pivot.y - top) / height) + + const tween = new Tween({ + scrollLeft, + scrollTop, width, height, }) + + tween + .on('update', (target) => { + $canvas.css({ + width: target.width, + height: target.height, + }) + viewport.scrollLeft = target.scrollLeft + viewport.scrollTop = target.scrollTop + }) + .on('end', () => { + this.isZooming = false + }) + + tween + .to( + { + scrollLeft: newScrollLeft, + scrollTop: newScrollTop, + width: newWidth, + height: newHeight, + }, + 300, + 'linear' + ) + .play() + } + private bindEvent() { + this.$viewport.on('wheel', this.onWheel) + } + private onWheel = (e: any) => { + e.preventDefault() + + e = e.origEvent + if (!e.altKey) { + return + } + + const delta = e.deltaY > 0 ? 1 : -1 + + const offset = this.$viewport.offset() + this.zoom(-delta * 0.5, { + x: eventPage('x', e) - offset.left, + y: eventPage('y', e) - offset.top, + }) } }