小程序 canvas 2D 生成海报

1,209 阅读2分钟

不讲废话,实际第一。

一、常见需求

  1. 活动扫码推广
  2. 关注公众号扫码推广
  3. 产品扫码推广
  4. 推荐人扫码推广
  5. 日常小程序扫码推广 这些需求很常见,一般也都是通过生成海报(二维码头像背景图)来进行朋友圈推广或者其他推广。

二、代码实现

WXML

//canvas先渲染,已有海报隐藏canvas,显示海报,进行长按保存操作。
<view class="wrap">
    <block wx:if="{{imgFilePath}}">
        <image 
            bindlongpress="handlePhotoSave" 
            bindtap="handlePreviewPhoto" 
            src="{{imgFilePath}}" 
            mode="widthFix">
         </image>
    </block>
    <block wx:else>
        <canvas 
            type="2d" 
            id="myCanvas" 
            style="width:{{width}};height:{{height}};">
        </canvas>
    </block>
</view>

js

onReady() {
    const query = wx.createSelectorQuery()
    query.select('#myCanvas')
    .fields({
        node: true,
        size: true
    }).exec((res) => {
        const canvasDom = res[0] 
        const canvas = canvasDom.node
        const ctx = canvas.getContext('2d')
        const dpr = wx.getSystemInfoSync().pixelRatio
        this.setData({
                canvasDom,
                canvas,
                ctx,
                dpr
        },()=>{
                this.drawing()
        })
    })
}, 

// 绘制画面 
drawing() {
    const that = this;
    wx.showLoading({ title: "生成中..." }) // 显示loading
    that.drawPoster()               // 绘制海报
    .then(function () { 
        that.drawPhoto() // 绘制头像
        that.drawQrcode() // 绘制推荐码
        setTimeout(()=>{
            wx.canvasToTempFilePath({ //将canvas生成图片
                canvas:that.data.canvas,
                fileType:'jpg',
                quality:1,
                success(res) {
                    that.setData({ imgFilePath: res.tempFilePath })
                },
                fail(err) {
                    console.log('生成图片失败:', err)
                },
            }, this)
            wx.hideLoading() // 隐藏loading
            wx.showToast({
                title: '生成成功,长按保存!',
                icon:'none'
            })
        }, 1000)
    })
},

// 绘制海报背景
drawPoster() {
    const that = this
    return new Promise((resolve, reject)=> {
        let poster = that.data.canvas.createImage();          // 创建一个图片对象
        poster.src = that.data.bgSrc                      // 图片对象地址赋值
        poster.onload = () => {
            that.setData({
                width:poster.width + 'rpx',
                height:poster.height  + 'rpx'
            })
            that.computeCanvasSize(poster.width, poster.height) // 计算画布尺寸
            .then((res)=>{
                that.data.ctx.drawImage(poster, 0, 0, poster.width, poster.height, 0, 0, res.width, res.height);
                resolve()
            })
        }
    })
},

// 计算canvas大小
computeCanvasSize(imgWidth, imgHeight){
    const that = this
    return new Promise((resolve, reject)=>{
        var canvasWidth = that.data.canvasDom.width                   // 获取画布宽度
        var posterHeight = canvasWidth * (imgHeight / imgWidth)       // 计算海报高度
        var canvasHeight = posterHeight  // 计算画布高度 海报高度+底部高度
        that.setData({
            canvasWidth: canvasWidth,                                   // 设置画布容器宽
            canvasHeight: canvasHeight,                                 // 设置画布容器高
            posterHeight: posterHeight                                  // 设置海报高
        }, () => { // 设置成功后再返回
            that.data.canvas.width = that.data.canvasWidth * that.data.dpr // 设置画布宽
            that.data.canvas.height = canvasHeight * that.data.dpr         // 设置画布高
            that.data.ctx.scale(that.data.dpr, that.data.dpr)              // 根据像素比放大
            setTimeout(()=>{
              resolve({ "width": canvasWidth, "height": posterHeight })    // 返回成功
            }, 1200)
        })
    })
},

// 绘制头像
drawPhoto() {	
    let photoWidth = 67;
    let marginLeft = 146;
    let photo = this.data.canvas.createImage();       // 创建一个图片对象
    photo.src = this.data.headPortrait; // 图片对象地址赋值
    photo.onload = () => {
        let radius = photoWidth / 2                      // 圆形头像的半径
        let x = marginLeft / 2                    // 左上角相对X轴的距离
        let y = this.data.canvasHeight - photoWidth - 230 // 左上角相对Y轴的距离 :整体高度 - 头像直径 - 微调
        this.data.ctx.save()
        this.data.ctx.arc(x + radius, y + radius, radius, 0, 2 * Math.PI) // arc方法画曲线,按照中心点坐标计算,所以要加上半径
        this.data.ctx.clip()
        this.data.ctx.drawImage(photo, x, y, photoWidth, photoWidth)
        this.data.ctx.restore();
    }
},

// 绘制推荐码
drawQrcode(){
    let photoWidth = 45;
    let marginLeft = 337;
    let photoNode = this.data.canvas.createImage();
    photoNode.src = this.data.qrcode;
    photoNode.onload = () => {
        let x = marginLeft / 2                    // 左上角相对X轴的距离
        let y = this.data.canvasHeight - photoWidth - 120 // 左上角相对Y轴的距离 :整体高度 - 头像直径 - 微调
        this.data.ctx.drawImage(photoNode, x, y, photoWidth, photoWidth)
        this.data.ctx.restore();
    }
},

// 保存图片
handlePhotoSave(){
    let imgFilePath = this.data.imgFilePath;
    wx.saveImageToPhotosAlbum({
        filePath:imgFilePath,
        success(){
            wx.showToast({
                title: '保存成功!',
            })
        },
        //fail报错的话 判断是否是权限没开调准权限,然后保存即可.....
    })
},

三、实现效果

61jDscNjeraL9dc793c27154dd0872bc1a3247a08eb1.jpg 需求变动:只需要背景图预留头像以及二维啊,通过简单修改位置,完成生成海报功能。

四、地址

github地址