leaferjs / ui

一款好用的 Canvas 渲染引擎,革新的体验。高效绘图 、UI 交互(小游戏、互动应用、组态)、图形编辑。
https://www.leaferjs.com
MIT License
2.32k stars 81 forks source link

在移动端拖动图片时,略卡,跟手度不够流畅 #169

Closed 826327700 closed 1 month ago

826327700 commented 1 month ago

如视频所示:

https://github.com/user-attachments/assets/bfd77189-7d97-4f29-8cb0-d5f006371bc3

测试手机是iPhone 15 pro,理论上应该算是中高端性能了。 在pc上chrome浏览器开移动窗口模式表现却非常好。

leaferjs commented 1 month ago

你这个是不是用了遮罩?

826327700 commented 1 month ago

经过测试初步发现,卡顿的根源在于,当画布内有一个图片元素是设置了mask后(用于裁剪图片的边缘):

let image = new Image({ id:"mask",url: url, mask: "pixel", draggable: false })
leafer.tree.add(image)

如果去掉这一个图层,只保留原本的图片,跟手度会流畅很多,接近pc体验。 对比测试了一下fabric.js,同样添加一层图片图层:

let img3=await Image.fromURL(maskUrl)
    img3.set({
        globalCompositeOperation: 'destination-in',
    })
    canvas.add(img3)

fabric的流畅度几乎不受影响。

leaferjs commented 1 month ago

给遮罩设置 path 类型就流畅了, safari运行混合模式比较慢

826327700 commented 1 month ago

给遮罩设置 path 类型就流畅了, safari运行混合模式比较慢

我对leafer还不是很熟,能不能给我一行示例代码😂

826327700 commented 1 month ago

给遮罩设置 path 类型就流畅了, safari运行混合模式比较慢

我对leafer还不是很熟,能不能给我一行示例代码😂

mask:"path"吗?

leaferjs commented 1 month ago

对的

leaferjs commented 1 month ago

另外正片叠底的混合模式也尽量用其他办法代替。

还有另外一种可能,就是你画布的pixelRatio不正常,导致画布过大,可以打印一下实际的画布尺寸 leafer.canvas.pixelWidth / leafer.canvas.pixelHeight 看看。

826327700 commented 1 month ago

对的

试了,mask: "path" 不符合业务需求啊,需要根据这招图片的形状 裁剪掉图片。只有pixel是符合这个需求的。能不能研究一下,使用类似fabric中的globalCompositeOperation: 'destination-in',这个是canvas自带的属性,应该不会卡

leaferjs commented 1 month ago

内部用的是类似这种方式做的,我到时测试看看原因

826327700 commented 1 month ago

内部用的是类似这种方式做的,我到时测试看看原因

作者大大,近期有解决的可能吗?公司项目正在犹豫要不要从fabric迁移到leaferjs中

leaferjs commented 1 month ago

可以给一下具体的示例代码吗(导出个json数据结构也可以)?估计还有些其他情况是我不知道的,下个版本可以有针对性的优化一下~

826327700 commented 1 month ago

可以给一下具体的示例代码吗(导出个json数据结构也可以)?估计还有些其他情况是我不知道的,下个版本可以有针对性的优化一下~

以下是我导出的json数据:

