微信小程序分享图片的简易canvas工具类

在微信小程序中生成一张海报以便用户可以分享到朋友圈的功能在很多微信小程序中都有,今天分享一个自己写的简易canvas工具类

如有需要请自取:GitHub微信小程序保存图片分享的 canvas 简易自用工具类

demo.jpg
所有参数按顺序传,没有默认值的必须传,如果需要修改最后一个参数,所有参数都需要传。。。(js小白,不知道怎么解决)

下面将大部分功能贴在下方

首先,一个将rpx转成px的方法,canvas使用px作为单位,(我这里UI使用750px宽做设计图,使用的时候将标注软件的px改成rpx就是我们需要的)这个方法我直接写在app.js里面,因为用到的地方很多,比如还有OSS
  onLaunch: function () {
    wx.getSystemInfo({
      success: res => {
        //获取屏幕宽度,然后根据宽度图获取图片
        this.globalData.screenWidth = res.screenWidth;
        this.globalData.pixelRatio = res.pixelRatio;
        this.globalData.factor = res.screenWidth / 750;
      },
    })
  },

  globalData: {
    userInfo: null,
    factor: 0.5,
    screenWidth: 375,
    pixelRatio: 2
  },
 
  toPx: function (rpx) {
    //这里的 2 可以改成 动态的 pixelRatio
    //设成固定的是为了在不同设备生成的图片可以在同一级分辨率 
    return rpx * this.globalData.factor * 2;
  }

1、绘制纯色背景

最开始没有写这个,但是安卓真机上需要所以加上了,
这个其实就是画一个矩形,记得宽高要超过你需要绘制的宽高

/**
 * @author 赵勇
 * @desc canvas 画纯色背景 (在iOS上不画默认白色,安卓上不画默认是透明色)
 * 
 *  @param ctx canvas上下文
 *  @param color 背景颜色
 *  @param width  背景宽
 *  @param height 背景高
 *  @param x 轴 坐标
 *  @param y 轴 坐标
 *
 */
function drawBackground(ctx, color = 'white', width = 1000, height = 3000, x = 0, y = 0) {
    ctx.rect(x, y, width, height)
    ctx.setFillStyle(color)
    ctx.fill()
}
使用

如果默认值满足,也可以只传一个参数即:canvas的上下文

canvasUtil.drawBackground(ctx);

要修改颜色可以

canvasUtil.drawBackground(ctx, '#999999');
2、绘制文字

这个是为了方便设置字体,字号,同时返回了最终的x轴坐标,是为了适应某些需要拼接文字的需求(比如价格拼接单位,但是单位字号不一样)

/**
 * @author 赵勇
 * @desc canvas 画文字
 * 
 *  @param ctx canvas上下文
 *  @param text 需要绘制的文字文字
 *  @param x 轴 坐标
 *  @param y 轴 坐标
 *  @param size 字体大小
 *  @param align 字体对齐方式
 *  @param color 字体颜色
 *  @param baseline 字体基线对齐方式
 *  @param fontWeight 字体粗细
 *
 *  @return 绘制完的 x轴 坐标值
 */
function drawText(ctx, text, x, y, size = '18', align = 'left', color = '#333333', baseline = 'top', fontWeight = 'normal') {
    size = parseInt(size)
    let font = `${fontWeight} ${size}px sans-serif`;
    ctx.font = font;

    ctx.setTextAlign(align);
    ctx.setFillStyle(color);
    ctx.setTextBaseline(baseline);
    ctx.fillText(text, x, y);
    let m = ctx.measureText("" + text)
    return m.width + x;
}
注意点:

(1)size 转成整数是因为字号设置是用rpx转px的,会出现小数的情况,而在canvas里面。。。一不小心就报错了
(2)ctx.measureText();可以测量字符串的长度,但是不能是数字、、、我这里就踩坑了,所以转成字符串

使用:

只传部分参数

canvasUtil.drawBreakText(ctx, "text", app.toPx(50), app.toPx(350));

所有参数都用上

  canvasUtil.drawBreakText(ctx, "text", app.toPx(50), app.toPx(350), app.toPx(38), app.toPx(40), app.toPx(650), 'left', '#999999', 'top', 'bold');
3、绘制换行文字

这个需求也算是常见了吧,这里将返回值改成最后的y坐标值,相信大家也是能理解的啦

/**
 * @author  赵勇
 * @desc  canvas画文字带换行 单位(px)
 * 
 * @param ctx canvas上下文
 * @param text 需要绘制的文字文字
 * @param x x轴 坐标值    
 * @param y y轴 坐标值
 * @param lineHeight 行高
 * @param maxW  文字最宽值
 * @param align 文字对齐方式
 * @param color 字体颜色
 * @param baseline 字体基线对齐方式
 * @param fontWeight 字体粗细
 *
 * @return 最后一行的底部 y轴 坐标值
 */
function drawBreakText(ctx, text, x, y, size, lineHeight, maxW, align = 'left', color = '#333333', baseline = 'top', fontWeight = 'normal') {
    size = parseInt(size)
    let font = `${fontWeight} ${size}px sans-serif`;
    ctx.font = font;
    ctx.setTextAlign(align);
    ctx.setFillStyle(color);
    ctx.setTextBaseline(baseline);

    let textArr = text.split("");
    const count = textArr.length;
    let tempText = "";
    let row = [];
    for (let i = 0; i < count; i++) {
        if (ctx.measureText(tempText).width < maxW) {
            tempText += textArr[i];
        } else {
            I--;
            row.push(tempText);
            tempText = "";
        }
    }
    row.push(tempText);

    let i = 0;
    for (i; i < row.length; i++) {
        ctx.fillText(row[i], x, y + i * lineHeight, maxW);
    }

    return y + i * lineHeight;
}

