c-canvas.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <view>
  3. <canvas type='2d' :canvas-id="canvasId" :id="canvasId" :style="{ width: `${width}px`, height: `${height}px`}" class="cCanvas"></canvas>
  4. </view>
  5. </template>
  6. <script>
  7. /**
  8. * c-canvas 绘制海报组件
  9. * @property {String} canvasId 画布id
  10. * @property {Boolean} isAuto 是否自动绘制
  11. * @property {Number} width 画布宽度
  12. * @property {Number} height 画布高度
  13. * @property {Array} drawData 绘制的数据 [{type: 'image',x: 0,y: 0,value: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3e51af12-a055-4f58-b1ad-7710dd05bfc4/17d972b6-6c4e-4cab-a8c0-e9ee832ecc63.jpg',width:375,height:375},
  14. {type: 'text',x: 10,y: 20,value: '这是一段文字',color: '#F1D7A4',textAlign: 'center',font:'normal 400 12px sans-serif',lineHeight:20,lineMaxWidth:200,lineNum:1},]
  15. * @event {Function()} drawSuccess 绘制成功 返回导出的图片路径
  16. * */
  17. let ctx=null,thumb = null
  18. export default {
  19. name:'c-canvas',
  20. props:{
  21. canvasId:{
  22. type:String,
  23. default:'myCanvas'
  24. },
  25. isAuto:{
  26. type:Boolean,
  27. default:true
  28. },
  29. //画布宽度
  30. width: {
  31. type: Number,
  32. default: 375
  33. },
  34. //画布高度
  35. height: {
  36. type: Number,
  37. default: 375
  38. },
  39. drawData: {
  40. type: Array,
  41. default: () => {
  42. return []
  43. }
  44. }
  45. },
  46. emits:['drawSuccess'],
  47. data() {
  48. return {
  49. }
  50. },
  51. methods: {
  52. drawText(item){
  53. if(item.font)ctx.font=item.font;
  54. // #ifdef MP
  55. if(item.color)ctx.fillStyle=item.color
  56. if(item.textAlign&&item.x&&item.y)ctx.textAlign = item.textAlign
  57. // #endif
  58. // #ifndef MP
  59. if(item.color)ctx.setFillStyle(item.color)
  60. if(item.textAlign&&item.x&&item.y)ctx.setTextAlign(item.textAlign)
  61. // #endif
  62. // if(item.value)ctx.fillText(item.value,item.x||0,item.y||0)
  63. if(item.value)this.textPrewrap(ctx,item.value,item.x||0,item.y||0,item.lineHeight||20,item.lineMaxWidth||200,item.lineNum||1)
  64. },
  65. async drawImage(item){
  66. let Img =item.value
  67. // #ifdef H5
  68. // if(this.checkUrl(Img)){
  69. // let res = await uni.downloadFile({url:item.value})
  70. // Img = res.tempFilePath
  71. // }
  72. // #endif
  73. if(item.radius){
  74. await this.drawRoundRect(item.radius,item.x||0,item.y||0,item.width,item.height,Img)
  75. }else{
  76. // #ifdef MP
  77. let imgBit = await this.loadImg(Img)
  78. ctx.drawImage(imgBit,item.x||0,item.y||0,item.width,item.height)
  79. // #endif
  80. // #ifndef MP
  81. ctx.drawImage(Img,item.x||0,item.y||0,item.width,item.height)
  82. // resolve()
  83. // #endif
  84. }
  85. },
  86. // #ifdef MP
  87. loadImg(src){
  88. return new Promise((resolve,reject)=>{
  89. let imgBit = this.canvas.createImage();
  90. imgBit.src = src;
  91. imgBit.onload = (e) => {
  92. resolve(imgBit)
  93. }
  94. })
  95. },
  96. // #endif
  97. /*
  98. * 参数说明
  99. * ctx Canvas实例
  100. * img 图片地址
  101. * x x轴坐标
  102. * y y轴坐标
  103. * w 宽度
  104. * h 高度
  105. * r 弧度大小
  106. */
  107. async drawRoundRect(r, x, y, w, h, img){
  108. ctx.save();
  109. if (w < 2 * r) r = w / 2;
  110. if (h < 2 * r) r = h / 2;
  111. ctx.beginPath();
  112. ctx.moveTo(x + r, y);
  113. ctx.arcTo(x + w, y, x + w, y + h, r);
  114. ctx.arcTo(x + w, y + h, x, y + h, r);
  115. ctx.arcTo(x, y + h, x, y, r);
  116. ctx.arcTo(x, y, x + w, y, r);
  117. ctx.closePath();
  118. ctx.clip();
  119. // #ifdef MP
  120. let imgBit = await this.loadImg(img);
  121. ctx.drawImage(imgBit, x, y, w, h)
  122. // #endif
  123. // #ifndef MP
  124. ctx.drawImage(img, x, y, w, h);
  125. // #endif
  126. ctx.restore(); // 返回上一状态
  127. },
  128. getContext(){
  129. return new Promise((resolve) => {
  130. const {pixelRatio} = uni.getSystemInfoSync()
  131. uni.createSelectorQuery()
  132. .in(this)
  133. .select(`#${this.canvasId}`)
  134. .fields({ node: true, size: true })
  135. .exec(res => {
  136. const {width,height}=res[0]
  137. const canvas = res[0].node
  138. canvas.width = res[0].width*pixelRatio
  139. canvas.height = res[0].height*pixelRatio
  140. resolve({canvas,width,height,pixelRatio})
  141. })
  142. })
  143. },
  144. async draw(){
  145. if(thumb){
  146. this.$emit('drawSuccess',thumb)
  147. return
  148. }
  149. // #ifdef MP
  150. const {canvas,pixelRatio}=await this.getContext()
  151. this.canvas = canvas
  152. ctx = canvas.getContext('2d')
  153. ctx.scale(pixelRatio,pixelRatio)
  154. // #endif
  155. // #ifndef MP
  156. ctx = uni.createCanvasContext(this.canvasId,this)
  157. // #endif
  158. for(let item of this.drawData){
  159. if (item.type == 'text') {
  160. this.drawText(item)
  161. }else if(item.type == 'image'){
  162. await this.drawImage(item)
  163. }
  164. }
  165. // #ifdef MP
  166. setTimeout(()=>{
  167. uni.canvasToTempFilePath({
  168. canvas: this.canvas,
  169. quality:1,
  170. success:(ret)=>{
  171. // 在H5平台下,tempFilePath 为 base64
  172. thumb = ret.tempFilePath
  173. this.$emit('drawSuccess',thumb)
  174. },
  175. fail: (err) => {
  176. console.log(err);
  177. }
  178. })
  179. },80)
  180. // #endif
  181. // #ifndef MP
  182. ctx.draw(false, () => {
  183. // console.log('绘制完成');
  184. // setTimeout(()=>{
  185. uni.canvasToTempFilePath({
  186. canvasId: this.canvasId,
  187. quality:1,
  188. success:(ret)=>{
  189. // 在H5平台下,tempFilePath 为 base64
  190. thumb = ret.tempFilePath
  191. this.$emit('drawSuccess',thumb)
  192. },
  193. fail: (err) => {
  194. console.log(err);
  195. }
  196. })
  197. // },80)
  198. })
  199. // #endif
  200. },
  201. checkUrl(url){
  202. return /(http|https):\/\/([\w.]+\/?)\S*/.test(url)
  203. },
  204. textPrewrap(ctx, content, drawX, drawY, lineHeight, lineMaxWidth, lineNum){
  205. let drawTxt = ''; // 当前绘制的内容
  206. let drawLine = 1; // 第几行开始绘制
  207. let drawIndex = 0; // 当前绘制内容的索引
  208. // 判断内容是否可以一行绘制完毕
  209. if(ctx.measureText(content).width <= lineMaxWidth) {
  210. ctx.fillText(content, drawX, drawY);
  211. } else {
  212. for (let i = 0; i <= content.length; i++) {
  213. drawTxt += content[i];
  214. if (drawLine === lineNum&&i==content.length) {
  215. if (ctx.measureText(drawTxt).width > lineMaxWidth) {
  216. // 最后一行添加省略号
  217. ctx.fillText(content.substring(drawIndex, i) + '...', drawX, drawY);
  218. }else{
  219. ctx.fillText(content.substring(drawIndex, i), drawX, drawY);
  220. }
  221. } else {
  222. if (ctx.measureText(drawTxt).width > lineMaxWidth) {
  223. // 不是最后一行的情况
  224. ctx.fillText(content.substring(drawIndex, i + 1), drawX, drawY);
  225. drawIndex = i + 1; // 记录当前行最后一个字符串的下一个idnex,用于绘制下行第一个字
  226. drawLine += 1; // 行数+1
  227. drawY += lineHeight; // 绘制内容的y坐标对应增加行高
  228. drawTxt = ''; // 重置绘制的内容
  229. }
  230. }
  231. }
  232. }
  233. }
  234. },
  235. mounted() {
  236. if(this.isAuto)this.draw()
  237. }
  238. }
  239. </script>
  240. <style lang="scss">
  241. .cCanvas{
  242. position: fixed;
  243. top: -10000px;
  244. }
  245. </style>