今天项目接到个任务,就是手写canvas海报,并能下载图片。百度了很多海报组件,都不尽人意。萌生了自己手写海报的想法。
话不多说先贴文档 uni-app官网
其实该文档和微信小程序类似
最终效果,微信小程序可用,一个一个代码敲出来的。只要把原理掌握了,任何海报都能迎刃而解
1.准备环境
因为海报涉及到第三方的图片存储地址,所以要加白名单
downloadFile 里加你海报的根域名
记住一定要在这看看有没有加好白名单,刷新一下要不然还是会加载中空白的效果
话不多说上代码
解读代码块1
// 加微信头像
context.save() // 对当前画布区域进行保存
context.beginPath()
context.arc(185, 160, 50, 0, 2 * Math.PI)
context.clip()
let touImg = await this.getImageInfo(this.touimg)
context.drawImage(touImg.tempFilePath, 135, 110)
context.restore() //对其进行恢复
如果想要让将图片转为圆,并且居中,context.arc(185, 160, 50, 0, 2 * Math.PI)这个方法是关键
因为画布为370px,所以我的X轴坐标 一定为185,Y轴的话看需求但是一定要大于半径(50px)。
我的圆半径为50,想象成一个正方形,它的右上角点的X轴位置就是185-50=185,Y轴160-50=110所以context.drawImage(touImg.tempFilePath, 135, 110) 就得这样写
解读代码块2
// 加活动标题
context.beginPath()
context.setFontSize(25)
context.setFillStyle('#ffffff')
context.setTextAlign('center')
context.fillText(this.title, 185,80)
如果想让字体居中显示,你的字体偏移量记得也得偏移到中间
话不多说上代码
<template>
<view>
<button type="default" @click="show">生成</button>
<canvas style="width: 370px; height: 580px;" canvas-id="firstCanvas" id="firstCanvas" v-if="isshow"></canvas>
<button type="default" @click="saveImage">下载</button>
</view>
</template>
<script>
export default {
components:{
},
data() {
return {
ctx:null,
isshow:false,
title:"草莓千层蛋糕",
touimg:"https://thirdwx.qlogo.cn/mmopen/vi_32/rmPJibkdEnAe9lss490vrCuGTS4X6lhY63XsbBOodXzTGVtwHmTyolbJfOVUq8cNUpzkOugqGqrzCxEhYUTGu1Q/132",
bg:"https://ss2.meipian.me/users/49976449/1a3553b0-affa-11ec-b874-df8ffd888308.png"
}
},
mounted() {
// this.show()
},
methods: {
async show(){
this.isshow=true
uni.showLoading({
title: '加载中...',
mask: true
})
var context = uni.createCanvasContext('firstCanvas')
// 加背景颜色
context.save() // 对当前画布区域进行保存
context.rect(0, 0,300, 200) // begin path
context.setFillStyle('red') //canvas背景颜色
context.fill() // 填充块的合成
context.restore() //对其进行恢复
// 加背景图片
context.save() // 对当前画布区域进行保存
context.beginPath()
let bgImg = await this.getImageInfo(this.bg)
context.drawImage(bgImg.tempFilePath, 0, 0)
context.restore() //对其进行恢复
// 加活动标题
context.beginPath()
context.setFontSize(25)
context.setFillStyle('#ffffff')
context.setTextAlign('center')
context.fillText(this.title, 185,80)
// 加微信头像
context.save() // 对当前画布区域进行保存
context.beginPath() //开始创建一个路径
context.arc(185, 160, 50, 0, 2 * Math.PI) //画一条弧线,2 * Math.PI 就是正圆
context.clip() //从原始画布中剪切任意形状和尺寸
let touImg = await this.getImageInfo(this.touimg) //获取图片信息
context.drawImage(touImg.tempFilePath, 135, 110) //将图片临时路径放入canvas
context.restore() //对其进行恢复
// 加微信昵称
context.beginPath()
context.setFontSize(18)
context.setTextAlign('center')
context.fillText('三线码工', 185, 250)
// 加二维码图片(CSDN不然显示二维码,我就用头像的地址模拟二维码,到时候换个URL就OK了)
context.save() // 对当前画布区域进行保存
let ewmImg = await this.getImageInfo(this.touimg)
context.drawImage(ewmImg.tempFilePath, 100, 370,170,170)
context.restore() //对其进行恢复
// context.draw()
setTimeout(()=>{
context.draw(true,()=>{ // 因为有图片下载,我一般延迟0.5秒
uni.hideLoading()
})
},500)
},
//获取图片
getImageInfo(imgSrc){
return new Promise((resolve, reject) => {
uni.downloadFile({
url: imgSrc,
success: function (res) {
resolve(res);
console.log('获取图片成功',res)
},
fail: (err) => {
reject(err);
console.log('获取图片失败',err)
}
})
});
},
//保存图片到相册
saveImage(){
//判断用户授权
uni.getSetting({
success(res) {
console.log('获取用户权限',res.authSetting)
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
}
}
})
var that = this
uni.canvasToTempFilePath({
canvasId: 'firstCanvas',
quality: 1,
complete: (res) => {
console.log('保存到相册',res);
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
uni.showToast({
title: '已保存到相册',
icon: 'success',
duration: 2000
})
setTimeout(()=>{
that.isShow = false
},2000)
}
})
}
},this);
}
}
}
</script>
<style scoped>
</style>
第二次更新,升级版,适应当前手机屏幕,夹杂其他代码仅供参考
<template>
<view class="pages" :style="{ 'backgroundImage': `url(${imgurl})` }">
<view class="fixed">
<button open-type="share" plain=none class="sharebutton">
<image src="db3.png" mode="widthFix">
</image>
</button>
</view>
<view class="button">
<view class="but" @click="showposter"> 点击生成海报</view>
</view>
<view v-if="isshow" class="model">
<view class="bg" @click="isshow = false"></view>
<view :style="{ 'width': `${width}px`, 'height': `${height}px` }" class="poster">
<canvas :style="{ 'width': `${width}px`, 'height': `${height}px` }" canvas-id="firstCanvas" id="firstCanvas"
v-if="isshow"></canvas>
</view>
<view class="save" @click="save"> 保存至相册转发朋友圈</view>
</view>
</view>
</template>
<script>
import { getCurrentUser } from "@/api/App";
import { getSharePost } from "../api";
import { base_url } from "@/common/togo.js";
export default {
data() {
return {
show: false,
imgurl: "",
ctx: null,
isshow: false,
name: "",
bankName: "",
width: "370",
height: 580
};
},
onLoad(option) {
this.activeId = option.activeId
this.sharePost(false)
},
created() {
this.getinfo()
},
onShareAppMessage() {
var list = ["千万消费券派送中,快来拼手气领取!", "您有消费券待领取,戳这里>>", "↓↓千万消费券已备好,等你哟↓↓您有一张消费券待领取,速来>"]
var sjs = Math.floor(Math.random() * Math.floor(3));
this.shareUrl = `active0601/invited/getInvited?activityId=${this.activeId}&shareUnionId=${this.$store.state.app.selfUnionId}&shareType=0`;
return {
path: this.shareUrl,
title:`【待领取】xxxx ${this.name}邀请您参与xxxx福利活动>>`,
imageUrl:"https://xxxxxxxxx.jpg"
};
},
methods: {
getinfo() {
getCurrentUser().then(res => {
this.name = res.data.realName
this.bankName = res.data.deptName
console.log(res)
})
},
showposter() {
this.showpost()
// this.sharePost(true)
},
async showpost() {
let device = uni.getSystemInfoSync();
let wid = (device.windowWidth) / 800;
this.width = wid * 750
this.height = wid * 1140
var w = wid * 2
console.log(wid)
this.isshow = true
uni.showLoading({
title: '加载中...',
mask: true
})
var context = uni.createCanvasContext('firstCanvas')
context.width = this.width * 5; // 实际渲染像素
context.height = this.height * 5; // 实际渲染像素
// 加背景图片
context.save() // 对当前画布区域进行保存
context.beginPath()
let bgImg = await this.getImageInfo(this.imgurl)
context.drawImage(bgImg.tempFilePath, 0, 0, this.width, this.height)
context.restore() //对其进行恢复
// 加
context.beginPath()
context.setFontSize(12 * w)
context.setFillStyle('#555555')
context.setTextAlign('right')
context.fillText(this.bankName, 320 * w, 360 * w)
// 加
context.beginPath()
context.setFontSize(12 * w)
context.setFillStyle('#555555')
context.setTextAlign('right')
context.fillText(this.name, 320 * w, 380 * w)
// context.draw()
setTimeout(() => {
context.draw(true, () => { // 因为有图片下载,我一般延迟0.5秒
uni.hideLoading()
})
}, 500)
},
//获取图片
getImageInfo(imgSrc) {
return new Promise((resolve, reject) => {
uni.downloadFile({
url: imgSrc,
success: function (res) {
resolve(res);
console.log('获取图片成功', res)
},
fail: (err) => {
reject(err);
console.log('获取图片失败', err)
}
})
});
},
async sharePost(flag) {
var obj = wx.getLaunchOptionsSync();
console.log(obj, "场景值");
const data = {
path: "active0601/invited/getInvited",
activityId: this.activeId,
type: 7,
scene: obj.scene,
referrerInfo: JSON.stringify(obj.referrerInfo),
shareTicket: obj.shareTicket && JSON.stringify(obj.shareTicket),
};
uni.showLoading({ title: " 海报生成中..." });
try {
const resp = await getSharePost(data);
if (resp.code == 0) {
uni.hideLoading();
this.imgurl = base_url + resp.data[0].url
this.show = flag;
} else {
uni.hideLoading();
uni.showToast({
title: resp.msg || "服务器开小差啦",
icon: "none",
});
}
} catch (e) {
uni.hideLoading();
uni.showToast({
title: e.msg || "服务器开小差啦",
icon: "none",
});
}
},
save() {
var that = this
uni.canvasToTempFilePath({
canvasId: 'firstCanvas',
width: this.width * 3,
height: this.height * 3,
destWidth: this.width * 3,
destHeight: this.height * 3,
quality: 1,
complete: (res) => {
console.log('保存到相册', res);
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
uni.showToast({
title: '已保存到相册',
icon: 'success',
duration: 2000
})
setTimeout(() => {
that.isShow = false
}, 2000)
}
})
}
}, this);
},
},
};
</script>
<style lang="scss" scoped>
.pages {
padding: 10rpx 30rpx;
background: #f8f8f8;
min-height: 100vh;
background: #700704 0 0 no-repeat;
background-size: contain;
.button {
position: absolute;
bottom: 70rpx;
left: 0;
width: 100vw;
view {
width: 90%;
background: linear-gradient(180deg, #f6a900 0%, #eb6120 100%);
box-shadow: 0px 2px 21px 0px #e74442;
border-radius: 20rpx;
line-height: 90rpx;
text-align: center;
margin: 0 auto;
font-weight: bold;
color: #ffffff;
}
}
.bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: RGBA(0, 0, 0, 0.7);
z-index: 2;
}
.model {
display: flex;
}
.poster {
margin: 110rpx auto;
background-color: #c11920;
position: relative;
z-index: 99;
}
.save {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100rpx;
background: linear-gradient(90deg, #f6a900 0%, #eb6120 100%);
box-shadow: 0px -4px 6px 0px rgba(186, 0, 12, 0.31);
z-index: 11;
font-weight: 600;
color: #ffffff;
line-height: 100rpx;
text-align: center;
text-shadow: 0px -4px 6px rgba(186, 0, 12, 0.31);
}
}
.fixed {
position: fixed;
right: 30rpx;
bottom: 200rpx;
width: 145rpx;
}
.sharebutton {
border: none;
padding: 0;
margin: 0;
display: flex;
align-items: center;
background-color: rgba($color: #000000, $alpha: 0);
}
</style>