index.vue 6.9 KB

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