评论

[填坑手册]小程序Canvas生成海报(一)--完整流程

跟大家分享下个人在小程序canvas生成海报遇到的一些问题和解决方案。公众号【智酷方程式】精选

海报生成示例

最近智酷君在做[小程序]canvas生成海报的项目中遇到一些棘手的问题,在网上查阅了各种资料,也踩扁了各种坑,智酷君希望把这些“填坑”经验整理一下分享出来,避免后来的兄弟重复“掉坑”。


原型图

这是一个大致的原型图,下面来看下如何制作这个海报,以及整体的思路。


海报生成流程

[代码片段]Canvas生成海报实战demo

demo的微信路径:https://developers.weixin.qq.com/s/Q74OU3m57c9x
demo的ID:Q74OU3m57c9x
如果你装了IDE工具,可以直接访问上面的demo路径
通过代码片段将demo的ID输入进去也可添加:

下面分享下主要的代码内容和“填坑现场”:

一、添加字体

https://developers.weixin.qq.com/miniprogram/dev/api/canvas/font.html

canvasContext.font = value //示例
ctx.font = `normal bold 20px sans-serif`//设置字体大小,默认10
ctx.setTextAlign('left');
ctx.setTextBaseline("top");
ctx.fillText("《智酷方程式》专注研究和分享前端技术", 50, 15, 250)//绘制文本

符合 CSS font 语法的 DOMString 字符串,至少需要提供字体大小和字体族名。默认值为 10px sans-serif

文字过长在canvas下换行问题处理(最多两行,超过“…”代替)

ctx.setTextAlign('left');
ctx.setFillStyle('#000');//文字颜色:默认黑色
ctx.font = `normal bold 18px sans-serif`//设置字体大小,默认10
let canvasTitleArray = canvasTitle.split("");
let firstTitle = ""; //第一行字
let secondTitle = ""; //第二行字
for (let i = 0; i < canvasTitleArray.length; i++) {
    let element = canvasTitleArray[i];
    let firstWidth = ctx.measureText(firstTitle).width;
    //console.log(ctx.measureText(firstTitle).width);
    if (firstWidth > 260) {
        let secondWidth = ctx.measureText(secondTitle).width;
        //第二行字数超过,变为...
        if (secondWidth > 260) {
            secondTitle += "...";
            break;
        } else {
            secondTitle += element;
        }
    } else {
        firstTitle += element;
    }
}
//第一行文字
ctx.fillText(firstTitle, 20, 278, 280)//绘制文本
//第二行问题
if (secondTitle) {
    ctx.fillText(secondTitle, 20, 300, 280)//绘制文本
}

通过 ctx.measureText 这个方法可以判断文字的宽度,然后进行切割。
(一行字允许宽度为280时,判断需要写小点,比如260)

二、获取临时地址并设置图片

let mainImg = "https://demo.com/url.jpg";
wx.getImageInfo({
    src: mainImg,//服务器返回的图片地址
    success: function (res) {
        //处理图片纵横比例过大或者过小的问题!!!
        let h = res.height;
        let w = res.width;
        let setHeight = 280, //默认源图截取的区域
            setWidth = 220; //默认源图截取的区域
        if (w / h > 1.5) {
            setHeight = h;
            setWidth = parseInt(280 / 220 * h);
        } else if (w / h < 1) {
            setWidth = w;
            setHeight = parseInt(220 / 280 * w);
        } else {
            setHeight = h;
            setWidth = w;
        };
        console.log(setWidth, setHeight)
        ctx.drawImage(res.path, 0, 0, setWidth, setHeight, 20, 50, 280, 220);
        ctx.draw(true);
    },
    fail: function (res) {
        //失败回调
    }
});

在开发过程中如果封面图无法按照约定的比例(280x220)给到:
那么我们就需要处理默认封面图过大或者过小的问题,大致思路是:代码中通过比较纵横比(280/220=1.27)正比例放大或者缩小原图,然后从左上切割,竟可能保证过高的图是宽度100%,过宽的图是高度100%。
在canvas中draw图片,必须是一个(相对)本地路径,我们可以通过将图片保存在本地后生成的临时路径
微信官方提供两个API:
wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。

