| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- <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>
|