feat:add lazy render mode
This commit is contained in:
@@ -10,16 +10,16 @@ describe('菜单-打印', () => {
|
||||
|
||||
it('打印', () => {
|
||||
|
||||
cy.getEditor().then((editor: Editor) => {
|
||||
const imageList2 = editor.command.getImage()
|
||||
cy.getEditor().then(async (editor: Editor) => {
|
||||
const imageList2 = await editor.command.getImage()
|
||||
expect(imageList2.length).to.eq(2)
|
||||
|
||||
editor.command.executeSelectAll()
|
||||
|
||||
editor.command.executeBackspace()
|
||||
|
||||
cy.wait(200).then(() => {
|
||||
const imageList1 = editor.command.getImage()
|
||||
cy.wait(200).then(async () => {
|
||||
const imageList1 = await editor.command.getImage()
|
||||
expect(imageList1.length).to.eq(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -381,7 +381,7 @@ export class Command {
|
||||
return Command.changeImageDisplay(element, display)
|
||||
}
|
||||
|
||||
public getImage(): string[] {
|
||||
public getImage(): Promise<string[]> {
|
||||
return Command.getImage()
|
||||
}
|
||||
|
||||
|
||||
@@ -327,7 +327,10 @@ export class CommandAdapt {
|
||||
selection.forEach(el => {
|
||||
el.color = payload
|
||||
})
|
||||
this.draw.render({ isSetCursor: false })
|
||||
this.draw.render({
|
||||
isSetCursor: false,
|
||||
isCompute: false
|
||||
})
|
||||
}
|
||||
|
||||
public highlight(payload: string) {
|
||||
@@ -338,7 +341,10 @@ export class CommandAdapt {
|
||||
selection.forEach(el => {
|
||||
el.highlight = payload
|
||||
})
|
||||
this.draw.render({ isSetCursor: false })
|
||||
this.draw.render({
|
||||
isSetCursor: false,
|
||||
isCompute: false
|
||||
})
|
||||
}
|
||||
|
||||
public rowFlex(payload: RowFlex) {
|
||||
@@ -1173,7 +1179,8 @@ export class CommandAdapt {
|
||||
options.watermark.font = payload.font || font
|
||||
this.draw.render({
|
||||
isSetCursor: false,
|
||||
isSubmitHistory: false
|
||||
isSubmitHistory: false,
|
||||
isCompute: false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1185,7 +1192,8 @@ export class CommandAdapt {
|
||||
options.watermark = { ...defaultWatermarkOption }
|
||||
this.draw.render({
|
||||
isSetCursor: false,
|
||||
isSubmitHistory: false
|
||||
isSubmitHistory: false,
|
||||
isCompute: false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1229,7 +1237,9 @@ export class CommandAdapt {
|
||||
if (index === null) return
|
||||
this.draw.render({
|
||||
isSetCursor: false,
|
||||
isSubmitHistory: false
|
||||
isSubmitHistory: false,
|
||||
isCompute: false,
|
||||
isLazy: false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1238,7 +1248,9 @@ export class CommandAdapt {
|
||||
if (index === null) return
|
||||
this.draw.render({
|
||||
isSetCursor: false,
|
||||
isSubmitHistory: false
|
||||
isSubmitHistory: false,
|
||||
isCompute: false,
|
||||
isLazy: false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1352,12 +1364,13 @@ export class CommandAdapt {
|
||||
})
|
||||
}
|
||||
|
||||
public print() {
|
||||
public async print() {
|
||||
const { width, height, scale } = this.options
|
||||
if (scale !== 1) {
|
||||
this.draw.setPageScale(1)
|
||||
}
|
||||
printImageBase64(this.draw.getDataURL(), width, height)
|
||||
const base64List = await this.draw.getDataURL()
|
||||
printImageBase64(base64List, width, height)
|
||||
if (scale !== 1) {
|
||||
this.draw.setPageScale(scale)
|
||||
}
|
||||
@@ -1393,7 +1406,7 @@ export class CommandAdapt {
|
||||
})
|
||||
}
|
||||
|
||||
public getImage(): string[] {
|
||||
public getImage(): Promise<string[]> {
|
||||
return this.draw.getDataURL()
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import { IMargin } from '../../interface/Margin'
|
||||
import { BlockParticle } from './particle/block/BlockParticle'
|
||||
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor'
|
||||
import { I18n } from '../i18n/I18n'
|
||||
import { ImageObserver } from '../observer/ImageObserver'
|
||||
|
||||
export class Draw {
|
||||
|
||||
@@ -96,6 +97,7 @@ export class Draw {
|
||||
private workerManager: WorkerManager
|
||||
private scrollObserver: ScrollObserver
|
||||
private selectionObserver: SelectionObserver
|
||||
private imageObserver: ImageObserver
|
||||
|
||||
private rowList: IRow[]
|
||||
private pageRowList: IRow[][]
|
||||
@@ -103,6 +105,7 @@ export class Draw {
|
||||
private painterOptions: IPainterOptions | null
|
||||
private visiblePageNoList: number[]
|
||||
private intersectionPageNo: number
|
||||
private lazyRenderIntersectionObserver: IntersectionObserver | null
|
||||
|
||||
constructor(
|
||||
rootContainer: HTMLElement,
|
||||
@@ -154,6 +157,7 @@ export class Draw {
|
||||
|
||||
this.scrollObserver = new ScrollObserver(this)
|
||||
this.selectionObserver = new SelectionObserver()
|
||||
this.imageObserver = new ImageObserver()
|
||||
|
||||
this.canvasEvent = new CanvasEvent(this)
|
||||
this.cursor = new Cursor(this, this.canvasEvent)
|
||||
@@ -169,6 +173,7 @@ export class Draw {
|
||||
this.painterOptions = null
|
||||
this.visiblePageNoList = []
|
||||
this.intersectionPageNo = 0
|
||||
this.lazyRenderIntersectionObserver = null
|
||||
|
||||
this.render({ isSetCursor: false })
|
||||
}
|
||||
@@ -412,6 +417,10 @@ export class Draw {
|
||||
return this.workerManager
|
||||
}
|
||||
|
||||
public getImageObserver(): ImageObserver {
|
||||
return this.imageObserver
|
||||
}
|
||||
|
||||
public getI18n(): I18n {
|
||||
return this.i18n
|
||||
}
|
||||
@@ -420,7 +429,14 @@ export class Draw {
|
||||
return this.rowList.length
|
||||
}
|
||||
|
||||
public getDataURL(): string[] {
|
||||
public async getDataURL(): Promise<string[]> {
|
||||
this.render({
|
||||
isLazy: false,
|
||||
isCompute: false,
|
||||
isSetCursor: false,
|
||||
isSubmitHistory: false
|
||||
})
|
||||
await this.imageObserver.allSettled()
|
||||
return this.pageList.map(c => c.toDataURL())
|
||||
}
|
||||
|
||||
@@ -1096,11 +1112,35 @@ export class Draw {
|
||||
}
|
||||
}
|
||||
|
||||
private _lazyRender() {
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
this.lazyRenderIntersectionObserver?.disconnect()
|
||||
this.lazyRenderIntersectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const index = Number((<HTMLCanvasElement>entry.target).dataset.index)
|
||||
this._drawPage(positionList, this.pageRowList[index], index)
|
||||
}
|
||||
})
|
||||
})
|
||||
this.pageList.forEach(el => {
|
||||
this.lazyRenderIntersectionObserver!.observe(el)
|
||||
})
|
||||
}
|
||||
|
||||
private _immediateRender() {
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
for (let i = 0; i < this.pageRowList.length; i++) {
|
||||
this._drawPage(positionList, this.pageRowList[i], i)
|
||||
}
|
||||
}
|
||||
|
||||
public render(payload?: IDrawOption) {
|
||||
const {
|
||||
isSubmitHistory = true,
|
||||
isSetCursor = true,
|
||||
isCompute = true
|
||||
isCompute = true,
|
||||
isLazy = true
|
||||
} = payload || {}
|
||||
let { curIndex } = payload || {}
|
||||
const innerWidth = this.getInnerWidth()
|
||||
@@ -1118,30 +1158,22 @@ export class Draw {
|
||||
this.search.compute(searchKeyword)
|
||||
}
|
||||
}
|
||||
// 清除光标
|
||||
// 清除光标等副作用
|
||||
this.imageObserver.clearAll()
|
||||
this.cursor.recoveryCursor()
|
||||
// 绘制元素
|
||||
// 创建纸张
|
||||
const positionList = this.position.getOriginalPositionList()
|
||||
const { visiblePageNoList } = this.scrollObserver.getPageVisibleInfo()
|
||||
const minPageNo = visiblePageNoList[0]
|
||||
const maxPageNo = visiblePageNoList[visiblePageNoList.length - 1]
|
||||
for (let i = 0; i < this.pageRowList.length; i++) {
|
||||
if (!this.pageList[i]) {
|
||||
this._createPage(i)
|
||||
}
|
||||
// 优先绘制可见页
|
||||
if (i >= minPageNo && i <= maxPageNo) {
|
||||
this._drawPage(positionList, this.pageRowList[i], i)
|
||||
}
|
||||
}
|
||||
// 渲染后续不可见页
|
||||
nextTick(() => {
|
||||
for (let i = 0; i < this.pageRowList.length; i++) {
|
||||
if (i < minPageNo || i > maxPageNo) {
|
||||
this._drawPage(positionList, this.pageRowList[i], i)
|
||||
}
|
||||
}
|
||||
})
|
||||
// 绘制元素
|
||||
if (isLazy) {
|
||||
this._lazyRender()
|
||||
} else {
|
||||
this._immediateRender()
|
||||
}
|
||||
// 移除多余页
|
||||
nextTick(() => {
|
||||
const curPageCount = this.pageRowList.length
|
||||
@@ -1187,9 +1219,8 @@ export class Draw {
|
||||
self.render({ curIndex, isSubmitHistory: false })
|
||||
})
|
||||
}
|
||||
|
||||
// 信息变动回调
|
||||
setTimeout(() => {
|
||||
nextTick(() => {
|
||||
// 页面尺寸改变
|
||||
if (this.listener.pageSizeChange) {
|
||||
this.listener.pageSizeChange(this.pageRowList.length)
|
||||
|
||||
@@ -261,7 +261,11 @@ export class Search {
|
||||
const searchMatchIndexList = this.getSearchNavigateIndexList()
|
||||
if (searchMatchIndexList.includes(s)) {
|
||||
ctx.fillStyle = searchNavigateMatchColor
|
||||
this.searchNavigateScrollIntoView(position)
|
||||
// 是否是第一个字符,则移动到可视范围
|
||||
const preSearchMatch = this.searchMatchList[s - 1]
|
||||
if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) {
|
||||
this.searchNavigateScrollIntoView(position)
|
||||
}
|
||||
} else {
|
||||
ctx.fillStyle = searchMatchColor
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@ import { Draw } from '../Draw'
|
||||
|
||||
export class ImageParticle {
|
||||
|
||||
private draw: Draw
|
||||
protected options: Required<IEditorOption>
|
||||
protected imageCache: Map<string, HTMLImageElement>
|
||||
|
||||
constructor(draw: Draw) {
|
||||
this.draw = draw
|
||||
this.options = draw.getOptions()
|
||||
this.imageCache = new Map()
|
||||
}
|
||||
|
||||
protected addImageObserver(promise: Promise<unknown>) {
|
||||
this.draw.getImageObserver().add(promise)
|
||||
}
|
||||
|
||||
public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) {
|
||||
const { scale } = this.options
|
||||
const width = element.width! * scale
|
||||
@@ -20,12 +26,19 @@ export class ImageParticle {
|
||||
const img = this.imageCache.get(element.id!)!
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
} else {
|
||||
const img = new Image()
|
||||
img.src = element.value
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
this.imageCache.set(element.id!, img)
|
||||
}
|
||||
const imageLoadPromise = new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = element.value
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
this.imageCache.set(element.id!, img)
|
||||
resolve(element)
|
||||
}
|
||||
img.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
this.addImageObserver(imageLoadPromise)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,19 @@ export class LaTexParticle extends ImageParticle {
|
||||
const img = this.imageCache.get(element.value)!
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
} else {
|
||||
const img = new Image()
|
||||
img.src = element.laTexSVG!
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
this.imageCache.set(element.value, img)
|
||||
}
|
||||
const laTexLoadPromise = new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = element.laTexSVG!
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, x, y, width, height)
|
||||
this.imageCache.set(element.value, img)
|
||||
resolve(element)
|
||||
}
|
||||
img.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
this.addImageObserver(laTexLoadPromise)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export class ImageObserver {
|
||||
|
||||
private promiseList: Promise<unknown>[]
|
||||
|
||||
constructor() {
|
||||
this.promiseList = []
|
||||
}
|
||||
|
||||
public add(payload: Promise<unknown>) {
|
||||
this.promiseList.push(payload)
|
||||
}
|
||||
|
||||
public clearAll() {
|
||||
this.promiseList = []
|
||||
}
|
||||
|
||||
public allSettled() {
|
||||
return Promise.allSettled(this.promiseList)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export interface IDrawOption {
|
||||
isSetCursor?: boolean;
|
||||
isSubmitHistory?: boolean;
|
||||
isCompute?: boolean;
|
||||
isLazy?: boolean;
|
||||
}
|
||||
|
||||
export interface IDrawImagePayload {
|
||||
|
||||
Reference in New Issue
Block a user