三、裁切“圆形”头像画图

ctx.save();  //保存画图板
ctx.beginPath()//开始创建一个路径
ctx.arc(35, 25, 15, 0, 2 * Math.PI, false)//画一个圆形裁剪区域
ctx.clip()//裁剪
ctx.closePath();
ctx.drawImage(headImageLocal, 20, 10, 30, 30);
ctx.draw(true);
ctx.restore()//恢复之前保存的绘图上下文

使用图形上下文的不带参数的clip()方法来实现Canvas的图像裁剪功能。该方法使用路径来对Canvas话不设置一个裁剪区域。因此,必须先创建好路径。创建完整后,调用clip()方法来设置裁剪区域。
需要注意的是裁剪是对画布进行的,裁切后的画布不能恢复到原来的大小,也就是说画布是越切越小的,要想保证最后仍然能在canvas最初定义的大小下绘图需要注意save()和restore()。画布是先裁切完了再进行绘图。并不一定非要是图片,路径也可以放进去~

小程序 canvas 裁切BUG

ctx.setFillStyle("#fff");
ctx.fillRect(0, 0, 320, 500);  //第一个填充矩形
wx.downloadFile({
    url: headUri,
    success(res) {
        ctx.beginPath()
        ctx.arc(50, 50, 25, 0, 2 * Math.PI)
        ctx.clip()
        ctx.drawImage(res.tempFilePath, 25, 25); //第二个填充图片
        ctx.draw()
        ctx.restore()
        ctx.setFillStyle("#fff");
        ctx.fillRect(0, 0, 320, 500);
        ctx.draw(true)
        ctx.restore()
    }
})

clip裁切这个功能,如果有超过一张图片/背景叠加,则裁切效果失效。
错误参考:http://html51.com/info-38753-1/

四、将canvas导出成虚拟地址

wx.canvasToTempFilePath({
    fileType: 'jpg',
    canvasId: 'customCanvas',
    success: (res) => {
        console.log(res.tempFilePath) //为canvas的虚拟地址
    }
})

res:
{
    errMsg: "canvasToTempFilePath:ok", 
    tempFilePath: "http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr….cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg"
}

这里需要把canvas里面的内容,导出成一个临时地址才能保存在相册,比如:
http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr5UfJVR4k.cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg

五、询问并获取访问手机本地相册权限

wx.getSetting({
    success(res) {
        console.log(res)
        if (!res.authSetting['scope.writePhotosAlbum']) { //判断权限
            wx.authorize({ //获取权限
                scope: 'scope.writePhotosAlbum',
                success() {
                    console.log('授权成功')
                    //转化路径

                    self.saveImg();
                }
            })
        } else {
            self.saveImg();
        }
    }
})

判断是否有访问相册的权限,如果没有,则请求权限。

六、保存到用户手机本地相册

wx.saveImageToPhotosAlbum({
    filePath: res.tempFilePath,
    success: function (data) {
        wx.showToast({
            title: '保存到系统相册成功',
            icon: 'success',
            duration: 2000
        })
    },
    fail: function (err) {
        console.log(err);
        if (err.errMsg === "saveImageToPhotosAlbum:fail auth deny") {
            console.log("当初用户拒绝,再次发起授权")
            wx.openSetting({
                success(settingdata) {
                    console.log(settingdata)
                    if (settingdata.authSetting['scope.writePhotosAlbum']) {
                        console.log('获取权限成功,给出再次点击图片保存到相册的提示。')
                    } else {
                        console.log('获取权限失败,给出不给权限就无法正常使用的提示')
                    }
                }
            })
        } else {
            wx.showToast({
                title: '保存失败',
                icon: 'none'
            });
        }
    },
    complete(res) {
        console.log(res);
    }
})

保存到本地需要一定的时间,需要加一个loading的状态。

七、关于组件中引用canvas

let ctx = wx.createCanvasContext('posterCanvas',this); //需要加this

在components中canvas无法选中的问题:
在components自定义组件下,当前组件实例的this,表示在这个自定义组件下查找拥有 canvas-id 的 <canvas> ,如果省略则不在任何自定义组件内查找。

最后一次编辑于  2021-09-13  
点赞 18
收藏
评论

