mosowe-canvas-image.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <!-- mosowe-canvas-image -->
  2. <template>
  3. <view class='mosowe-canvas-image'>
  4. <view class="slot-view" @click="createCanvas">
  5. <slot></slot>
  6. </view>
  7. <view class="canvas-wrap-box">
  8. <!-- 主面板绘制 -->
  9. <canvas class="canvas-wrap" canvas-id="canvas"
  10. :style="'width: '+ width +'px; height: '+ height +'px;'"></canvas>
  11. <!-- 这个是用来绘制圆形图片的 -->
  12. <canvas class="canvas-wrap" canvas-id="canvas-arc"
  13. :style="'width: '+ canvasArcWidth +'px; height: '+ canvasArcHeight +'px;'"></canvas>
  14. </view>
  15. </view>
  16. </template>
  17. <script>
  18. import QR from './wxqrcode.js';
  19. export default {
  20. name: 'mosowe-canvas-image',
  21. components: {},
  22. props: {
  23. imgType: { // 图片类型
  24. type: String,
  25. default: 'jpg',
  26. validator: () => {
  27. return ['jpg', 'png'];
  28. }
  29. },
  30. compress: { // 是否开启压缩
  31. type: Boolean,
  32. default: false
  33. },
  34. compressSize: { // 压缩界限,超过界限压缩,默认2M
  35. type: [Number, String],
  36. default: 1024 * 1024 * 2
  37. },
  38. showPreview: { // 生成图像后是否预览
  39. type: Boolean,
  40. default: false
  41. },
  42. height: { // canvas高度
  43. type: [String, Number],
  44. default: 200
  45. },
  46. width: { // canvas宽度
  47. type: [String, Number],
  48. default: 200
  49. },
  50. lists: {
  51. type: Array,
  52. default: () => {
  53. return [];
  54. }
  55. }
  56. },
  57. data() {
  58. return {
  59. canvas: null,
  60. listsIndex: 0,
  61. listsLength: 0,
  62. canvasArc: null,
  63. canvasArcWidth: 100,
  64. canvasArcHeight: 100,
  65. compressQuality: 20,
  66. compressQualityH5: 5,
  67. };
  68. },
  69. watch: {},
  70. // 组件实例化之前
  71. beforeCreate() {},
  72. // 组件创建完成
  73. created() {
  74. this.canvas = uni.createCanvasContext('canvas', this);
  75. this.canvasArc = uni.createCanvasContext('canvas-arc', this);
  76. },
  77. // 组件挂载之前
  78. beforeMount() {},
  79. // 组件挂载之后
  80. mounted() {},
  81. // 组件数据更新时
  82. beforeUpdate() {},
  83. // 组价更新
  84. updated() {},
  85. // 组件销毁前
  86. beforeDestroy() {},
  87. // 组件销毁后
  88. destroyed() {},
  89. // 页面方法
  90. methods: {
  91. // 开始绘制
  92. createCanvas() {
  93. this.clearCanvas();
  94. if (this.lists.length === 0) {
  95. uni.showToast({
  96. title: 'lists不能为空',
  97. icon: 'none'
  98. });
  99. return;
  100. }
  101. this.listsIndex = 0;
  102. this.listsLength = this.lists.length - 1;
  103. uni.showLoading({
  104. title: '正在生成图片...',
  105. mask: true
  106. });
  107. // 先生成一个白色背景
  108. this.canvas.setFillStyle('#fff');
  109. this.canvas.globalAlpha = 1;
  110. this.canvas.fillRect(0, 0, this.width, this.height);
  111. this.dataDrawCanvas();
  112. },
  113. // 数据绘制
  114. dataDrawCanvas() {
  115. let item = this.lists[this.listsIndex];
  116. if (item.type === 'image') { // 图片
  117. if (item.content.indexOf('https://') > -1) { // https://网络图片
  118. // #ifndef H5
  119. // 非H5
  120. this.downloadImageNotH5(item);
  121. // #endif
  122. // #ifdef H5
  123. // H5
  124. this.downloadImageH5(item);
  125. // #endif
  126. } else { // 本地选择图片
  127. if (this.compress && item.hasOwnProperty('file') && item.file.size > this
  128. .compressSize) { // 大于限制2M压缩
  129. this.compressImage(item);
  130. } else {
  131. if (item.arc) {
  132. this.drawImageArc(item);
  133. } else {
  134. this.drawImage(item);
  135. }
  136. }
  137. }
  138. } else if (item.type === 'text') { // 文本
  139. this.drawText(item);
  140. } else if (item.type === 'rect') { // 矩形(线条)
  141. this.drawRect(item);
  142. } else if (item.type === 'arc') { // 圆形
  143. this.drawArc(item);
  144. } else if (item.type === 'qr') { // 二维码
  145. this.drawQR(item);
  146. }
  147. },
  148. // #ifndef H5
  149. // https图片下载本地并绘制,非H5
  150. downloadImageNotH5(item) {
  151. uni.downloadFile({
  152. url: item.content,
  153. header: {
  154. 'Access-Control-Allow-Origin': '*',
  155. },
  156. success: (res) => {
  157. item.content = res.tempFilePath;
  158. if (item.arc) {
  159. this.drawImageArc(item);
  160. } else {
  161. this.drawImage(item);
  162. }
  163. },
  164. fail: (res) => {
  165. console.log(res);
  166. }
  167. });
  168. },
  169. // #endif
  170. // #ifdef H5
  171. // https图片下载本地并绘制,H5
  172. downloadImageH5(item) {
  173. let image = null;
  174. image = new Image();
  175. image.setAttribute('crossOrigin', 'anonymous');
  176. image.crossOrigin = 'Anonymous';
  177. image.src = item.content;
  178. image.onload = () => {
  179. let canvas = document.createElement('canvas');
  180. canvas.width = item.width;
  181. canvas.height = item.height;
  182. let ctx = canvas.getContext('2d');
  183. ctx.drawImage(
  184. image,
  185. 0,
  186. 0,
  187. item.width,
  188. item.height
  189. );
  190. let dataURL = canvas.toDataURL('image/png');
  191. if (item.arc) { // 绘制圆形
  192. item.content = dataURL;
  193. this.drawImageArc(item);
  194. } else {
  195. this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  196. this.canvas.drawImage(
  197. dataURL,
  198. item.x,
  199. item.y,
  200. item.hasOwnProperty('width') ? item.width : this.width,
  201. item.hasOwnProperty('height') ? item.height : this.height
  202. );
  203. this.checkDrawOver();
  204. }
  205. };
  206. },
  207. // #endif
  208. // 图片压缩
  209. compressImage(item) {
  210. uni.showLoading({
  211. title: '压缩中...',
  212. mask: true
  213. });
  214. // 非H5压缩
  215. // #ifndef H5
  216. uni.compressImage({
  217. src: item.content,
  218. quality: this.compressQuality,
  219. success: (res) => {
  220. uni.showLoading({
  221. title: '正在生成图片...',
  222. mask: true
  223. });
  224. item.content = res.tempFilePath;
  225. if (item.arc) {
  226. this.drawImageArc(item);
  227. } else {
  228. this.drawImage(item);
  229. }
  230. },
  231. fail: (res) => {
  232. console.log(res);
  233. uni.showToast({
  234. title: '压缩失败',
  235. icon: 'none'
  236. });
  237. }
  238. });
  239. // #endif
  240. // H5压缩
  241. // #ifdef H5
  242. let image = new Image();
  243. image.setAttribute('crossOrigin', 'anonymous');
  244. image.crossOrigin = 'Anonymous';
  245. image.src = item.content;
  246. image.onload = () => {
  247. let canvas = document.createElement('canvas');
  248. canvas.width = item.width;
  249. canvas.height = item.height;
  250. let ctx = canvas.getContext('2d');
  251. ctx.drawImage(
  252. image,
  253. 0,
  254. 0,
  255. item.width,
  256. item.height
  257. );
  258. let dataURL = canvas.toDataURL('image/png');
  259. item.content = dataURL;
  260. if (item.arc) {
  261. this.drawImageArc(item);
  262. } else {
  263. this.drawImage(item);
  264. }
  265. };
  266. // #endif
  267. },
  268. // 圆形图片另外绘制canvas,png格式
  269. drawImageArc(item) {
  270. this.canvasArc.clearRect(0, 0, this.canvasArcWidth, this.canvasArcHeight);
  271. this.canvasArcWidth = item.arcR * 2;
  272. this.canvasArcHeight = item.arcR * 2;
  273. this.canvasArc.save();
  274. let arcT = setTimeout(() => {
  275. clearTimeout(arcT);
  276. this.canvasArc.arc(item.arcR, item.arcR, item.arcR, 0, 2 * Math.PI);
  277. this.canvasArc.clip();
  278. // this.canvasArc.closePath();
  279. this.canvasArc.drawImage(
  280. item.content,
  281. item.arcX,
  282. item.arcY,
  283. item.width,
  284. item.height
  285. );
  286. this.canvasArc.draw(false, setTimeout(() => {
  287. let t = setTimeout(() => {
  288. clearTimeout(t);
  289. uni.canvasToTempFilePath({
  290. x: 0,
  291. y: 0,
  292. width: item.arcR * 2,
  293. height: item.arcR * 2,
  294. fileType: 'png',
  295. canvasId: 'canvas-arc',
  296. success: (res) => {
  297. item.width = item.arcR * 2;
  298. item.height = item.arcR * 2;
  299. item.content = res.tempFilePath;
  300. this.drawImage(item);
  301. },
  302. fail: (res) => {
  303. console.log(res);
  304. },
  305. complete: () => {
  306. this.canvasArc.restore();
  307. this.canvasArc.fillRect(0, 0, 0, 0);
  308. this.canvasArc.clearRect(0, 0, this
  309. .canvasArcWidth, this.canvasArcHeight);
  310. }
  311. }, this);
  312. }, 100);
  313. }));
  314. }, 100);
  315. },
  316. // 图片绘制
  317. drawImage(item) {
  318. this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  319. this.canvas.drawImage(
  320. item.content,
  321. item.x,
  322. item.y,
  323. item.hasOwnProperty('width') ? item.width : this.width,
  324. item.hasOwnProperty('height') ? item.height : this.height
  325. );
  326. this.checkDrawOver();
  327. },
  328. // 文本绘制
  329. drawText(item) {
  330. this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000');
  331. // this.canvas.setFontSize(item.hasOwnProperty('size') ? item.size : 20);
  332. this.canvas.setFontSize(10);
  333. let font = (item.hasOwnProperty('weight') ? item.weight : 'normal') + (item.hasOwnProperty('style') ? ' ' + item.style : ' normal') + (item.hasOwnProperty('size') ? ' ' + item.size : ' 10px') + (item.hasOwnProperty('family') ? ' ' + item.family : ' sans-serif')
  334. this.canvas.font = font
  335. this.canvas.setTextAlign(item.hasOwnProperty('align') ? item.align : 'left');
  336. this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  337. if (item.maxWidth) {
  338. this.canvas.fillText(item.content, item.x, item.y, item.maxWidth);
  339. } else {
  340. this.canvas.fillText(item.content, item.x, item.y);
  341. }
  342. this.checkDrawOver();
  343. },
  344. // 矩形(线条)绘制
  345. drawRect(item) {
  346. this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000');
  347. this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  348. this.canvas.fillRect(item.x, item.y, item.width, item.height);
  349. this.checkDrawOver();
  350. },
  351. // 圆形绘制
  352. drawArc(item) {
  353. this.canvas.arc(item.arcX, item.arcY, item.arcR, 0, 2 * Math.PI);
  354. this.canvas.setFillStyle(item.hasOwnProperty('color') ? item.color : '#000000');
  355. this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  356. this.canvas.fill();
  357. this.canvas.closePath();
  358. this.checkDrawOver();
  359. },
  360. // 二维码绘制
  361. async drawQR(item) {
  362. await this.removeSave();
  363. console.log("content:", item.content)
  364. let projectid = item.hasOwnProperty('projectid') ? item.projectid : "tmp_base64src";
  365. item.content = await this.base64ToSave(item.content, projectid);
  366. // item.content = "wxfile://usr/tmp_base64src.png"
  367. console.log("content:", item.content)
  368. if (item.content) {
  369. this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  370. this.canvas.drawImage(
  371. item.content,
  372. item.x,
  373. item.y,
  374. item.hasOwnProperty('width') ? item.width : this.width,
  375. item.hasOwnProperty('height') ? item.height : this.height
  376. );
  377. }
  378. // item['qr'] = QR.createQrCodeImg(item.content, {
  379. // size: parseInt(300)
  380. // });
  381. // console.log("qr: ", item.qr)
  382. // this.canvas.globalAlpha = item.hasOwnProperty('globalAlpha') ? item.globalAlpha : 1;
  383. // this.canvas.drawImage(
  384. // item.qr,
  385. // item.x,
  386. // item.y,
  387. // item.hasOwnProperty('width') ? item.width : this.width,
  388. // item.hasOwnProperty('height') ? item.height : this.height
  389. // );
  390. this.checkDrawOver();
  391. },
  392. // 判断是否绘制完
  393. checkDrawOver() {
  394. if (this.listsIndex < this.listsLength) { // lists未画完
  395. this.listsIndex++;
  396. this.dataDrawCanvas();
  397. } else {
  398. this.canvasImage();
  399. }
  400. },
  401. // 绘制到画布并生成图片
  402. canvasImage() {
  403. this.listsIndex = 0
  404. this.canvas.draw(false, setTimeout(() => {
  405. setTimeout(() => {
  406. uni.canvasToTempFilePath({
  407. x: 0,
  408. y: 0,
  409. width: Number(this.width),
  410. height: Number(this.height),
  411. fileType: this.imgType,
  412. canvasId: 'canvas',
  413. success: (res) => {
  414. this.$emit('canvasImage', res.tempFilePath);
  415. if (this.showPreview) {
  416. this.showPreviewFn(res.tempFilePath);
  417. }
  418. },
  419. fail: (res) => {
  420. console.log(res);
  421. },
  422. complete: () => {
  423. uni.hideLoading();
  424. }
  425. }, this);
  426. }, 500);
  427. }));
  428. },
  429. // 预览图
  430. showPreviewFn(img) {
  431. uni.previewImage({
  432. current: 0,
  433. urls: [img]
  434. });
  435. },
  436. // 清空画布
  437. clearCanvas() {
  438. this.canvas.clearRect(0, 0, this.width, this.height);
  439. },
  440. removeSave(FILE_BASE_NAME = 'tmp_base64src', format = 'jpg') {
  441. return new Promise((resolve) => {
  442. // 把文件删除后再写进,防止超过最大范围而无法写入
  443. const fsm = uni.getFileSystemManager(); //文件管理器
  444. const FILE_BASE_NAME = 'tmp_base64src';
  445. const format = 'png';
  446. const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
  447. fsm.unlink({
  448. filePath: filePath,
  449. success(res) {
  450. console.log('文件删除成功');
  451. resolve(true);
  452. },
  453. fail(e) {
  454. console.log('readdir文件删除失败:', e);
  455. resolve(true);
  456. }
  457. });
  458. })
  459. },
  460. base64ToSave(base64data, FILE_BASE_NAME = 'tmp_base64src') {
  461. const fsm = uni.getFileSystemManager();
  462. return new Promise((resolve, reject) => {
  463. //format这个跟base64数据的开头对应
  464. const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || [];
  465. if (!format) {
  466. reject(new Error('ERROR_BASE64SRC_PARSE'));
  467. }
  468. const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
  469. //const buffer = wx.base64ToArrayBuffer(bodyData);
  470. fsm.writeFile({
  471. filePath,
  472. data: bodyData,
  473. //data: base64data.split(";base64,")[1],
  474. encoding: 'base64',
  475. success() {
  476. console.log("保存成功")
  477. resolve(filePath);
  478. },
  479. fail() {
  480. console.log("保存失败")
  481. reject(new Error('ERROR_BASE64SRC_WRITE'));
  482. },
  483. });
  484. });
  485. },
  486. }
  487. };
  488. </script>
  489. <style lang='scss' scoped>
  490. .mosowe-canvas-image {
  491. overflow: hidden;
  492. .canvas-wrap-box {
  493. overflow: hidden;
  494. height: 0;
  495. width: 0;
  496. position: fixed;
  497. left: 200%;
  498. top: 0;
  499. }
  500. .canvas-wrap {
  501. overflow: hidden;
  502. height: 0;
  503. width: 0;
  504. }
  505. }
  506. </style>