前言
畢設的小程序要實現分享功能,我將分享功能按照不同的頁面分成了三塊。分享的內容都是動態定義的,而且因爲微信只支持圖片分享的模式,所以這裏只能使用後端將分享的內容動態寫進圖片裏,最後上傳到七牛雲,返回一個圖片地址
閱讀器頁面分享
就是在閱讀章節的時候選擇分享,分享出去的圖片應當展示的信息包括:書籍封面、書籍名稱、章節名稱、以及部分章節內容
書籍詳情頁分享
分享出去的圖片應當展示的信息包括:書籍封面、書籍名稱、作者、書籍類型、以及書籍簡介,這塊還在開發中,到時候再補進來
小程序首頁分享
這個功能,我想加入到後臺管理系統中,讓管理員能自定義分享的圖片,思路什麼的還在考慮中。
具體實現
說了那麼多,直接來看怎麼擼代碼吧
關於node自定義生成圖片,有一個很好的npm包,叫做node-canvas,它可以讓你在node中像在瀏覽器中一樣繪製圖像,諸如常見的drawImage、fillText、fillRect這些方法都支持。所以使用node繪製圖像就不成大問題了,但關鍵是怎麼繪製。
安裝node-canvas
# 安裝基礎依賴
# ubuntu
sudo apt-get install libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev build-essential g++
# mac OS
brew install pkg-config cairo libpng jpeg giflib
# 安裝canvas
npm install canvas
安裝出現了問題或者想要知道更多關於node-canvas的信息,可以看看它的github首頁
基礎應用
比如用node-canvas繪製一段文字
import Canvas from 'canvas'
const canvas = new Canvas(300, 120) // 按照微信官方要求,長寬比5:4
const context = canvas.getContext('2d')
ctx.font = '14px "Microsoft YaHei"' // 統一使用微軟雅黑字體
context.fillText('Hellow', 84, 24, 204)
fillText接受四個參數,第一個是需要繪製的文本,第二個和第三個參數是定義繪製的起始位置的x和y座標,第四個參數用來控制繪製文本的最大寬度maxWidth。想要繪製好一張圖片,這些位置信息都是要自己計算的哦!
drawImage的說明
在繪製書籍封面圖片的時候就會使用到drawImage方法,但是node-canvas的drawImage方法在傳遞圖片參數的時候只接收Buffer類型,所以這裏就涉及到如何將網絡圖片(從數據庫中取出的封面圖片地址,例如:)轉換成Buffer類型。
我們使用https的內置包(如果你請求的圖片地址是http的請使用http的內置包)請求封面圖片地址,在拼裝請求數據之後使用Buffer.concat方法將其裝換成Buffer類型。
另外應爲是在koa的router中寫的代碼,所以面對這種異步回調類型的操作,需要使用Promise。
return new Promise((resolve, reject) => {
// 將封面圖片轉成buffer格式,用於canvas繪製圖片
https.get(thisBook.img_url, imgRes => {
let chunks = [] // 用於保存網絡請求不斷加載傳輸的緩衝數據
let size = 0 // 保存緩衝數據的總長度
imgRes.on('data', chunk => {
/**
* 在進行網絡請求時,會不斷接收到數據(數據不是一次性獲取到的)
* node會把接收到的數據片段逐段的保存在緩衝區(Buffer)
* 這些數據片段會形成一個個緩衝對象(即Buffer對象)
* 而Buffer數據的拼接並不能像字符串那樣拼接(因爲一箇中文字符佔三個字節),
* 如果一個數據片段攜帶着一箇中文的兩個字節,下一個數據片段攜帶着最後一個字節,
* 直接字符串拼接會導致亂碼,爲避免亂碼,所以將得到緩衝數據推入到chunks數組中,
* 利用下面的node.js內置的Buffer.concat()方法進行拼接
*/
chunks.push(chunk)
size += chunk.length
})
imgRes.on('end', err => {
if (err) {
ctx.body = { ok: false, msg: '下載書籍封面圖片失敗' }
reject('下載書籍封面圖片失敗')
return
}
const buffer = Buffer.concat(chunks, size)
// 判斷是否是一個buffer對象
if (Buffer.isBuffer(buffer)) {
// 繼續你的canvas繪畫....
} else {
ctx.body = { ok: false, msg: '下載書籍封面圖片失敗' }
reject('下載書籍封面圖片失敗')
return
}
})
})
})
fillText的說明
之前提到過fillText方法可以接受一個名爲maxWidth的參數,一旦繪製的文本超過這個寬度就會自動換行。本來這個設計是很好的,但是實際使用過程中就會發現,自動換行的行高很大(大到影響美觀)。而且通過context.font也沒辦做到控制行高,因爲line-height和font-size在context.font中是同一個參數。
既然這樣,又只能自己想辦法了。行高沒法控制,就只能每次繪製一行文本,然後使用繪製的時候傳入的起始位置的y座標來控制兩行之間的間距。
// 首先對從數據庫中讀取到的文本去掉換行符和製表符這些
const noSpaceContent = thisBook.chapters[0].content.replace(/(\n|\t|\r)/g, '')
// 使用canvas的measureText方法得到一箇中文的寬度,然後用最大寬度除以它得到一行最多可容納的字符數
const oneTextWidth = context.measureText('測').width
const oneLineMaxTextNumber = Math.ceil(204 / oneTextWidth)
// 小說描述最大行數
let maxLineNumber = 4
// 當前行數
let current = 4
while (current > 0) {
let tmpText = noSpaceContent.substring(oneLineMaxTextNumber * (maxLineNumber - current), oneLineMaxTextNumber * (maxLineNumber + 1 - current))
// 最後一行文字顯示省略號
if (current === 1) {
tmpText = tmpText.substring(0, tmpText.length - 2) + '...'
}
context.fillText(tmpText, 84, 62 + (maxLineNumber - current) * 15, 204)
current --
}
圖片上傳七牛雲
我這裏使用的qn的包,大家去官網看看應該就知道怎麼用。爲了防止自己忘記,這裏貼下代碼。
首先將配置寫到config.js裏
const config = {
accessKey: 'xxx', //七牛賬號的key值,https://portal.qiniu.com/user/key
secretKey: 'xxx',
bucket: 'upload', //空間名稱
isUseHttps: true, //配置使用https,並且對應到正確的區域,詳情請查考https://github.com/gpake/qiniu-wxapp-sdk/blob/master/README.md
}
module.exports = config;
然後初始化七牛配置
import config from '../config'
import qn from 'qn'
// qiniu上傳設置
const client = qn.create({
accessKey: config.accessKey,
secretKey: config.secretKey,
bucket: 'upload',
origin: 'https://fs.andylistudio.com',
});
七牛雲允許直接上傳本地圖片,也可以直接是圖片的buffer對象,我們使用node-canvas提供的toBuffer方法直接將我們繪製的canvas轉成buffer對象然後上傳
// 上傳圖片到七牛雲
client.upload(canvas.toBuffer(), {key: 'mbook/share/' + uuid.v1() + '.png' }, function (err, result) {
if (err) {
ctx.body = { ok: false, msg: '分享圖片導出失敗' }
reject('分享圖片導出失敗')
return
}
ctx.body = { ok: true, msg: '分享圖片導出成功', url: result.url }
resolve(next())
})
效果圖
是不是感覺還可以,哈哈~,完整代碼請參考這裏
結束
原文請查看 https://andyliwr.github.io/2018/04/23/node_canvas_usage/