Skip to content

viewport-lighter

光速引擎 · Viewport Lighter —— 让复杂画布缩放如丝般顺滑。

📆 更新日志

当前为 v1.0.0,查看更新日志

📦 安装插件(本地安装)

本插件不发布于公开 NPM 仓库,通过本地 .tgz 文件安装使用,需 购买插件 授权后才能使用。

第一步:获取插件包

购买后,你将获得一个名为 pxgrow-viewport-lighter-1.0.0.tgz 的安装包。

将该文件放置在你的项目根目录下的 pxgrow 文件夹中统一管理,安装后请勿删除。

第二步:本地安装命令

根据你使用的包管理器,选择以下方式之一:

sh
npm install ./pxgrow/pxgrow-viewport-lighter-1.0.0.tgz
sh
pnpm add ./pxgrow/pxgrow-viewport-lighter-1.0.0.tgz
sh
yarn add ./pxgrow/pxgrow-viewport-lighter-1.0.0.tgz
sh
bun add ./pxgrow/pxgrow-viewport-lighter-1.0.0.tgz

将在 package.json 中自动增加本地依赖:

"@pxgrow/viewport-lighter": "file:pxgrow/pxgrow-viewport-lighter-1.0.0.tgz"


或通过 script 标签引入,使用全局变量 PxGrow.viewportLighter 访问插件内部功能。

需解压 pxgrow-viewport-lighter-1.0.0.tgz 文件,复制 package/dist/viewport-lighter.js 使用。

html
<script src="/lib/pxgrow/viewport-lighter.js"></script>
<script>
  const { ViewportLighter } = PxGrow.viewportLighter
</script>

关键方法

preMassCreate ( )

预备优化二次大量创建元素的性能,必须在下一次渲染前全部创建完成才能得到优化, 查看代码示例

示例

10 万级矩形元素流畅缩放

ts
// #Viewport Lighter [10万级矩形元素流畅缩放]
import { App, Rect, Group, IGroup } from 'leafer-ui'
import '@leafer-in/viewport'
import '@leafer-in/view'
import '@leafer-in/editor'

import { ViewportLighter } from '@pxgrow/viewport-lighter'

const app = new App({ view: window, editor: {} })

new ViewportLighter(app.tree, { // 加速 tree 层
    sliceRender: 10000 // 每个切片1万个元素
})

create(app.tree, 10) // 创建10万个矩形

app.nextRender(() => {
    app.tree.zoom('fit')
})


function create(view: IGroup, num: number) {
    const groupSize = 10 * 100 * 1.5
    const column = num > 25 ? 10 : 5

    for (let i = 0; i < num; i++) {
        const group = new Group()
        group.x = groupSize * (i % column)
        group.y = groupSize * Math.floor(i / column)
        view.add(group)
        createRects(group, 0, 0, `hsl(${(i * 10) % 360},70%,50%)`)
    }
}

function createRects(group: IGroup, startX: number, startY: number, color: string) {
    let y, rect
    for (let i = 0; i < 100; i++) {
        if (i % 10 === 0) startX += 10
        y = startY
        for (let j = 0; j < 100; j++) {
            if (j % 10 === 0) y += 10
            rect = new Rect()
            rect.x = startX
            rect.y = y
            rect.height = 10
            rect.width = 10
            rect.fill = color
            rect.editable = true
            group.add(rect)
            y += 12
        }
        startX += 12
    }
}

1 万张不同的小图片流畅缩放

ts
// #Viewport Lighter [1万张不同的小图片流畅缩放]
import { IGroup } from '@leafer-ui/interface'
import { App, Image, Group, Resource } from 'leafer-ui'
import '@leafer-in/viewport'
import '@leafer-in/view'
import '@leafer-in/editor'

import { ViewportLighter } from '@pxgrow/viewport-lighter'

const app = new App({ view: window, editor: {} })

new ViewportLighter(app.tree, { // 加速 tree 层
    sliceRender: 500 // 每个切片500张图片
})

createImages(app.tree, 1) // 1万张小图片

app.nextRender(() => {
    app.tree.zoom('fit')
})


function createImages(view: IGroup, num: number) {
    var group
    var groupSize = 10 * 100 * 1.5
    var column = num > 25 ? 10 : 5

    for (var i = 0; i < num; i++) {
        group = new Group()
        group.x = groupSize * 1.2 * (i % column)
        group.y = groupSize * Math.floor(i / column)
        view.add(group)
        create10000(group, i, 0, 0, `hsl(${i * 3},50%, 50%)`)
    }
}

