uniapp 微信小程序使用新旧版本 canvas 对比、消除锯齿、处理重绘
约 2234 字大约 7 分钟
2025-07-04
微信文档:canvas
微信小程序使用 canvas
时,分为旧版和新版。
旧版:不支持同层渲染,使用 canvas 的图层总在最上层。模拟器可能表现正常,但真机的 canvas 图层一定在最上层,设置了 z-index
也无效。
新版:2.9.0 起支持一套新 Canvas 2D 接口(需指定 type 属性),同时支持同层渲染,原有接口不再维护。
canvas 涉及到的单位都是 px
旧版 canvas 基础用法
<template>
<view id="app">
<canvas canvas-id="myCanvas" />
<view class="content"> 我应该在 canvas 最上层,不该被矩形挡住 </view>
</view>
</template>
<script>
export default {
data() {
return {};
},
mounted() {
this.drawCanvas();
},
methods: {
/**
* canvas 的单位都是 px
* 默认尺寸 300px*150px
* 先画的层级较低,后画的层级较高
* 超出画布的部分不会显示
* **/
drawCanvas() {
const ctx = wx.createCanvasContext("myCanvas");
ctx.setStrokeStyle("rgba(0, 128, 0,1)");
ctx.strokeRect(0, 0, 75, 75);
ctx.setFillStyle("rgba(0, 128, 0,1)");
ctx.fillRect(0, 0, 75, 75);
ctx.setFillStyle("red");
ctx.fillRect(50, 50, 75, 75);
ctx.setFillStyle("yellow");
ctx.fillRect(100, 100, 75, 75);
ctx.draw(); //一定要调用 draw(),否则不能画出图形
},
},
};
</script>
<style>
#app {
position: relative;
}
canvas {
position: absolute;
z-index: 0;
background-color: lightblue;
}
.content {
position: absolute;
z-index: 1;
}
</style>
旧版效果图
h5 和微信开发者工具正常显示,但是真机(安卓)上 canvas 中画的矩形在最上层,挡住了文字。
解决办法
改成新版 canvas 。
新版 canvas 基础用法
<template>
<view id="app">
<canvas id="myCanvas" type="2d" />
<view class="content">我应该在 canvas 最上层,不该被矩形挡住</view>
</view>
</template>
<script>
export default {
data() {
return {};
},
mounted() {
this.drawCanvas();
},
methods: {
/**
* canvas 的单位都是 px
* 默认尺寸 300px*150px
* 先画的层级较低,后画的层级较高
* 超出画布的部分不会显示
* **/
drawCanvas() {
wx.createSelectorQuery()
.select("#myCanvas") // 在 WXML 中填入的 id
.node(({ node: canvas }) => {
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "rgba(0, 128, 0,1)";
ctx.strokeRect(0, 0, 75, 75);
ctx.fillStyle = "rgba(0, 128, 0,1)";
ctx.fillRect(0, 0, 75, 75);
ctx.fillStyle = "red"; // 设置的方式从 ctx.setFillStyle("red") 改成了 ctx.fillStyle = "red";
ctx.fillRect(50, 50, 75, 75);
ctx.fillStyle = "yellow";
ctx.fillRect(100, 100, 75, 75);
// ctx.draw(); //新版 canvas 不需要调用 draw()
})
.exec();
},
},
};
</script>
<style>
#app {
position: relative;
}
canvas {
position: absolute;
z-index: 0;
background-color: lightblue;
}
.content {
position: absolute;
z-index: 1;
}
</style>
新版效果图:微信开发者工具上 canvas 中画的矩形在最上层,挡住了文字,但是真机(安卓)正常显示。
易错点注意!
- 设置
strokeStyle
和fillStyle
的方法都变了,从ctx.setFillStyle("red")
改成了ctx.fillStyle = "red";
从("参数")
的形式改成了用 = 赋值
的形式。其她类似的 api 如果使用时报错,都要修改为用=赋值
的形式。 - 不需要再调用
ctx.draw()
了。 - 新版 canvas 的文档写的不好(微信文档和 uniapp 文档写的都不好),有很多问题需要查看社区提问的回答,比如 创建'2d'canvas 出现 Cannot read 'node' of null 的问题?
新版 canvas 绘制消除锯齿
相关信息
不使用 ctx.scale(dpr, dpr) 代码:
<template>
<view id="app">
<canvas
type="2d"
id="canvasCircle"
:style="{ width: styleWidth + 'px', height: styleHeight + 'px' }"
>
</canvas>
<view class="content">不使用 ctx.scale(dpr, dpr) </view>
</view>
</template>
<script>
export default {
data() {
return {
canvasWidth: 300, //画布尺寸px
canvasHeight: 150, //画布尺寸 px
styleWidth: 300, //画板尺寸 px
styleHeight: 150, //画板尺寸 px
defaultCanvasW: 300,
defaultCanvasH: 150,
};
},
mounted() {
this.setCanvasSize();
this.drawCircle();
},
methods: {
/**
* 如何正确设置canvas尺寸,以及如何在高分辨率屏幕上清晰显示canvas图形 :
* -- https://segmentfault.com/a/1190000020189168
* */
setCanvasSize() {},
drawCircle() {
let radius = this.canvasHeight / 4;
let pointO = {
x: this.canvasWidth / 2,
y: this.canvasHeight / 2,
};
wx.createSelectorQuery()
.in(this)
.select("#canvasCircle")
.fields({
node: true,
size: true,
})
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
// // Canvas 画布的实际绘制宽高
// const width = res[0].width; //画布尺寸 300
// const height = res[0].height; //画布尺寸 150
// // 初始化画布大小
// const dpr = wx.getWindowInfo().pixelRatio;
// console.log({dpr});
// canvas.width = width * dpr;//设置画布尺寸
// canvas.height = height * dpr;//设置画布尺寸
// ctx.scale(dpr, dpr);//必须有
drawCircle(ctx);
drawText(ctx);
});
function drawCircle(ctx) {
// 画圆心
ctx.beginPath();
ctx.arc(pointO.x, pointO.y, 2, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
// 画圆
ctx.beginPath();
ctx.arc(pointO.x, pointO.y, radius, 0, 2 * Math.PI);
ctx.strokeStyle = "red";
ctx.stroke();
}
function drawText(ctx) {
ctx.fillStyle = "blue";
ctx.font = "20px Verdana";
ctx.textAlign = "center";
ctx.translate(pointO.x, pointO.y); //对当前坐标系的原点(0, 0)进行变换,默认的坐标系原点为页面左上角。
ctx.fillText("跑步", 0, 0);
}
},
},
};
</script>
<style scoped>
#app {
position: relative;
}
canvas {
position: absolute;
z-index: 0;
top: 0;
left: 0;
background-color: rgba(173, 216, 230, 0.5);
}
.content {
position: absolute;
z-index: 1;
/* right: 0; */
}
</style>
使用 ctx.scale(dpr, dpr) 代码:
<template>
<view id="app">
<canvas
type="2d"
id="canvasCircle"
:style="{ width: styleWidth + 'px', height: styleHeight + 'px' }"
>
</canvas>
<view class="content">使用了 ctx.scale(dpr, dpr) </view>
</view>
</template>
<script>
export default {
data() {
return {
canvasWidth: 300, //画布尺寸px
canvasHeight: 150, //画布尺寸 px
styleWidth: 300, //画板尺寸 px
styleHeight: 150, //画板尺寸 px
defaultCanvasW: 300,
defaultCanvasH: 150,
};
},
mounted() {
this.setCanvasSize();
this.drawCircle();
},
methods: {
/**
* 如何正确设置canvas尺寸,以及如何在高分辨率屏幕上清晰显示canvas图形 :
* -- https://segmentfault.com/a/1190000020189168
* */
setCanvasSize() {},
drawCircle() {
let radius = this.canvasHeight / 4;
let pointO = {
x: this.canvasWidth / 2,
y: this.canvasHeight / 2,
};
wx.createSelectorQuery()
.in(this)
.select("#canvasCircle")
.fields({
node: true,
size: true,
})
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
// Canvas 画布的实际绘制宽高
const width = res[0].width; //画布尺寸 300
const height = res[0].height; //画布尺寸 150
// 初始化画布大小
const dpr = wx.getWindowInfo().pixelRatio;
console.log({ dpr });
canvas.width = width * dpr; //设置画布尺寸
canvas.height = height * dpr; //设置画布尺寸
ctx.scale(dpr, dpr); //必须有
drawCircle(ctx);
drawText(ctx);
});
function drawCircle(ctx) {
// 画圆心
ctx.beginPath();
ctx.arc(pointO.x, pointO.y, 2, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
// 画圆
ctx.beginPath();
ctx.arc(pointO.x, pointO.y, radius, 0, 2 * Math.PI);
ctx.strokeStyle = "red";
ctx.stroke();
}
function drawText(ctx) {
ctx.fillStyle = "blue";
ctx.font = "20px Verdana";
ctx.textAlign = "center";
ctx.translate(pointO.x, pointO.y); //对当前坐标系的原点(0, 0)进行变换,默认的坐标系原点为页面左上角。
ctx.fillText("跑步", 0, 0);
}
},
},
};
</script>
<style scoped>
#app {
position: relative;
}
canvas {
position: absolute;
z-index: 0;
top: 0;
left: 0;
background-color: rgba(173, 216, 230, 0.5);
}
.content {
position: absolute;
z-index: 1;
/* right: 0; */
}
</style>
效果图对比:
新版 canvas 绘制有重绘的情况
- 点的坐标要用
canvasWidth
确定,而不是styleWidth
- 如果有重绘的话,
res[0].width
会变,所以用canvas.width = this.defaultCanvasW _ dpr;
而不是canvas.width = width _ dpr;
// %c 是改变 console.log 样式的。
console.log(`%cx:${pointO.x}, y:${pointO.y}`, "color:green;-size:large");
- 设置画布尺寸代码为:
canvas.width = this.defaultCanvasW _ dpr; canvas.height = this.defaultCanvasH _ dpr;
<template>
<view id="app">
<canvas
type="2d"
id="canvasCircle"
:style="{ width: styleWidth + 'px', height: styleHeight + 'px' }"
>
</canvas>
<view class="content">新版canvas绘制有重绘的情况</view>
</view>
</template>
<script>
export default {
data() {
return {
canvasWidth: 300, //画布尺寸px
canvasHeight: 150, //画布尺寸 px
styleWidth: 300, //画板尺寸 px
styleHeight: 150, //画板尺寸 px
defaultCanvasW: 300,
defaultCanvasH: 150,
};
},
mounted() {
this.setCanvasStyleSize();
this.drawCircle();
setTimeout(() => {
this.drawCircle();
}, 2000);
},
methods: {
/**
* 如何正确设置canvas尺寸,以及如何在高分辨率屏幕上清晰显示canvas图形 :
* -- https://segmentfault.com/a/1190000020189168
* */
setCanvasStyleSize() {
let windowInfo = uni.getWindowInfo();
let ratio = windowInfo.windowWidth / 300; //计算屏幕宽度和 canvas 默认尺寸的比例
this.styleWidth = windowInfo.windowWidth;
this.styleHeight *= ratio;
console.log(
`%cstyleWidth:${this.styleWidth}px, styleHeight:${this.styleHeight}px`,
"color:yellow"
);
},
drawCircle() {
// 点的坐标要用 canvasWidth 确定,而不是 styleWidth
// 如果有重绘的话,canvas.width 会变,而点的坐标固定的和300*150比较,所以点的坐标最好另存一个变量
let pointO = {
x: this.defaultCanvasW / 2,
y: this.defaultCanvasH / 2,
};
console.log(
`%cx:${pointO.x}, y:${pointO.y}`,
"color:green;font-size:large"
);
wx.createSelectorQuery()
.in(this)
.select("#canvasCircle")
.fields({
node: true,
size: true,
})
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
// Canvas 画布的实际绘制宽高
const width = res[0].width; //画布尺寸 300
const height = res[0].height; //画布尺寸 150
console.log("width:", width, " height:", height);
// 初始化画布大小
const dpr = wx.getWindowInfo().pixelRatio;
console.log("dpr:", dpr);
canvas.width = this.defaultCanvasW * dpr; //设置画布尺寸
canvas.height = this.defaultCanvasH * dpr; //设置画布尺寸
ctx.scale(dpr, dpr);
console.log(
"canvas.width:",
canvas.width,
" canvas.height:",
canvas.height
);
drawCircle(ctx); //一定要放在 ctx.scale(dpr, dpr); 之后,
});
function drawCircle(ctx) {
// 画圆心
ctx.beginPath();
ctx.arc(pointO.x, pointO.y, 5, 0, 2 * Math.PI);
ctx.fillStyle = "red";
ctx.fill();
}
},
},
};
</script>
<style scoped>
#app {
position: relative;
}
canvas {
position: absolute;
z-index: 0;
top: 0;
left: 0;
background-color: rgba(173, 216, 230, 0.5);
}
.content {
position: absolute;
z-index: 1;
/* right: 0; */
}
</style>
运行效果:
如果设置画布尺寸仍用 canvas.width = width * dpr;
则重绘的时候结果如下:
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
// Canvas 画布的实际绘制宽高
const width = res[0].width; //画布尺寸 300
const height = res[0].height; //画布尺寸 150
console.log("width:", width, " height:", height);
// 初始化画布大小
const dpr = wx.getWindowInfo().pixelRatio;
console.log("dpr:", dpr);
// canvas.width = this.defaultCanvasW * dpr; //设置画布尺寸
// canvas.height = this.defaultCanvasH * dpr; //设置画布尺寸
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
console.log("canvas.width:", canvas.width, " canvas.height:", canvas.height);
drawCircle(ctx); //一定要放在 ctx.scale(dpr, dpr); 之后,