first commit

This commit is contained in:
zhouyanli
2026-04-18 08:31:55 +08:00
commit eeb09abc04
989 changed files with 137562 additions and 0 deletions

View File

@@ -0,0 +1,423 @@
<template>
<view class="u-canvas"
:id="rootId"
:style="{
width: useRootHeightAndWidth ? '100%' : 'auto',
height: useRootHeightAndWidth ? '100%' : 'auto',
}">
<!-- #ifdef MP || H5 -->
<canvas
class="u-canvas__canvas"
:id="canvasId"
:canvas-id="canvasId"
type="2d"
:style="{ width: width + unit, height: height + unit }"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"/>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<canvas
class="u-canvas__canvas"
:id="canvasId"
:canvas-id="canvasId"
:style="{ width: width + unit, height: height + unit }"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"/>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<gcanvas class="u-canvas__canvas" ref="gcanvess"
:style="{ width: width + unit, height: height + unit }"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd">
</gcanvas>
<!-- #endif -->
</view>
</template>
<script>
// #ifdef APP-NVUE
// https://github.com/dcloudio/NvueCanvasDemo/blob/master/README.md
import {
enable,
WeexBridge,
Image as GImage
} from '../../libs/util/gcanvas/index.js';
// #endif
let canvasNode = null;
export default {
name: "u-canvas",
props: {
canvasId: {
type: String,
default: () => {
return `u-canvas${Math.floor(Math.random() * 1000000)}`
}
},
width: {
type: [String, Number],
default: 300
},
height: {
type: [String, Number],
default: 300
},
unit: {
type: String,
default: 'px'
},
useRootHeightAndWidth: {
type: Boolean,
default: false
},
// 背景色
bgColor: {
type: String,
default: '#ffffff'
}
},
data() {
return {
rootId: `rootId${Number(Math.random() * 100).toFixed(0)}`,
ganvas: null,
canvasContext: null,
widthLocal: this.width,
heightLocal: this.height,
ctx: null
};
},
computed: {
// 计算实际画布尺寸
actualWidth() {
return this.useRootHeightAndWidth ? this.widthLocal : Number(this.width);
},
actualHeight() {
return this.useRootHeightAndWidth ? this.heightLocal : Number(this.height);
}
},
methods: {
// 添加触摸事件处理方法
onTouchStart(e) {
this.$emit('touchstart', e);
},
onTouchMove(e) {
this.$emit('touchmove', e);
},
onTouchEnd(e) {
this.$emit('touchend', e);
},
/**
* 获取节点
* @param id 节点id
* @param isCanvas 是否为Canvas节点
* @return {Promise<unknown>}
*/
async getCanvasNode(id, isCanvas = true) {
let that = this
return new Promise((resolve, reject) => {
try {
// #ifdef APP-NVUE
setTimeout(() => {
/*获取元素引用*/
this.ganvas = this.$refs["gcanvess"]
/*通过元素引用获取canvas对象*/
let canvasNode = enable(this.ganvas, {
bridge: WeexBridge
})
resolve(canvasNode)
}, 200)
// #endif
// #ifndef APP-PLUS-NVUE
const query = uni.createSelectorQuery().in(that).select(`#${id}`);
query.fields({
node: true,
size: true
})
.exec((res) => {
if (isCanvas) {
if (res[0]?.node) {
resolve(res[0].node)
} else {
resolve(false)
console.error("获取节点出错", res)
}
} else {
resolve(res[0])
}
})
// #endif
} catch (e) {
console.error("获取节点失败", e)
}
})
},
/**
* 获取Canvas上下文
*/
getCanvasContext() {
// #ifdef APP-PLUS
return uni.createCanvasContext(this.canvasId, this);
// #endif
// #ifdef APP-PLUS-NVUE || MP || H5
return canvasNode.getContext('2d');
// #endif
},
/**
* 初始化Canvas
*/
async initCanvas() {
try {
canvasNode = await this.getCanvasNode(this.canvasId);
// #ifdef MP-WEIXIN
// 在微信小程序中,为了提高清晰度,需要考虑设备像素比
const dpr = uni.getSystemInfoSync().pixelRatio;
if(canvasNode) {
// 设置canvas实际绘制尺寸为显示尺寸的dpr倍
canvasNode.width = this.actualWidth * dpr;
canvasNode.height = this.actualHeight * dpr;
}
// #endif
this.ctx = this.getCanvasContext();
// #ifdef MP-WEIXIN
if(this.ctx) {
this.ctx.scale(dpr, dpr);
}
// #endif
// 初始化背景但不在微信小程序中调用draw
this.clearCanvas();
} catch (error) {
console.error("初始化Canvas失败:", error);
}
},
/**
* 清空画布
*/
clearCanvas() {
if (!this.ctx) return;
this.clearRect(0, 0, this.actualWidth, this.actualHeight);
// 填充背景色
this.beginPath();
this.rect(0, 0, this.actualWidth, this.actualHeight);
this.setFillStyle(this.bgColor);
this.fill();
this.draw();
},
rect(x, y, width, height) {
if (!this.ctx) return;
this.ctx.rect(x, y, width, height);
},
clearRect(x, y, width, height) {
if (!this.ctx) return;
this.ctx.clearRect(x, y, width, height);
},
fill() {
if (!this.ctx) return;
this.ctx.fill();
},
setFillStyle(color) {
if (!this.ctx) return;
// #ifndef APP-PLUS-NVUE
if (this.ctx.setFillStyle) {
this.ctx.setFillStyle(color);
} else {
this.ctx.fillStyle = color;
}
// #endif
// #ifdef APP-PLUS-NVUE
this.ctx.setFillStyle(color);
// #endif
},
/**
* 设置线条样式
*/
setLineStyle(lineColor, lineWidth) {
if (!this.ctx) return;
this.setLineCap('round');
this.setLineJoin('round');
this.setStrokeStyle(lineColor);
this.setLineWidth(lineWidth);
},
setLineCap(lineCap = 'round') {
if (!this.ctx) return;
if (this.ctx.setLineCap) {
this.ctx.setLineCap(lineCap);
} else {
this.ctx.lineCap = lineCap;
}
},
setLineJoin(lineJoin = 'round') {
if (!this.ctx) return;
if (this.ctx.setLineJoin) {
this.ctx.setLineJoin(lineJoin);
} else {
this.ctx.lineJoin = lineJoin;
}
},
setStrokeStyle(color) {
if (!this.ctx) return;
if (this.ctx.setStrokeStyle) {
this.ctx.setStrokeStyle(color);
} else {
this.ctx.strokeStyle = color;
}
},
setLineWidth(width) {
if (!this.ctx) return;
if (this.ctx.setLineWidth) {
this.ctx.setLineWidth(width);
} else {
this.ctx.lineWidth = width;
}
},
/**
* 开始路径
*/
beginPath() {
if (!this.ctx) return;
this.ctx.beginPath();
},
/**
* 移动到某点
*/
moveTo(x, y) {
if (!this.ctx) return;
this.ctx.moveTo(x, y);
},
/**
* 画线到某点
*/
lineTo(x, y) {
if (!this.ctx) return;
this.ctx.lineTo(x, y);
},
/**
* 描边
*/
stroke() {
if (!this.ctx) return;
this.ctx.stroke();
},
/**
* 关闭路径
*/
closePath() {
if (!this.ctx) return;
this.ctx.closePath();
},
/**
* 绘制操作
*/
draw(isLastDraw = false) {
// #ifndef MP-WEIXIN
if (this.ctx && typeof this.ctx.draw === 'function') {
this.ctx.draw(isLastDraw);
}
// #endif
},
/**
* 导出图片
*/
exportImage(fileType = 'png', quality = 1) {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
// 微信小程序中需要先完成绘制,然后导出图片
setTimeout(() => {
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: this.actualWidth,
height: this.actualHeight,
destWidth: this.actualWidth * 2, // 使用双倍尺寸以提高清晰度
destHeight: this.actualHeight * 2,
canvas: canvasNode, // 2d必须
canvasId: this.canvasId,
fileType: fileType,
quality: quality,
success: (res) => {
resolve(res.tempFilePath);
},
fail: (err) => {
console.error('导出图片失败:', err);
reject(err);
}
}, this);
}, 50); // 等待50毫秒确保绘制完成
// #endif
// #ifndef MP-WEIXIN
uni.canvasToTempFilePath({
canvas: canvasNode, // 2d必须
canvasId: this.canvasId,
fileType: fileType,
quality: quality,
success: (res) => {
resolve(res.tempFilePath);
},
fail: (err) => {
console.error('导出图片失败:', err);
reject(err);
}
}, this);
// #endif
});
},
/**
* 使用根节点宽高 设置新的size
* @return {Promise<void>}
*/
async setNewSize(){
const rootNode = await this.getCanvasNode(this.rootId, false);
const { width , height } = rootNode;
this.widthLocal = height;
this.heightLocal = width;
}
},
async mounted() {
// 如果使用根节点的宽高 则 重新设置 size
if(this.useRootHeightAndWidth){
await this.setNewSize();
}
// 初始化Canvas
await this.initCanvas();
}
};
</script>
<style lang="scss" scoped>
.u-canvas {
position: relative;
overflow: hidden;
}
.u-canvas__canvas {
display: block;
}
</style>