具体实现逻辑就是循环所有字符,然后去判断长度,看的不是很明白的可以cue我

4、绘制图片,但是保持比例填充

我想在小程序里面,只有mode:aspectFill才是我们真的用的最多的吧!

/**
 * @author 赵勇
 * @desc canvas 绘制图  mode:aspectFill 保持比例填充
 *
 * @param ctx canvas上下文
 * @param imagePath 图片url
 * @param sWidth 原图宽
 * @param sHeight 原图高
 * @param dx canvas x轴 坐标
 * @param dy canvas y轴 坐标
 * @param dWidth canvas 宽
 * @param dHeight canvas 高
 */
function drawImageAspectFill(ctx, imagePath, sWidth, sHeight, dx, dy, dWidth, dHeight) {
    //canvas与图片宽高比
    var wRatio = dWidth / sWidth;
    var hRatio = dHeight / sHeight;
    //裁剪图片中间部分
    if (sWidth >= dWidth && sHeight >= dHeight || sWidth <= dWidth && sHeight <= dHeight) {
        if (wRatio > hRatio) {
            ctx.drawImage(imagePath, 0, (sHeight - dHeight / wRatio) / 2, sWidth, dHeight / wRatio, dx, dy, dWidth, dHeight);
        } else {
            ctx.drawImage(imagePath, (sWidth - dWidth / hRatio) / 2, 0, dWidth / hRatio, sHeight, dx, dy, dWidth, dHeight);
        }
    }
    //拉伸图片
    else {
        if (sWidth <= dWidth) {
            ctx.drawImage(imagePath, 0, (sHeight - dHeight / wRatio) / 2, sWidth, dHeight / wRatio, dx, dy, dWidth, dHeight);
        } else {
            ctx.drawImage(imagePath, (sWidth - dWidth / hRatio) / 2, 0, dWidth / hRatio, sHeight, dx, dy, dWidth, dHeight);
        }
    }
}

这个具体看例子吧。。我懒得写了

5、绘制图片,但是保持比例填充,同时还要圆角

好像在安卓上会有bug。。。一脸尴尬。。。
(1)绘制圆角的方法,网上很多,我抄的,所以也就不做解释了


/**
 * @author 赵勇
 * @desc canvas 绘制圆角视图
 *
 * @param ctx canvas上下文
 * @param x canvas x轴 坐标
 * @param y canvas y轴 坐标
 * @param w 宽
 * @param h 高
 * @param r 圆角半径
 * @param fill 填充 true  边框 false
 */
function drawRoundRect(ctx, x, y, w, h, r, fill = true) {
    //开始绘制
    ctx.beginPath();
    // 因为边缘描边存在锯齿,最好指定使用 transparent 填充
    // 这里是使用 fill 还是 stroke都可以,二选一即可
    if (fill) {
        ctx.setFillStyle('transparent')
    } else {
        ctx.setStrokeStyle('transparent')
    }

    // 左上角
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)

    // border-top
    ctx.lineTo(x + w - r, y)

    // 右上角
    ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)

    // border-right
    ctx.lineTo(x + w, y + h - r)

    // 右下角
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)

    // border-bottom
    ctx.lineTo(x + r, y + h)

    // 左下角
    ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)

    // border-left
    ctx.lineTo(x, y + r)

    // 这里是使用 fill 还是 stroke都可以,二选一即可,但是需要与上面对应
    if (fill) {
        ctx.fill()
    } else {
        ctx.stroke()
    }

    ctx.closePath()
}

(2)最后调用绘制圆角和绘制图片的方法

/**
 * @author 赵勇
 * @desc canvas 绘制圆角图  mode:aspectFill 保持比例填充
 *
 * @param ctx canvas上下文
 * @param imagePath 图片url
 * @param sWidth 原图宽
 * @param sHeight 原图高
 * @param dx canvas x轴 坐标
 * @param dy canvas y轴 坐标
 * @param dWidth canvas 宽
 * @param dHeight canvas 高
 * @param radius 圆角半径
 */
function drawImageAspectFillWidthCorner(ctx, imagePath, sWidth, sHeight, dx, dy, dWidth, dHeight, radius = 0) {
    ctx.save();
    drawRoundRect(ctx, dx, dy, dWidth, dHeight, radius);
    // 剪切
    ctx.clip()
    drawImageAspectFill(ctx, imagePath, sWidth, sHeight, dx, dy, dWidth, dHeight);
    ctx.restore();
}

这里需要注意的就是这三个方法的调用时机啦

    ctx.save();
    ctx.clip()
    ctx.restore();
最后一个,下载图片。。。what?下载图片和canvas有啥关系、、、当我需要绘制很多图片时,当图片数量不固定时。。。我就需要他了

/**
 * @author 赵勇
 * @desc 下载图片数组
 *
 * @param images 图片url数组
 * @param success 成功回调
 * 
 */
function downloadImages(images, success, info = [], i = 0) {
    const wxGetImageInfo = utils.wxPromisify(wx.getImageInfo);
    wxGetImageInfo({
        src: images[I]
    }).then(res => {
        info = [...info, res];
        I++;
        if (i < images.length) {
            downloadImages(images, success, info, i);
        } else {
            success(info);
        }
    }).catch(err => {
        console.log(err)

    })
}

知识点

最后保存的时候,在安卓上面有bug,就是字体大小啦,颜色啦都不对了。。。
需要在draw的时候加个300毫秒左右的延时。。。。

 ctx.draw(false, () => {
      setTimeout(() => {
       //你的其他代码
       }, 300)
 });

写了一个简单的demo,需要的就自取啦:GitHub微信小程序保存图片分享的 canvas 简易自用工具类

当然,欢迎star~~~~

赏我一个赞吧~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容