Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 新增擦除功能 #536

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"VNode": true,
Expand Down Expand Up @@ -41,6 +44,7 @@
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"provide": true,
"reactive": true,
"readonly": true,
Expand All @@ -58,7 +62,10 @@
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useId": true,
"useModel": true,
"useSlots": true,
"useTemplateRef": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dayjs": "^1.11.11",
"events": "^3.3.0",
"fabric": "^5.3.0",
"fabric-eraser-brush": "^1.0.1",
"fontfaceobserver": "^2.1.0",
"lodash-es": "^4.17.21",
"number-precision": "^1.6.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import ImageStroke from './plugin/ImageStroke';
import ResizePlugin from './plugin/ResizePlugin';
import LockPlugin from './plugin/LockPlugin';
import AddBaseTypePlugin from './plugin/AddBaseTypePlugin';
import EarsePlugin from './plugin/EarsePlugin';

const AllEditor = {
Editor,
Expand Down Expand Up @@ -75,6 +76,7 @@ const AllEditor = {
ResizePlugin,
LockPlugin,
AddBaseTypePlugin,
EarsePlugin,
};

declare type KuaituEditor = typeof AllEditor;
Expand Down
2 changes: 2 additions & 0 deletions packages/core/ServersPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ class ServersPlugin implements IPluginTempl {
textPaths.push({ id: item.id, path: item.path });
item.path = null;
}
// 设置erasable属性
item.erasable = false;
});

// hookTransform遍历
Expand Down
2 changes: 2 additions & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export { default as ImageStroke } from './plugin/ImageStroke';
export { default as ResizePlugin } from './plugin/ResizePlugin';
export { default as LockPlugin } from './plugin/LockPlugin';
export { default as AddBaseTypePlugin } from './plugin/AddBaseTypePlugin';
export { default as EarsePlugin } from './plugin/EarsePlugin';

import EventType from './eventType';
import Utils from './utils/utils';
import CustomRect from './objects/CustomRect';
Expand Down
3 changes: 2 additions & 1 deletion packages/core/plugin/AddBaseTypePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export default class AddBaseTypePlugin implements IPluginTempl {
const { event = false, scale = false, center = true } = optons || {};
item.set({
id: uuid(),
});
erasable: false,
} as any);
scale && this._toScale(item);
event && this._toEvent(item, event);
this.canvas.add(item);
Expand Down
61 changes: 61 additions & 0 deletions packages/core/plugin/EarsePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { fabric } from 'fabric';
import type { IEditor, IPluginTempl } from '@kuaitu/core';
import 'fabric-eraser-brush';
type IPlugin = Pick<EarsePlugin, 'erase' | 'undoErasing' | 'select'>;

declare module '@kuaitu/core' {
type IEditor = IPlugin;
}

class EarsePlugin implements IPluginTempl {
static pluginName = 'EarsePlugin';
static apis = ['erase', 'undoErasing', 'select'];
public hotkeys: string[] = ['e', 'q', 's'];
static events = ['modeEvent'];
constructor(
public canvas: fabric.Canvas,
public editor: IEditor & { emit: (eventName: string, data: any) => void }
) {
this._init();
}

_init() {
const workspace = this.editor.getWorkspase();
workspace.set({ erasable: false } as any);
this.canvas.freeDrawingBrush = new fabric.EraserBrush(this.canvas);
this.canvas.freeDrawingBrush.width = 20; // 画笔宽度
}
select() {
this.canvas.isDrawingMode = false;
}
erase() {
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingBrush.inverted = false; // 复原擦除
}
undoErasing() {
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingBrush.inverted = true; // 复原擦除
}
// 快捷键扩展回调
hotkeyEvent(eventName: string, e: KeyboardEvent) {
// 擦除功能
if (eventName === 'e' && e.type === 'keydown') {
this.erase();
this.editor.emit('modeEvent', 'earse');
} else if (eventName === 'q' && e.type === 'keydown') {
//复原功能
this.undoErasing();
this.editor.emit('modeEvent', 'undoEarse');
} else if (eventName === 's' && e.type === 'keydown') {
// 框选功能
this.select();
this.editor.emit('modeEvent', 'select');
}
}

destroy() {
console.log('pluginDestroy');
}
}

