关于新旧canvas的比较我以前写过一篇博客 :https://www.cnblogs.com/sunshine233/p/17014701.html ,这里就不重复了。
但在正文开始之前,我不得不再说一遍微信的文档写的真垃圾。很多问题的答案都是在微信开发者社区里找到的。
一、新版canvas 基础用法:
<template><view><view class="divider">新版canvas 👇</view><!-- 1. canvas-id 换成了 id --><!-- 2. 增加了 type="2d" 表示新版canvas --><canvas id="myCanvas" type="2d" ></canvas></view> </template><script>export default {data() {return {}},mounted() {this.newCanvas();},methods: {newCanvas() {// 3. 获取canvas节点的方式变了,必须按照这个格式写// 3.1 如果不是直接页面而是子页面,把 wx 改成 this,否则获取不到 node节点// 也就是 wx.createSelectorQuery() 其她部分一样 wx.createSelectorQuery().select('#myCanvas').node(({node: canvas}) => {//4. 获取正确实例const ctx = canvas.getContext('2d');ctx.fillStyle = "green";ctx.fillRect(0, 0, 50, 50);// 4.1 设置字体大小(两个参数必须都写)ctx.font = "20px sans-serif";// 4.2 写文字ctx.fillText("我是新版canvas", 50, 30);// 4.3 新版 canvas 不需要调用 draw()// ctx.draw(); }).exec();},}} </script><style scoped>.divider {margin: 10px 0;}canvas {background-color: antiquewhite;} </style>
代码中获取实例的写法和官方实例效果一样
运行结果:
二、新版canvas画图模糊失真
但canvas实际上有两种设置尺寸的方式:
- 一种是默认尺寸300*150(文档中写可以直接用 <canvas id="myCanvas" type="2d" width="300" height="300"></canvas> 修改,但我2025/2/24尝试好像不起效了);
- 一种是在css中设置 canvas { width: 300px; height: 300px; }
这两个尺寸被成为渲染宽高和逻辑宽高。但我觉得微信文档写的似乎有点问题且模糊不清。
查了别的文档 + 实践,我发现如果在css中手动设置了canvas的宽高,画图就会出现模糊(因为canvaWidth和styleWidth不一致)。
和上一段代码差异只增加了css
<template><view><view class="divider">新版canvas 👇</view><!-- 1. canvas-id 换成了 id --><!-- 2. 增加了 type="2d" 表示新版canvas --><canvas id="myCanvas" type="2d" ></canvas></view> </template><script>export default {data() {return {}},mounted() {this.newCanvas();},methods: {newCanvas() {// 3. 获取canvas节点的方式变了,必须按照这个格式写// 3.1 如果不是直接页面而是子页面,把 wx 改成 this,否则获取不到 node节点// 也就是 wx.createSelectorQuery() 其她部分一样 wx.createSelectorQuery().select('#myCanvas').node(({node: canvas}) => {//4. 获取正确实例const ctx = canvas.getContext('2d');ctx.fillStyle = "green";ctx.fillRect(0, 0, 50, 50);// 4.1 设置字体大小(两个参数必须都写)ctx.font = "20px sans-serif";// 4.2 写文字ctx.fillText("我是新版canvas", 50, 30);// 4.3 新版 canvas 不需要调用 draw()// ctx.draw(); }).exec();},}} </script><style scoped>.divider {margin: 10px 0;}canvas {background-color: antiquewhite; width: 300px;height: 300px;} </style>
想解决模糊失真的问题就要使用 canvas.width = styleWidth*dpr; canvas.height = styleHeight*dpr; 也就是重新设置canvas的尺寸等于css中的尺寸*像素比。
获取像素比微信已经给出了api : const dpr = wx.getWindowInfo().pixelRatio;
现在css中宽高设置的都是300, 所以要设置 canvas.width = 300* dpr; canvas.height = 300 * dpr; 最后缩放 ctx.scale(dpr, dpr);
<template><view><view class="divider">新版canvas 👇</view><!-- 1. canvas-id 换成了 id --><!-- 2. 增加了 type="2d" 表示新版canvas --><canvas id="myCanvas" type="2d" ></canvas></view> </template><script>export default {data() {return {}},mounted() {this.newCanvas();},methods: {newCanvas() {// 3. 获取canvas节点的方式变了,必须按照这个格式写// 3.1 如果不是直接页面而是子页面,把 wx 改成 this,否则获取不到 node节点// 也就是 wx.createSelectorQuery() 其她部分一样 wx.createSelectorQuery().select('#myCanvas').node(({node: canvas}) => {//4. 获取正确实例const ctx = canvas.getContext('2d'); const dpr = wx.getWindowInfo().pixelRatio;console.log("default canvas.width:", canvas.width," default canvas.height:", canvas.height);console.log("dpr:", dpr);canvas.width = 300* dpr;canvas.height = 300 * dpr;ctx.scale(dpr, dpr);ctx.fillStyle = "green";ctx.fillRect(0, 0, 50, 50);// 4.1 设置字体大小(两个参数必须都写)ctx.font = "20px sans-serif";// 4.2 写文字ctx.fillText("我是新版canvas", 50, 30);// 4.3 新版 canvas 不需要调用 draw()// ctx.draw(); }).exec();},}} </script><style scoped>.divider {margin: 10px 0;}canvas {background-color: antiquewhite; width: 300px;height: 300px;} </style>
如果想用动态数据可以用
<template><view><view class="divider">新版canvas 👇</view><!-- 1. canvas-id 换成了 id --><!-- 2. 增加了 type="2d" 表示新版canvas --><canvas id="myCanvas" type="2d" :style="{width:styleWidth+'px',height:styleHeight+'px'}" ></canvas></view> </template><script>export default {data() {return { styleWidth:200,styleHeight:100}},mounted() {this.newCanvas();},methods: {newCanvas() {// 3. 获取canvas节点的方式变了,必须按照这个格式写// 3.1 如果不是直接页面而是子页面,把 wx 改成 this,否则获取不到 node节点// 也就是 wx.createSelectorQuery() 其她部分一样 wx.createSelectorQuery().select('#myCanvas').node(({node: canvas}) => {//4. 获取正确实例const ctx = canvas.getContext('2d');const dpr = wx.getWindowInfo().pixelRatio;console.log("default canvas.width:", canvas.width," default canvas.height:", canvas.height);console.log("dpr:", dpr); canvas.width = this.styleWidth* dpr;canvas.height = this.styleHeight * dpr;ctx.scale(dpr, dpr);ctx.fillStyle = "green";ctx.fillRect(0, 0, 50, 50);// 4.1 设置字体大小(两个参数必须都写)ctx.font = "20px sans-serif";// 4.2 写文字ctx.fillText("我是新版canvas", 50, 30);// 4.3 新版 canvas 不需要调用 draw()// ctx.draw(); }).exec();},}} </script><style scoped>.divider {margin: 10px 0;}canvas {background-color: antiquewhite;} </style>
三、新版canvas易错api
id不能用数字开头,字符串的数字也不行
括号赋值变成了等号赋值
旧版: ctx.setFillStyle("red"); ctx.fillRect(50, 50, 75, 75);
新版: // 设置的方式从 ctx.setFillStyle("red") 改成了 ctx.fillStyle = "red"; ctx.fillStyle = "red"; ctx.fillRect(50, 50, 75, 75);
画图片
旧版: ctx.drawImage("/static/cherry.png, 0,0,100,100");
新版:
const image = canvas.createImage(); image.src = "/static/cherry.png"; image.onload = () => {//等待图片资源加载完成才可以画ctx.drawImage(image, 0, 0,100,100); }
设置文字大小
旧版: ctx.setFontSize(20); ctx.fillText('20', 20, 20);
新版: ctx.font = "20px sans-serif"; ctx.fillText("我是新版canvas", 50, 30);
实时刷新抖动白屏
还有一点要注意,如果需要实时更新数据,就需要canvas不同绘画,但不要多次运行 const ctx = canvas.getContext('2d'); 否则页面刷新时会有白屏抖动。
正确的做法是分两个函数,一个初始化context,一个只专心画内容。
下面是我的一部分代码(不能直接运行,删去了部分敏感信息)
<template><view><view v-for="item in tankList"><canvas type="2d" :id="item.id" class="canvas" ></canvas></view></view> </template><script>export default {props: {tankList: {type: Array,default: []}},data() {return {// 油罐图片的宽高bgimgWidth: 200,bgimgHeight: 150,context: [],bgImg: null}},mounted() {this.createContexts(this.bgimgWidth, this.bgimgHeight);//每次进来的时候先画一次 },watch: {tankList(newValue, oldValue) {// if 防止没有context还要画图报错if (this.context.length != 0) {this.refreshCanvas(this.bgimgWidth, this.bgimgHeight);}}},methods: {// 这个页面只运行一次 createContexts() {// ❗❗❗ 新版canvas需要消除锯齿const windowInfo = wx.getWindowInfo();const availableWidth = windowInfo.windowWidth;const dpr = windowInfo.pixelRatio;//设备像素比// 1. 给 context[] 赋值(在这个页面只创建一次)this.tankList.forEach((tank, index) => {this.createSelectorQuery().select(`#${tank.id}`).node(({ node: canvas }) => {this.context[index] = canvas.getContext('2d');console.log('this.context[] 建立成功');canvas.width = availableWidth * dpr;canvas.height = availableWidth * 0.4 * dpr; //按照下面css的尺寸比例this.context[index].scale(dpr, dpr); //必须有//这里调用一遍,防止等待时间过长this.refreshCanvas(this.bgimgWidth, this.bgimgHeight);}).exec();})},refreshCanvas(bgimgWidth, bgimgHeight) {const context = this.context;// 2. 设置不同油罐油品的颜色let oilColors = [];//存储不同油罐油品的颜色this.tankList.forEach(tank => {oilColors.push(converIntToRgb(tank.oilColor));});this.tankList.forEach((tank, index) => {this.createSelectorQuery().select(`#hc${tank.id}`).node(({ node: canvas }) => {this.bgImg = canvas.createImage();this.bgImg.src = "/static/hcbgimg.png";this.bgImg.onload = () => {// ❗❗❗ 每次画新的之前先清空画布(包括图片)this.context[index].clearRect(0, 0, bgimgWidth, bgimgHeight);// 新版canvas画背景图片,将图片绘制到 canvas 上this.context[index].drawImage(this.bgImg, 0, 0, bgimgWidth, bgimgHeight);// 写详细信息(放外面会被图片挡住,因为图片加载较慢)const textColor = tank.connect ? "black" : "red";context[index].fillStyle = textColor;context[index].font = "20px sans-serif";context[index].fillText(tank.id, bgimgWidth * 0.06, bgimgHeight * 0.6);}}).exec();});},} } </script><style scoped> .canvas {width: 750rpx;height: 300rpx;margin-top: 30rpx;margin-left: 37rpx;margin-right: 37rpx; } </style>