Skip to content

Commit

Permalink
feat(painter): zoom
Browse files Browse the repository at this point in the history
  • Loading branch information
surunzi committed Jan 30, 2024
1 parent 7c58ed9 commit 69a789b
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 29 deletions.
15 changes: 9 additions & 6 deletions src/image-viewer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,17 @@ export default class ImageViewer extends Component<IOptions> {

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. */
Expand Down
20 changes: 6 additions & 14 deletions src/painter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export default class Painter extends Component<IOptions> {
private $tools: $.$
private $canvas: $.$
private $viewport: $.$
private viewport: HTMLDivElement
private $body: $.$
private canvas: HTMLCanvasElement
private ctx: CanvasRenderingContext2D
Expand All @@ -56,17 +55,13 @@ export default class Painter extends Component<IOptions> {
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

this.resizeSensor = new ResizeSensor(container)
this.canvasResizeSenor = new ResizeSensor(this.canvas)

this.resetViewport()
this.centerCanvas()

this.addLayer()
this.activeLayer = this.layers[0]

Expand All @@ -77,6 +72,9 @@ export default class Painter extends Component<IOptions> {
this.hand = new Hand(this)
this.zoom = new Zoom(this)

this.resetViewport()
this.hand.centerCanvas()

this.useTool(this.options.tool)
}
destroy() {
Expand All @@ -102,6 +100,7 @@ export default class Painter extends Component<IOptions> {

if (tool) {
this.currentTool = tool
tool.onUse()
$tools.find(c('.tool')).rmClass(c('selected'))
$tools.find(`${c('.tool')}[data-tool="${name}"]`).addClass(c('selected'))
}
Expand Down Expand Up @@ -200,7 +199,7 @@ export default class Painter extends Component<IOptions> {
const { width: viewportWidth, height: viewportHeight } =
this.getViewportSize()
if (canvasWidth < viewportWidth && canvasHeight < viewportHeight) {
this.centerCanvas()
this.hand.centerCanvas()
}
}
private resetViewport = () => {
Expand All @@ -216,14 +215,7 @@ export default class Painter extends Component<IOptions> {
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
Expand Down
2 changes: 1 addition & 1 deletion src/painter/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
}

.main-canvas {
transition: width 0.3s, height 0.3s;
image-rendering: pixelated;
}

.theme-dark {
Expand Down
127 changes: 119 additions & 8 deletions src/painter/tools.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()
}
Expand All @@ -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,
})
Expand All @@ -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,
})
}
}

0 comments on commit 69a789b

Please sign in to comment.