在電商系統中,商品海報是必不可少的功能,下面以Javashop電商系統為例,分享基於canvas實現的海報生成思路。
這是一款基於canvas的商品海報生成組件,可以根據圖片比例生成商品海報圖,適用於商城海報分享功能,基於uniapp框架,兼容app、h5、小程序。
效果圖
(app、h5、小程序):
使用方式:
在 script中引用組件(如果組件是在components內注冊的不需要引入直接使用即可):
import goodsQrcodePoster from './-goods-qrcode-poster.vue' components: { goodsQrcodePoster }, methods: { //生成海報,調用組件內的方法(組件標簽添加ref屬性,父級調用子組件showCanvas()方法使用) handlePoster() { this.$refs.poster.showCanvas() } }
在 template中使用組件:
<goods-qrcode-poster ref="poster" :goodsImg="商品圖片地址" :goodsName="商品名稱" :goodsId="商品ID" :price="商品價格" :promotion="商品促銷活動" :qrcode="商品小程序碼" :disParams="分享攜帶的參數" ></goods-qrcode-poster>
屬性說明:
屬性 |
類型 |
說明 |
goodsImg |
String |
商品圖片鏈接 |
goodsName |
String |
商品名稱 |
goodsId |
String |
商品ID |
price |
Number |
商品價格 |
promotion |
Object |
商品促銷活動 |
qrcode |
String |
商品小程序碼鏈接 |
disParams |
String |
分享攜帶的參數 |
源碼分享:
<template> <view class="content" v-if="isShow" @click.stop="isShow = false"> <canvas @click.stop="" disable-scroll="true" :style="{ width: canvasW + 'px', height: canvasH + 'px' }" canvas-id="my-canvas"></canvas> <view v-if="posterShow" class="save-btn" @click.stop="saveImage">保存圖片</view> </view> </template>
<script> import Qr from '@/common/wxqrcode.js' export default { props:{ goodsImg:{ type: String, default: '' }, goodsName:{ type: String, default: '' }, goodsId: { type: String, default: '' }, price:{ type: Number, default: 0.00 }, promotion: { type: Object, default() { return {} } }, qrcode: { type: String, default: '' }, disParams: { type: String, default: '' } }, data(){ return { canvasW: 0, canvasH: 0, ctx: null, isShow: false, posterShow: false, rpx: uni.getSystemInfoSync().windowWidth / 375 } }, methods: { //顯示 showCanvas(){ this.isShow = true this.__init() }, //初始化畫布 async __init() { uni.showLoading({ title: '圖片生成中', mask: true }) this.ctx = uni.createCanvasContext('my-canvas',this) this.canvasW = uni.upx2px(520); this.canvasH = uni.upx2px(850); //設置畫布背景透明 this.ctx.setFillStyle('rgba(255, 255, 255, 0)') //設置畫布大小 this.ctx.fillRect(0,0,this.canvasW,this.canvasH) //繪制圓角背景 this.drawRoundRect(this.ctx, 0, 0, this.canvasW, this.canvasH,uni.upx2px(20),'#FFFFFF') //獲取商品圖片 let goodsImg = await this.getImageInfo(this.goodsImg) let hW = uni.upx2px(480); let hH = uni.upx2px(480); //繪制商品圖 this.drawRoundImg(this.ctx,goodsImg.path,((this.canvasW-hW) / 2),((this.canvasW-hW) / 2),hW,hH,8) let pointWidth = '' //活動價 if (this.promotion && this.promotion.promotion_type) { this.ctx.setFontSize(16); this.ctx.setFillStyle('#FA3534'); this.ctx.fillText('¥',((this.canvasW-hW) / 2),(((this.canvasW-hW) / 2) + hH + 30)) this.ctx.setFontSize(18); if (this.promotion.promotion_type === 'EXCHANGE') { pointWidth = this.ctx.measureText(this.promotion.exchange.exchange_money).width + this.ctx.measureText(this.promotion.exchange.exchange_point).width + 150 this.ctx.fillText(this.promotion.exchange.exchange_money + ' + ' + this.promotion.exchange.exchange_point + '積分',(((this.canvasW-hW) / 2) + 14),(((this.canvasW-hW) / 2) + hH + 30)) } else if (this.promotion.promotion_type === 'SECKILL') { pointWidth = this.ctx.measureText(this.promotion.seckill_goods_vo.seckill_price).width + 65 this.ctx.fillText(this.promotion.seckill_goods_vo.seckill_price,(((this.canvasW-hW) / 2) + 14),(((this.canvasW-hW) / 2) + hH + 30)) } else if (this.promotion.promotion_type === 'GROUPBUY') { pointWidth = this.ctx.measureText(this.promotion.groupbuy_goods_vo.price).width + 65 this.ctx.fillText(this.promotion.groupbuy_goods_vo.price,(((this.canvasW-hW) / 2) + 14),(((this.canvasW-hW) / 2) + hH + 30)) } } else { this.ctx.setFontSize(16); this.ctx.setFillStyle('#FA3534'); this.ctx.fillText('¥',((this.canvasW-hW) / 2),(((this.canvasW-hW) / 2) + hH + 30)) this.ctx.setFontSize(20); this.ctx.fillText(this.price,(((this.canvasW-hW) / 2) + 14),(((this.canvasW-hW) / 2) + hH + 30)) } //活動 if (this.promotion && this.promotion.promotion_type) { this.ctx.setFontSize(12); this.ctx.setFillStyle('#FA3534'); this.ctx.setStrokeStyle("#FA3534")//設置線條的顏色 this.ctx.setLineWidth(1)//設置線條寬度 if (this.promotion.promotion_type === 'EXCHANGE') { this.ctx.fillText('積分活動' ,((this.canvasW-hW) / 2 + pointWidth + 6),(((this.canvasW-hW) / 2) + hH + 28)) this.ctx.strokeRect(((this.canvasW-hW) / 2 + pointWidth), (((this.canvasW-hW) / 2) + hH + 15), 60, 18); } else if (this.promotion.promotion_type === 'SECKILL') { this.ctx.fillText('限時搶購',((this.canvasW-hW) / 2 + pointWidth + 6),(((this.canvasW-hW) / 2) + hH + 28)) this.ctx.strokeRect(((this.canvasW-hW) / 2 + pointWidth), (((this.canvasW-hW) / 2) + hH + 15), 60, 18); } else if (this.promotion.promotion_type === 'GROUPBUY') { this.ctx.fillText('團購活動',((this.canvasW-hW) / 2 + pointWidth + 6),(((this.canvasW-hW) / 2) + hH + 28)) this.ctx.strokeRect(((this.canvasW-hW) / 2 + pointWidth), (((this.canvasW-hW) / 2) + hH + 15), 60, 18); } } //原價 if (this.promotion && this.promotion.promotion_type) { this.ctx.setFontSize(12); this.ctx.setFillStyle('#999999'); if (this.promotion.promotion_type === 'EXCHANGE') { this.ctx.fillText('原價:¥' + this.promotion.exchange.goods_price,((this.canvasW-hW) / 2),(((this.canvasW-hW) / 2) + hH + 60)) } else if (this.promotion.promotion_type === 'SECKILL') { this.ctx.fillText('原價:¥' + this.promotion.seckill_goods_vo.original_price,((this.canvasW-hW) / 2),(((this.canvasW-hW) / 2) + hH + 60)) } else if (this.promotion.promotion_type === 'GROUPBUY') { this.ctx.fillText('原價:¥' + this.promotion.groupbuy_goods_vo.original_price,((this.canvasW-hW) / 2),(((this.canvasW-hW) / 2) + hH + 60)) } } //繪制標題 let row = this.newLine(this.goodsName, this.ctx) let a = 20//定義行高20 for (let i = 0; i < row.length; i++) { this.ctx.setFontSize(14) this.ctx.setFillStyle("#000000") this.ctx.fillText(row[i], ((this.canvasW-hW) / 2),(((this.canvasW-hW) / 2 + a * i) + hH + 100)) } //小程序碼 二維碼 // #ifdef APP-PLUS || H5 let qrcodeImg = Qr.createQrCodeImg( `分享鏈接,掃碼跳轉的頁面`) this.ctx.drawImage(qrcodeImg, 165 * this.rpx,(((this.canvasW-hW) / 2) + hH + 80), 75 * this.rpx, 75 * this.rpx) // #endif // #ifdef MP let qrcodeImg = await this.getImageInfo(this.qrcode) this.ctx.drawImage(qrcodeImg.path, 170 * this.rpx,(((this.canvasW-hW) / 2) + hH + 80), 75 * this.rpx, 75 * this.rpx) // #endif //延遲渲染 setTimeout(()=>{ this.ctx.draw(true,()=>{ uni.hideLoading() this.posterShow = true }) },500) }, //帶圓角圖片 drawRoundImg(ctx, img, x, y, width, height, radius){ ctx.beginPath() ctx.save() // 左上角 ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 1.5) // 右上角 ctx.arc(x + width - radius, y + radius, radius, Math.PI * 1.5, Math.PI * 2) // 右下角 ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI * 0.5) // 左下角 ctx.arc(x + radius, y + height - radius, radius, Math.PI * 0.5, Math.PI) ctx.stroke() ctx.clip() ctx.drawImage(img, x, y, width, height); ctx.restore() ctx.closePath() }, //圓角矩形 drawRoundRect(ctx, x, y, width, height, radius, color){ ctx.save(); ctx.beginPath(); ctx.setFillStyle(color); ctx.setStrokeStyle(color) ctx.setLineJoin('round'); //交點設置成圓角 ctx.setLineWidth(radius); ctx.strokeRect(x + radius/2, y + radius/2, width - radius , height - radius ); ctx.fillRect(x + radius, y + radius, width - radius * 2, height - radius * 2); ctx.stroke(); ctx.closePath(); }, // canvas多文字換行 newLine(txt, context) { let txtArr = txt.split('') let temp = '' let row = [] for (let i = 0; i < txtArr.length; i++) { // #ifdef H5 || MP if (context.measureText(temp).width < 130 * this.rpx) { temp += txtArr[i] } // #endif // #ifdef APP-PLUS if (temp.length < 12) { temp += txtArr[i] } // #endif else { i-- row.push(temp) temp = '' } } row.push(temp) //如果數組長度大於3 則截取前三個 if (row.length > 3) { let rowCut = row.slice(0, 3); let rowPart = rowCut[2]; let test = ""; let empty = []; for (let a = 0; a < rowPart.length; a++) { if (context.measureText(test).width < 130 * this.rpx) { test += rowPart[a]; } else { break; } } empty.push(test); let group = empty[0] + "..." //這里只顯示三行,超出的用...表示 rowCut.splice(2, 1, group); row = rowCut; } return row }, //獲取圖片 getImageInfo(imgSrc){ return new Promise((resolve, reject) => { uni.getImageInfo({ src: imgSrc, success: (image) => { resolve(image); }, fail: (err) => { reject(err); } }); }); }, //保存圖片到相冊 saveImage(e){ // #ifdef MP //判斷用戶授權 uni.getSetting({ success(res) { if(Object.keys(res.authSetting).length>0){ //判斷是否有相冊權限 if(res.authSetting['scope.writePhotosAlbum']==undefined){ //打開設置權限 uni.openSetting({ success(res) { console.log('設置權限',res.authSetting) } }) }else{ if(!res.authSetting['scope.writePhotosAlbum']){ //打開設置權限 uni.openSetting({ success(res) { console.log('設置權限',res.authSetting) } }) } } }else{ return } } }) // #endif let that = this uni.canvasToTempFilePath({ canvasId: 'my-canvas', quality: 1, complete: (res) => { uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success(res) { that.isShow = false uni.showToast({ title: '已保存到相冊', icon: 'success', duration: 2000 }) } }) } },that); } } } </script>
<style scoped lang="scss"> .content{ position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999; background: rgba(0,0,0,.4); display: flex; flex-direction: column; justify-content: center; align-items: center; .save-btn{ margin-top: 35rpx; color: #FFFFFF; background: linear-gradient(to right, #FD5632 0%, #EF0D25 100%); padding: 20rpx 200rpx; border-radius: 50rpx; } } </style>