
微信小程序canvas生成圖片
在生成圖片時(shí)遇到以下問(wèn)題:
- canvas繪制文字不能換行,如果文字的長(zhǎng)度大于你所定的寬度的話,文字會(huì)超出你所定寬度,或者文字會(huì)擠壓變形
解決方法:
(1)截取一定的文字長(zhǎng)度,多余的文字省略
(2)通過(guò)技術(shù)來(lái)使文字換行,我們的需求就是 文字換行。
- 微信小程序canvas畫(huà)圖必須用本地的圖片路徑,所以要把圖片下載下來(lái)存為臨時(shí)路徑再繪制圖片。微信提供了個(gè)api可用:wx.downloadFile(OBJECT)和wx.getImageInfo(OBJECT)。都需先配置download域名才能生效。
這就涉及到一個(gè)問(wèn)題:封面圖是從自己的服務(wù)器請(qǐng)求的,所以可以下載并得到臨時(shí)路徑,但是用戶的頭像的域名地址是騰訊的,而微信公眾平臺(tái)的域名配置是不可能配置別人的域名的,所以是獲取不到用戶頭像的臨時(shí)路徑,解決辦法就是繪制時(shí)小程序端向服務(wù)器請(qǐng)求用戶頭像,服務(wù)器端向騰訊請(qǐng)求把用戶頭像下載下來(lái)臨時(shí)存儲(chǔ),然后發(fā)給小程序端。
index.wxml文件
<view catchtap='shareFrends'> // 點(diǎn)擊生成圖片事件
<view><image src='/imgs/topic-share-friends.png' mode='aspectFill'></image></view>
<view class='share-text'>朋友圈</view>
</view>
<view class='share-modal-bg' wx:if='{{showModal}}' bindtap='hideModal'>
<view class='canvas-wrap' catchlongpress='saveImg' catchtap='0'> //長(zhǎng)按保存圖片事件
<view><image src='{{shareImg}}' class='share-img'></image></view> // 顯示出生成圖片的容器
<view class='share-img-tips'>長(zhǎng)按圖片保存至相冊(cè),快去分享吧!</view>
</view>
</view>
//畫(huà)布
<canvas style="width: 286px;height: 415px;background:red;position: fixed;top: -10000px;" canvas-id="shareFrends"></canvas>
ps: canvas 大小一般是生成圖片的兩倍 而且canvas畫(huà)出來(lái)的圖有點(diǎn)丑,所以還是讓canvas看不到比較好
index.js文件
//點(diǎn)擊 按鈕開(kāi)始獲取畫(huà)圖所需的圖片信息 等
shareFrends() {
wx.showLoading({
title: '圖片生成中',
})
let that = this;
const detail = this.data.detail; // 海報(bào)圖的的一些信息,從后臺(tái)請(qǐng)求的數(shù)據(jù)
let avatar;// 頭像
const post_cover = detail.post_cover || '../../imgs/cars.png'; //沒(méi)有封面圖時(shí)設(shè)置默認(rèn)圖片
wx.$.fetch('api/setLocalAvatar', { //請(qǐng)求頭像的地址
method: 'post',
hideLoading: true,
showLoading: true,
data: {
api_token: wx.getStorageSync('token'),
member_id: detail.member.member_id,
}
}).then(res => {
avatar = res.data.url;
wx.getImageInfo({ // 根據(jù)頭像地址下載頭像并存為臨時(shí)路徑
src: avatar,
success: res => {
that.setData({
avatar: res.path
})
wx.getImageInfo({ // 封面圖
src: post_cover,
success: res => {
//如果是本地圖片的話此api返回的路徑有問(wèn)題,所以需要判斷是否是網(wǎng)絡(luò)圖片
if (!/^https/.test(post_cover)) {
res.path = post_cover
};
that.setData({
cover: res.path,
coverWidth: res.width, //封面圖的寬
coverHeight: res.height //封面圖的高
})
wx.$.fetch('api/getQrCode', { //獲取二維碼圖片
method: 'post',
hideLoading: true,
showLoading: true,
data: {
path: 'pages/topicdetail/index?id=' + this.data.id,
post_id: this.data.id,
width: 340
}
}).then(res => {
wx.getImageInfo({
src: res.data.path,
success: res => {
that.setData({
erweima: res.path
})
that.createdCode() // 根據(jù)以上信息開(kāi)始畫(huà)圖
//canvas畫(huà)圖需要時(shí)間而且還是異步的,所以加了個(gè)定時(shí)器
setTimeout(() => {
// 將生成的canvas圖片,轉(zhuǎn)為真實(shí)圖片
wx.canvasToTempFilePath({
x: 0,
y: 0,
canvasId: 'shareFrends',
success: function (res) {
let shareImg = res.tempFilePath;
that.setData({
shareImg: shareImg,
showModal: true,
showShareModal: false
}
wx.hideLoading()
},
fail: function (res) {
}
})
}, 500)
}
})
})
},
fail(err) {
console.log(err)
}
})
}
})
})
},
//開(kāi)始繪圖
createdCode() {
let that = this;
const detail = this.data.detail;
const ctx = wx.createCanvasContext('shareFrends'); //繪圖上下文
const date = new Date;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const time = year + '.' + month + '.' + day; // 繪圖的時(shí)間
const name = detail.post_title; //繪圖的標(biāo)題 需要處理?yè)Q行
const coverWidth = this.data.coverWidth; // 封面圖的寬度 裁剪需要
const coverHeight = this.data.coverHeight; // 封面圖的寬度 裁剪需要
let pichName = detail.member.name; //用戶昵稱
const explain = 'Hi,我想分享給你一條資訊猛料!';
// 截取昵稱 超出省略。。。
if (pichName.length > 16) { //用戶昵稱顯示一行 截取
pichName = pichName.slice(0, 9) + '...'
};
// 繪制logo
ctx.save()
// canvas 背景顏色設(shè)置不成功,只好用白色背景圖
ctx.drawImage('/imgs/canvas-bg.jpg', 0, 0, 286, 480);
//繪制logo
ctx.drawImage('/imgs/share-logo.png', 140, 25, 128, 34);
// 繪制時(shí)間
ctx.setFontSize(12);
ctx.setTextAlign('right');
const metrics = ctx.measureText(time).width; //時(shí)間文字的所占寬度
ctx.fillText(time, 266, 78, metrics + 5);
// 繪制 封面圖并裁剪(這里圖片確定是按100%寬度,同時(shí)高度按比例截取,否則圖片將會(huì)變形)
// 裁剪位置 圖片上的坐標(biāo) x:0 ,y: (coverHeight - 129 * coverWidth / 252) / 2
// 圖片比例 255:129 圖片寬度按原圖寬度即coverWidth 圖片高度按129*coverWidth/252
// 開(kāi)始繪圖的位置 16, 94
// 裁剪框的大小,即需要圖片的大小 252, 129
ctx.drawImage(this.data.cover, 0, (coverHeight - 129 * coverWidth / 252) / 2, coverWidth, 129*coverWidth/252 , 16, 94, 252, 129);
// 繪制標(biāo)題
ctx.font = 'normal bold 14px sans-serif';
ctx.setTextAlign('left');
const nameWidth = ctx.measureText(name).width;
// 標(biāo)題換行 16是自已定的,為字體的高度
this.wordsWrap(ctx, name, nameWidth, 252, 16, 252, 16);
// 計(jì)算標(biāo)題所占高度
const titleHight = Math.ceil(nameWidth / 252) * 16;
// 繪制頭像和昵稱
ctx.arc(36, 268 + titleHight, 20, 0, 2 * Math.PI);
ctx.clip()
ctx.drawImage(this.data.avatar, 16, 248 + titleHight, 40, 44);
ctx.restore();
ctx.font = 'normal normal 14px sans-serif';
ctx.setTextAlign('left');
ctx.setFillStyle('#bbbbbb')
ctx.fillText(pichName, 70, 270 + titleHight);
// 二維碼描述 及圖片
ctx.setStrokeStyle('#eeeeee');
ctx.strokeRect(16, 300 + titleHight, 252, 80);
ctx.setFillStyle('#333333')
ctx.fillText(explain.slice(0, 11), 30, 336 + titleHight); // 描述截取換行
ctx.fillText(explain.slice(11), 30, 358 + titleHight);
ctx.drawImage(this.data.erweima, 194, 308 + titleHight, 44, 44);
ctx.setFontSize(10);
ctx.setFillStyle('#bbbbbb')
ctx.fillText('長(zhǎng)按掃碼查看詳情', 175, 370 + titleHight);
// ctx.setFillStyle('#f7f7f7')
// ctx.fillRect(0, 400 + titleHight, 286, 48)
// ctx.setFontSize(14);
// ctx.setFillStyle('#bbbbbb')
// ctx.setTextAlign('center');
// ctx.fillText('長(zhǎng)按圖片保存至相冊(cè),并分享至朋友圈!', 142, 430 + titleHight);
ctx.draw()
},
//文字換行處理
// canvas 標(biāo)題超出換行處理
wordsWrap(ctx, name, nameWidth, maxWidth, startX, srartY, wordsHight) {
let lineWidth = 0;
let lastSubStrIndex = 0;
for (let i = 0; i < name.length; i++) {
lineWidth += ctx.measureText(name[i]).width;
if (lineWidth > maxWidth) {
ctx.fillText(name.substring(lastSubStrIndex, i), startX, srartY);
srartY += wordsHight;
lineWidth = 0;
lastSubStrIndex = i;
}
if (i == name.length - 1) {
ctx.fillText(name.substring(lastSubStrIndex, i + 1), startX, srartY);
}
}
},
// 長(zhǎng)按保存事件
saveImg() {
let that = this;
// 獲取用戶是否開(kāi)啟用戶授權(quán)相冊(cè)
wx.getSetting({
success(res) {
// 如果沒(méi)有則獲取授權(quán)
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
wx.saveImageToPhotosAlbum({
filePath: that.data.shareImg,
success() {
wx.showToast({
title: '保存成功'
})
},
fail() {
wx.showToast({
title: '保存失敗',
icon: 'none'
})
}
})
},
fail() {
// 如果用戶拒絕過(guò)或沒(méi)有授權(quán),則再次打開(kāi)授權(quán)窗口
//(ps:微信api又改了現(xiàn)在只能通過(guò)button才能打開(kāi)授權(quán)設(shè)置,以前通過(guò)openSet就可打開(kāi),下面有打開(kāi)授權(quán)的button彈窗代碼)
that.setData({
openSet: true
})
}
})
} else {
// 有則直接保存
wx.saveImageToPhotosAlbum({
filePath: that.data.shareImg,
success() {
wx.showToast({
title: '保存成功'
})
},
fail() {
wx.showToast({
title: '保存失敗',
icon: 'none'
})
}
})
}
}
})
},
// 授權(quán)彈窗
// wxml
<view class='open-seting-bg' wx:if='{{openSet}}' catchtap='cancleSet'>
<view class='open-set-inner'>
<view class='set-title'>是否打開(kāi)授權(quán)設(shè)置?</view>
<view class='btn-openset'>
<view catchtap='cancleSet'>取消</view>
<view>
<button open-type='openSetting' class='button-style' catchtap='cancleSet'>確定</button>
</view>
</view>
</view>
</view>
//wxss
.open-seting-bg {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
}
.btn-openset {
display: flex;
justify-content: space-around;
font-size: 30rpx;
margin-top: 60rpx;
}
.set-title {
font-size: 30rpx;
margin-top: 40rpx;
}
.open-set-inner {
width: 400rpx;
height: 220rpx;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
background: #ffffff;
padding: 30rpx;
}
.btn-openset > view:nth-child(1) {
color: #919191;
}
.button-style {
width: 100%;
height: 100%;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0 0;
font-size: 30rpx;
line-height: 0;
color: red;
}
button::after {
border: 0;
}
//js
// 授權(quán)
cancleSet() {
this.setData({
openSet: false
})
},
僅供參考,其中代碼最好提取并封裝起來(lái)