做了个海报生成的需求,顺便整合一下问题和js,以后再做海报生成方便些。
Canvas海报生成
问题
canvas
定义一个canvas标签并定义canvas-id
<canvas canvas-id="poster" ></canvas>
canvas的css属性
//注意这里的width和height就是你最终生成图片的宽高
<style>
canvas{
width:100px;
height:100px;
}
</style>
获取的canvas实例,this就是小程序的当前页面的this指向。(注意一下异步函数里的this指向)
let context = wx.createCanvasContext('poster', this);
矩形问题
圆角矩形
矩形中问题比较麻烦的就是圆角矩形了,css中可以直接使用border-radius来设置圆角,canvas就不行了。
查了一下做法差不多都是这种,我就简单点了。
1,四角个画一个圆 2,在画线或矩形来作为四边 3,最后一个矩形来完成中间的填充
//绘制top直线
this.context.fillRect(x + r, y, w - 2 * r, r)
//开始绘制左上角半圆
this.context.arc(x + r, y + r, r, 0, 2 * Math.PI)
//绘制bottom直线
this.context.fillRect(x + r, y + h - r, w - 2 * r, r)
//开始绘制右上角半圆
this.context.arc(x + w - r, y + r, r, 0, 2 * Math.PI)
//绘制left直线
this.context.fillRect(x, y + r, r, h - 2 * r)
//开始绘制左下角半圆
this.context.arc(x + r, y + h - r, r, 0, 2 * Math.PI)
//绘制right直线
this.context.fillRect(x + w - r, y + r, r, h - 2 * r)
//开始绘制右下角半圆
this.context.arc(x + w - r, y + h - r, r, 0, 2 * Math.PI)
//绘制中间层
this.context.fillRect(x + r, y + r, w - 2 * r, h - 2 * r)
这样一个圆角矩形就创建完成了!是不是很麻烦啊
图片问题
跨域!!!
最最最麻烦的图片跨域污染canvas的问题 微信小程序中不能new Image(),所以设置不了crossOrigin:Anonymous
crossOrigin:Anonymous可以解决js中图片跨域污染canvas画布的问题。
但是没设置Access-Control-Allow-Origin头部信息的图片使用crossOrigin属性也没用。
可以去阿里官网那里看看,有些图片上是没有Access-Control-Allow-Origin的。
小程序canvas图片跨域的坑
1,在微信调试工具上你能发现,哪怕是跨域并且没有设置Access-Control-Allow-Origin:*的图片在画布上一样可以渲染出来,并且有可以生成合成图片!(如果没有,就把不检验域名打开。另外那些404域名的图片就不用试了,比如谷歌的)
2,这里就是一个坑了,微信小程序canvas上图片只支持本地图片!本地图片!本地图片!。哪怕你能看见手机测试时候也是生成不了的。
3,网络图片是需要下载到本地才能进行渲染的
wx.getImageInfo({
src: src,//需要下载的图片网络路径
success: function (res) {
res.path//图片路径
}
});
4,网络图片下载需要注意你是否将域名添加到了Down的域名白名单下,注意https和http
上面说了canvas图片跨域的坑,这里说一下图片渲染时的坑吧
渲染时遇到最麻烦的坑就是下载网络图片时是异步的,而且有延时性(来自图片的下载速度)。所以你会发现下载完成后网络图片并没有渲染到canvas上去,这是应为draw已经先于图片下载完成了。
解决网络图片下载的问题
1,小图可以直接保存在本地
2,用户进入时找时机异步下载会使用的图片保存在本地,然后地址存在storage中。
3,应为一些需求原因,有些图片需要实时生成(例:二维码)在合成图片时在下载需要的图片。等待网络图片下载完成,在与其他文字等数据一同渲染。这步就需要久的时间了。
这里可以使用Promise.all来完成异步的图片下载,等待所有回调完成时。在去执行统一的canvas渲染。
如果你想等同步渲染完成,再去一步一步的渲染异步的图片,你会发现后面的异步图片遮盖了同步时渲染的数据。而且每张图片的下载时间是无法控制的。
文字问题
文字换行
canvas这里没有文字自动换行的功能,要使用js来控制每行的字数。
/**
* @param {breakLinesForCanvas}进行分割文字
* @param {String}text:文字
* @param {Number}width:文字每行宽度
*/
breakLinesForCanvas(text, width, Ellipsis) {
let result = [],
breakPoint = 0;
//进行文字换行分割
//breakPoint获取到分割的文字数量
while ((breakPoint = this.findBreakPoint(text, width)) !== -1) {
//保存分割的文字
result.push(text.substr(0, breakPoint));
//去除分割的文字
text = text.substr(breakPoint);
}
if (text) {
result.push(text);
}
if (Ellipsis && result.length >= Ellipsis) {
result[Ellipsis - 1] += '...'
result = this.breakLinesForCanvas(result.join(''), width, false)
result.length = Ellipsis
}
return result;
}
/**
* @param {findBreakPoint}进行二分法查找
* @param {String}text:文字
* @param {Number}width:宽度
*/
findBreakPoint(text, width) {
var min = 0;
var max = text.length - 1;
while (min <= max) {
let middle = Math.floor((min + max) / 2);
//使用measureText测量一半text的宽度
let middleWidth = this.context.measureText(text.substr(0, middle)).width;
//使用measureText测量一半 + 1text的宽度
let oneCharWiderThanMiddleWidth = this.context.measureText(text.substr(0, middle + 1)).width;
//获取width能获取的最大文字数量
if (middleWidth <= width && oneCharWiderThanMiddleWidth > width) {
return middle;
}
//一半文字的宽度大于文字的宽度时递减文字数量,小于时递增文字数量
if (middleWidth < width) {
min = middle + 1;
} else {
max = middle - 1;
}
}
return -1;
}
这里两个函数就解决了文字换行的问题。
主要使用findBreakPoint函数分割文字。
canvas.measureText来计算文字的width宽度是否超出限制
Ellipsis则是截断文字是否使用省略号。
文字根据上文来换行,防止文字重叠
文字就会有换行,但是下面另外的文字的x没有变化,这导致文字重叠。
还有同行文字不同颜色,这个有点麻烦还有换行的问题,想了思路但还没写。先把实例放上来了,可以康我的git连接。
另外再说一个问题,微信小程序碰到一些自带方法不可用及不报错,也不报问题。 Promise.all这里因为返回的是一个数组,所以我用了flat()方法来扁平化数组,但是好像现在版本不支持WDNMD。
这里说几个注意点吧
1, 海报前端合成的时间全看用户的网络和手机性能。网络图片尽量减少体积。
2, 海报合成时真机调试和手机都试一下,免得环境问题导致有点使用不了
3, 应为网络图片需要下载到本地,所以注意自己配置的downloadFile白名单(注意http和https)
这里贴一下最终canvas图片生成后下载图片的代码吧,传入图片的url就行。
DownImage(url) {
wx.getSetting({
success(res) {
// 如果没有则获取授权
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
wx.saveImageToPhotosAlbum({
filePath: url,
success() {
wx.showToast({
title: '保存成功'
});
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
});
}
});
},
fail() {
wx.openSetting({
success(res) {}
});
}
});
} else {
// 有权限直接保存
wx.saveImageToPhotosAlbum({
filePath: url,
success() {
wx.showToast({
title: '保存成功'
});
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
});
}
});
}
}
});
github地址:
github.com/WSQwansui/C…
//canvas的实例
let context = wx.createCanvasContext('poster', this)
//渲染所使用的数据
let List = [
{
type: 'background',
color: '#26B2AF',
x: 0,
y: 0,
width: 375,
height: 597,
id: 'poster'
},
{
x: 26,
y: 250,
width: 323,
height: 300,
type: 'view',
color: '#fff',
radius: true,
radiusSize: 10
},
{
src: 'https://docs.alibabagroup.com/assets2/images/cn/home/home_banner_1.png', //图片路径
x: 0,
y: 0,
width: 375,
height: 190,
type: 'image',
local: false,
zIndex: 10
},
{
src: '../../images/canvas_1.png',
x: 26,
y: 500,
width: 100,
height: 30,
type: 'image',
local: true,
TextAlign: true,
},
{
text: '232323232', //文字文本
x: 50, //x轴起始位置
y: 380, //y轴起始位置
width: 300, //文本宽度
font: 'bold 20px 微软雅黑', //字体大小与字体,默认14px 微软雅黑
color: '#26B2AF', //颜色
LineHeight: 0, //文字换行高度设置
TextAlign: true,
type: 'text',
Relation: {
id: 1,
height: 30
}
}, {
text: '232323232', //文字文本
x: 50, //x轴起始位置
y: 380, //y轴起始位置
width: 300, //文本宽度
font: 'bold 20px 微软雅黑', //字体大小与字体,默认14px 微软雅黑
color: '#26B2AF', //颜色
LineHeight: 0, //文字换行高度设置
TextAlign: true,
type: 'text',
Relation: {
id: 1,
height: 30
}
}
];
//初始化数据与canvas
let SynPoster = new poster.poster(context, List)
//最后合成,canvas合成也在这步,then中返回的就是最终的url
SynPoster.Synthesis()
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
给个三连吧,哐哐哐。