function create10000(group: IGroup, num: number, startX: number, startY: number, color: string) {
    var y, image

    for (var i = 0; i < 100; i++) {
        if (i % 10 === 0) startX += 10
        y = startY
        for (var j = 0; j < 100; j++) {
            if (j % 10 === 0) y += 10
            image = new Image(null)
            image.x = startX
            image.y = y
            image.height = 10
            image.width = 12
            image.editable = true
            createUrl(image, num * 10000 + i * 100 + j + 1, color)
            group.add(image)
            y += 12
        }
        startX += 12 + 2
    }
}


// 模拟不重复的图片
function createUrl(image: Image, count: number, color: string): void {
    setTimeout(() => {
        const canvas = new OffscreenCanvas(60, 50)
        const context = canvas.getContext('2d')

        context.fillStyle = color
        context.fillRect(0, 0, 60, 50)

        for (let i = 0; i < 10; i++) {
            context.fillStyle = `hsl(${Math.round(10 + Math.random() * 100)},70%,50%)`
            context.beginPath()
            context.arc(Math.random() * 60, Math.random() * 50, 1 + Math.random() * 10, 0, Math.PI * 2)
            context.fill()
        }

        context.fillStyle = 'rgba(255,255,255,0.8)'
        context.font = `bold 12px Arial`
        context.textAlign = 'center'
        context.textBaseline = 'middle'
        context.fillText(count.toString(), 30, 25)

        const bitmap = canvas.transferToImageBitmap()
        image.fill = {
            type: 'image',
            mode: 'stretch',
            changeful: true,
            url: Resource.setImage('leafer://' + count + '.jpg', bitmap).url,
        }
    }, count * 2)
}

3 千张不同的图片流畅缩放

ts
// #Viewport Lighter [3千张不同的图片流畅缩放]
import { IGroup } from '@leafer-ui/interface'
import { App, Image, Group, Resource } from 'leafer-ui'
import '@leafer-in/viewport'
import '@leafer-in/view'
import '@leafer-in/editor'

import { ViewportLighter } from '@pxgrow/viewport-lighter'

const app = new App({ view: window, editor: {} })

new ViewportLighter(app.tree, { // 加速 tree 层
    sliceRender: 1000 // 每个切片1000张图片
})

createImages(app.tree, 1) // 3千张图片

app.nextRender(() => {
    app.tree.zoom('fit')
})


function createImages(view: IGroup, num: number) {
    var group
    var groupSize = 10 * 100 * 1.5
    var column = num > 25 ? 10 : 5

    for (var i = 0; i < num; i++) {
        group = new Group()
        group.x = groupSize * (i % column)
        group.y = groupSize * 0.3 * Math.floor(i / column)
        view.add(group)
        createGroup(group, i, 0, 0, `hsl(${i * 3},60%,50%)`)
    }
}

function createGroup(group: IGroup, num: number, startX: number, startY: number, color: string) {
    var y, image

    for (var i = 0; i < 100; i++) {
        if (i % 10 === 0) startX += 10
        y = startY
        for (var j = 0; j < 30; j++) {
            if (j % 10 === 0) y += 10
            image = new Image()
            image.x = startX
            image.y = y
            image.width = 10
            image.height = 3
            image.editable = true
            createUrl(image, num * 10000 + i * 30 + j + 1, color)
            group.add(image)
            y += 5
        }
        startX += 10 + 2
    }
}


// 模拟不重复的图片(每张1000 * 300px)
function createUrl(image: Image, count: number, color: string): void {
    setTimeout(() => {
        const canvas = new OffscreenCanvas(1000, 300)
        const context = canvas.getContext('2d')

        context.fillStyle = color
        context.fillRect(0, 0, 1000, 300)

        for (let i = 0; i < 10; i++) {
            context.fillStyle = `hsl(${Math.round(10 + Math.random() * 100)},70%,50%)`
            context.beginPath()
            context.arc(Math.random() * 1000, Math.random() * 300, 5 + Math.random() * 60, 0, Math.PI * 2)
            context.fill()
        }

        context.fillStyle = 'rgba(255,255,255,0.8)'
        context.font = `bold 75px Arial`
        context.textAlign = 'center'
        context.textBaseline = 'middle'
        context.fillText(count.toString(), 500, 150)

        const bitmap = canvas.transferToImageBitmap()
        image.url = Resource.setImage('leafer://' + count + '.jpg', bitmap).url
    }, count * 5)
}

