feat:add lazy render mode

pr675
Hufe921 3 years ago
parent 8910c7cf0a
commit f428f566e9

@ -10,16 +10,16 @@ describe('菜单-打印', () => {
it('打印', () => { it('打印', () => {
cy.getEditor().then((editor: Editor) => { cy.getEditor().then(async (editor: Editor) => {
const imageList2 = editor.command.getImage() const imageList2 = await editor.command.getImage()
expect(imageList2.length).to.eq(2) expect(imageList2.length).to.eq(2)
editor.command.executeSelectAll() editor.command.executeSelectAll()
editor.command.executeBackspace() editor.command.executeBackspace()
cy.wait(200).then(() => { cy.wait(200).then(async () => {
const imageList1 = editor.command.getImage() const imageList1 = await editor.command.getImage()
expect(imageList1.length).to.eq(1) expect(imageList1.length).to.eq(1)
}) })
}) })

@ -381,7 +381,7 @@ export class Command {
return Command.changeImageDisplay(element, display) return Command.changeImageDisplay(element, display)
} }
public getImage(): string[] { public getImage(): Promise<string[]> {
return Command.getImage() return Command.getImage()
} }

@ -327,7 +327,10 @@ export class CommandAdapt {
selection.forEach(el => { selection.forEach(el => {
el.color = payload el.color = payload
}) })
this.draw.render({ isSetCursor: false }) this.draw.render({
isSetCursor: false,
isCompute: false
})
} }
public highlight(payload: string) { public highlight(payload: string) {
@ -338,7 +341,10 @@ export class CommandAdapt {
selection.forEach(el => { selection.forEach(el => {
el.highlight = payload el.highlight = payload
}) })
this.draw.render({ isSetCursor: false }) this.draw.render({
isSetCursor: false,
isCompute: false
})
} }
public rowFlex(payload: RowFlex) { public rowFlex(payload: RowFlex) {
@ -1173,7 +1179,8 @@ export class CommandAdapt {
options.watermark.font = payload.font || font options.watermark.font = payload.font || font
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false,
isCompute: false
}) })
} }
@ -1185,7 +1192,8 @@ export class CommandAdapt {
options.watermark = { ...defaultWatermarkOption } options.watermark = { ...defaultWatermarkOption }
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false,
isCompute: false
}) })
} }
} }
@ -1229,7 +1237,9 @@ export class CommandAdapt {
if (index === null) return if (index === null) return
this.draw.render({ this.draw.render({
isSetCursor: false, isSetCursor: false,
isSubmitHistory: false isSubmitHistory: false,
isCompute: false,
isLazy: false
}) })
} }
@ -1238,7 +1248,9 @@ export class CommandAdapt {
if (index === null) return if (index === null) return
this.draw.render({ this.draw.render({
isSetCursor: false, 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 const { width, height, scale } = this.options
if (scale !== 1) { if (scale !== 1) {
this.draw.setPageScale(1) this.draw.setPageScale(1)
} }
printImageBase64(this.draw.getDataURL(), width, height) const base64List = await this.draw.getDataURL()
printImageBase64(base64List, width, height)
if (scale !== 1) { if (scale !== 1) {
this.draw.setPageScale(scale) this.draw.setPageScale(scale)
} }
@ -1393,7 +1406,7 @@ export class CommandAdapt {
}) })
} }
public getImage(): string[] { public getImage(): Promise<string[]> {
return this.draw.getDataURL() return this.draw.getDataURL()
} }

