一、关于canvas绘图,小程序官方文档目前有新旧两种接口,分别如下:
1、Canvas 2D 示例代码
onReady() {
const query = wx.createSelectorQuery()
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr
ctx.scale(dpr, dpr)
ctx.fillRect(0, 0, 100, 100)
})
}
2、示例代码(旧的接口)
onReady() {
// 使用 wx.createContext 获取绘图上下文 context
var context = wx.createCanvasContext('firstCanvas')
context.setStrokeStyle("#00ff00")
context.setLineWidth(5)
context.rect(0, 0, 200, 200)
context.stroke()
context.setStrokeStyle("#ff0000")
context.setLineWidth(2)
context.moveTo(160, 100)
context.arc(100, 100, 60, 0, 2 * Math.PI, true)
context.moveTo(140, 100)
context.arc(100, 100, 40, 0, Math.PI, false)
context.moveTo(85, 80)
context.arc(80, 80, 5, 0, 2 * Math.PI, true)
context.moveTo(125, 80)
context.arc(120, 80, 5, 0, 2 * Math.PI, true)
context.stroke()
context.draw()
}
目前网上教程大部分是针对旧的api(第二种示例)的解析,相比于第一种示例(canvas2d)旧版本效率低下且官方明示不在维护。
二、鉴于canvas2d官方文档的某种特性(混乱不堪,投诉无门,秉承鹅厂爱答不理的一贯作风),故整理以下内容:
小程序分享海报案例
1、官网文档地址 https://developers.weixin.qq.com/miniprogram/dev/component/canvas.html
, 特别注意第6、7条:
1. 在ios中不设置宽高生成图片大小会错乱;
2. 安卓系统中dpr超过3时会报如下错误: canvasToTempFilePath:fail:convert native buffer parameter fail. native buffer exceed size limit,解决办法把dpr设置为1即可;
3.绘图初始化 ,由于绘图分先后顺序 所有方法用Promise,绘制文本时特别注意 字体一定要是系统中有的字体 否则文字绘制不上
/**
* 绘图初始化
* @constructor
*/
public DrawInit() {
const that = this;
const query = wx.createSelectorQuery().in(that); //如果是在组件中,则改成
this.createSelectorQuery()
(query as any)
.select("#sharePic")
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
//dpr大于3 安卓绘制失败 此处设为1
const dpr = wx.getSystemInfoSync().pixelRatio;
canvas.width = 750 * 1;
canvas.height = 1220 * 1;
ctx.scale(1, 1);
const CanvasD = new CanvasDraw(canvas, ctx);
that.CanvasD = CanvasD;
CanvasD.DrawImg2d(that.shareInfo.backImg).then(() => {
CanvasD.drawLogo().then(() => {
CanvasD.drawSiteInfo().then(() => {
CanvasD.roundRect(40, 268, 670, 912, 10).then(() => {
CanvasD.drawTXT(that.shareInfo.txtArr).then(() => {
CanvasD.drawQrCode(that.shareInfo.qrCode).then(() => {
CanvasD.DrawImg2d(that.shareInfo.liveIcons, 60, 1018, 92, 36).then(() => {
CanvasD.roundRect(40, 268, 670, 670, 0).then(() => {
that.covertImageAsround(that.shareInfo.actImg).then((actImg) => {
CanvasD.DrawImg2d(actImg, 40, 268, 670, 670).then(() => {
CanvasD._canvasToPath(that).then((res: string) => {
console.error(res, "-----------");
//七牛图片倒角方法
// that.canvasImage = `${res}?roundPic/radius/10`;
that.canvasImage = res;
wx.hideLoading();
});
});
});
});
});
});
});
});
});
});
});
});
// });
}
分装的canvasDwaw方法
import { AppUrls } from "@/utils/consts";
import { CacheManager } from "@/services/cache/cacheManger";
import { IPSMainService } from "@/services/ipsService/ipsMainService";
import { WXAppSDK } from "@/services/weixin/wxAppSDK";
// const debug = require("debug")("log:Canvas2d");
const getSiteInCache = CacheManager.getSiteInCache;
export default class CanvasDraw {
protected WIDTH: number;
protected HEIGHT: number;
protected rate: number;
protected ctx: any = null;
protected dpr = 0;
protected canvasNode: any = null;
protected canvasId = "";
protected siteInfo: SiteInfo = {};
protected canvasImage = "";
constructor(canvasNode, ctx, WIDTH?: number, HEIGHT?: number, rate?: number) {
// super(canvasId);
this.ctx = ctx;
this.canvasNode = canvasNode;
// debug(canvasId);
this.WIDTH = WIDTH || 750;
this.HEIGHT = HEIGHT || 1220;
this.rate = rate || 1;
this.siteInfo = getSiteInCache() || {};
this.init();
}
public init() {
this.dpr = wx.getSystemInfoSync().pixelRatio;
}
public DrawImg2d(url, x?, y?, w?, h?) {
console.error(url);
const that = this;
return new Promise((resolve, reject) => {
this.ctx.beginPath();
this.ctx.save();
this.ctx.restore();
if (!url) {
resolve(1);
return;
}
const img = this.canvasNode.createImage(); //canvas 2d 通过此函数创建一个图片对象
img.onload = (e) => {
console.log("img loaded");
console.log("drawImage", (x || 0) * that.rate, (y || 0) * that.rate, (w || that.WIDTH) * that.rate, (h || that.HEIGHT) * that.rate);
that.ctx.drawImage(img, (x || 0) * that.rate, (y || 0) * that.rate, (w || that.WIDTH) * that.rate, (h || that.HEIGHT) * that.rate);
that.ctx.restore();
// that.ctx.drawImage(res.path, (x || 0) * that.rate, (y || 0) * that.rate, res.width * that.rate, res.height * that.rate);
resolve(that.ctx);
console.log("getImageInfo", e);
};
img.onerror = (e) => {
console.error("err:", e);
};
img.src = url;
console.log("绘制图片公共方法", url);
});
}
/**
* 获取siteLogo
* (转换为七牛地址)
*/
public getSiteLogo() {
return new Promise((resolve) => {
IPSMainService.getSiteLog({
postParams: { siteId: this.siteInfo.siteId }
})
.then((siteLogo) => {
resolve(siteLogo);
})
.catch((errorMessage) => {
throw errorMessage;
});
});
}
/**
* 绘制店铺LOGO
* @param shareCanvas
* @param res
* @param ctx
* @param rate
*/
public drawLogo() {
const that = this;
return new Promise((resolve, reject) => {
that.getSiteLogo().then((siteLogo) => {
that.ctx.save();
// let siteLogo = shareCanvas.createImage();
//绘制外层圆
const avatarurl_width = 112 * that.rate, //绘制的头像宽度
avatarurl_heigth = 112 * that.rate, //绘制的头像高度
avatarurl_x = (that.WIDTH * that.rate - avatarurl_width) / 2, //绘制的头像在画布上的位置
avatarurl_y = 32 * that.rate, //绘制的头像在画布上的位置
//绘制内层圆
s_avatarurl_width = 100 * that.rate, //绘制的头像宽度
s_avatarurl_heigth = 100 * that.rate, //绘制的头像高度
s_avatarurl_x = (that.WIDTH * that.rate - s_avatarurl_width) / 2, //绘制的头像在画布上的位置
s_avatarurl_y = 40 * that.rate; //绘制的头像在画布上的位置
that.ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2);
that.ctx.fillStyle = "#e4e4e4";
that.ctx.fill();
that.ctx.restore();
that.ctx.save();
that.ctx.beginPath();
that.ctx.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, s_avatarurl_width / 2, 0, Math.PI * 2);
that.ctx.fillStyle = "#fff";
that.ctx.fill();
that.ctx.clip(); //画了圆 再剪切 原始画布中剪切任意形状和尺寸。一旦剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内
console.log("绘制店铺LOGO", (siteLogo as any).siteLog);
that.DrawImg2d((siteLogo as any).siteLog, s_avatarurl_x, s_avatarurl_y, s_avatarurl_width, s_avatarurl_heigth).then(() => {
resolve(that.ctx);
});
});
});
}
/**
* 绘制店铺信息
* @param res
* @param ctx
* @param rate
*/
public drawSiteInfo() {
return new Promise((resolve, reject) => {
//siteName
this.ctx.font = `${32 * this.rate}px Arial`;
this.ctx.fillStyle = "#f7f7f7";
this.ctx.textAlign = "center";
this.ctx.fillText(this.siteInfo.siteName, (750 * this.rate) / 2, 186 * this.rate);
// ctx.fillText('139南东 JORDAN', (this.ComputedFontOffsetLeft('139南东 JORDAN', 32 * rate, res[0].width)), 186 * rate);
//siteAddr
this.ctx.font = `${24 * this.rate}px Arial`;
this.ctx.fillStyle = "#DFDFDF";
// ctx.fillText(this.shareInfo.siteInfo.siteAddress, (this.ComputedFontOffsetLeft(this.shareInfo.siteInfo.siteAddress, 24 * rate, res[0].width)), 230 * rate);
this.ctx.fillText(this.siteInfo.siteAddress, (750 * this.rate) / 2, 230 * this.rate);
resolve(this.ctx);
console.log("绘制店铺信息");
});
}
/**
* 圆角矩形
* @param x X轴
* @param y y轴
* @param w 宽
* @param h 高
* @param r 半径
* @param ctx
*/
public roundRect(x, y, w, h, r) {
x = x * this.rate;
y = y * this.rate;
w = w * this.rate;
h = h * this.rate;
r = r * this.rate;
return new Promise((resolve) => {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.ctx.beginPath();
this.ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
this.ctx.moveTo(x + r, y);
this.ctx.lineTo(x + w - r, y);
this.ctx.lineTo(x + w, y + r);
this.ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
this.ctx.lineTo(x + w, y + h - r);
this.ctx.lineTo(x + w - r, y + h);
this.ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
this.ctx.lineTo(x + r, y + h);
this.ctx.lineTo(x, y + h - r);
this.ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
this.ctx.lineTo(x, y + r);
this.ctx.lineTo(x + r, y);
this.ctx.closePath();
this.ctx.fillStyle = "#fff";
this.ctx.fill();
this.ctx.clip();
resolve(this.ctx);
this.ctx.save();
this.ctx.restore();
console.log("圆角矩形");
});
}
/**
* 绘制海报文本
* @param shareCanvas
* @param res
* @param ctx
* @param rate
*/
public drawTXT(arr) {
const TXTArr = arr.filter((item) => {
return item.title;
});
console.log("绘制海报文本", TXTArr);
const that = this;
return new Promise((resolve, reject) => {
for (let index = 0; index < TXTArr.length; index++) {
const item = TXTArr[index];
if (item.title) {
const title = that.workSpace(item.title);
//自定义行数
const len = title.length > item.line ? item.line : title.length || 1;
for (let i = 0; i < len; i++) {
console.log(title[i], that.ctx.measureText(title[i]).width);
let str = title[i];
if (i === len - 1 && str && that.ctx.measureText(str).width > 280 * that.rate) {
str += "...";
}
that.ctx.font = `normal ${item.fontWeight} ${item.fontSize * that.rate}px Arial`; // 设置字体大小
that.ctx.fillStyle = item.color; // 设置文字颜色
that.ctx.textAlign = item.textAlign;
that.ctx.fillText(str, item.x * that.rate, (item.y + (item.fontSize + 10) * i) * that.rate);
// that.ctx.fillText(title[i], item.x * that.rate, item.y * that.rate);
// 是否显示删除线
if (item.isDelLine) {
that.ctx.beginPath();
that.ctx.moveTo(item.x * that.rate, (item.y - item.fontSize / 2 + 2) * that.rate);
that.ctx.lineTo(item.x * that.rate + (item.title.length - 1) * item.fontSize * that.rate, (item.y - item.fontSize / 2 + 2) * that.rate);
that.ctx.lineWidth = 2;
that.ctx.strokeStyle = item.color;
that.ctx.stroke();
}
that.ctx.save();
that.ctx.restore();
}
}
}
resolve(this.ctx);
console.log("绘制海报文本");
});
}
/**
* 文字自动换行
*/
public workSpace(txt) {
const text = this.ctx.measureText(txt);
console.log("txt", txt, text.width);
const chr = txt.split("");
// 保存计算换行后字符
let newTxt = "";
// 保存结果字符数组
const resArr = [];
for (let i = 0; i <= chr.length + 1; i++) {
if (this.ctx.measureText(newTxt).width < 300 * this.rate) {
if (i == chr.length && newTxt) {
resArr.push(newTxt);
} else {
newTxt += chr[i];
}
} else {
i--;
newTxt && resArr.push(newTxt);
newTxt = "";
}
}
console.log("文字自动换行--------------", resArr);
return resArr;
}
/**
* 绘制二维码
* @param shareCanvas
* @param res
* @param ctx
* @param rate
*/
public drawQrCode(url) {
const that = this;
return new Promise((resolve) => {
that.ctx.save();
that.ctx.restore();
that.DrawImg2d(url, 522 * that.rate, (938 + 20) * that.rate, 154 * that.rate, 154 * that.rate).then(() => {
resolve(that.ctx);
console.log("绘制二维码", url);
});
});
}
/**
* 转图片格式
*/
_canvasToPath(evt) {
const that = this;
return new Promise((resolve) => {
(wx as any).canvasToTempFilePath(
{
x: 0,
y: 0,
width: that.WIDTH * that.rate,
height: that.HEIGHT * that.rate,
destWidth: that.WIDTH * that.rate,
destHeight: that.HEIGHT * that.rate,
fileType: "jpg",
quality: 1,
canvas: that.canvasNode,
success(res) {
that.canvasImage = res.tempFilePath;
resolve(res.tempFilePath);
console.log(res, "canvasToTempFilePath");
},
fail(err) {
console.log(err, "canvasToTempFilePath-fail");
}
},
that
);
});
}
/**
* @param qrCode 保存图片
*/
public saveImage(qrCode) {
wx.saveImageToPhotosAlbum({
filePath: this.canvasImage,
success: (img) => {
console.error(img);
WXAppSDK.errorMessage("图片保存成功");
},
fail: () => {
WXAppSDK.errorMessage("图片保存失败");
},
complete: () => {
}
});
}
}