微信小程序开发之卡片分享

本文详细介绍了如何在微信小程序中使用canvas绘制卡片分享,包括计算尺寸、绘制图片和文字、处理圆角和虚线边框等关键步骤,涉及小程序基础库更新后的canvas接口使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在微信小程序的开发中,可能会涉及到一个功能,就是卡片分享,卡片分享的主要技术点就是canvas(画布),这篇文章就是记录一下如何用canvas制作一个卡片分享。

开发准备

本篇文章中用到的小程序基础库版本为2.22.0,因此,在canvas的使用上与低版本基础库有所区别,具体如下:

  1. 基础库 2.9.0 开始,停止维护wx.createCanvasContext,改为使用canvas.getContext(‘2d’)
    在这里插入图片描述

  2. ctx.drawImage方法第一个参数不能直接使用图片地址,需要使用canvas.createImage()进行创建;

  3. 2.9.0 起支持一套新 Canvas 2D 接口(需指定 type 属性);

  4. 从基础库 1.9.90 开始,舍弃了部分属性方法,需要使用新版方法。

开始开发

先来看一下效果图:
在这里插入图片描述
1、变量定义

data: {
    name: '', //植物名称
    imageUrl: '', //图片
    localImageUrl: '', //本地临时路径
    codeUrl: '../../images/code.jpg', //小程序码
    appName: '植晓', //应用名称
    desc: '长按识别小程序码 / 带你认识植物',// 说明
    date: f.formatYmd(util.formatDate(new Date())),// 格式化日期
    _width: 0, //canvas区域的宽
    _height: 0, //canvas区域的高
    imgHeigth: 0, //原图片高度
    imgWidth: 0, //原图片宽度
    loadImagePath: '', //保存图片本地路径
    len: {
      "9": 280,
      "10": 302,
      "11": 327,
      "12": 352
    }, //计算日期的位置
    winWidth: app.globalData.systemInfo.windowWidth,
    winHeight: app.globalData.systemInfo.windowHeight,
    blc: 750, //比例,用来自适应图片和文字
  }

以上就是canvas绘制时需要用到的变量。
2、布局代码

<view class="index-container">
  <view class="index-daily-info">
    <canvas canvas-id="myCanvas" id="myCanvas" style="width:100%;height:100%;border-radius: 5px;" type="2d"></canvas>
  </view>
  <view class="index-operation">
    <view class="operation-btn">
      <button class="btn btn-share" open-type='share' style="width:280rpx;">分享给好友</button>
      <button class="btn btn-save" bindtap='saveImg' style="width:320rpx;">
        <image src="../../images/download.png" mode="aspectFill" style="width:40rpx;height:40rpx;margin-right:10rpx;">
        </image>保存植物美图
      </button>
    </view>
  </view>
</view>

注意:canvas上必须有id和type,type的值为2d。
3、功能代码
利用canvas进行图片绘制,需要注意的一个问题就是图片的适配,要保证绘制出来的图片不变形,因此,在绘制图片之前,我们需要进行一些必要计算。
首先,需要计算我们的卡片在每个机型上的宽和高,这里就需要用到节点选择wx.createSelectorQuery(),代码如下:

var query = wx.createSelectorQuery();
query.select('.index-daily-info').boundingClientRect(function (rect) {
	_this.setData({
        _width: rect.width.toFixed(0),// canvas的宽
        _height: rect.height.toFixed(2)// canvas的高
    })
    // 获取图片的信息
    wx.getImageInfo({
        src: _this.data.imageUrl, //服务器返回的带参数的小程序码地址
        success: function (res) {
          //res.path是网络图片的本地地址
          _this.setData({
            localImageUrl: res.path,
            imgHeigth: res.height,
            imgWidth: res.width
          })
          // 创建canvas图片
          _this.canvasImg()
        },
        fail: function (res) {
          //失败回调
        }
      });
}).exec();

然后,就是正式开始绘制,由于文字要适配各种机型,所以,需要计算一个缩放比例:

let scale = _this.data.winWidth / _this.data.blc //缩放比例

从效果图上可以看出,图片只占了卡片的一半高度,因此,需要将图片的高度固定在canvas一半高度左右:

let imgH = _height * 0.54 // 要显示的图片高度

除此之外,还需要计算图片与canvas的宽高比:

let dw = _width / imgWidth, //canvas与图片的宽高比
      dh = imgH / imgHeight

效果图的上左和右有圆角的效果,代码如下:

