|
|
<template> <view class="lime-painter" ref="limepainter"> <view v-if="canvasId && size" :style="styles"> <!-- #ifndef APP-NVUE --> <canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas> <canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size" :width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>
<!-- #endif --> <!-- #ifdef APP-NVUE --> <web-view :style="size" ref="webview" src="/uni_modules/lime-painter/hybrid/html/index.html" class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage"> </web-view> <!-- #endif --> </view> <slot /> </view></template>
<script> import { parent } from '../common/relation' import props from './props' import {toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo }from './utils'; // #ifndef APP-NVUE
import { canIUseCanvas2d, isPC} from './utils'; import Painter from './painter'; // import Painter from '@painter'
const nvue = {} // #endif
// #ifdef APP-NVUE
import nvue from './nvue' // #endif
export default { name: 'lime-painter', mixins: [props, parent('painter'), nvue], data() { return { use2dCanvas: false, canvasHeight: 150, canvasWidth: null, parentWidth: 0, inited: false, progress: 0, firstRender: 0, done: false, tasks: [] }; }, computed: { styles() { return `${this.size}${this.customStyle||''};` + (this.hidden && 'position: fixed; left: 1500rpx;') }, canvasId() { return `l-painter${this._ && this._.uid || this._uid}` }, size() { if (this.boardWidth && this.boardHeight) { return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`; } }, dpr() { return this.pixelRatio || uni.getSystemInfoSync().pixelRatio; }, boardWidth() { const {width = 0} = (this.elements && this.elements.css) || this.elements || this const w = toPx(width||this.width) return w || Math.max(w, toPx(this.canvasWidth)); }, boardHeight() { const {height = 0} = (this.elements && this.elements.css) || this.elements || this const h = toPx(height||this.height) return h || Math.max(h, toPx(this.canvasHeight)); }, hasBoard() { return this.board && Object.keys(this.board).length }, elements() { return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el)) } }, created() { this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC }, async mounted() { await sleep(30) await this.getParentWeith() this.$nextTick(() => { setTimeout(() => { this.$watch('elements', this.watchRender, { deep: true, immediate: true }); }, 30) }) }, // #ifdef VUE3
unmounted() { this.done = false this.inited = false this.firstRender = 0 this.progress = 0 this.painter = null clearTimeout(this.rendertimer) }, // #endif
// #ifdef VUE2
destroyed() { this.done = false this.inited = false this.firstRender = 0 this.progress = 0 this.painter = null clearTimeout(this.rendertimer) }, // #endif
methods: { async watchRender(val, old) { if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return; this.firstRender = 1 this.progress = 0 this.done = false clearTimeout(this.rendertimer) this.rendertimer = setTimeout(() => { this.render(val); }, this.beforeDelay) }, async setFilePath(path, param) { let filePath = path const {pathType = this.pathType} = param || this if (pathType == 'base64' && !isBase64(path)) { filePath = await pathToBase64(path) } else if (pathType == 'url' && isBase64(path)) { filePath = await base64ToPath(path) } if (param && param.isEmit) { this.$emit('success', filePath); } return filePath }, async getSize(args) { const {width} = args.css || args const {height} = args.css || args if (!this.size) { if (width || height) { this.canvasWidth = width || this.canvasWidth this.canvasHeight = height || this.canvasHeight await sleep(30); } else { await this.getParentWeith() } } }, canvasToTempFilePathSync(args) { // this.stopWatch && this.stopWatch()
// this.stopWatch = this.$watch('done', (v) => {
// if (v) {
// this.canvasToTempFilePath(args)
// this.stopWatch && this.stopWatch()
// }
// }, {
// immediate: true
// })
this.tasks.push(args) if(this.done){ this.runTask() } }, runTask(){ while(this.tasks.length){ const task = this.tasks.shift() this.canvasToTempFilePath(task) } }, // #ifndef APP-NVUE
getParentWeith() { return new Promise(resolve => { uni.createSelectorQuery() .in(this) .select(`.lime-painter`) .boundingClientRect() .exec(res => { const {width, height} = res[0]||{} this.parentWidth = Math.ceil(width||0) this.canvasWidth = this.parentWidth || 300 this.canvasHeight = height || this.canvasHeight||150 resolve(res[0]) }) }) }, async render(args = {}) { if(!Object.keys(args).length) { return console.error('空对象') } this.progress = 0 this.done = false // #ifdef APP-NVUE
this.tempFilePath.length = 0 // #endif
await this.getSize(args) const ctx = await this.getContext(); let { use2dCanvas, boardWidth, boardHeight, canvas, afterDelay } = this; if (use2dCanvas && !canvas) { return Promise.reject(new Error('canvas 没创建')); } this.boundary = { top: 0, left: 0, width: boardWidth, height: boardHeight }; this.painter = null if (!this.painter) { const {width} = args.css || args const {height} = args.css || args if(!width && this.parentWidth) { Object.assign(args, {width: this.parentWidth}) } const param = { context: ctx, canvas, width: boardWidth, height: boardHeight, pixelRatio: this.dpr, useCORS: this.useCORS, createImage: getImageInfo.bind(this), performance: this.performance, listen: { onProgress: (v) => { this.progress = v this.$emit('progress', v) }, onEffectFail: (err) => { this.$emit('faill', err) } } } this.painter = new Painter(param) } try{ // vue3 赋值给data会引起图片无法绘制
const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args))) this.boundary.height = this.canvasHeight = height this.boundary.width = this.canvasWidth = width await sleep(this.sleep); await this.painter.render() await new Promise(resolve => this.$nextTick(resolve)); if (!use2dCanvas) { await this.canvasDraw(); } if (afterDelay && use2dCanvas) { await sleep(afterDelay); } this.$emit('done'); this.done = true if (this.isCanvasToTempFilePath) { this.canvasToTempFilePath() .then(res => { this.$emit('success', res.tempFilePath) }) .catch(err => { this.$emit('fail', new Error(JSON.stringify(err))); }); } this.runTask() return Promise.resolve({ ctx, draw: this.painter, node: this.node }); }catch(e){ //TODO handle the exception
} }, canvasDraw(flag = false) { return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this .afterDelay))); }, async getContext() { if (!this.canvasWidth) { this.$emit('fail', 'painter no size') console.error('[lime-painter]: 给画板或父级设置尺寸') return Promise.reject(); } if (this.ctx && this.inited) { return Promise.resolve(this.ctx); } const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this; const _getContext = () => { return new Promise(resolve => { uni.createSelectorQuery() .in(this) .select(`#${this.canvasId}`) .boundingClientRect() .exec(res => { if (res) { const ctx = uni.createCanvasContext(this.canvasId, this); if (!this.inited) { this.inited = true; this.use2dCanvas = false; this.canvas = res; } // 钉钉小程序框架不支持 measureText 方法,用此方法 mock
if (!ctx.measureText) { function strLen(str) { let len = 0; for (let i = 0; i < str.length; i++) { if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) { len++; } else { len += 2; } } return len; } ctx.measureText = text => { let fontSize = ctx.state && ctx.state.fontSize || 12; const font = ctx.__font if (font && fontSize == 12) { fontSize = parseInt(font.split(' ')[3], 10); } fontSize /= 2; return { width: strLen(text) * fontSize }; } } // #ifdef MP-ALIPAY
ctx.scale(dpr, dpr); // #endif
this.ctx = ctx resolve(this.ctx); } else { console.error('[lime-painter] no node') } }); }); }; if (!use2dCanvas) { return _getContext(); } return new Promise(resolve => { uni.createSelectorQuery() .in(this) .select(`#${this.canvasId}`) .node() .exec(res => { let {node: canvas} = res && res[0]||{}; if(canvas) { const ctx = canvas.getContext(type); if (!this.inited) { this.inited = true; this.use2dCanvas = true; this.canvas = canvas; } this.ctx = ctx resolve(this.ctx); } else { console.error('[lime-painter]: no size') } }); }); }, canvasToTempFilePath(args = {}) { return new Promise(async (resolve, reject) => { const { use2dCanvas, canvasId, dpr, fileType, quality } = this; const success = async (res) => { try { const tempFilePath = await this.setFilePath(res.tempFilePath || res, args) const result = Object.assign(res, {tempFilePath}) args.success && args.success(result) resolve(result) } catch (e) { this.$emit('fail', e) } } let { top: y = 0, left: x = 0, width, height } = this.boundary || this; // let destWidth = width * dpr;
// let destHeight = height * dpr;
// #ifdef MP-ALIPAY
// width = destWidth;
// height = destHeight;
// #endif
const copyArgs = Object.assign({ // x,
// y,
// width,
// height,
// destWidth,
// destHeight,
canvasId, id: canvasId, fileType, quality, }, args, {success}); // if(this.isPC || use2dCanvas) {
// copyArgs.canvas = this.canvas
// }
if (use2dCanvas) { copyArgs.canvas = this.canvas try{ // #ifndef MP-ALIPAY
const oFilePath = this.canvas.toDataURL(`image/${args.fileType||fileType}`.replace(/pg/, 'peg'), args.quality||quality) if(/data:,/.test(oFilePath)) { uni.canvasToTempFilePath(copyArgs, this); } else { const tempFilePath = await this.setFilePath(oFilePath, args) args.success && args.success({tempFilePath}) resolve({tempFilePath}) } // #endif
// #ifdef MP-ALIPAY
this.canvas.toTempFilePath(copyArgs) // #endif
}catch(e){ args.fail && args.fail(e) reject(e) } } else { // #ifdef MP-ALIPAY
if(this.ctx.toTempFilePath) { // 钉钉
const ctx = uni.createCanvasContext(canvasId); ctx.toTempFilePath(copyArgs); } else { my.canvasToTempFilePath(copyArgs); } // #endif
// #ifndef MP-ALIPAY
uni.canvasToTempFilePath(copyArgs, this); // #endif
} }) } // #endif
} };</script><style> .lime-painter, .lime-painter__canvas { // #ifndef APP-NVUE
width: 100%; // #endif
// #ifdef APP-NVUE
flex: 1; // #endif
}</style>
|