renderjs保存编辑后的图片


uni-app 里面做图片编辑,页面上能拖拽旋转只是第一步,真正麻烦的是怎么把编辑后的效果保存成图片

图片编辑组件里面,用户操作的是页面上的图片。

图片可以拖动、缩放、旋转、镜像。

这些操作本质上都是 DOM 和 CSS transform 的结果。

但是保存相册时,需要的是一张真实图片。

所以问题就变成了:如何把当前编辑区域重新生成成图片。

为什么需要renderjs

在普通 H5 项目里,如果想把 DOM 转成图片,可以直接在页面里用 html2canvas

但是在 uni-app 里面,尤其是 App 端,逻辑层和视图层是分开的。

普通 script 里面不适合直接拿 DOM,也不适合直接操作浏览器环境里的对象。

所以这里用了 renderjs

renderjs 可以运行在视图层,更接近真实 DOM。

这个场景刚好需要拿到页面里的编辑区域,然后用 html2canvas 截图。

触发方式

组件里面定义了一个状态:

1
startStatus: 0

页面点击保存时,不是直接在普通 methods 里面截图,而是让这个值加一:

1
2
3
saveImage() {
this.startStatus++
}

模板里通过 change:prop 监听这个变化:

1
<text :prop="startStatus" :change:prop="canvas.down"></text>

startStatus 变化时,就会调用 renderjs 里面的 down 方法。

这个处理方式有点绕,但是它解决的是 uni-app 逻辑层和视图层通信的问题。

renderjs截图

renderjs 里面引入 html2canvas

1
2
3
4
5
6
7
8
9
10
11
<script module="canvas" lang="renderjs">
import html2canvas from './html2canvas.min.js'

export default {
methods: {
down(newValue, oldValue, ownerInstance, instance) {
const wrapper = document.getElementById('drop-center')
}
}
}
</script>

这里的 document.getElementById 就是在视图层执行的。

拿到 DOM 后,再交给 html2canvas

1
2
3
4
5
6
7
8
9
html2canvas(wrapper, {
width: wrapper.clientWidth,
height: wrapper.clientHeight,
backgroundColor: null,
scrollY: 0,
scrollX: 0,
useCORS: true,
scale: 2
})

几个配置比较关键。

backgroundColor: null 是为了保留透明背景。

scrollYscrollX 设置为 0,是为了避免页面滚动影响截图位置。

useCORS: true 是为了处理跨域图片。

scale: 2 是为了让生成图片清晰一点。

为什么要忽略旋转图标

组件里面有一个右下角旋转图标。

它只是交互工具,最终保存图片时不应该出现。

所以截图时过滤掉这个元素:

1
2
3
ignoreElements: function(element) {
return element.classList.contains('rotate-icon')
}

这个细节挺重要的。

很多时候编辑界面里会有辅助线、按钮、遮罩、图标。

这些东西是给用户操作看的,不一定要进入最终图片。

生成base64

html2canvas 截图完成后,会得到一个 canvas。

然后转成 base64:

1
let tempFilePath = canvas.toDataURL('image/png')

这里拿到的还不是最终保存相册要用的路径。

它只是一个 base64 字符串。

所以还要通过组件普通 methods 继续处理。

renderjs 调用普通 script 里的方法:

1
ownerInstance.callMethod('saveImg', tempFilePath)

这一步相当于是把视图层生成的图片结果,传回逻辑层。

转成临时路径

普通 methods 里面的 saveImg 会继续处理:

1
2
3
4
5
6
7
8
9
saveImg(url) {
this.tempFilePath = url
base64ToPath(url).then(path => {
this.$emit('sendUrl', {
base64: url,
filePath: path
})
})
}

这里又做了一次转换。

因为最终外部页面保存到相册时,调用的是:

1
2
3
uni.saveImageToPhotosAlbum({
filePath: obj.filePath
})

这个 API 需要的是本地路径。

所以流程是:

1
2
3
4
5
6
DOM编辑区域
-> html2canvas
-> base64
-> base64ToPath
-> filePath
-> saveImageToPhotosAlbum

为什么不直接返回base64

如果只是在 H5 页面展示,base64 其实已经够了。

但是在 App 或小程序里面,保存图片、上传文件、预览文件,很多 API 都更依赖本地临时路径。

base64 太长,也不适合到处传。

所以组件最后返回了两个值:

1
2
3
4
{
base64: url,
filePath: path
}

这样外部页面既可以预览 base64,也可以直接拿 filePath 保存。

总结

这次保存图片的重点不是 html2canvas 本身,而是为什么要这样绕一圈。

因为 uni-app 里面视图层和逻辑层是分开的。

DOM 截图这件事更适合放在 renderjs

生成结果以后,再通过 ownerInstance.callMethod 传回普通 methods。

最后再把 base64 转成本地路径,交给保存相册 API。

以上就是我对 renderjs 保存编辑后图片的理解,如有错误,欢迎大佬指出。

-------------本文结束感谢您的阅读-------------