// 图片的x坐标
let bg_x = 0
// 图片的y坐标
let bg_y = 0
// 图片圆角
let bg_r = 5
// 图片宽度
let bg_w = _width
// 图片高度
let bg_h = _height
//绘制上左和上右圆角
ctx.arc(bg_x + bg_r, bg_y + bg_r, bg_r, Math.PI, Math.PI * 1.5)
ctx.arc(bg_x + bg_w - bg_r, bg_y + bg_r, bg_r, Math.PI * 1.5, Math.PI * 2)
ctx.arc(bg_x + bg_w - bg_r, bg_y + bg_h - bg_r, bg_r, 0, Math.PI * 0.5)
ctx.arc(bg_x + bg_r, bg_y + bg_h - bg_r, bg_r, Math.PI * 0.5, Math.PI)
ctx.clip()

接下来就是绘制图片了,这里在绘制时,需要计算以保证图片不会变形,代码如下:

// 这里需要先创建一个图片实例才能在canvas上使用
const img = canvas.createImage()
await new Promise(resolve => {
  img.onload = resolve
  img.src = imgurl
})
//绘制图片
if (imgWidth > _width && imgHeight > imgH || imgWidth < _width && imgHeight < imgH) {
  if (dw > dh) {
    ctx.drawImage(img, 0, (imgHeight - imgH / dw) / 2, imgWidth, imgH / dw, 0, 0, _width - 0.1, imgH)
  } else {
    ctx.drawImage(img, (imgWidth - _width / dh) / 2, 0, _width / dh, imgHeight, 0, 0, _width - 0.1, imgH)
  }
} else {
  if (imgWidth < _width) {
    ctx.drawImage(img, 0, (imgHeight - imgH / dw) / 2, imgWidth, imgH / dw, 0, 0, _width - 0.1, imgH)
  } else {
    ctx.drawImage(img, (imgWidth - _width / dh) / 2, 0, _width / dh, imgHeight, 0, 0, _width - 0.1, imgH)
  }
}

绘制完图片后需要恢复之前保存的绘图上下文,避免接下来绘制文字影响图片。

ctx.restore()

接下来就是绘制文字了,需要绘制的文字有标题、小程序介绍、竖排的日期和小程序名称:
绘制标题:

// 字体适配,rpx换算px原理 1rpx = 屏幕宽度/750
ctx.font = parseInt(36 * scale)
ctx.fillStyle = '#000000'
ctx.fillText(_this.data.name.split('').join(' / '), parseInt(56 * scale), imgH + parseInt(74 * scale));

绘制小程序说明:

//绘制说明
ctx.font = parseInt(20 * scale)
ctx.fillStyle = '#000000'
ctx.textAlign = 'center'
ctx.fillText(_this.data.desc, _width / 2, _height - parseInt(74 * scale));

绘制竖排的日期和小程序名称:

//绘制日期
ctx.font = parseInt(20 * scale)
ctx.fillStyle = '#999'
ctx.textAlign = 'right'
c.drawTextVertical(ctx, _this.data.date, _width - parseInt(56 * scale), _height - parseInt(_this.data.len[_this.data.date.length] * scale))
c.drawTextVertical(ctx, _this.data.appName, _width - parseInt(92 * scale), _height - parseInt(98 * scale))

在上述代码中用到了drawTextVertical,这个是自定义的绘制竖排文字的方法,文件命名canvas.js,存放位置可以自己定义,这里是放在了pages同级目录下的utils目录中,代码如下:

const app = getApp()
var winWidth = app.globalData.systemInfo.windowWidth
var scale = winWidth / 750
function drawTextVertical(context, text, x, y) {
  var arrText = text.split('');
  var arrWidth = arrText.map(function (letter) {
    return parseInt(26 * scale);
  });
  var align = context.textAlign;
  var baseline = context.textBaseline;
  if (align == 'left') {
    x = x + Math.max.apply(null, arrWidth) / 2;
  } else if (align == 'right') {
    x = x - Math.max.apply(null, arrWidth) / 2;
  }
  if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
    y = y - arrWidth[0] / 2;
  } else if (baseline == 'top' || baseline == 'hanging') {
    y = y + arrWidth[0] / 2;
  }
  context.textAlign = 'center';
  context.textBaseline = 'middle';
  // 开始逐字绘制
  arrText.forEach(function (letter, index) {
    // 确定下一个字符的纵坐标位置
    var letterWidth = arrWidth[index];
    // 是否需要旋转判断
    var code = letter.charCodeAt(0);
    if (code <= 256) {
      context.translate(x, y);
      // 英文字符,旋转90°
      context.rotate(90 * Math.PI / 180);
      context.translate(-x, -y);
    } else if (index > 0 && text.charCodeAt(index - 1) < 256) {
      // y修正
      y = y + arrWidth[index - 1] / 2;
    }
    context.fillText(letter, x, y);
    // 旋转坐标系还原成初始态
    context.setTransform(1, 0, 0, 1, 0, 0);
    // 确定下一个字符的纵坐标位置
    var letterWidth = arrWidth[index];
    y = y + letterWidth;
  });
  // 水平垂直对齐方式还原
  context.textAlign = align;
  context.textBaseline = baseline;
}
module.exports = {
  drawTextVertical: drawTextVertical
}

