123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660 |
- <template>
- <view class="lime-painter" ref="limepainter">
- <view v-if="canvasId && size" :style="size + customStyle">
- <!-- #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 :canvas-id="canvasId" :style="size" :id="canvasId"
- :width="boardWidth * dpr" :height="boardHeight * dpr"></canvas>
- <!-- #endif -->
- <!-- #ifdef APP-NVUE -->
- <web-view v-if="hybrid || isInitFile" :style="size" ref="webview"
- :src="hybrid ? '/hybrid/html/lime-painter/index.html' :'_doc/uni_modules/lime-painter/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'
- // #ifndef APP-NVUE
- import {
- toPx,
- compareVersion,
- sleep,
- base64ToPath,
- pathToBase64,
- isBase64
- } from './utils';
- import Painter from './painter'
- // #endif
- // #ifdef APP-NVUE
- import {
- toPx,
- sleep,
- base64ToPath,
- pathToBase64,
- getImageInfo,
- isBase64,
- useNvue
- } from './utils';
- const dom = weex.requireModule('dom')
- import {
- version
- } from '../../package.json'
- // #endif
- export default {
- name: 'lime-painter',
- mixins: [parent('painter')],
- props: {
- board: Object,
- pathType: String, // 'base64'、'url'
- fileType: {
- type: String,
- default: 'png'
- },
- quality: {
- type: Number,
- default: 1
- },
- css: [String, Object],
- width: [Number, String],
- height: [Number, String],
- pixelRatio: Number,
- customStyle: String,
- isCanvasToTempFilePath: Boolean,
- sleep: {
- type: Number,
- default: 1000 / 30
- },
- beforeDelay: {
- type: Number,
- default: 100
- },
- afterDelay: {
- type: Number,
- default: 100
- },
- // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
- type: {
- type: String,
- default: '2d'
- },
- // #endif
- // #ifdef APP-NVUE
- hybrid: Boolean,
- timeout: {
- type: Number,
- default: 2000
- }
- // #endif
- },
- data() {
- return {
- // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
- use2dCanvas: true,
- // #endif
- // #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
- use2dCanvas: false,
- // #endif
- canvasHeight: 150,
- canvasWidth: null,
- isPC: false,
- inited: false,
- progress: 0,
- // #ifdef APP-NVUE
- tempFilePath: [],
- isInitFile: false
- // #endif
- };
- },
- computed: {
- canvasId() {
- // #ifdef VUE3
- return `l-painter${this._.uid}`
- // #endif
- // #ifdef VUE2
- return `l-painter${this._uid}`
- // #endif
- },
- 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.board && this.board.css) || this.board || this
- return toPx(width) || Math.max(toPx(width), toPx(this.canvasWidth));
- },
- boardHeight() {
- const {
- height = 0
- } = (this.board && this.board.css) || this.board || this
- return toPx(height) || Math.max(toPx(height), toPx(this.canvasHeight));
- },
- elements() {
- return JSON.parse(JSON.stringify(this.el))
- }
- },
- watch: {
- canvasWidth(v) {
- if (this.el.css && !this.el.css.width) {
- this.el.css.width = v
- }
- },
- // #ifdef MP-WEIXIN || MP-ALIPAY
- size(v) {
- // #ifdef MP-WEIXIN
- if (this.use2dCanvas) {
- this.inited = false;
- }
- // #endif
- // #ifdef MP-ALIPAY
- this.inited = false;
- // #endif
- },
- // #endif
- },
- // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
- created() {
- const {
- SDKVersion,
- version,
- platform,
- environment
- } = uni.getSystemInfoSync();
- // #ifdef MP-WEIXIN
- this.isPC = /windows/i.test(platform)
- this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0 && !((/ios/i.test(
- platform) && /7.0.20/.test(version)) || /wxwork/i.test(environment)) && !this.isPC;
- // #endif
- // #ifdef MP-TOUTIAO
- this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
- // #endif
- // #ifdef MP-ALIPAY
- this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.7.0') >= 0;
- // #endif
- },
- // #endif
- // #ifdef APP-NVUE
- created() {
- if (this.hybrid) return
- useNvue('_doc/uni_modules/lime-painter/', version, this.timeout).then(res => {
- this.isInitFile = true
- })
- },
- // #endif
- async mounted() {
- await this.getParentWeith()
- this.$nextTick(() => {
- setTimeout(() => {
- if (this.board) {
- this.$watch('board', this.watchRender, {
- deep: true,
- immediate: true
- });
- } else {
- this.$watch('elements', this.watchRender, {
- deep: true,
- immediate: true
- });
- }
- }, 30)
- })
- },
- methods: {
- async watchRender(val, old) {
- this.progress = 0
- if (!val || !val.views || !val.views.length || JSON.stringify(val) === '{}' || JSON.stringify(val) ==
- JSON.stringify(old)) return;
- clearTimeout(this.rendertimer)
- this.rendertimer = setTimeout(() => {
- this.render(val);
- }, this.beforeDelay)
- },
- async setFilePath(path, isEmit) {
- let filePath = path
- const {
- pathType
- } = this
- if (pathType == 'base64' && !isBase64(path)) {
- filePath = await pathToBase64(path)
- } else if (pathType == 'url' && isBase64(path)) {
- filePath = await base64ToPath(path)
- }
- if (isEmit) {
- this.$emit('success', filePath);
- }
- return filePath
- },
- async getSize(args) {
- if (!this.size) {
- const {
- width
- } = args.css || args
- const {
- height
- } = args.css || args
- if (width || height) {
- this.canvasWidth = width || this.canvasWidth
- this.canvasHeight = height || this.canvasHeight
- await sleep(30);
- } else {
- await this.getParentWeith()
- }
- }
- },
- canvasToTempFilePathSync(args) {
- this.$watch('progress', (v) => {
- if (v == 1) {
- this.canvasToTempFilePath(args)
- }
- }, {
- immediate: true
- })
- },
- // #ifdef APP-NVUE
- onPageFinish() {
- this.$refs.webview.evalJS(`init(${this.dpr})`)
- },
- onMessage(e) {
- const res = e.detail.data[0] || null;
- if (res.event) {
- if (res.event == 'inited') {
- this.inited = true
- }
- if (res.event == 'layoutChange') {
- const data = JSON.parse(res.data)
- this.canvasWidth = data.width;
- this.canvasHeight = data.height;
- }
- if (res.event == 'progressChange') {
- this.progress = res.data * 1
- }
- if (res.event == 'file') {
- this.tempFilePath.push(res.data)
- if (this.tempFilePath.length > 7) {
- this.tempFilePath.shift()
- }
- return
- }
- if (res.event == 'success') {
- if (res.data) {
- this.tempFilePath.push(res.data)
- if (this.tempFilePath.length > 8) {
- this.tempFilePath.shift()
- }
- if (this.isCanvasToTempFilePath) {
- this.setFilePath(this.tempFilePath.join(''), true)
- }
- } else {
- this.$emit('fail', 'canvas no data')
- }
- return
- }
- this.$emit(res.event, JSON.parse(res.data));
- } else if (res.file) {
- this.file = res.data;
- } else if (/ms$/.test(res[0])) {
- console.info(res[0])
- } else {
- this.$emit('fail', res)
- }
- },
- getWebViewInited() {
- if (this.inited) return Promise.resolve(this.inited);
- return new Promise((resolve) => {
- this.$watch(
- 'inited',
- async val => {
- if (val) {
- resolve(val)
- }
- }, {
- immediate: true
- }
- );
- })
- },
- getTempFilePath() {
- if (this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
- return new Promise((resolve) => {
- this.$watch(
- 'tempFilePath',
- async val => {
- if (val.length == 8) {
- resolve(val.join(''))
- }
- }
- );
- })
- },
- getWebViewDone() {
- if (this.progress == 1) return Promise.resolve(this.progress);
- return new Promise((resolve) => {
- this.$watch(
- 'progress',
- async val => {
- if (val == 1) {
- this.$emit('done')
- resolve(val)
- }
- }, {
- immediate: true
- }
- );
- })
- },
- async render(args) {
- try {
- await this.getSize(args)
- const newNode = await this.calcImage(args);
- await this.getWebViewInited()
- const webview = this.$refs.webview;
- webview.evalJS(`source(${JSON.stringify(newNode)})`)
- await this.getWebViewDone()
- await sleep(this.afterDelay)
- if (this.isCanvasToTempFilePath) {
- const params = {
- fileType: this.fileType,
- quality: this.quality
- }
- webview.evalJS(`save(${JSON.stringify(params)})`)
- }
- return Promise.resolve()
- } catch (e) {
- this.$emit('fail', e)
- }
- },
- async calcImage(args) {
- let node = JSON.parse(JSON.stringify(args))
- const urlReg = /url\((.+)\)/
- const isBG = node.css.backgroundImage && urlReg.exec(node.css.backgroundImage)?. [1]
- const url = node.url || node.src || isBG
- if ((node.type === "image" || isBG) && url && !isBase64(url)) {
- const {
- path,
- type
- } = await getImageInfo(url)
- if (isBG) {
- node.css.backgroundImage = `url(${path})`
- } else {
- node.src = path
- }
- } else if (node.views && node.views.length) {
- for (let i = 0; i < node.views.length; i++) {
- node.views[i] = await this.calcImage(node.views[i])
- }
- }
- return node
- },
- async canvasToTempFilePath(args = {}) {
- if (!this.inited) {
- return this.$emit('fail', 'no init')
- }
- this.tempFilePath = []
- if (args.fileType == 'jpg') {
- args.fileType = 'jpeg'
- }
- this.$refs.webview.evalJS(`save(${JSON.stringify(args)})`)
- try {
- let tempFilePath = await this.getTempFilePath()
- tempFilePath = await this.setFilePath(tempFilePath)
- args.success({
- errMsg: "canvasToTempFilePath:ok",
- tempFilePath
- })
- } catch (e) {
- args.fail({
- error: e
- })
- }
- },
- // #endif
- getParentWeith() {
- return new Promise(resolve => {
- // #ifdef APP-NVUE
- dom.getComponentRect(this.$refs.limepainter, (res) => {
- this.canvasWidth = this.canvasWidth || Math.ceil(res.size.width)||300
- this.canvasHeight = res.size.height || this.canvasHeight||150
- resolve(res.size)
- })
- // #endif
- // #ifndef APP-NVUE
- uni.createSelectorQuery()
- .in(this)
- .select(`.lime-painter`)
- .boundingClientRect()
- .exec(res => {
- this.canvasWidth = Math.ceil(res[0].width)||300
- this.canvasHeight = res[0].height || this.canvasHeight||150
- resolve(res[0])
- })
- // #endif
- })
- },
- // #ifndef APP-NVUE
- async render(args = {}) {
- await this.getSize(args)
- const ctx = await this.getContext();
- let {
- use2dCanvas,
- boardWidth,
- boardHeight,
- canvas,
- afterDelay
- } = this;
- if (use2dCanvas && !canvas) {
- return Promise.reject(new Error('render: fail canvas has not been created'));
- }
- this.boundary = {
- top: 0,
- left: 0,
- width: boardWidth,
- height: boardHeight
- };
- if (!this.painter) {
- this.painter = new Painter({
- context: ctx,
- canvas,
- width: boardWidth,
- height: boardHeight,
- pixelRatio: this.dpr,
- fixed: `${this.width?'width':''}${this.height?'height':''}`,
- listen: {
- onProgress: (v) => {
- this.progress = v
- this.$emit('progress', v)
- },
- onEffectFail: (err) => {
- this.$emit('faill', err)
- }
- }
- }, this)
- }
- const {
- width,
- height
- } = await this.painter.source(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');
- if (this.isCanvasToTempFilePath) {
- this.canvasToTempFilePath()
- .then(async res => {
- this.$emit('success', res.tempFilePath)
- })
- .catch(err => {
- this.$emit('fail', new Error(JSON.stringify(err)));
- });
- }
- return Promise.resolve({
- ctx,
- draw: this.painter,
- node: this.node
- });
- },
- 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('painter no size: 请给画板或父级设置尺寸')
- 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;
- }
- if (this.isPC) {
- ctx.scale(1 / dpr, 1 / dpr);
- }
- // #ifdef MP-ALIPAY
- ctx.scale(dpr, dpr);
- // #endif
- this.ctx = ctx
- resolve(this.ctx);
- }
- });
- });
- };
- // #ifndef MP-WEIXIN
- return _getContext();
- // #endif
- if (!use2dCanvas) {
- return _getContext();
- }
- return new Promise(resolve => {
- uni.createSelectorQuery()
- .in(this)
- .select(`#${this.canvasId}`)
- .node()
- .exec(res => {
- let {
- node: canvas
- } = res[0];
- if (!canvas) {
- this.use2dCanvas = false;
- resolve(this.getContext());
- }
- const ctx = canvas.getContext(type);
- if (!this.inited) {
- this.inited = true;
- this.use2dCanvas = true;
- this.canvas = canvas;
- }
- this.ctx = ctx
- resolve(this.ctx);
- });
- });
- },
- canvasToTempFilePath(args = {}) {
- const {
- use2dCanvas,
- canvasId,
- dpr,
- fileType,
- quality
- } = this;
- return new Promise((resolve, reject) => {
- 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 success = async (res) => {
- try {
- const tempFilePath = await this.setFilePath(res.tempFilePath)
- resolve(Object.assign(res, {
- tempFilePath
- }))
- } catch (e) {
- this.$emit('fail', e)
- }
- }
- const copyArgs = Object.assign({
- x,
- y,
- width,
- height,
- destWidth,
- destHeight,
- canvasId,
- fileType,
- quality,
- success,
- fail: reject
- }, args);
- if (use2dCanvas) {
- delete copyArgs.canvasId;
- copyArgs.canvas = this.canvas;
- }
- uni.canvasToTempFilePath(copyArgs, this);
- });
- }
- // #endif
- }
- };
- </script>
- <style>
- .lime-painter,
- .lime-painter__canvas {
- // #ifndef APP-NVUE
- width: 100%;
- // #endif
- // #ifdef APP-NVUE
- flex: 1;
- // #endif
- }
- </style>
|