uniapp + 微信小程序:新版canvas常用api及注意事项
约 2188 字大约 7 分钟
2025-07-04
在正文开始之前,我不得不再说一遍微信的文档写的太差了,很多问题的答案都是在微信开发者社区里找到的。
关于新旧 canvas
的比较我以前写过一篇博客 :uniapp 微信小程序使用新旧版本 canvas 对比、消除锯齿、处理重绘 ,这里就不重复了。
新版 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
的宽高分为渲染宽高和逻辑宽高:
- 渲染宽高为
canvas
画布在页面中所实际占用的宽高大小,即通过对节点进行boundingClientRect
请求获取到的大小。 - 逻辑宽高为
canvas
在渲染过程中的逻辑宽高大小,如绘制一个长方形与逻辑宽高相同,最终长方形会占满整个画布。逻辑宽高默认为 300 * 150。
不同的设备上,存在 物理像素和逻辑像素不相等 的情况,所以一般我们需要用 wx.getWindowInfo
获取设备的像素比,乘上 canvas
的渲染大小,作为画布的逻辑大小。
但我觉得微信文档写的似乎有点问题且模糊不清。
canvas
实际上有两种设置尺寸的方式:
在
canvas
组件上直接设置width
和height
属性(默认尺寸 300*150 , 但我2025/2/24
尝试好像不起效了)<canvas id="myCanvas" type="2d" width="300" height="300"></canvas>
在 css 中设置
width
、height
canvas {
width: 300px;
height: 300px;
}
canvas 组件上设置的尺寸、在 css 中设置的尺寸是两个尺寸,它们分别叫渲染宽高 、 逻辑宽高。不过我更喜欢称之为 canvaWidth
和 styleWidth
(以 width 为例)。
查了别的文档 + 实践,我发现 如果在 css 中手动设置了 canvas 的宽高,画图就会出现模糊,因为 canvaWidth 和 styleWidth 不一致。
给上文的代码中增加 css 设置 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;
width: 300px;
height: 300px;
}
</style>
文字出现模糊了。因为 canvaWidth 和 styleWidth 不一致
想解决模糊失真的问题就要重新设置 canvas的尺寸 = css中的尺寸*像素比
canvas.width = styleWidth * dpr;
canvas.height = styleHeight * dpr;
dpr
是设备的 像素比。
微信获取像素比 api : const dpr = wx.getWindowInfo().pixelRatio;
在上个例子中 css 中宽高设置的都是 300
canvas {
background-color: antiquewhite;
width: 300px;
height: 300px;
}
所以要设置 canvas的尺寸 = css中的尺寸*像素比
:
canvas.width = 300 * dpr;
canvas.height = 300 * dpr;
最后缩放 canvas : 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>
文字变清晰了:
如果 css 尺寸 想用动态数据可以参考如下代码:
<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>