使用时需要在js文件顶部进行引入:

const app = getApp()
const c = require('../../utils/canvas')
const util = require('../../utils/util')
const f = require('../../utils/format')
var _this;

前面提到了竖排的日期,从效果图中可以看出日期大写的格式,这个也是用自定义的方法进行转换得来,也就是上面代码中的const f = require(’…/…/utils/format’):

function formatYmd(dateStr) {
  var dict = {
    "0": "零",
    "1": "一",
    "2": "二",
    "3": "三",
    "4": "四",
    "5": "五",
    "6": "六",
    "7": "七",
    "8": "八",
    "9": "九",
    "10": "十",
    "11": "十一",
    "12": "十二",
    "13": "十三",
    "14": "十四",
    "15": "十五",
    "16": "十六",
    "17": "十七",
    "18": "十八",
    "19": "十九",
    "20": "二十",
    "21": "二十一",
    "22": "二十二",
    "23": "二十三",
    "24": "二十四",
    "25": "二十五",
    "26": "二十六",
    "27": "二十七",
    "28": "二十八",
    "29": "二十九",
    "30": "三十",
    "31": "三十一"

  };
  var date = dateStr.split('-'),
    yy = date[0],
    mm = date[1],
    dd = date[2];

  var yearStr = dict[yy[0]] + dict[yy[1]] + dict[yy[2]] + dict[yy[3]] + '年',
    monthStr = dict['' + Number(mm)] + '月',
    dayStr = dict['' + Number(dd)] + '日';
  return yearStr + monthStr + dayStr
}

module.exports = {
  formatYmd: formatYmd
}

在使用时,传入日期即可,日期格式为yyyy-mm-dd,在前面变量定义中有提到。
最后,从效果图中可以看出,有一圈浅灰色的虚线,也很简单:

//绘制虚线边框
 ctx.strokeStyle = '#ccc';
 ctx.beginPath();
 ctx.lineWidth = 1
 ctx.setLineDash([2, 4]);
 //顶部
 ctx.moveTo(15, 15);
 ctx.lineTo(_width - 15, 15);
 //底部
 ctx.moveTo(15, _height - 15);
 ctx.lineTo(_width - 15, _height - 15);
 //左边
 ctx.moveTo(15, 15);
 ctx.lineTo(15, _height - 15);
 //右边
 ctx.moveTo(_width - 15, 15);
 ctx.lineTo(_width - 15, _height - 15);
 ctx.stroke();

至此,一个使用canvas绘制卡片的功能就完成了。
下面给出绘制部分的全部代码:

