canvas/pages/index/transform-canvas.vue
2025-07-25 09:09:08 +08:00

172 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 子组件 transform-canvas.vue -->
<template>
<view>
<canvas
id="myCanvas"
canvas-id="myCanvas"
type="2d"
:style="{
width: `${canvasDisplayWidth}px`,
height: `${canvasDisplayHeight}px`,
border: '1px solid #eee'
}"
></canvas>
</view>
</template>
<script>
export default {
props: {
width: Number, // 画布实际宽度(像素)
height: Number, // 画布实际高度(像素)
imgUrl: String, // 图片URL
matrix: { // 变换矩阵
type: Object,
default: () => ({
a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0
})
}
},
data() {
return {
canvasContext: null,
image: null,
dpr: 1,
canvasDisplayWidth: 300, // 默认显示尺寸
canvasDisplayHeight: 300,
canvasActualWidth: 300, // 实际像素尺寸
canvasActualHeight: 300
};
},
watch: {
imgUrl: {
handler(newUrl) {
if (newUrl) this.loadImage(newUrl);
},
immediate: true
},
width: {
handler() {
this.updateCanvasSize();
},
immediate: true
},
height: {
handler() {
this.updateCanvasSize();
},
immediate: true
},
matrix: {
deep: true,
handler() {
this.$nextTick(() => {
if (this.canvasContext) {
this.redraw();
}
});
}
}
},
mounted() {
this.initCanvas();
},
methods: {
initCanvas() {
const query = uni.createSelectorQuery().in(this);
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec(async (res) => {
if (!res[0]) return;
this.canvas = res[0].node;
this.ctx = res[0].node.getContext('2d');
// 获取设备像素比
this.dpr = uni.getSystemInfoSync().pixelRatio;
// 更新画布尺寸
this.updateCanvasSize();
// 如果已有图片URL加载图片
if (this.imgUrl) {
await this.loadImage(this.imgUrl);
}
});
},
updateCanvasSize() {
// 显示尺寸使用prop传入的width/height
this.canvasDisplayWidth = this.width;
this.canvasDisplayHeight = this.height;
// 实际像素尺寸考虑DPR
this.canvasActualWidth = this.width * this.dpr;
this.canvasActualHeight = this.height * this.dpr;
if (this.canvas) {
this.canvas.width = this.canvasActualWidth;
this.canvas.height = this.canvasActualHeight;
this.ctx.scale(this.dpr, this.dpr);
this.redraw();
}
},
async loadImage(src) {
try {
this.image = await new Promise((resolve, reject) => {
const image = this.canvas.createImage();
image.src = src;
image.onload = () => resolve(image);
image.onerror = reject;
});
this.redraw();
} catch (e) {
console.error("图片加载失败:", e);
this.image = null;
this.redraw(); // 加载失败时也需要重绘画布
}
},
redraw() {
if (!this.ctx) return;
// 保存当前状态
this.ctx.save();
// 重置为初始状态
this.ctx.resetTransform();
this.ctx.clearRect(0, 0, this.canvasActualWidth, this.canvasActualHeight);
// 应用设备像素比缩放
this.ctx.scale(this.dpr, this.dpr);
// 应用变换矩阵
const { a, b, c, d, tx, ty } = this.matrix;
this.ctx.setTransform(a, b, c, d, tx, ty);
// 绘制图片(或错误信息)
if (this.image) {
this.ctx.drawImage(
this.image,
0,
0,
this.canvasDisplayWidth,
this.canvasDisplayHeight
);
} else {
// 图片加载失败的占位图
this.ctx.fillStyle = '#f0f0f0';
this.ctx.fillRect(0, 0, this.width, this.height);
this.ctx.fillStyle = '#999';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText('图片加载失败', this.width / 2, this.height / 2);
}
// 恢复上下文状态
this.ctx.restore();
}
}
}
</script>