最近做了一個(gè)問卷類的小程序纳账,其中的結(jié)果頁想讓用戶進(jìn)行朋友圈分享轉(zhuǎn)發(fā)幅聘,網(wǎng)上搜索資料,得出解決思路余寥,用 canvas 將頁面繪制生成圖片领铐,然后保存到手機(jī)相冊,最終效果如下:
在這里我只寫頁面里關(guān)于 canvas 生成圖片并進(jìn)行保存這個(gè)流程的相關(guān)代碼宋舷,并且會(huì)在我踩過的坑那里進(jìn)行具體的講述绪撵。廢話不多說直接上干貨
wxml
<!-- 調(diào)用canvas圖片繪制方法 按鈕 -->
<view class="share" bindtap="share">
<image class="share-img" src="/images/icon-share.png" mode="widthFix" lazy-load="true"></image>
分享助力
</view>
<!-- 黑色透明層 -->
<view hidden="{{hiddenImg}}" class="mask"></view>
<!-- 調(diào)用保存圖片到手機(jī)相冊方法 按鈕 -->
<view class="share-btn" hidden="{{hiddenImg}}" bindtap="save">保存到相冊</view>
<!-- canvas -->
<view class='canvas-box'>
<canvas canvas-id='share' style='width:100vw;height:100vh;' hidden='{{canvasHidden}}'></canvas>
</view>
<!-- canvas 繪制完成后顯示的圖片 -->
<image class="shareimg" src="{{shareImgPath}}" style="height:{{winHeight}}" mode="widthFix" hidden="{{hiddenImg}}"></image>
wxss
.share {
width: 356rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
color: #e73322;
font-size: 32rpx;
border-radius: 37rpx;
background: #fff;
margin: 70rpx auto 0;
}
.share-img {
width: 31rpx;
}
.canvas-box {
position: fixed;
top: 99999px;
left: 0;
width: 100%;
}
.shareimg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.mask {
position: fixed;
z-index: 1;
background: rgba(0, 0, 0, .5);
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.share-btn {
position: fixed;
width: 356rpx;
bottom: 50rpx;
left: 197rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
color: #e73322;
font-size: 32rpx;
border-radius: 37rpx;
background: #fff;
z-index: 999;
}
js
1. 設(shè)置 data 相關(guān)默認(rèn)值
-
canvasHidden: true
設(shè)置使 canvas 隱藏,因?yàn)?canvas 是原生組件祝蝠,擁有最高層級莲兢,如果不隱藏汹来,會(huì)影響頁面正常使用 -
hiddenImg: true
設(shè)置使黑色遮罩、保存按鈕和顯示生成后的圖片隱藏
data: {
canvasHidden: true,
wxappName: "來「 老字號文化影響力 」測試你的知識等級",
hiddenImg: true
},
2. 在 onLoad 方法中準(zhǔn)備需要用到的參數(shù)
這里需要注意一點(diǎn)我踩過的坑改艇,
- 由于小程序的canvas 不能使用網(wǎng)絡(luò)圖片,所以緩存中的頭像不能直接用坟岔,需要使用
wx.downloadFile
方法將頭像路徑存儲為臨時(shí)路徑谒兄,以供 canvas 使用,由于之前不知道這個(gè)社付,所有折騰半天承疲,畫出來的圖片就是沒有頭像 - 這里獲取設(shè)備寬度、高度以及設(shè)備像素比也非常重要鸥咖,用于最后顯示圖片的大小以及canvas畫圖過程中的寬高顯示
onLoad: function (o) {
var that = this;
//讀取緩存燕鸽,獲取微信頭像和昵稱
wx.getStorage({
key: 'user',
success: function (res) {
var nickName = res.data.nickName,
avatarUrl = res.data.avatarUrl;
that.setData({
nickName: nickName,
})
// 由于canvas不能使用網(wǎng)絡(luò)圖片,所以此處進(jìn)行頭像臨時(shí)路徑存儲
wx.downloadFile({
url: avatarUrl,
success: (res) => {
that.setData({
avatarUrl: res.tempFilePath,
})
},
});
}
})
//獲取用戶設(shè)備信息啼辣,屏幕寬度
wx.getSystemInfo({
success: res => {
that.setData({
screenWidth: res.screenWidth,
winHeight: res.windowHeight,
ratio: res.pixelRatio
})
}
})
},
3. 編寫 canvas 繪制方法
- 第一步顯示畫板啊研,配置需要顯示的元素
這里需要注意
unit = that.data.screenWidth / 375
用于使用像素值進(jìn)行繪制后進(jìn)行手機(jī)不同機(jī)型大小的適配,context = wx.createCanvasContext('share')
用于指定要繪制的 canvas鸥拧,其余的參數(shù)都是我項(xiàng)目中需要用到的党远,各位童鞋可以根據(jù)自己的需求進(jìn)行配置
//定義的保存圖片方法
share: function () {
wx.showLoading({
title: '圖片生成中...',
})
var that = this;
//設(shè)置畫板顯示,才能開始繪圖
that.setData({
canvasHidden: false
})
var res = that.data.res.result;
var resImg;
switch (res) {
case 1:
resImg = '/images/sdj.png';
break;
case 2:
resImg = '/images/js.png';
break;
case 3:
resImg = '/images/jr.png';
break;
case 4:
resImg = '/images/gs.png';
break;
case 5:
resImg = '/images/xc.png';
break;
}
var unit = that.data.screenWidth / 375;
var ratio = that.data.ratio;
var screenWidth = that.data.screenWidth;
var winHeight = that.data.winHeight;
var bg = "/images/bg.png"
var avatarUrl = that.data.avatarUrl;
var bgleavel = "/images/bg-leavel.png";
var qrcode = "/images/qrcode.jpg";
var nickName = that.data.nickName;
var context = wx.createCanvasContext('share');
var idnum = that.data.res.id;
var num = idnum.toString();
var length = num.length;
var left;
switch (length) {
case 2:
left = 375 - 208 - length * 26
break;
case 3:
left = 375 - 208 - length * 24
break;
case 4:
left = 375 - 208 - length * 20
break;
case 5:
left = 375 - 208 - length * 19
break;
default:
left = 375 - 208 - length * 18
break;
}
var wxappName = that.data.wxappName;
- 第二步開始繪制將所需元素逐一繪制到畫板上
這里我有踩到的坑富弦,就是圖片繪制完成后沟娱,在手機(jī)上顯示非常模糊,多番查找折騰后腕柜,發(fā)現(xiàn)以下幾個(gè)地方設(shè)置好之后就OK了
width: screenWidth
設(shè)定指定的畫布區(qū)域的寬度
height: winHeight
設(shè)定指定的畫布區(qū)域的高度
destWidth: ratio * screenWidth
設(shè)定輸出圖片寬度
destHeight: ratio * winHeight
設(shè)定輸出圖片高度
quality: 1,
設(shè)定圖片質(zhì)量
destWidth 和 destHeight 需要設(shè)置為width 和height 的 2倍或以上才能讓圖片清晰济似,而現(xiàn)在的智能手機(jī)設(shè)備像素比一般都在2以上,所以這里直接用 ratio 來進(jìn)行設(shè)置
圖片繪制完成且臨時(shí)路徑生成之后盏缤,打開隱藏的遮罩層和保存按鈕以及供用戶瀏覽的生成之后的圖片
// 繪制紅色背景
context.drawImage(bg, 0, 0, that.data.screenWidth, winHeight)
// 繪制頭像
var avatarurl_width = unit * 75; //繪制的頭像寬度
var avatarurl_heigth = unit * 75; //繪制的頭像高度
var avatarurl_x = unit * 150; //繪制的頭像在畫布上的位置
var avatarurl_y = unit * 35; //繪制的頭像在畫布上的位置
context.save();
//先畫個(gè)圓 前兩個(gè)參數(shù)確定了圓心 (x,y) 坐標(biāo) 第三個(gè)參數(shù)是圓的半徑 四參數(shù)是繪圖方向 默認(rèn)是false砰蠢,即順時(shí)針
context.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y, avatarurl_width / 2, 0, Math.PI * 2, false);
context.clip(); //畫好了圓 剪切 原始畫布中剪切任意形狀和尺寸。一旦剪切了某個(gè)區(qū)域蛾找,則所有之后的繪圖都會(huì)被限制在被剪切的區(qū)域內(nèi) 這也是我們要save上下文的原因
context.drawImage(avatarUrl, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth); // 將頭像放到繪制好的圓中
context.restore(); //恢復(fù)之前保存的繪圖上下文狀態(tài) 還可以繼續(xù)繪制.
// 繪制昵稱
context.setFontSize(14)
context.setFillStyle('#fff')
context.setTextAlign('center')
context.fillText(nickName, unit * 187, unit * 135)
// 繪制知識等級背景圖
context.drawImage(bgleavel, unit * 110, unit * 165, unit * 312 / 2, unit * 307 / 2)
context.drawImage(resImg, unit * 160, unit * 180, unit * 110 / 2, unit * 238 / 2)
// 繪制第幾位宣傳者
context.setFontSize(16)
context.setFillStyle('#fff')
context.setTextAlign('right')
context.fillText('你是第', left * unit, unit * 382)
context.setFontSize(24)
context.setFillStyle('#fff')
context.setTextAlign('center')
context.fillText(num, unit * (left + length * 12), unit * 382)
context.setFontSize(16)
context.setFillStyle('#fff')
context.setTextAlign('left')
context.fillText('位老字號文化傳播大使', unit * (left + length * 24), unit * 382)
// 繪制二維碼
context.drawImage(qrcode, unit * 138, unit * 410, unit * 204 / 2, unit * 204 / 2)
// 繪制二維碼下部文字
context.setFontSize(12)
context.setFillStyle("#fff")
context.setTextAlign("center")
context.fillText("長按識別小程序", unit * 187.5, unit * 540)
context.fillText(wxappName, unit * 187.5, unit * 560)
//把畫板內(nèi)容繪制成圖片娩脾,并回調(diào) 畫板圖片路徑
context.draw(false, function () {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: screenWidth,
height: winHeight,
destWidth: ratio * screenWidth,
destHeight: ratio * winHeight,
canvasId: 'share',
quality: 1,
success: function (res) {
that.setData({
shareImgPath: res.tempFilePath,
hiddenImg: false,
pathRes: res
})
if (!res.tempFilePath) {
wx.showModal({
title: '提示',
content: '圖片繪制中,請稍后重試',
showCancel: false
})
}
wx.hideLoading()
}
})
});
},
4. 編寫保存到手機(jī)相冊方法
這里沒什么好說的打毛,就是調(diào)用微信提供的API 進(jìn)行圖片保存柿赊,成功或者失敗之后,進(jìn)行相應(yīng)提示并將一開始在data中設(shè)置的需要默認(rèn)隱藏的元素進(jìn)行隱藏幻枉,然后就OK了碰声,打開手機(jī)相冊就可以看到完成的圖片進(jìn)行朋友圈分享了。
save: function () {
var that = this;
var res = that.data.pathRes;
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
//保存成功失敗之后熬甫,都要隱藏畫板胰挑,否則影響界面顯示。
success: (res) => {
wx.showToast({
title: '保存成功',
icon: 'none',
duration: 1500,
mask: false,
success: function () {
that.setData({
canvasHidden: true,
hiddenImg: true
})
}
});
},
fail: (err) => {
wx.showToast({
title: '保存失敗',
icon: 'none',
duration: 1500,
mask: false,
success: function () {
that.setData({
canvasHidden: true,
hiddenImg: true
})
}
});
}
})
}
最后寫一下用到的API吧,常用的就不寫了主要寫一下我自己平時(shí)不怎么經(jīng)常用到的
-
wx.downloadFile
用于將網(wǎng)絡(luò)圖片生成臨時(shí)路徑瞻颂,這里也有一個(gè)坑豺谈,需要在小程序公眾平臺將騰訊的wx.qlogo.cn
這個(gè)域名設(shè)置為合法域名,否則會(huì)報(bào)錯(cuò)贡这,在之后的繪制中圖片盡量用本地路徑 -
wx.createCanvasContext
其中在畫布中進(jìn)行繪制的時(shí)候如果有什么不明白的茬末,可以到 w3cschool 看看,鏈接是直接跳轉(zhuǎn)到 canvas 繪圖這一節(jié)的 -
wx.canvasToTempFilePath
將畫板內(nèi)容繪制成圖片的方法盖矫,需要注意上面提到的影響手機(jī)圖片清晰度的參數(shù)丽惭,其中所有參數(shù)具體配置以及含義,可以直接到 官方文檔 查看 -
wx.saveImageToPhotosAlbum
保存圖片到本地的API辈双,這個(gè)沒有難點(diǎn)责掏,不多說了
整篇文章看著代碼多,其實(shí)用到的不常見的API也就這幾個(gè)湃望,注意一下文章中我踩過的那幾個(gè)坑换衬,相信你可以開發(fā)出一個(gè)完美的帶小程序碼的用于分享到朋友圈的圖片了。
========================================================================================================================
2020年4月16日 補(bǔ)充
1喜爷、字體加粗
通過文字多次繪制可以模擬字體的加粗
2冗疮、標(biāo)題文字過長加省略號
通過 measureText
方法獲取標(biāo)題長度,與自己實(shí)際展示標(biāo)題長度做判斷檩帐,循環(huán)進(jìn)行截取术幔,直到滿足條件為止
var titleWidth = context.measureText(title).width;
context.fontSize = 16 * unit;
if(titleWidth > (320*unit)){
while (titleWidth > (320*unit)) {
title = title.substring(0, (title.length - 1));
titleWidth = context.measureText(title).width;
// console.log(title,titleWidth,i,title.length,(title.length - 1))
}
title = title + '...';
}