19 个评论

  • 一面
    一面
    2022-09-12

    lz你好,如果想在一块画布上循环生成图片,存为临时文件,再清空画布再生成第二张,把第二张存为临时图片...有什么比较好的解决思路吗?直接循环drawimage的话,因为是异步函数,两次绘图会同时叠加在一起,setTimeout也有类似的问题。

    2022-09-12
    赞同
    回复
  • H.
    H.
    2022-07-25

    你好,请问一下,我的海报就是两张图叠着的,这个要怎么处理呢,我看到你记录说多张背景图或者叠图会报错,每次执行renderToCanvas时就总是报错 了,请问可以怎么解决呢

    2022-07-25
    赞同
    回复
  • ......
    ......
    2022-04-18

    给楼主点赞

    2022-04-18
    赞同
    回复
  • 琦琦
    琦琦
    2022-04-11

    本地和真机都正常,提交体验之后图片文字都不显示(直接写的图片线上地址不存在接口报错问题)

    2022-04-11
    赞同
    回复 2
    • 游戏人生
      游戏人生
      2022-04-11
      本地和真机都行,按理来说体验版不太会有问题才对,报什么错?
      2022-04-11
      1
      回复
    • 吃饭叫我
      吃饭叫我
      2023-02-21
      有解决嘛
      2023-02-21
      回复
  • 慧源
    慧源
    2021-12-25

    兄弟,你代码里的utils是自己定义的么?

    2021-12-25
    赞同
    回复 1
    • 游戏人生
      游戏人生
      2022-04-11
      2022-04-11
      回复
  • 神经蛙
    神经蛙
    2021-11-18

    生成的canvas图片临时路径保存到本地后(当时图片能正常显示),下次再打开这个页面再取出来图片无法显示,有什么好的解决办法吗

    2021-11-18
    赞同
    回复 1
    • 游戏人生
      游戏人生
      2021-11-19
      首先“临时”图片肯定无法长期打开,我想到的是2个点,1、引导用户存在手机相册,2、传到后台或者云存储上,帮用户保存
      2021-11-19
      回复
  • 杰
    2021-10-18

    为什么圆形头像苹果真机无效,右边是真机效果

    2021-10-18
    赞同
    回复 2
    • 游戏人生
      游戏人生
      2021-10-18
      试试后面加ctx.draw(true); 先切出来
      然后ctx.restore() 继续,这是老版本的写法
      2021-10-18
      回复
    • 琦琦
      琦琦
      2022-04-11
      请问你这个上线能用吗?我遇到了真机正常上体验就空白
      2022-04-11
      回复
  • null
    null
    2020-11-23

    页面中有两个canvas,并且要求两个canvas左右滑动显示,用了swiper组件实现,但是翻页过程中数据显示问题,我就想问问还有没有其他更好的办法实现左右滑动的效果

    2020-11-23
    赞同
    回复 1
    • 游戏人生
      游戏人生
      2020-11-25
      是不是2个canvas会显示错位?重叠什么?
      2020-11-25
      回复
  • zhou
    zhou
    2020-09-25

    http://tmp/wx02935bb29080a7b4.o6zAJswFAuZuKQ5NZfPr5UfJVR4k.cGnD1a02PlVC0b3284be3a41d08986c2477579a5fd8e.jpg 请问这串字符是按什么规则生成的?

    2020-09-25
    赞同
    回复 1
    • 游戏人生
      游戏人生
      2020-09-26
      这是微信自己的临时存储地址,一般来说这种规则都是基于MD5或者SHA这一类的“APpID的加密+特殊字符串+时间戳”的加密。具体如何加密的,目前没有查到微信官方公布的内容,个人觉得也不太会公布具体的
      2020-09-26
      回复
  • 原来这名字可以取这么长不能再长?
    原来这名字可以取这么长不能再长?
    2020-04-02

    这个路劲没有办法直接访问,请问有什么解决方法吗



    2020-04-02
    赞同
    回复 1
    • 游戏人生
      游戏人生
      2020-04-02
      短期请求是可以访问的,当时它是临时路径,随时可能被清理,你可以转化为base64保存,但是不能作为image展示
      2020-04-02
      回复

正在加载...

登录 后发表内容