背景
在一个完整带用户交互的小程序项目开发中,总会遇到分享这个功能,转发给好友用通用的api方法,分享朋友圈总是有各种各样的招式,一般的交互方案是生成一个带二维码的图,二维码有时候也分带参数和默认的。分享一个前段时间开发的生成分享图的功能,我当时的业务二维码是带参数的,为了识别能定位到固定产品页(生成带参数二维码是用微信云调用提供的方法处理,点此看帖
分享图用canvas画布开发,所以对wx封装的canvas相关api要有一定了解
开发步骤
新建一个share组件文件包,并开发业务逻辑和样式编写含小程序规范的几个文件js/wxml/wxss/json
开发代码,在需要引用页面的对应配置文件中加入组件配置
在引用的wxml中加入组件代码和传参,js文件写参数的交互
效果图
代码概览
1.share / share.wxml 【参考效果图 步骤2/3
备注:
1.第一模块 步骤2的分享引导层,点击分享票圈,出来第二模块弹框
2.第二模块 步骤3效果图,share-load.gif是一个加载中动画的gif;close.png是关闭图标
- <!--分享弹窗-->
- <view class="share-wrap" bindtap="toClose">
- <!-- 分享的引导层 分 转发票圈或好友 -->
- <view class="share-mod" catchtap="doNothing">
- <view class="share-hd">
- <text class="fl">分享</text>
- <image class="share-close" catchtap="toClose" src="../../images/icons/close.png"></image>
- </view>
- <view class="share-guide">
- <view class="share-wx">
- <button class="share-btn" open-type="share"></button>
- <image src="../../images/icons/wx_friend.png"></image>
- <text>分享给好友</text>
- </view>
- <view class="share-line"></view>
- <view class="share-wx" catchtap="toShowShareImg">
- <image src="../../images/icons/wx_quan.png"></image>
- <text>生成分享海报</text>
- </view>
- </view>
- </view>
- <!-- 点击引导层的 转发票圈 触发的生成画布图弹层 -->
- <view class="share-mod" hidden="{{!showShareImg}}" catchtap="doNothing">
- <view class="share-hd">
- <text class="fl">保存到相册</text>
- <image class="share-close" catchtap="toCloseShareImg" src="../../images/icons/close.png"></image>
- </view>
- <view class="share-quan">
- <image wx:if="{{imgSrc!=''}}" class="share-img" src="{{imgSrc}}"></image>
- <view wx:else>
- <image class="share-img-load" src="../../images/icons/share-load.gif"></image>
- <text class="share-load-text">{{loadText}}</text>
- </view>
- <view class="save-btn" catchtap="saveImg">保存图片</view>
- <view class="save-tip">保存图片到手机相册后,就可以分享至您的圈子啦</view>
- </view>
- </view>
- </view>
2.share/ share.json
- {
- "component": true
- }
3.share/ share.js
- /* 使用说明↓↓↓
- 1.需要引用的页面json配置文件新增如下配置项
- "usingComponents": {
- "share-win": "/component/share/share"
- }
- 2.页面wxml文件使用如下 shareInfo格式说明见下文 该组件是否渲染根据该对象是否有具体数据
- <canvas canvas-id="shareCanvas" style="position:fixed;top:0;left:999rpx;width:1000px;height:750px;"></canvas>
- <share-win share-info="{{shareInfo}}" bindcloseshare="closeShareWin"></share-win>
- 3.页面对应js需要定义一个closeshare事件[由组件里的toClose触发] 内部主要是将shareInfo参数置空
- 4.shareInfo格式
- */
- Component({
- properties: {
- shareInfo: {
- type: Object,
- value: {},
- }
- },
- data: {
- imgSrc:'',
- showShareImg:false,
- hasDownload:false,
- loadText:'分享图绘制准备中...'
- },
- ready:function(){
- },
- methods: {
- //点击浮层区域关闭弹窗
- toClose:function(){
- this.toCloseShareImg();
- this.triggerEvent("closeshare")
- },
- toCloseShareImg:function(){
- this.setData({
- showShareImg:false
- })
- if(this.data.hasDownload){
- this.triggerEvent("closeshare")
- }
- },
- toShowShareImg:function(){
- this.setData({
- showShareImg:true
- })
- this.renderShareImg();
- },
- doNothing:function(){
- return false;
- },
- setLoadText:function(txt){
- this.setData({
- loadText:txt
- })
- },
- //渲染分享图
- renderShareImg:function(){
- //1000x750
- const _this = this;
- const _obj = _this.data.shareInfo;
- //默认题图
- let promise1 = new Promise(function (resolve, reject) {
- if(_obj.cover==undefined || _obj.cover==''){
- _obj.cover = '../../images/share_default.jpg'
- resolve({path:_obj.cover});
- }else{
- _obj.cover = "https://"+_obj.cover.split('//')[1]
- wx.getImageInfo({
- src: _obj.cover,
- success: function (res) {
- resolve(res);
- }, fail: function (error) {
- console.log(error);
- _obj.cover = '../../images/share_default.jpg'
- resolve({path:_obj.cover});
- }
- })
- }
- });
- //小程序码
- let promise2 = new Promise(function (resolve, reject) {
- wx.cloud.callFunction({
- name: 'openapi',
- data: {
- action:'getWXACodeUnlimit',
- page: 'pages/detail/detail',
- width: 220,
- scene: _obj.id+"_"+_obj.goodsId,
- },
- success: res => {
- wx.getImageInfo({
- src: res.result[0].tempFileURL,
- success: function (suc) {
- resolve(suc);
- }, fail: function (error) {
- resolve({path:"../../images/qrcode.jpg"})
- console.log(error)
- }
- })
- },
- fail: error => {
- console.log(JSON.stringify(error))
- resolve({path:"../../images/qrcode.jpg"})
- }
- });
- });
- //加载所有完图片后绘制画布
- Promise.all(
- [promise1,promise2]
- ).then(res => {
- //绘制头图的圆角效果
- const ctx = wx.createCanvasContext('shareCanvas')
- ctx.setFillStyle('#ffffff');
- ctx.fillRect(0, 0, 750, 1125);
- //绘制题图
- _this.setLoadText("绘制商品图...")
- ctx.drawImage(res[0].path, 25, 25, 700, 700)
- // ...删除了部分 绘制逻辑代码...
- //绘制小程序码
- _this.setLoadText("绘制小程序码...")
- ctx.drawImage(res[1].path, 35, 874, 228, 228);
- //画布绘制完成转图片,将地址赋值给图片
- _this.setLoadText("分享图生成中...")
- ctx.draw();
- setTimeout(function () {
- wx.canvasToTempFilePath({
- width: 750,
- height: 1125,
- destWidth: 750,
- destHeight: 1125,
- quality: 1,
- canvasId: 'shareCanvas',
- success: function (res) {
- // console.log("canvasToTempFilePath success:"+res.tempFilePath);
- wx.hideLoading({})
- _this.setData({
- imgSrc: res.tempFilePath,
- shareShow: true
- })
- },
- fail: function (res) {
- }
- })
- }, 200)
- })
- },
- //保存图片
- saveImg:function(){
- //下载文件
- const _this = this;
- if(_this.data.imgSrc==''){
- wx.showToast({
- title:"分享图还在生成中...",
- icon: 'none',
- duration:3000
- })
- return false;
- }
- wx.saveImageToPhotosAlbum({
- filePath: _this.data.imgSrc,
- success(res) {
- wx.showToast({
- title:"已保存至相册,可以分享啦",
- icon: 'none',
- duration:3000
- })
- _this.setData({
- hasDownload:true
- })
- }
- })
- },
- //图片按比例居中裁剪
- calClipImg(oW,oH,mW,mH){
- var oR = parseFloat(oW/oH).toFixed(5);
- var mR = parseFloat(mW/mH).toFixed(5);
- if(oR == mR){
- return [0,0,oW,oH]
- }else if(oR > mR){
- var ratio = parseFloat(mH/oH).toFixed(5);
- return [((oW*ratio-mW)/2)/ratio,0,mW/ratio,mH/ratio];
- }else{
- var ratio = mW/oW;
- return [0,((oH*ratio-mH)/2)/ratio,mW/ratio,mH/ratio];
- }
- },
- //绘制圆角
- roundRect(x, y, w, h, r,ctx){
- var min_size = Math.min(w, h);
- if (r > min_size / 2) r = min_size / 2;
- // 开始绘制
- ctx.beginPath();
- ctx.moveTo(x + r, y);
- ctx.arcTo(x + w, y, x + w, y + h, r);
- ctx.arcTo(x + w, y + h, x, y + h, r);
- ctx.arcTo(x, y + h, x, y, r);
- ctx.arcTo(x, y, x + w, y, r);
- ctx.closePath();
- },
- //绘制文本方法
- drawText(str,ctx,initX,initY,lineHeight,minusW,maxLine){
- var curLine = 1;
- var lineWidth = 0;
- var canvasWidth = 750;
- var lastSubStrIndex= 0;
- var d = 0;
- for(var i=0;i<str.length;i++){
- lineWidth += ctx.measureText(str[i]).width;
- //判断当前文字行是否超过一行 [减minusW,防止边界溢出]
- if((d==0 && lineWidth>canvasWidth-minusW)||(d>0 && ((lineWidth>=canvasWidth-minusW) || ((lineWidth+ctx.measureText(str[i+1]).width)>canvasWidth-minusW)))){
- d++;
- ctx.fillText(str.substring(lastSubStrIndex,i),initX,initY);
- initY+=lineHeight;
- lineWidth=0;
- lastSubStrIndex=i;
- curLine = curLine+1;
- if(maxLine!=-1 && curLine>maxLine)break; //最多绘制六行
- }
- //最后一个字的时候 绘制一行
- if(i==str.length-1){
- ctx.fillText(str.substring(lastSubStrIndex,i+1),initX,initY);
- }
- }
- }
- }
- })
4.share.wxss
- .share-wrap{
- position:fixed;
- top:0;
- width:750rpx;
- height:100%;
- background:rgba(0,0,0,.4);
- overflow: hidden;
- z-index:1001;
- }
- .share-mod{
- position:fixed;
- bottom:0;
- width:100%;
- background:#fff;
- z-index:1001;
- overflow: hidden;
- }
- .share-mod .share-hd{
- padding-left:20rpx;
- height:80rpx;
- line-height:80rpx;
- background:#efefef;
- color:#666;
- font-size:32rpx;
- }
- .share-mod .share-close{
- float:right;
- margin:15rpx 20rpx;
- height:50rpx;
- width:50rpx;
- }
- .share-guide{
- padding:35rpx;
- width:680rpx;
- height:180rpx;
- }
- .share-guide .share-wx,
- .share-guide .share-line{
- float:left;
- }
- .share-guide .share-line{
- margin-top:60rpx;
- height:160rpx;
- width:1rpx;
- color:#cdcdcd;
- }
- .share-guide .share-wx{
- width:339rpx;
- height:180rpx;
- text-align:center;
- font-size:24rpx;
- }
- .share-guide .share-wx image{
- display: block;
- margin:20rpx auto;
- padding:10rpx;
- width:64rpx;
- height:64rpx;
- border-radius:43rpx;
- border:1rpx solid #dedede;
- }
- .share-guide .share-btn{
- position: absolute;
- margin:0;
- padding:0;
- bottom:40rpx;
- left:35rpx;
- width:340rpx;
- height:180rpx;
- background:none;
- }
- .share-guide .share-btn:after{
- border:none;
- }
- .share-quan{
- margin:20rpx;
- overflow: hidden;
- }
- .share-quan .share-img{
- display:block;
- margin:10rpx auto 28rpx;
- height:600rpx;
- width:400rpx;
- border-radius:8rpx;
- box-shadow:0 0 10rpx #cdcdcd;
- }
- .share-quan .share-img-load{
- display:block;
- margin:260rpx auto 20rpx;
- height:80rpx;
- width:80rpx;
- }
- .share-quan .share-load-text{
- margin:0 auto 220rpx;
- display:block;
- widows:100%;
- text-align:center;
- font-size:28rpx;
- color:#b7b7b7;
- }
- .share-quan .save-btn{
- width:710rpx;
- height:80rpx;
- line-height:80rpx;
- color:#fff;
- text-align:center;
- letter-spacing:4rpx;
- background-color:#e2633f;
- border-radius:6rpx;
- font-size:34rpx;
- }
- .share-quan .save-tip{
- margin:18rpx;
- text-align:center;
- font-size:24rpx;
- }
↓组件开发已经完成,接下去是组件的使用↓
1.demo.json 备注:usingComponents加入对应组件配置即可
- {
- "navigationBarBackgroundColor": "#ffffff",
- "navigationBarTextStyle": "black",
- "navigationBarTitleText": "xxx",
- "usingComponents": {
- "share-win": "/component/share/share"
- }
- }
2.demo.wxml 备注:点击分享按钮的时候 showShareWin值改变,shareInfo根据渲染需求赋值
- <view>
- <!-- S 其他业务代码 -->
- <!-- E 其他业务代码 -->
- <!-- 分享 -->
- <canvas canvas-id="shareCanvas" style="position:fixed;top:0;left:999rpx;width:750px;height:1125px;"></canvas>
- <share-win wx:if="{{showShareWin}}" share-info="{{shareInfo}}" bindcloseshare="closeShareWin"></share-win>
- </view>
3.demo.js 备注:删除了其他业务代码,仅剩和分享的交互,便于阅读。shareInfo数据在load时就塞进去了,下面没有放出来~
- Page({
- data: {
- showTop:false
- },
- //点击右侧悬浮的分享按钮
- doShare:function(){
- this.setData({
- showShareWin:true
- })
- },
- //触发关闭分享弹框
- closeShareWin:function(){
- this.setData({
- showShareWin:false
- })
- },
- })
其他说明: 步骤4为最终生成效果图,微信识别二维码就可定位到具体业务页~