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