From 13e5c83fdd33a90dbe789a15bfd86f5678e1678f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BA=91=E9=A3=9E?= Date: Sun, 21 Nov 2021 18:45:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=B0=BA=E5=AF=B8=E6=8B=96=E6=8B=BD=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/editor/assets/css/index.css | 15 ++++ src/editor/core/cursor/Cursor.ts | 16 ----- src/editor/core/draw/Draw.ts | 8 ++- .../core/draw/particle/ImageParticle.ts | 68 +++++++++++++++++-- src/editor/core/event/CanvasEvent.ts | 23 ++++++- src/editor/core/event/GlobalEvent.ts | 4 ++ src/editor/core/position/Position.ts | 46 +++++++------ src/editor/index.ts | 2 + src/editor/interface/Draw.ts | 5 ++ src/editor/interface/Editor.ts | 2 + src/editor/interface/Position.ts | 5 ++ 11 files changed, 147 insertions(+), 47 deletions(-) create mode 100644 src/editor/interface/Position.ts diff --git a/src/editor/assets/css/index.css b/src/editor/assets/css/index.css index 02c31fe..8bf6c07 100644 --- a/src/editor/assets/css/index.css +++ b/src/editor/assets/css/index.css @@ -55,4 +55,19 @@ to { opacity: 1 } +} + +.resizer-selection { + position: absolute; + border: 1px solid; +} + +.resizer-selection>div { + position: absolute; + width: 10px; + height: 10px; + box-shadow: 0 1px 4px 0 rgb(0 0 0 / 30%); + border-radius: 5px; + border: 2px solid #ffffff; + box-sizing: border-box; } \ No newline at end of file diff --git a/src/editor/core/cursor/Cursor.ts b/src/editor/core/cursor/Cursor.ts index 74d3af7..6d40a65 100644 --- a/src/editor/core/cursor/Cursor.ts +++ b/src/editor/core/cursor/Cursor.ts @@ -1,24 +1,18 @@ import { CURSOR_AGENT_HEIGHT } from "../../dataset/constant/Cursor" import { Draw } from "../draw/Draw" import { CanvasEvent } from "../event/CanvasEvent" -import { Position } from "../position/Position" -import { RangeManager } from "../range/RangeManager" import { CursorAgent } from "./CursorAgent" export class Cursor { private canvas: HTMLCanvasElement private draw: Draw - private range: RangeManager - private position: Position private cursorDom: HTMLDivElement private cursorAgent: CursorAgent constructor(canvas: HTMLCanvasElement, draw: Draw, canvasEvent: CanvasEvent) { this.canvas = canvas this.draw = draw - this.range = this.draw.getRange() - this.position = this.draw.getPosition() this.cursorDom = document.createElement('div') this.cursorDom.classList.add('cursor') @@ -34,16 +28,6 @@ export class Cursor { return this.cursorAgent.getAgentCursorDom() } - public setCursorPosition(evt: MouseEvent) { - const positionIndex = this.position.getPositionByXY(evt.offsetX, evt.offsetY) - if (~positionIndex) { - this.range.setRange(positionIndex, positionIndex) - setTimeout(() => { - this.draw.render({ curIndex: positionIndex, isSubmitHistory: false }) - }) - } - } - public drawCursor() { const cursorPosition = this.draw.getPosition().getCursorPosition() if (!cursorPosition) return diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 9d92e96..defe96e 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -67,7 +67,7 @@ export class Draw { this.underline = new Underline(ctx, options) this.strikeout = new Strikeout(ctx, options) this.highlight = new Highlight(ctx, options) - this.imageParticle = new ImageParticle(ctx) + this.imageParticle = new ImageParticle(canvas, ctx, options) const canvasEvent = new CanvasEvent(canvas, this) this.cursor = new Cursor(canvas, this, canvasEvent) @@ -110,6 +110,10 @@ export class Draw { return this.cursor } + public getImageParticle(): ImageParticle { + return this.imageParticle + } + public getRowCount(): number { return this.rowCount } @@ -153,7 +157,7 @@ export class Draw { public render(payload?: IDrawOption) { let { curIndex, isSubmitHistory = true, isSetCursor = true } = payload || {} - // 清除光标 + // 清除光标等副作用 this.cursor.recoveryCursor() this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) this.position.setPositionList([]) diff --git a/src/editor/core/draw/particle/ImageParticle.ts b/src/editor/core/draw/particle/ImageParticle.ts index 42b2eab..d93fad3 100644 --- a/src/editor/core/draw/particle/ImageParticle.ts +++ b/src/editor/core/draw/particle/ImageParticle.ts @@ -1,20 +1,80 @@ -import { IElement } from "../../../interface/Element"; +import { IImageParticleCreateResult } from "../../../interface/Draw"; +import { IEditorOption } from "../../../interface/Editor"; +import { IElement, IElementPosition } from "../../../interface/Element"; export class ImageParticle { + private canvas: HTMLCanvasElement private ctx: CanvasRenderingContext2D - private imageCache: Map; + private options: Required + private imageCache: Map + private resizerSelection: HTMLDivElement + private resizerHandleList: HTMLDivElement[] - constructor(ctx: CanvasRenderingContext2D) { + constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, options: Required) { + this.canvas = canvas this.ctx = ctx + this.options = options this.imageCache = new Map() + const { resizerSelection, resizerHandleList } = this.createResizerDom() + this.resizerSelection = resizerSelection + this.resizerHandleList = resizerHandleList + } + + private createResizerDom(): IImageParticleCreateResult { + const resizerSelection = document.createElement('div') + resizerSelection.classList.add('resizer-selection') + resizerSelection.style.display = 'none' + resizerSelection.style.borderColor = this.options.resizerColor + const resizerHandleList: HTMLDivElement[] = [] + for (let i = 0; i < 8; i++) { + const handleDom = document.createElement('div') + handleDom.style.background = this.options.resizerColor + handleDom.classList.add(`handle-${i}`) + resizerSelection.append(handleDom) + resizerHandleList.push(handleDom) + } + this.canvas.parentNode!.append(resizerSelection) + return { resizerSelection, resizerHandleList } } public getImageCache(): Map { return this.imageCache } - render(element: IElement, x: number, y: number) { + public drawResizer(element: IElement, position: IElementPosition) { + const { coordinate: { leftTop: [left, top] } } = position + const width = element.width! + const height = element.height! + const handleSize = this.options.resizerSize + // 边框 + this.resizerSelection.style.left = `${left}px` + this.resizerSelection.style.top = `${top}px` + this.resizerSelection.style.width = `${element.width}px` + this.resizerSelection.style.height = `${element.height}px` + // handle + for (let i = 0; i < 8; i++) { + const left = i === 0 || i === 6 || i === 7 + ? -handleSize + : i === 1 || i === 5 + ? width / 2 + : width - handleSize + const top = i === 0 || i === 1 || i === 2 + ? -handleSize + : i === 3 || i === 7 + ? height / 2 - handleSize + : height - handleSize + this.resizerHandleList[i].style.left = `${left}px` + this.resizerHandleList[i].style.top = `${top}px` + } + this.resizerSelection.style.display = 'block' + } + + public clearResizer() { + this.resizerSelection.style.display = 'none' + } + + public render(element: IElement, x: number, y: number) { const width = element.width! const height = element.height! if (this.imageCache.has(element.id!)) { diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index 3bb661a..dd65e9f 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -5,6 +5,7 @@ import { IElement } from "../../interface/Element" import { writeTextByElementList } from "../../utils/clipboard" import { Cursor } from "../cursor/Cursor" import { Draw } from "../draw/Draw" +import { ImageParticle } from "../draw/particle/ImageParticle" import { HistoryManager } from "../history/HistoryManager" import { Position } from "../position/Position" import { RangeManager } from "../range/RangeManager" @@ -21,6 +22,7 @@ export class CanvasEvent { private range: RangeManager private cursor: Cursor | null private historyManager: HistoryManager + private imageParticle: ImageParticle constructor(canvas: HTMLCanvasElement, draw: Draw) { this.isAllowDrag = false @@ -33,12 +35,12 @@ export class CanvasEvent { this.position = this.draw.getPosition() this.range = this.draw.getRange() this.historyManager = this.draw.getHistoryManager() + this.imageParticle = this.draw.getImageParticle() } public register() { // 延迟加载 this.cursor = this.draw.getCursor() - this.canvas.addEventListener('mousedown', this.cursor.setCursorPosition.bind(this)) this.canvas.addEventListener('mousedown', this.mousedown.bind(this)) this.canvas.addEventListener('mouseleave', this.mouseleave.bind(this)) this.canvas.addEventListener('mousemove', this.mousemove.bind(this)) @@ -68,7 +70,7 @@ export class CanvasEvent { public mousemove(evt: MouseEvent) { if (!this.isAllowDrag) return // 结束位置 - const endIndex = this.draw.getPosition().getPositionByXY(evt.offsetX, evt.offsetY) + const { index: endIndex } = this.draw.getPosition().getPositionByXY(evt.offsetX, evt.offsetY) let end = ~endIndex ? endIndex : 0 // 开始位置 let start = this.mouseDownStartIndex @@ -86,7 +88,22 @@ export class CanvasEvent { public mousedown(evt: MouseEvent) { this.isAllowDrag = true - this.mouseDownStartIndex = this.draw.getPosition().getPositionByXY(evt.offsetX, evt.offsetY) || 0 + const { index, isDirectHit, isImage } = this.draw.getPosition().getPositionByXY(evt.offsetX, evt.offsetY) + // 记录选区开始位置 + this.mouseDownStartIndex = index + // 绘制 + const isDirectHitImage = isDirectHit && isImage + if (~index) { + this.range.setRange(index, index) + this.draw.render({ curIndex: index, isSubmitHistory: false, isSetCursor: !isDirectHitImage }) + } + // 图片尺寸拖拽组件 + this.imageParticle.clearResizer() + if (isDirectHitImage) { + const elementList = this.draw.getElementList() + const positionList = this.position.getPositionList() + this.imageParticle.drawResizer(elementList[index], positionList[index]) + } } public mouseleave(evt: MouseEvent) { diff --git a/src/editor/core/event/GlobalEvent.ts b/src/editor/core/event/GlobalEvent.ts index f304587..80eb979 100644 --- a/src/editor/core/event/GlobalEvent.ts +++ b/src/editor/core/event/GlobalEvent.ts @@ -2,6 +2,7 @@ import { EDITOR_COMPONENT } from "../../dataset/constant/Editor" import { findParent } from "../../utils" import { Cursor } from "../cursor/Cursor" import { Draw } from "../draw/Draw" +import { ImageParticle } from "../draw/particle/ImageParticle" import { RangeManager } from "../range/RangeManager" import { CanvasEvent } from "./CanvasEvent" @@ -12,6 +13,7 @@ export class GlobalEvent { private cursor: Cursor | null private canvasEvent: CanvasEvent private range: RangeManager + private imageParticle: ImageParticle constructor(canvas: HTMLCanvasElement, draw: Draw, canvasEvent: CanvasEvent) { this.canvas = canvas @@ -19,6 +21,7 @@ export class GlobalEvent { this.canvasEvent = canvasEvent this.cursor = null this.range = draw.getRange() + this.imageParticle = draw.getImageParticle() } register() { @@ -56,6 +59,7 @@ export class GlobalEvent { this.cursor.recoveryCursor() this.range.recoveryRangeStyle() this.range.setRange(0, 0) + this.imageParticle.clearResizer() } setDragState() { diff --git a/src/editor/core/position/Position.ts b/src/editor/core/position/Position.ts index e742d38..602aa5a 100644 --- a/src/editor/core/position/Position.ts +++ b/src/editor/core/position/Position.ts @@ -1,5 +1,7 @@ +import { ElementType } from "../.." import { ZERO } from "../../dataset/constant/Common" import { IElement, IElementPosition } from "../../interface/Element" +import { ICurrentPosition } from "../../interface/Position" import { Draw } from "../draw/Draw" export class Position { @@ -34,45 +36,45 @@ export class Position { return this.cursorPosition } - public getPositionByXY(x: number, y: number): number { + public getPositionByXY(x: number, y: number): ICurrentPosition { this.elementList = this.draw.getElementList() - let isTextArea = false for (let j = 0; j < this.positionList.length; j++) { const { index, coordinate: { leftTop, rightTop, leftBottom } } = this.positionList[j]; // 命中元素 if (leftTop[0] <= x && rightTop[0] >= x && leftTop[1] <= y && leftBottom[1] >= y) { let curPostionIndex = j - // 判断是否元素中间前后 + const element = this.elementList[j] + // 图片区域均为命中 + if (element.type === ElementType.IMAGE) { + return { index: curPostionIndex, isDirectHit: true, isImage: true } + } + // 判断是否在文字中间前后 if (this.elementList[index].value !== ZERO) { const valueWidth = rightTop[0] - leftTop[0] if (x < leftTop[0] + valueWidth / 2) { curPostionIndex = j - 1 } } - isTextArea = true - return curPostionIndex + return { index: curPostionIndex } } } // 非命中区域 - if (!isTextArea) { - let isLastArea = false - let curPostionIndex = -1 - // 判断所属行是否存在元素 - const firstLetterList = this.positionList.filter(p => p.isLastLetter) - for (let j = 0; j < firstLetterList.length; j++) { - const { index, coordinate: { leftTop, leftBottom } } = firstLetterList[j] - if (y > leftTop[1] && y <= leftBottom[1]) { - curPostionIndex = index - isLastArea = true - break - } + let isLastArea = false + let curPostionIndex = -1 + // 判断所属行是否存在元素 + const firstLetterList = this.positionList.filter(p => p.isLastLetter) + for (let j = 0; j < firstLetterList.length; j++) { + const { index, coordinate: { leftTop, leftBottom } } = firstLetterList[j] + if (y > leftTop[1] && y <= leftBottom[1]) { + curPostionIndex = index + isLastArea = true + break } - if (!isLastArea) { - return this.positionList.length - 1 - } - return curPostionIndex } - return -1 + if (!isLastArea) { + return { index: this.positionList.length - 1 } + } + return { index: curPostionIndex } } } \ No newline at end of file diff --git a/src/editor/index.ts b/src/editor/index.ts index de5694c..82b30f0 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -29,6 +29,8 @@ export default class Editor { searchMatchAlpha: 0.6, searchMatchColor: '#FFFF00', highlightAlpha: 0.6, + resizerColor: '#4182D9', + resizerSize: 5, marginIndicatorSize: 35, marginIndicatorColor: '#BABABA', margins: [100, 120, 100, 120], diff --git a/src/editor/interface/Draw.ts b/src/editor/interface/Draw.ts index 521d7f9..80d8263 100644 --- a/src/editor/interface/Draw.ts +++ b/src/editor/interface/Draw.ts @@ -8,4 +8,9 @@ export interface IDrawImagePayload { width: number; height: number; value: string; +} + +export interface IImageParticleCreateResult { + resizerSelection: HTMLDivElement; + resizerHandleList: HTMLDivElement[]; } \ No newline at end of file diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index 285c2d1..8afaa27 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -11,6 +11,8 @@ export interface IEditorOption { searchMatchColor?: string; searchMatchAlpha?: number; highlightAlpha?: number; + resizerColor?: string; + resizerSize?: number; marginIndicatorSize?: number; marginIndicatorColor?: string, margins?: [top: number, right: number, bootom: number, left: number] diff --git a/src/editor/interface/Position.ts b/src/editor/interface/Position.ts new file mode 100644 index 0000000..89ab4a8 --- /dev/null +++ b/src/editor/interface/Position.ts @@ -0,0 +1,5 @@ +export interface ICurrentPosition { + index: number; + isImage?: boolean; + isDirectHit?: boolean; +} \ No newline at end of file