【小程序】利用canvas实现分享功能

一、分享功能核心思路

1.在wxml页面中添加最重要的两个标签:imagecanvas, image标签用于展示分享的图片,canvas用于画图、生成路径赋值给image的src,最后保存分享的图片

二、分享功能实现路线

1.UI图如下

这张设计图比较简单,数据都是从后端返回,直接渲染上去就行。主要注意标题超长时的特殊处理。此外,二维码是后端返回,然后渲染上去。实际工作中后端返回base64编码,需要编译后利用ctx.drarImage画上去

image

2.wxml和wxss布局

(1) 在移动端呈现的效果如下,因为做的是一个简单的demo,没有后端返回数据,所以没有做二维码。布局和样式做得比较简单。

image

1.1 canvas.wxml

<view class="model-wrap">
    <view class="model-box">
        <image src="{{imgUrl}}"></image>
        <canvas canvas-id="myCanvas"></canvas>
    </view>
    <view class="save-btn" bindtap="save">保存图片</view>
</view>

1.2 canvas.wxss

page {height: 100%}
.model-wrap {
    position: relative;
    overflow: hidden;
    height: 100%;
    background: rgba(0,0,0,.4);
}
.model-box {
    position: absolute;
    top: 50%;
    left: 0;
    transform: translate(0, -50%);
}
image {
    width: 750rpx;
    height: 788rpx;
}
canvas {
    position: absolute;
    top: 0;
    left: 750px;
    width: 750px;
    height: 788px;
}
.save-btn {
    text-align: center;
    color: #fff;
    position: absolute;
    top: 90%;
    left: 43%;
    transform: translate(0, -50%);
}

3. 开始画图

3.1 首先设置 imgUrl 并模拟一些数据

data: {
        imgUrl: '', // 用于获取canvas图片路径并渲染在image标签中
        shareDate: {
            name: 'Damon',
            title: '标题知识产权专项资金知识产权专项资金项资金项资金 ',
            status: '有效',
            area: '四川省成都市'
        }
    },

3.2 开始画图
背景图的路径需要是 https 的

