mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
248 lines
7.6 KiB
TypeScript
248 lines
7.6 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
// https://codepen.io/amsunny/pen/XWGLxye
|
|
// canvas.viewportTransform = [ scaleX, skewX, skewY, scaleY, translateX, translateY ]
|
|
|
|
import type { fabric } from "fabric";
|
|
import Hammer from "hammerjs";
|
|
|
|
import { isDesktop } from "$lib/tslib/platform";
|
|
|
|
import type { Size } from "../types";
|
|
import { getBoundingBoxSize, redraw } from "./lib";
|
|
|
|
const minScale = 0.5;
|
|
const maxScale = 5;
|
|
let zoomScale = 1;
|
|
export let currentScale = 1;
|
|
|
|
export const enableZoom = (canvas: fabric.Canvas) => {
|
|
canvas.on("mouse:wheel", onMouseWheel);
|
|
};
|
|
|
|
export const enablePan = (canvas: fabric.Canvas) => {
|
|
canvas.on("mouse:down", onMouseDown);
|
|
canvas.on("mouse:move", onMouseMove);
|
|
canvas.on("mouse:up", onMouseUp);
|
|
};
|
|
|
|
export const disableZoom = (canvas: fabric.Canvas) => {
|
|
canvas.off("mouse:wheel", onMouseWheel);
|
|
};
|
|
|
|
export const disablePan = (canvas: fabric.Canvas) => {
|
|
canvas.off("mouse:down", onMouseDown);
|
|
canvas.off("mouse:move", onMouseMove);
|
|
canvas.off("mouse:up", onMouseUp);
|
|
};
|
|
|
|
export const zoomIn = (canvas: fabric.Canvas): void => {
|
|
let zoom = canvas.getZoom();
|
|
zoom = Math.min(maxScale, zoom * 1.1);
|
|
canvas.zoomToPoint({ x: canvas.width! / 2, y: canvas.height! / 2 }, zoom);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
export const zoomOut = (canvas): void => {
|
|
let zoom = canvas.getZoom();
|
|
zoom = Math.max(minScale, zoom / 1.1);
|
|
canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, zoom / 1.1);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
export const zoomReset = (canvas: fabric.Canvas): void => {
|
|
zoomResetInner(canvas);
|
|
// reset again to update the viewportTransform
|
|
zoomResetInner(canvas);
|
|
};
|
|
|
|
const zoomResetInner = (canvas: fabric.Canvas): void => {
|
|
fitCanvasVptScale(canvas);
|
|
const vpt = canvas.viewportTransform!;
|
|
canvas.zoomToPoint({ x: canvas.width! / 2, y: canvas.height! / 2 }, vpt[0]);
|
|
};
|
|
|
|
export const enablePinchZoom = (canvas: fabric.Canvas) => {
|
|
const hammer = new Hammer(upperCanvasElement(canvas));
|
|
hammer.get("pinch").set({ enable: true });
|
|
hammer.on("pinchin pinchout", ev => {
|
|
currentScale = Math.min(Math.max(minScale, ev.scale * zoomScale), maxScale);
|
|
canvas.zoomToPoint({ x: canvas.width! / 2, y: canvas.height! / 2 }, currentScale);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
});
|
|
hammer.on("pinchend pinchcancel", () => {
|
|
zoomScale = currentScale;
|
|
});
|
|
};
|
|
|
|
function upperCanvasElement(canvas: fabric.Canvas): HTMLElement {
|
|
return canvas["upperCanvasEl"] as HTMLElement;
|
|
}
|
|
|
|
export const disablePinchZoom = (canvas: fabric.Canvas) => {
|
|
const hammer = new Hammer(upperCanvasElement(canvas));
|
|
hammer.get("pinch").set({ enable: false });
|
|
hammer.off("pinch pinchmove pinchend pinchcancel");
|
|
};
|
|
|
|
export const onResize = (canvas: fabric.Canvas) => {
|
|
setCanvasSize(canvas);
|
|
zoomReset(canvas);
|
|
};
|
|
|
|
const onMouseWheel = (opt) => {
|
|
const canvas = globalThis.canvas;
|
|
const delta = opt.e.deltaY;
|
|
let zoom = canvas.getZoom();
|
|
zoom *= 0.999 ** delta;
|
|
zoom = Math.max(minScale, Math.min(zoom, maxScale));
|
|
canvas.zoomToPoint({ x: opt.pointer.x, y: opt.pointer.y }, zoom);
|
|
opt.e.preventDefault();
|
|
opt.e.stopPropagation();
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
const onMouseDown = (opt) => {
|
|
const canvas = globalThis.canvas;
|
|
canvas.discardActiveObject();
|
|
const { e } = opt;
|
|
const clientX = e.type === "touchstart" ? e.touches[0].clientX : e.clientX;
|
|
const clientY = e.type === "touchstart" ? e.touches[0].clientY : e.clientY;
|
|
canvas.lastPosX = clientX;
|
|
canvas.lastPosY = clientY;
|
|
redraw(canvas);
|
|
};
|
|
|
|
export const onMouseMove = (opt) => {
|
|
const canvas = globalThis.canvas;
|
|
canvas.discardActiveObject();
|
|
if (!canvas.viewportTransform) {
|
|
return;
|
|
}
|
|
|
|
// handle pinch zoom and pan for mobile devices
|
|
if (onPinchZoom(opt)) {
|
|
return;
|
|
}
|
|
|
|
onDrag(canvas, opt);
|
|
};
|
|
|
|
export const onPinchZoom = (opt): boolean => {
|
|
const { e } = opt;
|
|
const canvas = globalThis.canvas;
|
|
if ((e.type === "touchmove") && (e.touches.length > 1)) {
|
|
onDrag(canvas, opt);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const onDrag = (canvas, opt) => {
|
|
const { e } = opt;
|
|
const clientX = e.type === "touchmove" ? e.touches[0].clientX : e.clientX;
|
|
const clientY = e.type === "touchmove" ? e.touches[0].clientY : e.clientY;
|
|
const vpt = canvas.viewportTransform;
|
|
|
|
vpt[4] += clientX - canvas.lastPosX;
|
|
vpt[5] += clientY - canvas.lastPosY;
|
|
canvas.lastPosX += clientX - canvas.lastPosX;
|
|
canvas.lastPosY += clientY - canvas.lastPosY;
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
export const onWheelDrag = (canvas: fabric.Canvas, event: WheelEvent) => {
|
|
const deltaX = event.deltaX;
|
|
const deltaY = event.deltaY;
|
|
const vpt = canvas.viewportTransform!;
|
|
canvas["lastPosX"] = event.clientX;
|
|
canvas["lastPosY"] = event.clientY;
|
|
|
|
vpt[4] -= deltaX;
|
|
vpt[5] -= deltaY;
|
|
|
|
canvas["lastPosX"] -= deltaX;
|
|
canvas["lastPosY"] -= deltaY;
|
|
canvas.setViewportTransform(vpt);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
export const onWheelDragX = (canvas: fabric.Canvas, event: WheelEvent) => {
|
|
const delta = event.deltaY;
|
|
const vpt = canvas.viewportTransform!;
|
|
(canvas as any).lastPosY = event.clientY!;
|
|
vpt[4] -= delta;
|
|
(canvas as any).lastPosX -= delta;
|
|
canvas.setViewportTransform(vpt);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
const onMouseUp = () => {
|
|
const canvas = globalThis.canvas;
|
|
canvas.setViewportTransform(canvas.viewportTransform);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
export const constrainBoundsAroundBgImage = (canvas: fabric.Canvas) => {
|
|
const boundingBox = getBoundingBoxSize();
|
|
const ioImage = document.getElementById("image") as HTMLImageElement;
|
|
|
|
const width = boundingBox.width * canvas.getZoom();
|
|
const height = boundingBox.height * canvas.getZoom();
|
|
|
|
const left = canvas.viewportTransform![4];
|
|
const top = canvas.viewportTransform![5];
|
|
|
|
ioImage.width = width;
|
|
ioImage.height = height;
|
|
ioImage.style.left = `${left}px`;
|
|
ioImage.style.top = `${top}px`;
|
|
};
|
|
|
|
export const setCanvasSize = (canvas: fabric.Canvas) => {
|
|
const width = window.innerWidth - 39;
|
|
let height = window.innerHeight;
|
|
height = isDesktop() ? height - 76 : height - 46;
|
|
canvas.setHeight(height);
|
|
canvas.setWidth(width);
|
|
redraw(canvas);
|
|
};
|
|
|
|
const fitCanvasVptScale = (canvas: fabric.Canvas) => {
|
|
const boundingBox = getBoundingBoxSize();
|
|
const ratio = getScaleRatio(boundingBox);
|
|
const vpt = canvas.viewportTransform!;
|
|
|
|
const boundingBoxWidth = boundingBox.width * canvas.getZoom();
|
|
const boundingBoxHeight = boundingBox.height * canvas.getZoom();
|
|
const center = canvas.getCenter();
|
|
const translateX = center.left - (boundingBoxWidth / 2);
|
|
const translateY = center.top - (boundingBoxHeight / 2);
|
|
|
|
vpt[0] = ratio;
|
|
vpt[3] = ratio;
|
|
vpt[4] = Math.max(1, translateX);
|
|
vpt[5] = Math.max(1, translateY);
|
|
|
|
canvas.setViewportTransform(canvas.viewportTransform!);
|
|
constrainBoundsAroundBgImage(canvas);
|
|
redraw(canvas);
|
|
};
|
|
|
|
const getScaleRatio = (boundingBox: Size) => {
|
|
const h1 = boundingBox.height!;
|
|
const w1 = boundingBox.width!;
|
|
const w2 = innerWidth - 42;
|
|
let h2 = window.innerHeight;
|
|
h2 = isDesktop() ? h2 - 79 : h2 - 48;
|
|
return Math.min(w2 / w1, h2 / h1);
|
|
};
|