export default EarsePlugin;
177 changes: 177 additions & 0 deletions src/components/earse.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<template>
<div class="box attr-item-box">
<Divider plain orientation="left"><h4>擦除属性</h4></Divider>
</div>
<div class="flex-view">
<div class="flex-item">
<span class="label">开启</span>
<div class="content">
<Switch v-model="baseAttr.erasable" @on-change="onChange" />
</div>
</div>
</div>
</template>

<script setup name="Earse">
import useSelect from '@/hooks/select';

import 'fabric-eraser-brush';

// isOne 选取一个对象监听获取它的属性值 isMatchType 匹配类型
const { canvasEditor } = useSelect();

const baseAttr = ref({
erasable: false, // 是否可以擦除
});

// 通用属性改变
const onChange = () => {
const activeObject = canvasEditor.canvas.getActiveObject();
activeObject.set({ erasable: baseAttr.value.erasable });
};

const init = (e) => {
const activeObject = canvasEditor.canvas.getActiveObject();
// 不是当前obj,跳过
if (e && e.target && e.target !== activeObject) return;
if (activeObject) {
baseAttr.value.erasable = activeObject.get('erasable');
}
};

onMounted(() => {
canvasEditor.on('selectOne', init);
});
onBeforeUnmount(() => {
canvasEditor.off('selectOne', init);
});
</script>

<style scoped lang="less">
:deep(.ivu-input-number) {
display: block;
width: 100%;
}

:deep(.ivu-color-picker) {
display: block;
}
.ivu-row {
margin-bottom: 8px;
.ivu-col {
position: inherit;
&__box {
display: flex;
align-items: center;
background: #f8f8f8;
border-radius: 4px;
gap: 8px;
}
}

.label {
padding-left: 8px;
}
.content {
flex: 1;
:deep(.--input),
:deep(.ivu-select-selection) {
background-color: transparent;
border: none !important;
box-shadow: none !important;
}
}
}
.font-selector {
:deep(.ivu-select-item) {
padding: 1px 4px;
}

.font-item {
height: 40px;
width: 330px;
background-size: auto 40px;
background-repeat: no-repeat;
}
}

.flex-view {
width: 100%;
margin-bottom: 5px;
padding: 5px;
display: inline-flex;
justify-content: space-between;
border-radius: 5px;
background: #f6f7f9;
}
.flex-item {
display: inline-flex;
flex: 1;
.label {
width: 32px;
height: 32px;
line-height: 32px;
display: inline-block;
font-size: 14px;
// color: #333333;
}
.content {
flex: 1;
align-content: center;
// width: 60px;
}
.slider-box {
width: calc(100% - 50px);
margin-left: 10px;
}
.left {
flex: 1;
}
.right {
flex: 1;
margin-left: 10px;
:deep(.ivu-input-number) {
display: block;
width: 100%;
}
}
:deep(.ivu-slider-wrap) {
margin: 13px 0;
}
:deep(.ivu-radio-group-button) {
display: flex;
flex: 1;
width: 100%;
& .ivu-radio-wrapper {
// width: 48px;
flex: 1;
line-height: 40px;
text-align: center;
svg {
vertical-align: baseline;
}
}
}

:deep(.ivu-btn-group) {
display: flex;
flex: 1;
.ivu-btn {
flex: 1;
}
}

:deep(.ivu-btn-group-large) {
& > .ivu-btn {
font-size: 24px;
flex: 1;
}
}

:deep(.ivu-radio-group-button) {
&.ivu-radio-group-large .ivu-radio-wrapper {
font-size: 24px;
}
}
}
</style>
47 changes: 47 additions & 0 deletions src/components/selectMode.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<Select v-model="mode" style="width: 120px" @on-change="onChange">
<Option v-for="item in modeList" :value="item.value" :key="item.value">{{ item.label }}</Option>
</Select>
</template>

<script setup name="SelectMode" lang="ts">
import useSelect from '@/hooks/select';
import { ref, onMounted } from 'vue';
const mode = ref('select');
const { canvasEditor } = useSelect();

const modeList = [
{
label: '框选模式(s)',
value: 'select',
},
{
label: '擦除模式(e)',
value: 'earse',
},
{
label: '复原模式(q)',
value: 'undoEarse',
},
];
const onChange = () => {
switch (mode.value) {
case 'select':
canvasEditor.select();
break;
case 'earse':
canvasEditor.erase();
break;
case 'undoEarse':
canvasEditor.undoErasing();
break;
default:
break;
}
};
onMounted(() => {
canvasEditor.on('modeEvent', (data) => {
mode.value = data;
});
});
</script>
Loading