在電商系統中,商品海報是必不可少的功能,下面以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>