{"tag":"App","pixelRatio":3,"width":390,"height":745,"hittable":true,"children":[{"tag":"Leafer","children":[{"tag":"Image","url":"https://gallery-1318352346.cos.ap-guangzhou.myqcloud.com/upload/gallery/public/2024-05-21/PrAPChDteCx2Y6sm_600x960.png","id":"background","width":390,"height":624,"draggable":false},{"tag":"Image","url":"https://gallery-1318352346.cos.ap-guangzhou.myqcloud.com/upload/gallery/public/2024-05-21/HXQGyyQZtsSsmmCb_600x960.png","id":"mask","mask":"pixel","width":390,"height":624,"draggable":false},{"tag":"Image","url":"https://gallery-dev-1318352346.cos.ap-guangzhou.myqcloud.com/upload/gallery/public/2024-05-25/jaibrhrCBWPfpa5w.png","x":49,"y":19.5,"width":292,"height":584,"draggable":true,"editable":true},{"tag":"Image","url":"https://gallery-1318352346.cos.ap-guangzhou.myqcloud.com/upload/gallery/public/2024-05-21/HS7befmZbNNnw8Fb_600x960.png","id":"multiply","blendMode":"multiply","zIndex":999999,"width":390,"height":624,"hittable":false}]},{"tag":"Leafer","children":[{"tag":"Group","children":[{"tag":"Leaf","hittable":false},{"tag":"Group","children":[{"tag":"Leaf","hittable":false,"strokeAlign":"center"},{"tag":"Leaf","hittable":false,"strokeAlign":"center"},{"tag":"Group","visible":false,"hittable":false,"children":[{"tag":"Rect"},{"tag":"Rect","strokeAlign":"center"}]}]},{"tag":"Group","visible":false,"children":[{"tag":"Group","children":[{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"bottom-right","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"bottom","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"bottom-left","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"left","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"top-left","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"top","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"top-right","hitFill":"all","children":[]},{"tag":"Box","name":"rotate-point","width":15,"height":15,"around":"right","hitFill":"all","children":[]},{"tag":"Box","name":"rect","hitFill":"all","hitStroke":"none","hitRadius":5,"strokeAlign":"center","children":[]},{"tag":"Group","around":"center","hitSelf":false,"children":[{"tag":"Box","name":"circle","around":"center","hitRadius":5,"cursor":"crosshair","strokeAlign":"center","children":[]}]},{"tag":"Box","name":"resize-line","width":10,"height":10,"around":"center","hitFill":"all","children":[]},{"tag":"Box","name":"resize-line","width":10,"height":10,"around":"center","hitFill":"all","children":[]},{"tag":"Box","name":"resize-line","width":10,"height":10,"around":"center","hitFill":"all","children":[]},{"tag":"Box","name":"resize-line","width":10,"height":10,"around":"center","hitFill":"all","children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]},{"tag":"Box","name":"resize-point","hitRadius":5,"children":[]}]}]}]}]}]}

截图如: image

826327700 commented 1 month ago

作者大大 关于这个问题有什么进展吗?

leaferjs commented 1 month ago

在优化,你的这个结构本身也有很大的优化空间(我估计你整体用fabric.js也是一样的结果):

  1. 比如正片叠底底模式,改成在Rect图案填充中加这个模式性能会好很多,否则会经过一次临时画布(这个是我们现在在优化的)
  2. 遮罩可以换成路径遮罩,把摄像头盖顶上

目前看在Android上是非常流畅的,iOS上应该是用了两次临时画布造成的(遮罩和混合模式)

826327700 commented 1 month ago

在优化,你的这个结构本身也有很大的优化空间(我估计你整体用fabric.js也是一样的结果):

  1. 比如正片叠底底模式,改成在Rect图案填充中加这个模式性能会好很多,否则会经过一次临时画布(这个是我们现在在优化的)
  2. 遮罩可以换成路径遮罩,把摄像头盖顶上

目前看在Android上是非常流畅的,iOS上应该是用了两次临时画布造成的(遮罩和混合模式)

针对你的反馈:

第1点:我直接在程序中去除了正片叠底,只保留 手机壳底图+图案+mask剪切蒙版 三个图层。感觉流畅度变化不大,但当我去除mask剪切蒙版时,流畅度巨幅提升,看来问题还是在mask层。

第2点:路径遮罩对业务来讲应该是不行的,剪切蒙版在实际场景中是可能是不规则的边缘曲线。

我用两台手机测试了,确实如你所说,安卓是非常流畅的,只有ios卡顿:

https://github.com/user-attachments/assets/a99da250-185c-44c1-87e9-cf55efe05742

下面是我今天再一次测试时的思路和代码:

//以下三个方法 分别载入三个图层
public setBackgroundLayer(url: string) {
        return new Promise<void>((resolve, reject) => {
            let image = new Image({ url: url })
            image.once(ImageEvent.LOADED, () => {
                let { width, height } = this.getConstraintSize(image.width!, image.height!, this.leafer.width!, this.leafer.height!)
                this.printRect = this.getOpaqueSize(image.image!.getCanvas(image.width!, image.height!) as any, 1)
                const rect = new Rect({
                    width: width,
                    height: height,
                    fill: {
                        type: 'image',
                        url: url,
                        mode: 'strench',
                    }
                })
                this.leafer.tree.add(rect)
                resolve()
            })
            image.load()
        })
    }

    public setMaskLayer(url: string) {
        return new Promise<void>((resolve, reject) => {
            let image = new Image({ url: url})
            image.once(ImageEvent.LOADED, () => {
                let { width, height } = this.getConstraintSize(image.width!, image.height!, this.leafer.width!, this.leafer.height!)
                image.set({ width, height })
                const rect = new Rect({
                    draggable: false,
                    editable: false,
                    width: width,
                    height: height,
                    mask: true,//这个地方 只要我设置为false,流畅度立马得到巨大提升
                    fill: {
                        type: 'image',
                        url: url,
                        mode: 'strench',
                    }
                })
                this.leafer.tree.add(rect)
                resolve()
            })
            image.load()
        })
    }

    public addImageLayer(url: string) {
        return new Promise<void>((resolve, reject) => {
            let image = new Image({ url: url })
            image.once(ImageEvent.LOADED, () => {
                let { width, height } = this.getCoverModeSize(image.width!, image.height!, this.printRect.width, this.printRect.height)
                let x= this.printRect.left + (this.printRect.width - width) / 2
                let y= this.printRect.top + (this.printRect.height - height) / 2
                const rect = new Rect({
                    draggable: true,
                    editable: true,
                    width: width,
                    height: height,
                    x: x,
                    y: y,
                    fill: {
                        type: 'image',
                        url: url,
                        mode: 'strench',
                    }
                })
                this.leafer.tree.add(rect)
                resolve()
            })
            image.load()
        })
    }

然后在vue中调用:

onMounted(async ()=>{
    editor = new Editor(canvasRef.value!)
    await editor.setBackgroundLayer(backgroundUrl)
    await editor.setMaskLayer(maskUrl)
    // editor.setMultiplyLayer(multiplyUrl)
    await editor.addImageLayer(imageUrl)
})

作者大大,根据我上述的情况,你再帮我确认一下是否有我这边自身代码或者逻辑不合理的地方。 ps:这三层图层结构已经是最简化测试了,再精简就不符合业务需求了,我在测试代码中用同样的图片资源+fabric实现这三层结构是真的不卡...

leaferjs commented 1 month ago

通过代码貌似看不太出来,再检查看看有没有可能出现重复创建mask的情况(打印一下树结构),或者你先弄一个最简单的遮罩示例在iphone上测试一下会不会卡

826327700 commented 1 month ago

通过代码貌似看不太出来,再检查看看有没有可能出现重复创建mask的情况(打印一下树结构),或者你先弄一个最简单的遮罩示例在iphone上测试一下会不会卡

感谢作者大大一直这么有耐心的回复。

我针对我的需求用例,写了一个最简化版本的vue+leaferjs的代码,来还原所有讨论中的问题。 https://gitee.com/crazybaozi/leafer-mask-test 你可以拉取一下代码,查看一下问题。🙏

leaferjs commented 1 month ago
  1. 这个改成div元素,可以节省二次canvas的绘制 image

  2. App配置增加mobile参数, 可以有一些优化 https://www.leaferjs.com/ui/reference/config/app/base.html#mobile-boolean

然后再试试看

826327700 commented 1 month ago
  1. 这个改成div元素,可以节省二次canvas的绘制

罪魁祸首居然是这个!将canvas改成div后,明显提升流畅度,我反复测试div和canvas,确认就是这个问题。 现在流畅度已达到我的期望了,谢谢作者大大!