172 lines
4.2 KiB
Vue
172 lines
4.2 KiB
Vue
<!-- 子组件 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> |