前言
最近在做小程序场景下关于对图像进行二次处理的需求,在进行图像操作流程中遇到了一些坑点,希望读者可以通过这篇文章解决相似的问题或者学会如何在小程序内使用 Canvas 来处理图片,主要遇到的坑点问题如下:
- 小程序 Canvas 容器宽高与画布宽高问题
- 小程序 Canvas 导出图像模糊,质量不高
- 小程序 Canvas 画布宽高 4096 大小限制
- 小程序 Canvas 绘制base 64图片
- 小程序 Canvas 隐藏绘制/离屏绘制问题
技术栈为 Taro + React,当然如果使用 Taro + Vue 或者原生的朋友也可以参考来解决自己的问题
Canvas 图片绘制
在小程序内想对图片进行操作的正常流程有
- 通过 DOM 获取 Canvas 节点并创建 Canvas 实例
- 通过路径读取图片信息,获取图片的宽高以及本地的临时路径,并根据图片宽高设置画布宽高
- 创建 Image 图片对象赋予临时路径,并在其 onLoad 内进行相应的图片处理
- 导出图片到本地获取相应的临时路径,完成图片处理
小程序大部分的 API 都是回调函数的形式,如果习惯 Promsie 的写法也可以将回调函数封装起来使用,上述流程的示例代码可以参考下面的例子
import { View, Text, WebView, Canvas, Image } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro'
import { useEffect, useState, useRef } from 'react'
export default function Index() {
const url = ''
useEffect(() => {
const query = Taro.createSelectorQuery()
query
.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
Taro.getImageInfo({
src: url,
success: (res) => {
console.log(res)
canvas.width = res.width
canvas.height = res.height
const img = canvas.createImage()
img.src = url
img.onload = () => {
ctx.drawImage(img, 0, 0)
Taro.canvasToTempFilePath({
x: 0,
y: 0,
width: res.width,
height: res.height,
destWidth: res.width,
destHeight: res.height,
canvas: canvas,
success: (res) => {
console.log(res.tempFilePath)
}
})
}
}
})
})
}, [])
return (
<View>
<Canvas canvasId="myCanvas" id="myCanvas" type="2d" />
</View>
)
}
有点回调地狱那味了,所以使用场景多可以封装一下,当这段程序运行下来后发现一个问题就是为什么绘制在画布上的图片发生了变形,那是因为我们没有设置Canvas 容器宽高,仅仅设置了画布的宽高,那么第一个问题来了,什么是容器宽高,什么又是画布宽高呢?
容器尺寸与画布尺寸
- 容器尺寸:指的是 Canvas 元素在页面上显示的尺寸,它决定了展示的实际大小,其默认为宽 300px,高 150px,所以导致了上面的效果,又比如我下面设置了容器宽高为页面的宽高和高度,那么画布的显示将铺满全屏
<Canvas canvasId="myCanvas" id="myCanvas" type="2d" style={{ height: '100vh', width: '100vw' }} />
- 画布尺寸:指的是 Canvas 的绘图时的画布尺寸,决定了开发者的绘图范围
所以如果想要保证 Canvas 的显示正常,可以调增Canvas的容器宽高
export default function Index() {
const url =
''
const systemInfo = useRef(Taro.getSystemInfoSync())
useEffect(() => {
console.log('useEffect')
const query = Taro.createSelectorQuery()
query
.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
Taro.getImageInfo({
src: url,
success: (res) => {
console.log(res)
canvas.width = systemInfo.current.windowWidth
canvas.height = systemInfo.current.windowHeight
const img = canvas.createImage()
img.src = url
img.onload = () => {
ctx.drawImage(img, 0, 0)
}
}
})
})
}, [])
return (
<View>
<Canvas
canvasId="myCanvas"
id="myCanvas"
type="2d"
style={{
height: systemInfo.current.windowHeight + 'px',
width: systemInfo.current.windowWidth + 'px'
}}
/>
</View>
)
}
这里就可以发现图片是按照正常比例绘制上去了,但是由于图片的宽度大于画布和页面的宽度,所以没有展示出来,可以对图片的宽高进行等比例缩放,图像的绘制这里就基本OK了,但是此刻你会发现图像的清晰度没有原图高,而且在下一步图像导出的时候又发现我导出的图像质量也极差,非常模糊,这是因为我没有处理**
devicePixelRatio
**,下面我们来讲一下这个
通过 devicePixelRatio 提高 Canvas 的图像质量
devicePixelRatio
表示设备物理像素和逻辑像素之间比例的值。所谓物理像素是终端设备显示颜色的最小单位,而逻辑像素可以理解为CSS规范的单位,可以理解为抽象单位,而
devicePixelRatio
是两者的比值,即
devicePixelRatio
= 屏幕的物理像素 / 屏幕的CSS像素 。
canvas.width = res.width * systemInfo.current.pixelRatio
canvas.height = res.height * systemInfo.current.pixelRatio
ctx.scale(systemInfo.current.pixelRatio, systemInfo.current.pixelRatio)
我们在设置画布尺寸的时候需要乘以设备的
devicePixelRatio
来增加实际的像素数量,同时为了适应逻辑像素,也需要对 Canvas 的上下文进行缩放,这样就可以保证画出来的质量和原图一致。
但是在实际真机调试的过程中我们又发现了一个问题,就是在有些机型无法绘制出 Canvas,在真机调试的过程中发现,原来小程序的画布宽高有限制,最大不能超过4096,所以有的机型的
devicePixelRatio
过高,图片的尺寸也大,就导致在给canvas赋予宽高发送了错误,无法进行绘制,解决方法也很简单,等比例地缩小宽度和高度即可。
const canvasWidth = systemInfo.current.windowWidth
const canvasHeight = (res.height / res.width) * canvasWidth
canvas.width = canvasWidth * systemInfo.current.pixelRatio
canvas.height = canvasHeight * systemInfo.current.pixelRatio
ctx.scale(systemInfo.current.pixelRatio, systemInfo.current.pixelRatio)
在上面的代码片段中,我们导出的是一个本地的临时路径,那么有的场景下是需要base64,这里有两种方法可以进行参考
const baseData = canvas.toDataURL()
const fs = Taro.getFileSystemManager()
fs.readFile({
filePath: res.tempFilePath,
encoding: 'base64',
success: (res) => {
console.log(res.data)
}
})
如果有图片转base64的需求也可以利用Canvas封装转换函数,也可以使用文件管理实例来进行转换
Canvas 隐藏问题与离屏绘制
很多朋友会有离屏绘制的需求,所以一般会给
Canvas
一个隐藏属性,比如说下面的三种方式
<Canvas
canvasId="myCanvas"
id="myCanvas"
type="2d"
style={{
// 第一种
display: 'none',
// 第二种
visibility: 'hidden',
// 第三种
position: 'fixed',
top: '-100vh',
left: '-100vw'
}}
/>
但是会发现,如果使用
display: 'none'
,在部分真机上抛出了错误,Canvas的宽高为0,所以不能使用这种方式,而其他两种使用最为常见,我开发过程中使用
position
使元素离开了视图,但是又发现了问题,因为开发场景涉及到了小程序的
ScrollView
滚动原生组件,而微信官方也是指出不能在
ScrollView
内使用
Canvas
等元素组件,我试了一下,离屏渲染的效果非常不好,Canvas还是会根据滚动随机出现在视图内,所以我改变了策略,微信提供了
createOffscreenCanvas
创建离屏 canvas 实例的能力.
const canvas = Taro.createOffscreenCanvas({ type: '2d', width: 300, height: 150 })
const context = canvas.getContext('2d')
后续流程和使用元素创建是相同的了。
最后
小程序的
Canvas
在几年前完成了升级,更符合Web的Canvas标准,但是也有一些坑需要开发者去踩,如果有问题可以在评论区讨论。
版权归原作者 偏爱前端的晓羽 所有, 如有侵权,请联系我们删除。