如何自适应实现微信小程序生成海报
前言
说实话,刚收到这个需求时,领导给我演示了一下,以为是个很高大上的功能。找了领导给的小程序生成海报功能的图片做测试,经过我不懈的 努力后 百度后,发现这原来只不过是canvas生成一张图片罢了。,那么问题来了,什么是canvas,呃呃,canvas就是。。。
读者:废话少说,先给看看效果吧,我:好嘞,客官
补充:手机上图片需要网络图片,且先下载准备临时图片地址,最下面有代码演示
(https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.saveImageToPhotosAlbum.html).
效果
原图
点击生成海报
啪,灯神出。。。
好了,效果图也看完了,是不是你想要的效果?如果不是你也不用看下去了。好,接下来咱进入下一个阶段,代码实现
还想多说几句
因为我百度了其他大佬们的代码,发现他们居然没怎么考虑自适应问题。小程序本身就自带了rpx这样的玩意,那我们也就更方便了。先把设计稿放到ps里,Alt+i+i将设计稿缩小为750px的宽,然后直接测量每一个元素的坐标,如下图(相信大家都是一个成熟的前端了,怎么用测量工具就不使用用我来过多说了)
这次真的不多说了,下面上代码
wxml
<!--pages/detail/index.wxml-->
<view class="container">
<view class="btn">
<button open-type="getUserInfo" bindgetuserinfo="getUserInfo">
<view class="text">生成海报</view>
</button>
</view>
<!-- 生成海报 -->
<view
class="poster-box"
wx:if='{{isPhotoModel}}'
bindtap="setPhotoModel"
>
<canvas canvas-id="mycanvas" style="width:{{posterW}}px;height:{{posterW}}px; "/>
<image mode="widthFix" src='{{shareImagePath}}' wx:if='{{shareImagePath}}'></image>
<view class="close-save">关闭并保存海报</view>
</view>
</view>
CSS
/* pages/poster/index.wxss */
.container{
padding-top: 400rpx;
}
/* 海报弹框 */
.poster-box{
position: fixed;
top:0;
left:0;
width: 100%;
height: 100%;
background: rgba(0,0,0,.4);
z-index: 100;
}
.poster-box canvas{
position: absolute;
top:0;
left:10000px;
bottom:0;
right:0;
margin:auto;
background: #fff;
}
.poster-box image{
width: 90%;
position: absolute;
top:0;
left:0;
bottom:0;
right:0;
margin:auto;
}
.poster-box .close-save{
width: 300rpx;
position: absolute;
left:0;
bottom: 10%;
right:0;
margin:auto;
border-radius: 10rpx;
line-height: 80rpx;
font-size: 30rpx;
text-align: center;
background: #fff;
color: #aaa;
}
JS
// pages/detail/index.js
Page({
/**
* 页面的初始数据
*/
data: {
// 分享海报图片
shareImagePath: "",
// 分享的小程序码
qtsheXcxCode: "/images/image/wx-code.png",
// 分享的用户姓名
userName: "你的名字",
// 分享的用户头像
userHeadUrl: "/images/image/head-img.png",
// 分享的海报背景图片
qtsheBackground: "/images/image/poster-img.png",
// 海报弹框
isPhotoModel: false,
// 海报宽高
posterW: 0,
posterH: 0,
Rpx: 0,
},
/**
* 功能函数 *
*/
// 获取容器高度 提前设置好canvas宽高
getContentHeight() {
wx.getSystemInfo({
success: (res) => {
// console.log(res)
this.setData({
Rpx: res.windowWidth / 750,
posterW: res.windowWidth,
posterH: res.windowWidth,
})
}
})
},
// 获取用户信息
getUserInfo(res) {
console.log(res)
// 获取用户信息失败
if (res.detail.errMsg !== "getUserInfo:ok") {
return false;
}
this.setData({
userName: res.detail.userInfo.nickName,
// userHeadUrl: res.detail.userInfo.avatarUrl,
// qtsheBackground: "",
});
this.createNewImg();
},
// 海报弹框隐藏显示
setPhotoModel() {
this.setData({
isPhotoModel: !this.data.isPhotoModel
})
},
/**
* 海报绘制函数 *
*/
//将canvas转换为图片保存到本地,然后将图片路径传给image图片的src
createNewImg() {
wx.showLoading({
title: '正在生成海报...',
mask: true
})
let that = this
let ctx = wx.createCanvasContext('mycanvas')
// 绘制背景
this.setBg(ctx);
//绘制背景图片
this.setBgImage(ctx);
// 绘制头像
this.setAvatar(ctx);
// 名字
this.setName(ctx);
// 名字下的标题1
this.setText({ ctx: ctx, text: "邀请你免费学习", size: 30, x: 150, y: 412, color: "#A2A2A2" });
// 名字下的标题2
this.setText({ ctx: ctx, text: "混沌学院", size: 30, x: 393, y: 412, color: "#c1bba8" });
// 名字下的标题3
this.setText({ ctx: ctx, text: "重磅好课", size: 30, x: 536, y: 412, color: "#A2A2A2" });
// 绘制线条
this.setLine(ctx);
// 绘制小程序码
this.setQrcode(ctx);
// 小程序码旁边的文字
this.setText({ ctx: ctx, text: "扫码升级你的思维", size: 30, x: 257, y: 577, color: "#d3d3d3" });
this.setText({ ctx: ctx, text: "开课后72小时内免费学习此课程", size: 30, x: 257, y: 634, color: "#d3d3d3" });
// 结束绘制
ctx.draw()
// 保存
ctx.save()
// 展示海报弹框
this.setPhotoModel();
//将生成好的图片保存到本地,需要延迟一会,绘制期间耗时
setTimeout(() => {
wx.canvasToTempFilePath({
canvasId: 'mycanvas',
success: function (res) {
console.log(res)
wx.hideLoading()
that.setData({
shareImagePath: res.tempFilePath
});
},
fail: function (res) {
console.log(res.errMsg)
wx.showToast({
title: '保存失败,请刷新页面重试',
icon: 'none'
})
}
}, this)
}, 2000)
},
// 绘制背景
setBg(ctx) {
let { Rpx } = this.data
let W = 750 * Rpx
let H = 750 * Rpx
ctx.setFillStyle('#FFFFFF')
ctx.fillRect(0, 0, W, H)
},
// 绘制背景海报
setBgImage(ctx) {
let { qtsheBackground } = this.data
this.setImage({ ctx: ctx, imageUrl: qtsheBackground, x: 0, y: 0, W: 750, H: 305 })
},
/*
* 绘制用户头像
* cx cy 是圆心到画布原点坐标
* r*2是上面圆的直径,也是头像的宽高
*/
setAvatar(ctx) {
let { Rpx } = this.data
// 先画个圆形区域
let cx = 83 * Rpx;
let cy = 400 * Rpx;
let r = 50 * Rpx;
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
ctx.clip();
// 将头像绘制在圆里边
let path = this.data.userHeadUrl;
this.setImage({ ctx: ctx, imageUrl: path, x: 35, y: 353, W: 100, H: 100 })
},
// 昵称绘制
setName(ctx) {
let text = this.data.userName + "我是测试昵称";
if (text.length >= 8) {
text = text.substring(0, 7) + '...'
}
this.setText({ ctx: ctx, text: text, size: 34, x: 150, y: 354, color: "#000" });
},
/**
* @title 文字绘制
* @params obj object 绘制对象
* @params obj.ctx object 画布对象
* @params obj.text string 绘制文字
* @params obj.size number 绘制文字尺寸
* @params obj.x number 绘制文字x坐标
* @params obj.y number 绘制文字y坐标
* @params obj.color string 绘制文字颜色
*/
setText(obj) {
let { Rpx } = this.data
let ctx = obj.ctx;
let text = obj.text || "";
let size = obj.size * Rpx || 30;
let x = obj.x * Rpx || 0;
let y = obj.y * Rpx || 0;
let color = obj.color || "black";
ctx.setTextBaseline('top')
ctx.setFillStyle(color)
ctx.setFontSize(size)
ctx.fillText(text, x, y)
},
/**
* @title 图片绘制
* @params obj object 绘制对象
* @params obj.ctx object 画布对象
* @params obj.imageUrl string 绘制图片地址(真实地址)
* @params obj.x number 绘制图片x坐标
* @params obj.y number 绘制图片y坐标
* @params obj.W number 绘制图片宽
* @params obj.H string 绘制图片高
*/
setImage(obj) {
let { Rpx } = this.data
let ctx = obj.ctx;
let imageUrl = obj.imageUrl || "";
let x = obj.x * Rpx || 0;
let y = obj.y * Rpx || 0;
let W = obj.W * Rpx || 0;
let H = obj.H * Rpx || 0;
ctx.drawImage(imageUrl, x, y, W, H);
},
// 绘制昵称下的线条
setLine(ctx) {
let { Rpx } = this.data
let x1 = 50 * Rpx;
let y1 = 493 * Rpx;
let x2 = 700 * Rpx;
let y2 = 493 * Rpx;
ctx.setStrokeStyle('#d8d8d8')
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.closePath()
ctx.stroke()
},
// 绘制小程序码
setQrcode(ctx) {
let { qtsheXcxCode } = this.data
this.setImage({ ctx: ctx, imageUrl: qtsheXcxCode, x: 50, y: 534, W: 180, H: 180 })
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
// 获取容器高度 需要提前预设好canvas的宽高啊 提前预设好canvas的宽高啊 提前预设好canvas的宽高啊
this.getContentHeight();
},
})
下载网络图片保存临时路径
这是一个关键步骤,很重要,我们需要先将网络图片保存到本地,再去绘制海报,图片地址路径需要加入下载白名单
代码如下
/**
* 保存网络图片临时地址
* @imgSrc string 图片地址
* @dataValue string 图片地址存储属性
* @func function 回调函数
*/
savaImageUrl({ imgSrc, dataValue, func}={}){
let that = this;
wx.downloadFile({
url: imgSrc,
success: function (res) {
console.log(res);
that.setData({ [dataValue]: res.tempFilePath})
func();
},
fail: function (res) {
console.log(res.errMsg)
wx.showToast({
title: '保存失败,请刷新页面重试',
icon: 'none'
})
}
})
},
当需要下载多张图片时, 调用演示如下
// 获取用户信息 该函数挂在小程序的button上
getUserInfo(res){
console.log(res)
// 获取用户信息失败
if (res.detail.errMsg !== "getUserInfo:ok"){
return false;
}
this.setData({
userName: res.detail.userInfo.nickName,
userHeadUrl: res.detail.userInfo.avatarUrl,
});
const { qtsheXcxCode, qtsheBackground ,userHeadUrl } = this.data;
wx.showLoading({
title: '正在生成海报...',
mask: true
})
const func1 = ()=>{
this.savaImageUrl({
imgSrc: userHeadUrl,
dataValue: "userHeadUrl1",
func:()=>{
this.createNewImg()
}
})
}
const func2 = ()=>{
this.savaImageUrl({
imgSrc: qtsheXcxCode,
dataValue: "qtsheXcxCode1",
func: func1
})
}
const func3 = ()=>{
this.savaImageUrl({
imgSrc: qtsheBackground,
dataValue: "qtsheBackground1",
func: func2
})
}
func3();
},
保存海报
弄个按钮点击保存当前生成的海报,放在生成canvas图片函数里也行
// 保存海报
saveImg() {
let that = this;
// 获取用户是否开启用户授权相册
wx.getSetting({
success(res) {
// 如果没有则获取授权
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
wx.saveImageToPhotosAlbum({
filePath: that.data.shareImagePath,
success() {
wx.showToast({
title: '保存成功'
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
},
fail() {
// 如果用户拒绝过或没有授权,则再次打开分享口
}
})
} else {
// 有则直接保存
wx.saveImageToPhotosAlbum({
filePath: that.data.shareImagePath,
success() {
wx.showToast({
title: '保存成功'
})
},
fail() {
wx.showToast({
title: '保存失败',
icon: 'none'
})
}
})
}
}
})
},
结语
看完了吧,canvas真的难吗,难!但是我们现在想要实现的效果难吗?现在这个效果就是切图画界面嘛。多去看看文档和大佬们的代码,有些以前的难,也就不难了
需要提前预设好canvas的宽高啊 提前预设好canvas的宽高啊 提前预设好canvas的宽高啊 重要的事情说三遍