feat:add lazy render mode
This commit is contained in:
@@ -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)
|
|
||||||
}
|
}
|
||||||
|
// 绘制元素
|
||||||
|
if (isLazy) {
|
||||||
|
this._lazyRender()
|
||||||
|
} else {
|
||||||
|
this._immediateRender()
|
||||||
}
|
}
|
||||||
// 渲染后续不可见页
|
|
||||||
nextTick(() => {
|
|
||||||
for (let i = 0; i < this.pageRowList.length; i++) {
|
|
||||||
if (i < minPageNo || i > maxPageNo) {
|
|
||||||
this._drawPage(positionList, this.pageRowList[i], i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// 移除多余页
|
// 移除多余页
|
||||||
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
|
||||||
|
// 是否是第一个字符,则移动到可视范围
|
||||||
|
const preSearchMatch = this.searchMatchList[s - 1]
|
||||||
|
if (!preSearchMatch || preSearchMatch.groupId !== searchMatch.groupId) {
|
||||||
this.searchNavigateScrollIntoView(position)
|
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 imageLoadPromise = new Promise((resolve, reject) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.src = element.value
|
img.src = element.value
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
ctx.drawImage(img, x, y, width, height)
|
ctx.drawImage(img, x, y, width, height)
|
||||||
this.imageCache.set(element.id!, img)
|
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 laTexLoadPromise = new Promise((resolve, reject) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.src = element.laTexSVG!
|
img.src = element.laTexSVG!
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
ctx.drawImage(img, x, y, width, height)
|
ctx.drawImage(img, x, y, width, height)
|
||||||
this.imageCache.set(element.value, img)
|
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user