canvasImg: function () {
    let _width = _this.data._width,
      _height = _this.data._height //宽与高

    let imgHeight = _this.data.imgHeight,
      imgWidth = _this.data.imgWidth,
      scale = _this.data.winWidth / _this.data.blc //缩放比例

    let imgH = _height * 0.54 // 要显示的图片高度
    let dw = _width / imgWidth, //canvas与图片的宽高比
      dh = imgH / imgHeight

    let imgurl = _this.data.localImageUrl
    // 图片的x坐标
    let bg_x = 0
    // 图片的y坐标
    let bg_y = 0
    // 图片圆角
    let bg_r = 5
    // 图片宽度
    let bg_w = _width
    // 图片高度
    let bg_h = _height
    wx.createSelectorQuery()
      .select('#myCanvas')
      .fields({
        node: true,
        size: true
      })
      .exec(async (res) => {
        var canvas = res[0].node
        canvas.width = _width
        canvas.height = _height
        const ctx = canvas.getContext('2d')
        // let ctx = wx.createCanvasContext('myCanvas')
        ctx.fillStyle = '#fff'
        ctx.fillRect(0, 0, _width, _height);
        ctx.save()
        ctx.beginPath()
        //绘制上左和上右圆角
        ctx.arc(bg_x + bg_r, bg_y + bg_r, bg_r, Math.PI, Math.PI * 1.5)
        ctx.arc(bg_x + bg_w - bg_r, bg_y + bg_r, bg_r, Math.PI * 1.5, Math.PI * 2)
        ctx.arc(bg_x + bg_w - bg_r, bg_y + bg_h - bg_r, bg_r, 0, Math.PI * 0.5)
        ctx.arc(bg_x + bg_r, bg_y + bg_h - bg_r, bg_r, Math.PI * 0.5, Math.PI)
        ctx.clip()
        const img = canvas.createImage()
        await new Promise(resolve => {
          img.onload = resolve
          img.src = imgurl
        })
        //绘制图片
        if (imgWidth > _width && imgHeight > imgH || imgWidth < _width && imgHeight < imgH) {
          if (dw > dh) {
            ctx.drawImage(img, 0, (imgHeight - imgH / dw) / 2, imgWidth, imgH / dw, 0, 0, _width - 0.1, imgH)
          } else {
            ctx.drawImage(img, (imgWidth - _width / dh) / 2, 0, _width / dh, imgHeight, 0, 0, _width - 0.1, imgH)
          }
        } else {
          if (imgWidth < _width) {
            ctx.drawImage(img, 0, (imgHeight - imgH / dw) / 2, imgWidth, imgH / dw, 0, 0, _width - 0.1, imgH)
          } else {
            ctx.drawImage(img, (imgWidth - _width / dh) / 2, 0, _width / dh, imgHeight, 0, 0, _width - 0.1, imgH)
          }
        }

        ctx.restore()
        //绘制标题
        // 字体适配,rpx换算px原理 1rpx = 屏幕宽度/750
        ctx.font = parseInt(36 * scale)
        ctx.fillStyle = '#000000'
        ctx.fillText(_this.data.name.split('').join(' / '), parseInt(56 * scale), imgH + parseInt(74 * scale));
        //绘制小程序码
        const codeImg = canvas.createImage()
        await new Promise(resolve => {
          codeImg.onload = resolve
          codeImg.src = _this.data.codeUrl
        })
        ctx.drawImage(codeImg, _width / 2 - (_width * 0.25 / 2), _height - parseInt(262 * scale), _width * 0.25, _width * 0.25)

        //绘制说明
        ctx.font = parseInt(20 * scale)
        ctx.fillStyle = '#000000'
        ctx.textAlign = 'center'
        ctx.fillText(_this.data.desc, _width / 2, _height - parseInt(74 * scale));

        //绘制日期
        ctx.font = parseInt(20 * scale)
        ctx.fillStyle = '#999'
        ctx.textAlign = 'right'
        c.drawTextVertical(ctx, _this.data.date, _width - parseInt(56 * scale), _height - parseInt(_this.data.len[_this.data.date.length] * scale))
        c.drawTextVertical(ctx, _this.data.appName, _width - parseInt(92 * scale), _height - parseInt(98 * scale))
        //绘制虚线边框
        ctx.strokeStyle = '#ccc';
        ctx.beginPath();
        ctx.lineWidth = 1
        ctx.setLineDash([2, 4]);
        //顶部
        ctx.moveTo(15, 15);
        ctx.lineTo(_width - 15, 15);
        //底部
        ctx.moveTo(15, _height - 15);
        ctx.lineTo(_width - 15, _height - 15);
        //左边
        ctx.moveTo(15, 15);
        ctx.lineTo(15, _height - 15);
        //右边
        ctx.moveTo(_width - 15, 15);
        ctx.lineTo(_width - 15, _height - 15);
        ctx.stroke();
        // 显示绘制
        // ctx.draw()
        //将生成好的图片保存到本地,需要延迟一会,绘制期间耗时
        setTimeout(function () {
          wx.canvasToTempFilePath({
            canvas: canvas,
            success: function (res) {
              var tempFilePath = res.tempFilePath;
              _this.setData({
                loadImagePath: tempFilePath,
              });
            },
            fail: function (res) {
              console.log(res);
            }
          });
        }, 500);
      })

  }

最后,再次给出效果图,扫描图片小程序码即可体验 ! ! !
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码的灵魂是bug!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值