feat:增加图片尺寸拖拽组件
This commit is contained in:
@@ -56,3 +56,18 @@
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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([])
|
||||
|
||||
@@ -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<string, HTMLImageElement>;
|
||||
private options: Required<IEditorOption>
|
||||
private imageCache: Map<string, HTMLImageElement>
|
||||
private resizerSelection: HTMLDivElement
|
||||
private resizerHandleList: HTMLDivElement[]
|
||||
|
||||
constructor(ctx: CanvasRenderingContext2D) {
|
||||
constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, options: Required<IEditorOption>) {
|
||||
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<string, HTMLImageElement> {
|
||||
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!)) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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],
|
||||
|
||||
@@ -9,3 +9,8 @@ export interface IDrawImagePayload {
|
||||
height: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IImageParticleCreateResult {
|
||||
resizerSelection: HTMLDivElement;
|
||||
resizerHandleList: HTMLDivElement[];
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface ICurrentPosition {
|
||||
index: number;
|
||||
isImage?: boolean;
|
||||
isDirectHit?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user