feat:优化渲染性能
This commit is contained in:
@@ -20,6 +20,7 @@ import { Strikeout } from "./richtext/Strikeout"
|
||||
import { Underline } from "./richtext/Underline"
|
||||
import { ElementType } from "../../dataset/enum/Element"
|
||||
import { ImageParticle } from "./particle/ImageParticle"
|
||||
import { TextParticle } from "./particle/TextParticle"
|
||||
|
||||
export class Draw {
|
||||
|
||||
@@ -40,8 +41,9 @@ export class Draw {
|
||||
private highlight: Highlight
|
||||
private historyManager: HistoryManager
|
||||
private imageParticle: ImageParticle
|
||||
private textParticle: TextParticle
|
||||
|
||||
private rowCount: number
|
||||
private rowList: IRow[]
|
||||
private painterStyle: IElementStyle | null
|
||||
private searchMatchList: number[][] | null
|
||||
|
||||
@@ -68,6 +70,7 @@ export class Draw {
|
||||
this.strikeout = new Strikeout(ctx, options)
|
||||
this.highlight = new Highlight(ctx, options)
|
||||
this.imageParticle = new ImageParticle(canvas, ctx, options, this)
|
||||
this.textParticle = new TextParticle(ctx)
|
||||
|
||||
const canvasEvent = new CanvasEvent(canvas, this)
|
||||
this.cursor = new Cursor(canvas, this, canvasEvent)
|
||||
@@ -75,7 +78,7 @@ export class Draw {
|
||||
const globalEvent = new GlobalEvent(canvas, this, canvasEvent)
|
||||
globalEvent.register()
|
||||
|
||||
this.rowCount = 0
|
||||
this.rowList = []
|
||||
this.painterStyle = null
|
||||
this.searchMatchList = null
|
||||
|
||||
@@ -115,7 +118,7 @@ export class Draw {
|
||||
}
|
||||
|
||||
public getRowCount(): number {
|
||||
return this.rowCount
|
||||
return this.rowList.length
|
||||
}
|
||||
|
||||
public getDataURL(): string {
|
||||
@@ -155,25 +158,13 @@ export class Draw {
|
||||
return `${el.italic ? 'italic ' : ''}${el.bold ? 'bold ' : ''}${el.size || defaultSize}px ${el.font || defaultFont}`
|
||||
}
|
||||
|
||||
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([])
|
||||
const positionList = this.position.getPositionList()
|
||||
// 基础信息
|
||||
private computeRowList() {
|
||||
const { defaultSize } = this.options
|
||||
const canvasRect = this.canvas.getBoundingClientRect()
|
||||
// 绘制背景
|
||||
this.background.render(canvasRect)
|
||||
// 绘制页边距
|
||||
const { width } = canvasRect
|
||||
const { margins, defaultRowMargin, defaultBasicRowMarginHeight } = this.options
|
||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||
const rightTopPoint: [number, number] = [width - margins[1], margins[0]]
|
||||
this.margin.render(canvasRect)
|
||||
// 计算行信息
|
||||
const rowList: IRow[] = []
|
||||
if (this.elementList.length) {
|
||||
rowList.push({
|
||||
@@ -213,13 +204,17 @@ export class Draw {
|
||||
this.ctx.font = this.getFont(element)
|
||||
const fontMetrics = this.ctx.measureText(element.value)
|
||||
metrics.width = fontMetrics.width
|
||||
metrics.boundingBoxAscent = i === 0 ? defaultSize : fontMetrics.actualBoundingBoxAscent
|
||||
metrics.boundingBoxAscent = element.value === ZERO ? defaultSize : fontMetrics.actualBoundingBoxAscent
|
||||
metrics.boundingBoxDescent = fontMetrics.actualBoundingBoxDescent
|
||||
}
|
||||
const ascent = metrics.boundingBoxAscent + rowMargin
|
||||
const descent = metrics.boundingBoxDescent + rowMargin
|
||||
const height = ascent + descent
|
||||
const rowElement: IRowElement = { ...element, metrics }
|
||||
const rowElement: IRowElement = {
|
||||
...element,
|
||||
metrics,
|
||||
style: this.ctx.font
|
||||
}
|
||||
// 超过限定宽度
|
||||
if (curRow.width + metrics.width > innerWidth || (i !== 0 && element.value === ZERO)) {
|
||||
rowList.push({
|
||||
@@ -243,12 +238,39 @@ export class Draw {
|
||||
}
|
||||
this.ctx.restore()
|
||||
}
|
||||
this.rowList = rowList
|
||||
}
|
||||
|
||||
public render(payload?: IDrawOption) {
|
||||
let {
|
||||
curIndex,
|
||||
isSubmitHistory = true,
|
||||
isSetCursor = true,
|
||||
isComputeRowList = true
|
||||
} = payload || {}
|
||||
// 清除光标等副作用
|
||||
this.cursor.recoveryCursor()
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
this.position.setPositionList([])
|
||||
const positionList = this.position.getPositionList()
|
||||
// 基础信息
|
||||
const canvasRect = this.canvas.getBoundingClientRect()
|
||||
// 绘制背景
|
||||
this.background.render(canvasRect)
|
||||
// 绘制页边距
|
||||
const { margins } = this.options
|
||||
const leftTopPoint: [number, number] = [margins[3], margins[0]]
|
||||
this.margin.render(canvasRect)
|
||||
// 计算行信息
|
||||
if (isComputeRowList) {
|
||||
this.computeRowList()
|
||||
}
|
||||
// 渲染元素
|
||||
let x = leftTopPoint[0]
|
||||
let y = leftTopPoint[1]
|
||||
let index = 0
|
||||
for (let i = 0; i < rowList.length; i++) {
|
||||
const curRow = rowList[i]
|
||||
for (let i = 0; i < this.rowList.length; i++) {
|
||||
const curRow = this.rowList[i]
|
||||
// 计算行偏移量(行居左、居中、居右)
|
||||
if (curRow.rowFlex && curRow.rowFlex !== RowFlex.LEFT) {
|
||||
const canvasInnerWidth = this.canvas.width - margins[1] - margins[3]
|
||||
@@ -259,15 +281,8 @@ export class Draw {
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < curRow.elementList.length; j++) {
|
||||
this.ctx.save()
|
||||
const element = curRow.elementList[j]
|
||||
const metrics = element.metrics
|
||||
if (!element.type || element.type === ElementType.TEXT) {
|
||||
this.ctx.font = this.getFont(element)
|
||||
if (element.color) {
|
||||
this.ctx.fillStyle = element.color
|
||||
}
|
||||
}
|
||||
const offsetY = element.type === ElementType.IMAGE
|
||||
? curRow.ascent - element.height!
|
||||
: curRow.ascent
|
||||
@@ -301,9 +316,10 @@ export class Draw {
|
||||
}
|
||||
// 元素绘制
|
||||
if (element.type === ElementType.IMAGE) {
|
||||
this.textParticle.complete()
|
||||
this.imageParticle.render(element, x, y + offsetY)
|
||||
} else {
|
||||
this.ctx.fillText(element.value, x, y + offsetY)
|
||||
this.textParticle.record(element, x, y + offsetY)
|
||||
}
|
||||
// 选区绘制
|
||||
const { startIndex, endIndex } = this.range.getRange()
|
||||
@@ -312,11 +328,12 @@ export class Draw {
|
||||
}
|
||||
index++
|
||||
x += metrics.width
|
||||
this.ctx.restore()
|
||||
}
|
||||
this.textParticle.complete()
|
||||
x = leftTopPoint[0]
|
||||
y += curRow.height
|
||||
}
|
||||
|
||||
// 搜索匹配绘制
|
||||
if (this.searchMatchList) {
|
||||
this.search.render()
|
||||
@@ -336,9 +353,12 @@ export class Draw {
|
||||
const height = Math.ceil(leftBottom[1] + (leftBottom[1] - leftTop[1]) + margins[2])
|
||||
this.canvas.height = height
|
||||
this.canvas.style.height = `${height}px`
|
||||
this.render({ curIndex, isSubmitHistory: false })
|
||||
this.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
this.rowCount = rowList.length
|
||||
// 历史记录用于undo、redo
|
||||
if (isSubmitHistory) {
|
||||
const self = this
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { IRowElement } from "../../../interface/Row"
|
||||
|
||||
export class TextParticle {
|
||||
|
||||
private ctx: CanvasRenderingContext2D
|
||||
private curX: number
|
||||
private curY: number
|
||||
private text: string
|
||||
private curStyle: string
|
||||
private curColor?: string
|
||||
|
||||
constructor(ctx: CanvasRenderingContext2D) {
|
||||
this.ctx = ctx
|
||||
this.curX = -1
|
||||
this.curY = -1
|
||||
this.text = ''
|
||||
this.curStyle = ''
|
||||
}
|
||||
|
||||
public complete() {
|
||||
this.render()
|
||||
this.text = ''
|
||||
}
|
||||
|
||||
public record(element: IRowElement, x: number, y: number) {
|
||||
// 主动完成的重设起始点
|
||||
if (!this.text) {
|
||||
this.setCurXY(x, y)
|
||||
}
|
||||
// 样式发生改变
|
||||
if (
|
||||
(this.curStyle && element.style !== this.curStyle) ||
|
||||
(element.color !== this.curColor)
|
||||
) {
|
||||
this.complete()
|
||||
this.setCurXY(x, y)
|
||||
}
|
||||
this.text += element.value
|
||||
this.curStyle = element.style
|
||||
this.curColor = element.color
|
||||
}
|
||||
|
||||
private setCurXY(x: number, y: number) {
|
||||
this.curX = x
|
||||
this.curY = y
|
||||
}
|
||||
|
||||
private render() {
|
||||
if (!this.text || !~this.curX || !~this.curX) return
|
||||
this.ctx.save()
|
||||
this.ctx.font = this.curStyle
|
||||
if (this.curColor) {
|
||||
this.ctx.fillStyle = this.curColor
|
||||
}
|
||||
this.ctx.fillText(this.text, this.curX, this.curY)
|
||||
this.ctx.restore()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -82,7 +82,8 @@ export class CanvasEvent {
|
||||
// 绘制
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,7 +96,12 @@ export class CanvasEvent {
|
||||
const isDirectHitImage = isDirectHit && isImage
|
||||
if (~index) {
|
||||
this.range.setRange(index, index)
|
||||
this.draw.render({ curIndex: index, isSubmitHistory: false, isSetCursor: !isDirectHitImage })
|
||||
this.draw.render({
|
||||
curIndex: index,
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: !isDirectHitImage,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
// 图片尺寸拖拽组件
|
||||
this.imageParticle.clearResizer()
|
||||
@@ -151,13 +157,21 @@ export class CanvasEvent {
|
||||
if (index > 0) {
|
||||
const curIndex = index - 1
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex, isSubmitHistory: false })
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.key === KeyMap.Right) {
|
||||
if (index < position.length - 1) {
|
||||
const curIndex = index + 1
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex, isSubmitHistory: false })
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.key === KeyMap.Up || evt.key === KeyMap.Down) {
|
||||
const { rowNo, index, coordinate: { leftTop, rightTop } } = cursorPosition
|
||||
@@ -194,7 +208,11 @@ export class CanvasEvent {
|
||||
}
|
||||
const curIndex = maxIndex
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
this.draw.render({ curIndex, isSubmitHistory: false })
|
||||
this.draw.render({
|
||||
curIndex,
|
||||
isSubmitHistory: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.Z) {
|
||||
this.historyManager.undo()
|
||||
@@ -216,7 +234,11 @@ export class CanvasEvent {
|
||||
}
|
||||
} else if (evt.ctrlKey && evt.key === KeyMap.A) {
|
||||
this.range.setRange(0, position.length - 1)
|
||||
this.draw.render({ isSubmitHistory: false, isSetCursor: false })
|
||||
this.draw.render({
|
||||
isSubmitHistory: false,
|
||||
isSetCursor: false,
|
||||
isComputeRowList: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,10 +255,16 @@ export class CanvasEvent {
|
||||
const inputData: IElement[] = data.split('').map(value => ({
|
||||
value
|
||||
}))
|
||||
let start = 0
|
||||
if (isCollspace) {
|
||||
elementList.splice(index + 1, 0, ...inputData)
|
||||
start = index + 1
|
||||
} else {
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex, ...inputData)
|
||||
start = startIndex + 1
|
||||
elementList.splice(startIndex + 1, endIndex - startIndex)
|
||||
}
|
||||
// 禁止直接使用解构存在性能问题
|
||||
for (let i = 0; i < inputData.length; i++) {
|
||||
elementList.splice(start + i, 0, inputData[i])
|
||||
}
|
||||
const curIndex = (isCollspace ? index : startIndex) + inputData.length
|
||||
this.range.setRange(curIndex, curIndex)
|
||||
|
||||
+5
-2
@@ -47,12 +47,15 @@ export default class Editor {
|
||||
value: ZERO
|
||||
})
|
||||
}
|
||||
elementList.forEach(el => {
|
||||
for (let i = 0; i < elementList.length; i++) {
|
||||
const el = elementList[i]
|
||||
if (el.value === '\n') {
|
||||
el.value = ZERO
|
||||
}
|
||||
if (el.type === ElementType.IMAGE) {
|
||||
el.id = getUUID()
|
||||
})
|
||||
}
|
||||
}
|
||||
// 监听
|
||||
this.listener = new Listener()
|
||||
// 启动
|
||||
|
||||
@@ -2,6 +2,7 @@ export interface IDrawOption {
|
||||
curIndex?: number;
|
||||
isSetCursor?: boolean;
|
||||
isSubmitHistory?: boolean;
|
||||
isComputeRowList?: boolean;
|
||||
}
|
||||
|
||||
export interface IDrawImagePayload {
|
||||
|
||||
@@ -2,7 +2,8 @@ import { RowFlex } from "../dataset/enum/Row"
|
||||
import { IElement, IElementMetrics } from "./Element"
|
||||
|
||||
export type IRowElement = IElement & {
|
||||
metrics: IElementMetrics
|
||||
metrics: IElementMetrics;
|
||||
style: string;
|
||||
}
|
||||
|
||||
export interface IRow {
|
||||
|
||||
Reference in New Issue
Block a user