1 万条路径流畅缩放

ts
// #Viewport Lighter [1万条路径流畅缩放]
import { IGroup } from '@leafer-ui/interface'
import { App, Polygon, Group } from 'leafer-ui'
import '@leafer-in/viewport'
import '@leafer-in/view'
import '@leafer-in/editor'

import { ViewportLighter } from '@pxgrow/viewport-lighter'

const app = new App({ view: window, editor: {} })

new ViewportLighter(app.tree, { // 加速 tree 层
    sliceRender: 500 // 每个切片500条路径
})

createPaths(app.tree, 1) // 1万条路径

app.nextRender(() => {
    app.tree.zoom('fit')
})

function createPaths(view: IGroup, num: number) {
    var group
    var groupSize = 10 * 100 * 1.5
    var column = num > 25 ? 10 : 5

    for (var i = 0; i < num; i++) {
        group = new Group()
        group.x = groupSize * (i % column)
        group.y = groupSize * Math.floor(i / column)
        view.add(group)
        create10000(group, 0, 0, `hsl(${i * 3},50%, 50%)`)
    }
}

function create10000(group: IGroup, startX: number, startY: number, color: string) {
    var y, path

    for (var i = 0; i < 100; i++) {
        if (i % 10 === 0) startX += 10
        y = startY
        for (var j = 0; j < 100; j++) {
            if (j % 10 === 0) y += 10
            path = new Polygon()
            path.x = startX
            path.y = y
            path.fill = color
            path.curve = true
            path.points = [0, 9, Math.random() * 2, Math.random() * 3 + 3, Math.random() * 2 + 2, Math.random() * 3 + 5, Math.random() * 2 + 4, Math.random() * 2 + 2, Math.random() * 2 + 5, Math.random() * 2 + 3, Math.random() * 3 + 6, Math.random() * 1, Math.random() * 3 + 7, Math.random() * 3 + 6, Math.random() * 2 + 8, Math.random() * 3 + 6, 0, 9]
            path.editable = true
            group.add(path)
            y += 12
        }
        startX += 12
    }
}

优化二次大量创建元素的性能

ts
// #Viewport Lighter [优化二次大量创建元素的性能]
import { App, Rect, Group, IGroup, RenderEvent, Debug } from 'leafer-ui'
import '@leafer-in/viewport'
import '@leafer-in/view'
import '@leafer-in/editor'

import { ViewportLighter } from '@pxgrow/viewport-lighter'

const app = new App({ view: window, editor: {} })

const viewportLighter = new ViewportLighter(app.tree, { // 加速 tree 层
    sliceRender: 10000 // 每个切片1万个元素
})

app.tree.add(new Rect({
    x: 100,
    y: 100,
    width: 200,
    height: 200,
    fill: '#32cd79',
    draggable: true
}))

setTimeout(() => {
    Debug.enable = true
    Debug.filter = 'RunTime' // 控制台看 Layout 时间

    viewportLighter.preMassCreate() // 预备优化二次大量创建元素的性能,必须在下一次渲染前全部创建完成才能得到优化

    const startTime = Date.now()
    create(app.tree, 100) // 创建100万个矩形

    app.tree.once(RenderEvent.END, () => {
        app.tree.add({ tag: 'Text', x: 20, y: 20, text: `二次创建100万个矩形至渲染用时:` + (Date.now() - startTime) + '毫秒', fontWeight: 'bold', fontSize: 20 })
    })

}, 1000)


function create(view: IGroup, num: number) {
    const groupSize = 10 * 100 * 1.5
    const column = num > 25 ? 10 : 5

    for (let i = 0; i < num; i++) {
        const group = new Group()
        group.x = groupSize * (i % column)
        group.y = groupSize * Math.floor(i / column) + 100
        createRects(group, 0, 0, `hsl(${(i * 10) % 360},70%,50%)`)
        view.add(group)
    }
}

function createRects(group: IGroup, startX: number, startY: number, color: string) {
    let y, rect
    for (let i = 0; i < 100; i++) {
        if (i % 10 === 0) startX += 10
        y = startY
        for (let j = 0; j < 100; j++) {
            if (j % 10 === 0) y += 10
            rect = new Rect()
            rect.x = startX
            rect.y = y
            rect.height = 10
            rect.width = 10
            rect.fill = color
            rect.editable = true
            group.add(rect)
            y += 12
        }
        startX += 12
    }
}

Released under the Commercial License Agreement.