index.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <template>
  2. <canvas
  3. v-if="use2dCanvas"
  4. :id="canvasId"
  5. type="2d"
  6. :style="style"
  7. >
  8. </canvas>
  9. <canvas
  10. v-else
  11. :canvas-id="canvasId"
  12. :style="style"
  13. :id="canvasId"
  14. :width="boardWidth * dpr"
  15. :height="boardHeight * dpr"
  16. >
  17. </canvas>
  18. </template>
  19. <script>
  20. import { toPx, base64ToPath, compareVersion} from './utils';
  21. import { Draw } from './draw';
  22. import { Layout } from './layout';
  23. import { adaptor, expand } from './canvas';
  24. export default {
  25. // version: '1.5.9.7',
  26. name: 'l-painter',
  27. props: {
  28. board: Object,
  29. fileType: {
  30. type: String,
  31. default: 'png'
  32. },
  33. quality: {
  34. type: Number,
  35. default: 1
  36. },
  37. width: [Number, String],
  38. height: [Number, String],
  39. pixelRatio: Number,
  40. customStyle: String,
  41. isRenderImage: Boolean,
  42. isBase64ToPath: Boolean,
  43. isH5PathToBase64: Boolean,
  44. sleep: {
  45. type: Number,
  46. default: 1000/30
  47. },
  48. // #ifdef MP-WEIXIN
  49. type: {
  50. type: String,
  51. default: '2d',
  52. },
  53. // #endif
  54. },
  55. data() {
  56. return {
  57. // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU
  58. canvasId: `l-painter${this._uid}`,
  59. // #endif
  60. // #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
  61. canvasId: `l-painter`,
  62. // #endif
  63. // #ifdef MP-WEIXIN
  64. use2dCanvas: true,
  65. // #endif
  66. // #ifndef MP-WEIXIN
  67. use2dCanvas: false,
  68. // #endif
  69. draw: null,
  70. ctx: null,
  71. layout: new Layout()
  72. };
  73. },
  74. computed: {
  75. newboard() {
  76. return this.board && JSON.parse(JSON.stringify(this.board))
  77. },
  78. style() {
  79. return `width:${this.boardWidth}px; height: ${this.boardHeight}px; ${this.customStyle}`;
  80. },
  81. dpr() {
  82. return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
  83. },
  84. boardWidth() {
  85. const { width = 200 } = this.board || {};
  86. return toPx(this.width || width);
  87. },
  88. boardHeight() {
  89. const { height = 200 } = this.board || {};
  90. return toPx(this.height || height);
  91. }
  92. },
  93. watch: {
  94. style() {
  95. // #ifdef MP-WEIXIN
  96. if(this.use2dCanvas) {
  97. this.inited = false;
  98. }
  99. // #endif
  100. // #ifdef MP-ALIPAY
  101. this.inited = false;
  102. // #endif
  103. }
  104. },
  105. mounted() {
  106. // #ifdef MP-WEIXIN
  107. const {SDKVersion, version, platform} = wx.getSystemInfoSync()
  108. // ios wx7.0.20 createImage bug
  109. this.use2dCanvas = (this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0) && !(/ios/.test(platform) && /7.0.20/.test(version));
  110. // #endif
  111. this.$watch('board', async (val, old) => {
  112. if (JSON.stringify(val) === '{}' || !val) return;
  113. this.render();
  114. }, {
  115. deep: true,
  116. immediate: true,
  117. })
  118. },
  119. methods: {
  120. async render(args = {}, single = false) {
  121. const isArgsNoEmpty = JSON.stringify(args) != '{}'
  122. const ctx = await this.getContext()
  123. const { use2dCanvas, boardWidth, boardHeight, board, canvas, isBase64ToPath, isH5PathToBase64, sleep } = this;
  124. if (use2dCanvas && !canvas) {
  125. return Promise.reject(new Error('render: fail canvas has not been created'));
  126. }
  127. this.boundary = {
  128. top: 0,
  129. left: 0,
  130. width: boardWidth,
  131. height: boardHeight,
  132. }
  133. if(!single) {
  134. ctx.clearRect(0, 0, boardWidth, boardHeight);
  135. }
  136. if(!this.draw || isArgsNoEmpty) {
  137. this.draw = new Draw(ctx, canvas, use2dCanvas, isH5PathToBase64, sleep);
  138. }
  139. this.layout.init(ctx, this.boundary, this.isH5PathToBase64)
  140. if(isArgsNoEmpty || board && JSON.stringify(board) != '{}') {
  141. this.node = await this.layout.calcNode(isArgsNoEmpty ? args : board)
  142. }
  143. if(this.node) {
  144. await this.draw.drawNode(this.node);
  145. }
  146. await new Promise(resolve => this.$nextTick(resolve))
  147. if (!use2dCanvas && !single) {
  148. await this.canvasDraw(ctx);
  149. }
  150. this.$emit('done')
  151. if(this.isRenderImage && !single) {
  152. this.canvasToTempFilePath()
  153. .then(async res => {
  154. if(/^data:image\/(\w+);base64/.test(res.tempFilePath) && isBase64ToPath) {
  155. const img = await base64ToPath(res.tempFilePath)
  156. this.$emit('success', img)
  157. } else {
  158. this.$emit('success', res.tempFilePath)
  159. }
  160. })
  161. .catch(err => {
  162. this.$emit('fail', err)
  163. new Error(JSON.stringify(err))
  164. console.error(JSON.stringify(err))
  165. })
  166. }
  167. return Promise.resolve({ctx, draw: this.draw});
  168. },
  169. async custom(cb) {
  170. const {ctx, draw} = await this.render({}, true)
  171. ctx.save()
  172. await cb(ctx, draw)
  173. ctx.restore()
  174. return Promise.resolve(true);
  175. },
  176. async single(args = {}) {
  177. await this.render(args, true)
  178. return Promise.resolve(true);
  179. },
  180. canvasDraw(flag = false) {
  181. const {ctx} = this
  182. return new Promise(resolve => {
  183. ctx.draw(flag, () => {
  184. resolve(true);
  185. });
  186. });
  187. },
  188. async getContext() {
  189. if(this.ctx && this.inited) {
  190. return Promise.resolve(this.ctx)
  191. };
  192. const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
  193. await new Promise(resolve => this.$nextTick(resolve))
  194. const _getContext = () => {
  195. return new Promise(resolve => {
  196. uni.createSelectorQuery()
  197. .in(this)
  198. .select('#' + this.canvasId)
  199. .boundingClientRect()
  200. .exec(res => {
  201. if(res) {
  202. const ctx = uni.createCanvasContext(this.canvasId, this);
  203. if (!this.inited) {
  204. this.inited = true;
  205. this.use2dCanvas = false;
  206. this.canvas = res
  207. }
  208. // #ifdef MP-ALIPAY
  209. ctx.scale(dpr, dpr);
  210. // #endif
  211. this.ctx = expand(ctx)
  212. resolve(this.ctx);
  213. }
  214. })
  215. })
  216. }
  217. // #ifndef MP-WEIXIN
  218. return _getContext()
  219. // #endif
  220. if(!use2dCanvas) {
  221. return _getContext()
  222. }
  223. return new Promise(resolve => {
  224. uni.createSelectorQuery()
  225. .in(this)
  226. .select('#l-painter')
  227. .node()
  228. .exec(res => {
  229. const canvas = res[0].node;
  230. if(!canvas) {
  231. this.use2dCanvas = false;
  232. return this.getContext()
  233. }
  234. const ctx = canvas.getContext(type);
  235. if (!this.inited) {
  236. this.inited = true;
  237. canvas.width = boardWidth * dpr;
  238. canvas.height = boardHeight * dpr;
  239. this.use2dCanvas = true;
  240. this.canvas = canvas
  241. ctx.scale(dpr, dpr);
  242. }
  243. this.ctx = adaptor(ctx)
  244. resolve(this.ctx);
  245. });
  246. });
  247. },
  248. canvasToTempFilePath(args = {}) {
  249. const {use2dCanvas, canvasId} = this
  250. return new Promise((resolve, reject) => {
  251. let { top: y = 0, left: x = 0, width, height } = this.boundary || this
  252. let destWidth = width * this.dpr
  253. let destHeight = height * this.dpr
  254. // #ifdef MP-ALIPAY
  255. width = width * this.dpr
  256. height = height * this.dpr
  257. // #endif
  258. const copyArgs = {
  259. x,
  260. y,
  261. width,
  262. height,
  263. destWidth,
  264. destHeight,
  265. canvasId,
  266. fileType: args.fileType || this.fileType,
  267. quality: /\d/.test(args.quality) ? args.quality : this.quality,
  268. success: resolve,
  269. fail: reject
  270. }
  271. if (use2dCanvas) {
  272. delete copyArgs.canvasId
  273. copyArgs.canvas = this.canvas
  274. }
  275. uni.canvasToTempFilePath(copyArgs, this)
  276. })
  277. }
  278. }
  279. };
  280. </script>
  281. <style></style>