微信小程序的canvas诟病颇多,直到调试及出库 2.2.0 版本依然如此。
1. 微信 canvas 绘制也是一个异步函数,在 ctx.draw() 后面直接获取 canvasToTempPath 是不能获取到 canvas 的具体内容的。
2. 微信小程序有很多 bug,尤其在布局上,例如 z-index 无效,定位问题,拖动问题等;因此在界面上展示 canvas 绘制的内容时,通常使用 image 作为显示主体。
3. ctx.draw() 异步操作
ctx.draw(false,function(){
// 当 canvas 绘制完成之后,调用,可以生成临时路径,并且获取完整的图片,但是在安卓的部分设备上会出现渲染不全的效果,因此需要使用延迟加载 setTimeout 函数回避渲染过慢的问题。
})
4. 将 canvas 绘制到 page 中才能获取准确的图片元素,因此,需要设置单独的 canvas 不能为 hidden,隐藏到 page的最底部,不显示出来是一种处理方式
示例部分:
但是在安卓设备上使用这种方法进行绘制时,也会造成一些莫名错误,例如,随机的会产生绘制元素走样的情况
布局部分
wxml:: v2.2.0 官方提示
https://developers.weixin.qq.com/miniprogram/dev/component/canvas.html#canvas
# 微信的 canvas 必须使用 canvas-id 作为身份标识
# 微信的 canvas 不再 demo 中,只存在于最高层,不能通过 z-index 控制
# 请勿在 scroll-view、swiper、picker-view、movable-view 中使用 canvas 组件。
# css 动画对 canvas 无效
# bug: 避免设置过大的宽高,在安卓下会有crash的问题
因此,在使用微信的 canvas 渲染 demo 中的元素时,要回避直接使用微信 canvas 组建;使用获取 canvas 绘制的图片文件,将其保存成临时文件,然后绘制到 image 元素上。所幸,微信小程序在保存临时文件这块功能十分强大。
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: conf.draw_width,
height: conf.draw_height,
destWidth: conf.draw_width,
destHeight: conf.draw_height,
canvasId: target_canID,
fail: function (res) {
console.log(res)
},
success: function (res) {
if (cb) {
cb(res.tempFilePath);
};
}
});
很明显,在渲染字体参数时,微信的 canvas 并没有按照原设定参数绘制。这种事件产生的概率在安卓设备上很大,尤其时华为设备(华为的安卓机两台都出现了该现象),然而在苹果或者微信自己的模拟器上就没有类似 Bug 产生。
解决方法:使用惰性加载(延迟加载)方法,在调用 ctx.draw(false,function(){}) 的回调函数部分,使用 setTimeout 延迟获取 canvasToTempFilePath 可以避免 canvas 在安卓机器上绘制不全的情况。
<view class="top">
<view class="text">历史测试结果</view>
<view class="card">
<view class="t">当前词汇量 :<text class="big">{{vocabulary}}</text></view>
<image src="{{wx_his_img}}" mode="aspectFit" />
</view>
</view>
<view class="canWrap">
<canvas canvas-id="canHis" style="width: 1200rpx; height: 800rpx;" />
</view>
wxss::
# 微信 2.2.0 版本之前,试验过 display : none; height : 1px 等常用的隐藏元素方法,都不能准确的读取 canvas 中的元素内容
# 因此使用将显示元素移动到显示区域外的做法,将 canvas 隐藏起来
.canWrap{
/* 该处必须使用绝对定位,并且定位到显示区域外面,如果 canvas 的大小显示不全,则保存到 image 元素中的大小也是不准确的 */
position: fixed;
bottom: -2000rpx;
left: 0rpx;
width: 100%;
height: 100%;
overflow: visible;
}
.canWrap canvas{
/* 该处必须给定 canvas 大小 */
width: 690rpx;
height: 450rpx;
}
测试数据:
bar_list = [
{
"date": "2018\u5e747\u670824\u65e5",
"amount": 557
},
{
"date": "2018\u5e747\u670824\u65e5",
"amount": 8801
},
{
"date": "2018\u5e747\u670824\u65e5",
"amount": 8023
}
]
具体绘制部分参考:
function wtHistory_canvas(canID, bar_list, cb){
// 需要对应 canvas 的比例
const f = 2;
let conf = {
// 画布大小
draw_width : 600 * f,
draw_height: 400 * f,
font_size: 15 * f,
font_size_lit: 12 * f,
bar : {
x: 0,
y: 0,
interval: 60 * f,
width: 40 * f,
max_height: 150 * f,
text_y: 170 * f,
amount_max: 0
},
// 图形直线
start : {
x: 0 * f,
y: 170 * f
},
to : {
x: 320 * f,
y: 170 * f
}
}
}
// 初始化部分
for (let i = 0; i < bar_list.length; i++){
if ( conf.bar.amount_max < bar_list[i].amount ) {
conf.bar.amount_max = bar_list[i].amount;
}
};
const ctx = wx.createCanvasContext(canID);
// 绘制列表部分
for (let i = 0; i < bar_list.length; i++){
let _h = bar_list[i].amount / conf.bar.amount_max * conf.bar.max_height;
draw_lit(
i * (conf.bar.width + conf.bar.interval) ,
conf.bar.max_height - _h,
conf.bar.width,
_h,
bar_list[i].date,
conf.bar.text_y + 15 * f,
bar_list[i].amount
);
};
// x 坐标直线
coords_x();
// 绘制直方图
function draw_lit(x, y, width, height, text, text_y, amount_text){
// amount_text 图形上面文字
ctx.setFillStyle('#494949');
ctx.setFontSize(conf.font_size);
ctx.fillText(amount_text, x + 25*f, y + 12*f);
// 绘制条形区域
ctx.setFillStyle('#17b1ef');
ctx.fillRect(x + 25*f, y + 20*f, width, height);
// 线下文字
ctx.setFillStyle('#cccccc');
ctx.setFontSize(conf.font_size_lit);
ctx.fillText(text, x, text_y);
};
// 绘制 x 坐标线
function coords_x(){
ctx.setStrokeStyle("#00ade1");
ctx.moveTo(conf.line.start.x, conf.line.start.y);
ctx.lineTo(conf.line.to.x, conf.line.to.y);
ctx.stroke();
};
// 绘制三角形
function triangle(opts, color, rot) {
_ctx.beginPath();
_ctx.setFillStyle(color);
_ctx.moveTo(opts.x, opts.y);
_ctx.lineTo(opts.x + 5, opts.y - 5);
_ctx.lineTo(opts.x + 5, opts.y + 5);
_ctx.closePath();
_ctx.fill();
}
ctx.draw(true, function () {
// 判断绘制方法,判断手机类型
if (app_global.cache.phoneInfo.brand == 'iPhone'){
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: conf.draw_width,
height: conf.draw_height,
destWidth: conf.draw_width,
destHeight: conf.draw_height,
canvasId: canID,
fail: function (res) {
console.log(res)
},
success: function (res) {
if (cb) {
cb(res.tempFilePath);
};
}
})
} else {
// 延迟加载
setTimeout(function () {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: conf.draw_width,
height: conf.draw_height,
destWidth: conf.draw_width,
destHeight: conf.draw_height,
canvasId: canID,
fail: function (res) {
console.log(res)
},
success: function (res) {
if (cb) {
cb(res.tempFilePath);
};
}
})
}, 80)
}
});
}