@ -49,6 +49,7 @@ import { IMargin } from '../../interface/Margin'
import { BlockParticle } from './particle/block/BlockParticle' import { BlockParticle } from './particle/block/BlockParticle'
import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor' import { EDITOR_COMPONENT, EDITOR_PREFIX } from '../../dataset/constant/Editor'
import { I18n } from '../i18n/I18n' import { I18n } from '../i18n/I18n'
import { ImageObserver } from '../observer/ImageObserver'
export class Draw { export class Draw {
@ -96,6 +97,7 @@ export class Draw {
private workerManager: WorkerManager private workerManager: WorkerManager
private scrollObserver: ScrollObserver private scrollObserver: ScrollObserver
private selectionObserver: SelectionObserver private selectionObserver: SelectionObserver
private imageObserver: ImageObserver
private rowList: IRow[] private rowList: IRow[]
private pageRowList: IRow[][] private pageRowList: IRow[][]
@ -103,6 +105,7 @@ export class Draw {
private painterOptions: IPainterOptions | null private painterOptions: IPainterOptions | null
private visiblePageNoList: number[] private visiblePageNoList: number[]
private intersectionPageNo: number private intersectionPageNo: number
private lazyRenderIntersectionObserver: IntersectionObserver | null
constructor( constructor(
rootContainer: HTMLElement, rootContainer: HTMLElement,
@ -154,6 +157,7 @@ export class Draw {
this.scrollObserver = new ScrollObserver(this) this.scrollObserver = new ScrollObserver(this)
this.selectionObserver = new SelectionObserver() this.selectionObserver = new SelectionObserver()
this.imageObserver = new ImageObserver()
this.canvasEvent = new CanvasEvent(this) this.canvasEvent = new CanvasEvent(this)
this.cursor = new Cursor(this, this.canvasEvent) this.cursor = new Cursor(this, this.canvasEvent)
@ -169,6 +173,7 @@ export class Draw {
this.painterOptions = null this.painterOptions = null
this.visiblePageNoList = [] this.visiblePageNoList = []
this.intersectionPageNo = 0 this.intersectionPageNo = 0
this.lazyRenderIntersectionObserver = null
this.render({ isSetCursor: false }) this.render({ isSetCursor: false })
} }
@ -412,6 +417,10 @@ export class Draw {
return this.workerManager return this.workerManager
} }
public getImageObserver(): ImageObserver {
return this.imageObserver
}
public getI18n(): I18n { public getI18n(): I18n {
return this.i18n return this.i18n
} }
@ -420,7 +429,14 @@ export class Draw {
return this.rowList.length 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()) 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) { public render(payload?: IDrawOption) {
const { const {
isSubmitHistory = true, isSubmitHistory = true,
isSetCursor = true, isSetCursor = true,
isCompute = true isCompute = true,
isLazy = true
} = payload || {} } = payload || {}
let { curIndex } = payload || {} let { curIndex } = payload || {}
const innerWidth = this.getInnerWidth() const innerWidth = this.getInnerWidth()
@ -1118,30 +1158,22 @@ export class Draw {
this.search.compute(searchKeyword) this.search.compute(searchKeyword)
} }
} }
// 清除光标 // 清除光标等副作用
this.imageObserver.clearAll()
this.cursor.recoveryCursor() this.cursor.recoveryCursor()
// 绘制元素 // 创建纸张
const positionList = this.position.getOriginalPositionList() 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++) { for (let i = 0; i < this.pageRowList.length; i++) {
if (!this.pageList[i]) { if (!this.pageList[i]) {
this._createPage(i) this._createPage(i)
} }
// 优先绘制可见页
if (i >= minPageNo && i <= maxPageNo) {
this._drawPage(positionList, this.pageRowList[i], i)
}
} }
// 渲染后续不可见页 // 绘制元素
nextTick(() => { if (isLazy) {
for (let i = 0; i < this.pageRowList.length; i++) { this._lazyRender()
if (i < minPageNo || i > maxPageNo) { } else {
this._drawPage(positionList, this.pageRowList[i], i) this._immediateRender()
} }
}
})
// 移除多余页 // 移除多余页
nextTick(() => { nextTick(() => {
const curPageCount = this.pageRowList.length const curPageCount = this.pageRowList.length
@ -1187,9 +1219,8 @@ export class Draw {
self.render({ curIndex, isSubmitHistory: false }) self.render({ curIndex, isSubmitHistory: false })
}) })
} }
// 信息变动回调 // 信息变动回调
setTimeout(() => { nextTick(() => {
// 页面尺寸改变 // 页面尺寸改变
if (this.listener.pageSizeChange) { if (this.listener.pageSizeChange) {
this.listener.pageSizeChange(this.pageRowList.length) this.listener.pageSizeChange(this.pageRowList.length)

@ -261,7 +261,11 @@ export class Search {
const searchMatchIndexList = this.getSearchNavigateIndexList() const searchMatchIndexList = this.getSearchNavigateIndexList()
if (searchMatchIndexList.includes(s)) { if (searchMatchIndexList.includes(s)) {
ctx.fillStyle = searchNavigateMatchColor ctx.fillStyle = searchNavigateMatchColor
this.searchNavigateScrollIntoView(position) // 是否是第一个字符,则移动到可视范围
const preSearchMatch = this.searchMatchList[s - 1]
if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) {
this.searchNavigateScrollIntoView(position)
}
} else { } else {
ctx.fillStyle = searchMatchColor ctx.fillStyle = searchMatchColor
} }

@ -4,14 +4,20 @@ import { Draw } from '../Draw'
export class ImageParticle { export class ImageParticle {
private draw: Draw
protected options: Required<IEditorOption> protected options: Required<IEditorOption>
protected imageCache: Map<string, HTMLImageElement> protected imageCache: Map<string, HTMLImageElement>
constructor(draw: Draw) { constructor(draw: Draw) {
this.draw = draw
this.options = draw.getOptions() this.options = draw.getOptions()
this.imageCache = new Map() this.imageCache = new Map()
} }
protected addImageObserver(promise: Promise<unknown>) {
this.draw.getImageObserver().add(promise)
}
public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) { public render(ctx: CanvasRenderingContext2D, element: IElement, x: number, y: number) {
const { scale } = this.options const { scale } = this.options
const width = element.width! * scale const width = element.width! * scale
@ -20,12 +26,19 @@ export class ImageParticle {
const img = this.imageCache.get(element.id!)! const img = this.imageCache.get(element.id!)!
ctx.drawImage(img, x, y, width, height) ctx.drawImage(img, x, y, width, height)
} else { } else {
const img = new Image() const imageLoadPromise = new Promise((resolve, reject) => {
img.src = element.value const img = new Image()
img.onload = () => { img.src = element.value
ctx.drawImage(img, x, y, width, height) img.onload = () => {
this.imageCache.set(element.id!, img) 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)! const img = this.imageCache.get(element.value)!
ctx.drawImage(img, x, y, width, height) ctx.drawImage(img, x, y, width, height)
} else { } else {
const img = new Image() const laTexLoadPromise = new Promise((resolve, reject) => {
img.src = element.laTexSVG! const img = new Image()
img.onload = () => { img.src = element.laTexSVG!
ctx.drawImage(img, x, y, width, height) img.onload = () => {
this.imageCache.set(element.value, img) 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; isSetCursor?: boolean;
isSubmitHistory?: boolean; isSubmitHistory?: boolean;
isCompute?: boolean; isCompute?: boolean;
isLazy?: boolean;
} }
export interface IDrawImagePayload { export interface IDrawImagePayload {

Loading…
Cancel
Save