在电商系统中,商品海报是必不可少的功能,下面以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="{ 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>