const bgUrl = "https://cdn.chacha.top/mini_pro/share_bg.png";
draw() {
    let _that = this; // 先保存this,如果你以组件的方式写分享功能,在生成图片时要传入this
    wx.getImageInfo({
            src: bgUrl,
            success(res) {
                let {shareDate} = _that.data;
                let titleHeight;  // 标题高度
                let ctx = wx.createCanvasContext('myCanvas');
  
                   ctx.drawImage(res.path, 0, 0, 750, 788);
                         // 这里放 画在画布的文字
                   ctx.draw(true, (res)=> {
                        wx.canvasToTempFilePath({
                            canvasId: 'myCanvas',
                            success(res) {
                                _that.setData({
                                        imgUrl: res.tempFilePath
                                })
                            }
                    }, _that)
             })
         }
})

完成以上两步,就能把背景图画出来了,如下图所示


image.png

3.3 补充用户名、标题、其他内容

//  用户名
util.wrapText({
    ctx,
    text:shareDate.name,
    x: 120,
    y: 75,
    w: 170,
    fontStyle: {
       lineHeight: 83,
       textAlign: 'left',
       textBaseline: 'top',
       font: 'normal 24px arial',
       fillStyle: '#333'
    }
})

// 标题
// 如果标题过长时截取
let title = shareDate.title.length < 80 ? shareDate.title : shareDate.title.substring(0,80)+'...'
titleHeight = util.wrapText({
    ctx,
    text: title,
    x: 42,
    y: 195,
    w: 645,
    fontStyle: {
        lineHeight: 58,
        textAlign: 'left',
        textBaseline: 'top',
        font: 'normal 40px arial',
        fillStyle: '#535353'
    }
});

                
let top1 = titleHeight + 120; // 地区 在y轴上的值
let top2 = top1 + 45; // 状态 在y轴上的值

// 地区
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#b4a296';
ctx.fillText('适用于 ', 42, top1);

let w1 = ctx.measureText('适用于 ').width;
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#ff7010';
ctx.fillText(shareDate.area, 42+w1, top1);

let w2 = ctx.measureText(shareDate.area).width;
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#b4a296';
ctx.fillText(' 的机构', 42+w1+w2, top1);

 // 政策状态
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#b4a296';
ctx.fillText('政策状态:', 42, top2);

let w3 = ctx.measureText('政策状态:').width;
ctx.font = 'normal normal 28px arial';
ctx.fillStyle = '#ff7010';
ctx.fillText(shareDate.status, 42+w3, top2);


// 长按小程序码查看详情
ctx.font = 'normal normal 24px arial';
ctx.fillStyle = '#535353';
ctx.fillText('长按小程序码查看详情', 35, 670);

呈现的效果如下,由于 标题的高度变化 会影响到适用地区和政策状态的渲染高度,所以动态设置。

image.png

3.4 用户头像
用户头像放在最后是因为 头像链接 是外链,通过异步加载获取,所以放在最后画方便控制,ctx.draw()也需要放在成功的回调中,不然会渲染不上

// 头像
wx.downloadFile({
      url: 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1543030619359&di=d26dfbfe8898517f3fc0d14dd7fb1437&imgtype=0&src=http%3A%2F%2Fb.hiphotos.baidu.com%2Fzhidao%2Fwh%253D450%252C600%2Fsign%3Df6277da51a4c510fae91ea1e5569091b%2F4b90f603738da97748712546b051f8198618e305.jpg',
      success(res) {

           let width = 63; //头像宽
           let height = 63; //头像高
           let x = 40; // 头像距x轴距离
           let y = 55; // 头像距y轴距离

           ctx.beginPath();
           ctx.arc(width/2+x, height/2+y, width/2, 0, Math.PI*2);
           ctx.clip();
           ctx.drawImage(res.tempFilePath, x, y, width, height);


           ctx.draw(true, (res)=> {
                wx.canvasToTempFilePath({
                    canvasId: 'myCanvas',
                    success(res) {

                       _that.setData({
                           imgUrl: res.tempFilePath
                       })
                    }
               }, _that)
          })
       }
 })

3.5 保存图片
保存图片功能比较简单,主要下载canvas图片生成的路径,在下载时调用wx.saveImageToPhotosAlbum({})这个api

save() {
        let _that = this;
        wx.showLoading({
            mask: true
        })
        wx.canvasToTempFilePath({
            x: 0,
            y: 0,
            width: 750,
            height: 788,
            destWidth: 750,
            destHeight: 788,
            canvasId: 'myCanvas',
            success(res) {
                
                wx.saveImageToPhotosAlbum({
                    filePath: res.tempFilePath,
                    success(res) {
                        wx.hideLoading();
                        wx.showModal({
                            title: '提示',
                            showCancel: false,
                            confirmText: '知道了',
                            confirmColor: '#0facf3',
                            content: '已成功为您保存图片到手机相册,请自行前往朋友圈分享。',
                            success: (res) => {
                                if (res.confirm) {
                                    console.log('保存成功,隐藏模态框')
                                }
                            }
                        })
                    },
                    fail(res) {
                        wx.hideLoading();
                        wx.showModal({
                            title: '保存出错',
                            showCancel: false,
                            confirmText: '知道了',
                            confirmColor: '#0facf3',
                            content: '您拒绝了授权 ,如果您要保存图片,请删除小程序,再重新打开。',
                            success: (res) => {
                                console.log(res)
                            }
                        })
                    }
                })
            }
        }, _that)
    }

4.用到的文本折行方法

const wrapText = ({
    ctx,
    text,
    x,
    y,
    w,
    fontStyle: {
        lineHeight = 60,
        textAlign = 'left',
        textBaseline = 'top',
        font = 'normal 40px arial',
       fillStyle = '#000'
    }
}) => {
      ctx.save();
      ctx.font = font;
      ctx.fillStyle = fillStyle;
      ctx.textAlign = textAlign;
      ctx.textBaseline = textBaseline;
      const chr = text.split('');
      const row = [];
      let temp = '';

      for (let a = 0 ; a < chr.length ; a++) {
          if (ctx.measureText(temp).width<w) { } else {
              if (/[,。!》]/im.test(chr[a])) {
                  temp += ` ${chr[a]} `;
                  a++;
              }

        if (/[《]/im.test(chr[a-1])) {
                temp = temp.substr(0, temp.length -1);
                a--;
        }

            row.push(temp);
            temp = '';
      }
            temp += chr[a] ? chr[a]: '';
    }
        row.push (temp);
        for(let b = 0 ; b < row.length ; b++) {
            ctx.fillText(row[b], x, y+b*lineHeight)

        }
    
        ctx.restore();
        return y+(row.length-1)*lineHeight //返回文本高度
}

三、总结

第一次实现分享功能时一头雾水,不知道从哪入手,看了很多教程,发现有两种方法实现。

第一种是 在页面布局时放两个 canvas,一个用于展示,另一个用于保存。这种方法要根据UI图去计算两个canvas的缩放比例,在画图时也需要画两次,第一次花在展示的canvas上,第二次根据第一次画距离*比例。我利用这种方法去做,发现比例不太方便控制,如果内容再多一些,要写更多的代码。

第二种就是今天分享的,在计算距离时,只需要根据UI图给出来的数字填上去就行,偶尔微调一下。保存出来的效果图也很好看。

项目地址:
https://github.com/keepi/canvasshare

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

推荐阅读更多精彩内容

  • 你运气不好,竟是因为经常叹气?!——崇坛 面相风水 你身边是否有这样的人,总是唉声叹气,抱怨自己运气不好。但是好像...
    美相般若阅读 229评论 0 0
  • 电视开了一下午,也不知道在放个什么,没带眼镜,好像连声音也听不清楚了。窝在沙发上,一手拿着手机,一手往嘴里吃零食。...
    Hey你好啊阅读 202评论 1 1