feat:add lazy render mode

pr675
Hufe921 3 years ago
parent 8910c7cf0a
commit f428f566e9

@ -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 {

Loading…
Cancel
Save