leaferjs/leafer-ui

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

Closed this issue · 19 comments

如视频所示:

1721294876515946.mp4

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

你这个是不是用了遮罩?

经过测试初步发现,卡顿的根源在于,当画布内有一个图片元素是设置了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的流畅度几乎不受影响。

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

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

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

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

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

mask:"path"吗?

对的

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

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

对的

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

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

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

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

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

可以给一下具体的示例代码吗(导出个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

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

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

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

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

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

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

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

针对你的反馈:

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

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

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

1722322901555153.mp4

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

//以下三个方法 分别载入三个图层
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实现这三层结构是真的不卡...

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

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

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

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

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

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

然后再试试看

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

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