Skip to content

Commit

Permalink
feat: 替换fabric为konva
Browse files Browse the repository at this point in the history
  • Loading branch information
JessYan0913 committed Jul 28, 2023
1 parent 5c3bf19 commit 8cdce09
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 307 deletions.
5 changes: 0 additions & 5 deletions main/src/view/canvas/index.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { App, drawingTool, ellipseTool, polylineTool, rectTool, selectTool, triangleTool } from '@pictode/core';
import { HistoryPlugin } from '@pictode/plugin-history';
const containerRef = ref<HTMLDivElement>();
const app = new App();
app.use(new HistoryPlugin());
onMounted(() => {
if (containerRef.value) {
app.mount(containerRef.value);
Expand All @@ -17,8 +14,6 @@ onMounted(() => {
<template>
<div class="wrapper">
<div class="tools">
<button @click="app.undo()">回退</button>
<button @click="app.redo()">恢复</button>
<button @click="app.setTool(selectTool)">选择🖱️</button>
<button @click="app.setTool(rectTool)">矩形⬜️</button>
<button @click="app.setTool(ellipseTool)">圆形⭕️</button>
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@pictode/utils": "workspace:^0.0.1",
"dot": "2.0.0-beta.1",
"fabric": "6.0.0-beta9",
"konva": "^9.2.0",
"rimraf": "^3.0.2",
"roughjs": "^4.5.2"
},
Expand Down
84 changes: 33 additions & 51 deletions packages/core/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,60 @@
import { BaseService } from '@pictode/utils';
import { Canvas, Object as FabricObject, Point } from 'fabric';
import Konva from 'konva';

import { MouseService } from './services/mouse';
import { AppOption, CanvasConfig, EventArgs, ObjectConfig, Plugin, Tool } from './types';
import { DEFAULT_APP_OPTION } from './utils';
import { ChildType, EventArgs, Plugin, Tool } from './types';

export class App extends BaseService<EventArgs> {
public canvas: Canvas;
public mouseService: MouseService;
public stage: Konva.Stage;
public currentTool: Tool | null = null;
public mainLayer: Konva.Layer;

private option: AppOption & { canvasConfig: CanvasConfig; objectConfig: ObjectConfig };
private mouse: MouseService;
private containerElement: HTMLDivElement;
private installedPlugins: Map<string, Plugin> = new Map();

constructor(option?: AppOption) {
constructor() {
super();
this.option = Object.assign({}, DEFAULT_APP_OPTION, option ?? DEFAULT_APP_OPTION);
this.canvas = new Canvas('canvas', {
backgroundColor: this.option.backgroundColor,
this.containerElement = document.createElement('div');
this.containerElement.setAttribute(
'style',
`
width: 100%;
height: 100%;
`
);
this.stage = new Konva.Stage({
container: this.containerElement,
width: 500,
height: 500,
});
this.setCanvasConfig(this.option.canvasConfig);
this.setObjectConfig(this.option.objectConfig);
this.mouseService = new MouseService(this);
}
this.mainLayer = new Konva.Layer();
this.mainLayer.name('pictode:main:layer');
this.stage.add(this.mainLayer);

public get pointer(): Point {
return this.mouseService.pointer;
this.mouse = new MouseService(this);
}

public mount(element: HTMLElement) {
element.appendChild(this.canvas.elements.container);
this.canvas.setDimensions({
element.appendChild(this.containerElement);
this.stage.setSize({
width: element.clientWidth,
height: element.clientHeight,
});
}

public setObjectConfig(objectConfig: ObjectConfig | boolean): void {
this.option.objectConfig = objectConfig;
if (typeof objectConfig === 'boolean') {
this.option.objectConfig.hasControls = objectConfig;
}

const originalDefaults = FabricObject.getDefaults;
FabricObject.getDefaults = (): Record<string, any> => ({
...originalDefaults(),
...(this.option.objectConfig as ObjectConfig),
});
this.render(true);
this.mouse = new MouseService(this);
}

public setCanvasConfig(canvasConfig: CanvasConfig | boolean): void {
this.option.canvasConfig = canvasConfig;
if (typeof canvasConfig === 'boolean') {
this.option.canvasConfig.selection = canvasConfig;
}
Object.entries(this.option.canvasConfig).forEach(([key, value]) => this.canvas.set({ [key]: value }));
public setTool(tool: Tool): void {
console.log('===>', tool);
}

public setTool(curTool: Tool): void {
const oldTool = this.currentTool;
this.currentTool = curTool;
this.canvas.selection = !curTool.drawable;
this.canvas.discardActiveObject();
this.render();
this.emit('tool:changed', { oldTool, curTool });
public add(...children: ChildType[]): void {
this.mainLayer.add(...children);
}

public render(asyncRedraw?: boolean): void {
if (asyncRedraw) {
this.canvas.requestRenderAll();
} else {
this.canvas.renderAll();
}
public render(): void {
this.mainLayer.draw();
}

public use(plugin: Plugin, ...options: any[]): App {
Expand Down Expand Up @@ -125,7 +107,7 @@ export class App extends BaseService<EventArgs> {
public dispose(): void {
this.currentTool = null;
this.disposePlugins(Array.from(this.installedPlugins.keys()));
this.canvas.dispose();
// this.canvas.dispose();
this.removeAllListeners();
}
}
Expand Down
131 changes: 49 additions & 82 deletions packages/core/src/services/mouse.ts
Original file line number Diff line number Diff line change
@@ -1,122 +1,89 @@
import { Object, Point, TPointerEvent, TPointerEventInfo } from 'fabric';

import { App } from '../app';
import { Service } from '../types';

type IMouseEvent = TPointerEventInfo<TPointerEvent>;
import { KonvaMouseEvent, Service } from '../types';

export class MouseService extends Service {
private event?: IMouseEvent;
private targets: Object[];

constructor(app: App) {
super(app);
this.targets = [];
(
['onMouseDown', 'onMouseUp', 'onMouseMove', 'onMouseDoubleClick', 'onMouseOver', 'onMouseOut'] as (keyof this)[]
[
'onMouseDown',
'onMouseUp',
'onMouseMove',
'onMouseDoubleClick',
'onMouseOver',
'onMouseOut',
'onMouseClick',
] as (keyof this)[]
).forEach((method) => {
method = method as keyof MouseService;
this[method] = (this[method] as Function).bind(this);
});

this.app.canvas.on('mouse:down', this.onMouseDown);
this.app.canvas.on('mouse:up', this.onMouseUp);
this.app.canvas.on('mouse:move', this.onMouseMove);
this.app.canvas.on('mouse:dblclick', this.onMouseDoubleClick);
this.app.canvas.on('mouse:over', this.onMouseOver);
this.app.canvas.on('mouse:out', this.onMouseOut);
this.app.stage.on<'mousedown'>('mousedown', this.onMouseDown);
this.app.stage.on('mouseup', this.onMouseUp);
this.app.stage.on('mousemove', this.onMouseMove);
this.app.stage.on('mouseover', this.onMouseOver);
this.app.stage.on('mouseout', this.onMouseOut);
this.app.stage.on('dblclick', this.onMouseDoubleClick);
this.app.stage.on('click', this.onMouseClick);
}

public get pointer(): Point {
if (!this.event) {
return new Point(0, 0);
private onMouseDown(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseDown) {
return;
}
return this.event.pointer;
this.app.currentTool.onMouseDown({ event, app: this.app });
}

public toGlobal(point: Point): Point {
/**
* 变换矩阵包含了画布的平移、缩放和旋转信息
* [ a, b, c, d, e, f]
*
* viewportX = canvasX * a + canvasY * c + e
* viewportY = canvasX * b + canvasY * d + f
*/
const transformMatrix = this.app.canvas.viewportTransform;
if (!transformMatrix) {
return point;
private onMouseUp(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseUp) {
return;
}
const x = point.x * transformMatrix[0] + point.y * transformMatrix[2] + transformMatrix[4];
const y = point.x * transformMatrix[1] + point.y * transformMatrix[3] + transformMatrix[5];
return new Point(x, y);
this.app.currentTool.onMouseUp({ event, app: this.app });
}

private onMouseDown(event: IMouseEvent): void {
this.event = event;
if (!this.app.currentTool) {
private onMouseMove(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseMove) {
return;
}
if (this.app.currentTool.drawable && event.target) {
this.targets.push(event.target);
event.target.set({ evented: false });
}
if (typeof this.app.currentTool.onMouseDown === 'function') {
this.app.currentTool.onMouseDown({ event, app: this.app });
}
this.app.currentTool.onMouseMove({ event, app: this.app });
}

private onMouseUp(event: IMouseEvent): void {
this.event = event;
if (!this.app.currentTool) {
private onMouseOver(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseOver) {
return;
}
this.targets.forEach((obj) => {
obj.set({ evented: true });
});
this.targets = [];
if (typeof this.app.currentTool.onMouseUp === 'function') {
this.app.currentTool.onMouseUp({ event, app: this.app });
}
this.app.currentTool.onMouseOver({ event, app: this.app });
}

private onMouseMove(event: IMouseEvent): void {
this.event = event;
if (!this.app.currentTool) {
private onMouseOut(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseOut) {
return;
}
if (this.app.currentTool.drawable && event.target) {
this.targets.push(event.target);
event.target.set({ evented: false });
}
if (typeof this.app.currentTool.onMouseMove === 'function') {
this.app.currentTool.onMouseMove({ event, app: this.app });
}
this.app.currentTool.onMouseOut({ event, app: this.app });
}

private onMouseDoubleClick(event: IMouseEvent): void {
this.event = event;
if (!this.app.currentTool) {
private onMouseDoubleClick(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseDoubleClick) {
return;
}
if (typeof this.app.currentTool.onMouseDoubleClick === 'function') {
this.app.currentTool.onMouseDoubleClick({ event, app: this.app });
}
this.app.currentTool.onMouseDoubleClick({ event, app: this.app });
}

private onMouseOver(event: IMouseEvent): void {
this.event = event;
}

private onMouseOut(event: IMouseEvent): void {
this.event = event;
private onMouseClick(event: KonvaMouseEvent): void {
if (!this.app.currentTool || !this.app.currentTool.onMouseClick) {
return;
}
this.app.currentTool.onMouseClick({ event, app: this.app });
}

public dispose(): void {
this.app.canvas.off('mouse:down', this.onMouseDown);
this.app.canvas.off('mouse:up', this.onMouseUp);
this.app.canvas.off('mouse:move', this.onMouseMove);
this.app.canvas.off('mouse:dblclick', this.onMouseDoubleClick);
this.app.canvas.off('mouse:over', this.onMouseOver);
this.app.canvas.off('mouse:out', this.onMouseOut);
this.app.stage.off('mousedown', this.onMouseDown);
this.app.stage.off('mouseup', this.onMouseUp);
this.app.stage.off('mousemove', this.onMouseMove);
this.app.stage.off('mouseover', this.onMouseOver);
this.app.stage.off('mouseout', this.onMouseOut);
this.app.stage.off('dblclick', this.onMouseDoubleClick);
this.app.stage.off('click', this.onMouseClick);
}
}
4 changes: 1 addition & 3 deletions packages/core/src/tools/drawing-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { Tool } from '../types';
export const drawingTool: Tool = {
name: 'drawingTool',
drawable: true,
onMouseDown({ app }) {
app.canvas.isDrawingMode = true;
},
onMouseDown() {},
};

export default drawingTool;
50 changes: 4 additions & 46 deletions packages/core/src/tools/ellipse-tool.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,19 @@
import { Point } from 'fabric';

import { PEllipse } from '../customs/ellipse';
import { AppMouseEvent, Tool } from '../types';

import { selectTool } from './select-tool';
import { Tool } from '../types';

class EllipseTool implements Tool {
public name: string = 'ellipseTool';
public drawable: boolean = true;
private startPointer: Point = new Point(0, 0);
private ellipse: PEllipse | null = null;

public onMouseDown({ app }: AppMouseEvent): void {
this.startPointer = app.pointer;
this.ellipse = new PEllipse({
id: Date.now().toString(),
left: this.startPointer.x,
top: this.startPointer.y,
rx: 0,
ry: 0,
fill: 'transparent',
stroke: 'black',
strokeWidth: 2,
});
app.canvas.add(this.ellipse);
}

public onMouseMove({ app }: AppMouseEvent): void {
if (!this.ellipse) {
return;
}

// 计算起点和当前鼠标位置之间的距离
const dx = app.pointer.x - this.startPointer.x;
const dy = app.pointer.y - this.startPointer.y;

// 计算椭圆的宽度和高度的绝对值
const radiusX = Math.abs(dx) / 2;
const radiusY = Math.abs(dy) / 2;

// 根据起点和鼠标位置计算椭圆的中心位置
const centerX = this.startPointer.x + dx / 2;
const centerY = this.startPointer.y + dy / 2;
public onMouseDown(): void {}

this.ellipse.set({ rx: radiusX, ry: radiusY, left: centerX - radiusX, top: centerY - radiusY });
app.render();
}
public onMouseMove(): void {}

public onMouseUp({ app }: AppMouseEvent): void {
app.setTool(selectTool);
this.startPointer.setXY(0, 0);
if (this.ellipse) {
app.canvas.setActiveObject(this.ellipse);
}
this.ellipse = null;
app.render(true);
}
public onMouseUp(): void {}
}

export const ellipseTool = new EllipseTool();
Expand Down
Loading

0 comments on commit 8cdce09

Please sign in to comment.