前言
- ?? 一下這幾天寫小程序频丘,使用 canvas 動態(tài)生成海報郁稍,下載分享的效果
- 使用 taro 做小程序辜梳,因為可以用 React粱甫,使用了最新的 React hooks 的方式(好像也不新了)
-
剛開始寫感覺很累,因為沒有搞過小程序作瞄,并且也不會用 canvas茶宵,我只能寫一點,編譯一下看效果??宗挥,走了很多彎路乌庶,花了很多心思,結(jié)果出來后契耿,感覺自己太笨了(學(xué)習(xí)能力還有待提高)
- 按鈕樣式還沒有改瞒大,等成品出來改一下 (打個小廣告)
下載網(wǎng)絡(luò)圖片,處理多張圖片
- 因為要動態(tài)畫頭像搪桂,需要把網(wǎng)絡(luò)圖片地址透敌,下載后畫到 canvas 中
封裝一個處理多張圖片的 promise
- 因為不想使用 getImageInfo 一層層嵌套,圖片很多的話,豈不是亂的像??一樣酗电。
// 處理多張網(wǎng)絡(luò)圖片
const processMultipleImages = (url) => {
return new Promise((resolve, reject) => {
Taro.getImageInfo({
src: url,
success: (res) => {
resolve(res)
},
fail: () => {
Taro.showToast({
title: '下載失敗!'
})
}
})
})
}
處理所有圖片
- 使用 promise.all 轉(zhuǎn)換所有的圖片地址
// 獲取基本信息
useEffect(() => {
// 獲取所有的網(wǎng)絡(luò)圖片
Promise.all(
imageAry.map(img => processMultipleImages(img))
).then(images => {
const imgAll = images.map(i => i.path)
SetUrl(imgAll)
})
}, [])
畫主體
獲取屏幕信息
- 主要是獲取寬度和高度魄藕,動態(tài)給 canvas 設(shè)置寬高,已經(jīng) canvas 間距顾瞻,rate 表示轉(zhuǎn)換的倍數(shù)(根據(jù)設(shè)計稿的標準寬高)泼疑,目前是用的這種方式,網(wǎng)上看了很多都沒有轉(zhuǎn)換荷荤,難道不用根據(jù)設(shè)備適配嗎退渗?還是說我用的方式不對,業(yè)務(wù)催得緊沒有仔細研究蕴纳,如果你知道的話会油,歡迎交流~
const d = Taro.getSystemInfoSync()
const w = d.windowWidth * 0.85
const h = (w / 0.75).toFixed(2)
const rate = (d.windowWidth / 375).toFixed(2)
SetWidth(w)
SetHeight(h)
SetRate(rate)
- 寬度和高度
return (
<View className="share-user-container">
<Canvas style={{width: `${width}px`, height: `${height}px`}} canvasId="shareuser" id="shareuser" className="canvas-wrapper"></Canvas>
<Button onClick={onClickSaveImage}>保存到相冊</Button>
</View>
)
圓形頭像
const ctx = Taro.createCanvasContext('shareuser');
// 畫內(nèi)圓 并 填充頭像
ctx.beginPath()
const x = 56 * rate
const y = 74 * rate + 64 * rate
ctx.arc(x, y, 64 * rate, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(imageUrl[0], 0 * rate, 74 * rate, 128 * rate, 128 * rate)
ctx.closePath()
ctx.restore()
// 繪制文本
drawText(ctx, '#1D1E1F', '來自xxx 的脫單團', 66 * rate, 24 * rate, 12)
ctx.save()
這里頭像可能會壓縮,導(dǎo)致很丑
drawText 繪制文字方法
// 繪制文本
const drawText = (ctx, color, text, x, y, font = 16) => {
ctx.setFontSize(font)
ctx.setFillStyle(color)
ctx.setTextAlign('left')
ctx.fillText(text, x, y)
ctx.stroke()
ctx.closePath()
}
畫圓
// 畫外圓
ctx.beginPath()
ctx.arc(56 * rate, 140 * rate, 80 * rate, 0, 2*Math.PI)
ctx.lineWidth = 16 * rate
ctx.clip()
ctx.strokeStyle = "#FFE04A";
ctx.stroke()
ctx.closePath()
二維碼
- 這里的二維碼目前是寫死的古毛,但業(yè)務(wù)需要動態(tài)生成翻翩,等我做了加上待更新...
- 更新了這里,可以去看一下 小程序動態(tài)生成二維碼
const size14 = 14 * rate
// 繪制二維碼
ctx.drawImage(imageUrl[1], 210 * rate, 120 * rate, 86 * rate, 86 * rate)
drawText(ctx, '#1D1E1F', '掃碼認識Ta', 216 * rate, 220 * rate, size14)
最后生成圖片
- 這里微信的官方說稻薇,放到 ctx.draw() 的 callback 里面嫂冻,但是沒有執(zhí)行,不知道為啥塞椎,這里就先使用了 setTimeout 模擬異步生成
setTimeout(() => {
Taro.canvasToTempFilePath({
x:0,
y:0,
width,
height,
canvasId: 'shareuser',
success: (result) => {
SetImage(result.tempFilePath)
},
fail: (err) => {
Taro.showToast('圖片生成失斀胺隆!')
}
})
}, 600)
保存到相冊中案狠,需要用戶授權(quán)
- 調(diào)用授權(quán)服傍,需要兼容如果用戶點擊取消的操作
// 保存到相冊
const onClickSaveImage = () => {
Taro.getSetting({
success(res) {
// 如果沒有授權(quán)過,則要獲取授權(quán)
if (!res.authSetting['scope.writePhotosAlbum']) {
Taro.authorize({
scope: 'scope.writePhotosAlbum',
success() {
// 保存圖片
savePictureSystem()
},
fail() { // 用戶拒絕
Taro.showModal({
title: '授權(quán)',
content: '您拒絕了授權(quán)請求骂铁,是否要手動開啟吹零?',
success: function (res) {
if (res.confirm) {
Taro.openSetting({
success: function (res) {
console.log(res.authSetting)
res.authSetting = {
"scope.userInfo": true,
"scope.userLocation": true
}
}
})
} else if (res.cancel) {
Taro.showToast({
title: '保存失敗拉庵!',
icon: 'close',
duration: 2000
})
}
}
})
}
})
} else { // 如果已經(jīng)授權(quán)過灿椅,可以直接保存
savePictureSystem()
}
}
})
}
// 把圖片保存到系統(tǒng)中
const savePictureSystem = () => {
Taro.saveImageToPhotosAlbum({
filePath: saveImage,
success(res) {
Taro.showToast({
title: '保存成功!'
})
},
fail() {
Taro.showToast({
title: '保存失敗!',
icon: 'close',
duration: 2000
})
}
})
}
所有代碼
// 獲取基本信息
useEffect(() => {
const d = Taro.getSystemInfoSync()
const w = d.windowWidth * 0.85
const h = (w / 0.75).toFixed(2)
const rate = (d.windowWidth / 375).toFixed(2)
SetWidth(w)
SetHeight(h)
SetRate(rate)
// 獲取所有的網(wǎng)絡(luò)圖片
Promise.all(
imageAry.map(img => processMultipleImages(img))
).then(images => {
const imgAll = images.map(i => i.path)
SetUrl(imgAll)
})
}, [])
useEffect(() => {
if (imageUrl.length > 0) {
drawContent()
}
}, [imageUrl])
// 畫主體內(nèi)容
const drawContent = () => {
const ctx = Taro.createCanvasContext('shareuser');
const cx = 5 * rate + 20 * rate
const cy = 12 * rate + 20 * rate
// 背景顏色
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, width, height)
ctx.save()
// 頭像
ctx.beginPath()
ctx.arc(cx, cy, 20 * rate, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(imageUrl[0], 6 * rate, 12 * rate, 40 * rate, 40 * rate)
ctx.restore()
drawText(ctx, '#1D1E1F', '來自xxx 的脫單團', 66 * rate, 24 * rate, 12)
ctx.save()
// 畫外圓
ctx.beginPath()
ctx.arc(56 * rate, 140 * rate, 80 * rate, 0, 2*Math.PI)
ctx.lineWidth = 16 * rate
ctx.clip()
ctx.strokeStyle = "#FFE04A";
ctx.stroke()
ctx.closePath()
// 畫內(nèi)圓 并 填充頭像
ctx.beginPath()
const x = 56 * rate
const y = 74 * rate + 64 * rate
ctx.arc(x, y, 64 * rate, 0, 2 * Math.PI)
ctx.clip()
ctx.drawImage(imageUrl[0], 0 * rate, 74 * rate, 128 * rate, 128 * rate)
ctx.closePath()
ctx.restore()
// 繪制圓圈裝飾
ctx.beginPath()
ctx.arc(250 * rate, 47 * rate, 18 * rate, 0, 2*Math.PI)
ctx.lineWidth = 4 * rate
ctx.strokeStyle = "#FFE04A";
ctx.stroke()
ctx.closePath()
ctx.beginPath()
ctx.arc(200 * rate, 80 * rate, 9 * rate, 0, 2*Math.PI)
ctx.lineWidth = 5 * rate
ctx.strokeStyle = "#FFE04A";
ctx.stroke()
ctx.closePath()
ctx.beginPath()
ctx.arc(280 * rate, 98 * rate, 14 * rate, 0, 2*Math.PI)
ctx.lineWidth = 10 * rate
ctx.strokeStyle = "#FFE04A";
ctx.stroke()
ctx.closePath()
drawFillCircle(ctx, 238, 83, 9)
drawFillCircle(ctx, 220, 106, 8)
drawFillCircle(ctx, 200, 140, 8)
ctx.restore()
const size14 = 14 * rate
// 繪制二維碼
ctx.drawImage(imageUrl[1], 210 * rate, 120 * rate, 86 * rate, 86 * rate)
drawText(ctx, '#1D1E1F', '掃碼認識Ta', 216 * rate, 220 * rate, size14)
// 繪制個人基本信息
ctx.beginPath()
const margin56 = 56 * rate
drawText(ctx, '#1D1E1F', '某個用戶的昵稱', size14, 270 * rate, 20 * rate)
drawText(ctx, '#1D1E1F', '資料', size14, 300 * rate, size14)
drawText(ctx, '#1D1E1F', '這是個人信息|什么|換行', margin56 * rate, 300 * rate, size14)
drawText(ctx, '#1D1E1F', '興趣', size14, 336 * rate, size14)
drawText(ctx, '#1D1E1F', '唱歌、籃球名段、rap...', margin56 * rate, 336 * rate, size14)
drawText(ctx, '#1D1E1F', '簡介', size14, 372 * rate, size14)
drawText(ctx, '#1D1E1F', '這是一段很長的簡介...', margin56 * rate, 372 * rate, size14)
ctx.draw()
setTimeout(() => {
Taro.canvasToTempFilePath({
x:0,
y:0,
width,
height,
canvasId: 'shareuser',
success: (result) => {
SetImage(result.tempFilePath)
},
fail: (err) => {
Taro.showToast('圖片生成失斱逖铩!')
}
})
}, 600)
}
// 處理多張網(wǎng)絡(luò)圖片
const processMultipleImages = (url) => {
return new Promise((resolve, reject) => {
Taro.getImageInfo({
src: url,
success: (res) => {
resolve(res)
},
fail: () => {
Taro.showToast({
title: '生成失敗!'
})
}
})
})
}
// 繪制實心圓
const drawFillCircle = (ctx, x, y, r, w) => {
ctx.beginPath()
ctx.arc(x * rate, y * rate, r * rate, 0, 2*Math.PI)
ctx.fillStyle = "#FFE04A";
ctx.fill()
ctx.closePath()
}
// 繪制文本
const drawText = (ctx, color, text, x, y, font = 16) => {
ctx.setFontSize(font)
ctx.setFillStyle(color)
ctx.setTextAlign('left')
ctx.fillText(text, x, y)
ctx.stroke()
ctx.closePath()
}
// 保存到相冊
const onClickSaveImage = () => {
Taro.getSetting({
success(res) {
// 如果沒有授權(quán)過伸辟,則要獲取授權(quán)
if (!res.authSetting['scope.writePhotosAlbum']) {
Taro.authorize({
scope: 'scope.writePhotosAlbum',
success() {
savePictureSystem()
},
fail() { // 用戶拒絕
Taro.showModal({
title: '授權(quán)',
content: '您拒絕了授權(quán)請求麻惶,是否要手動開啟?',
success: function (res) {
if (res.confirm) {
Taro.openSetting({
success: function (res) {
console.log(res.authSetting)
res.authSetting = {
"scope.userInfo": true,
"scope.userLocation": true
}
}
})
} else if (res.cancel) {
Taro.showToast({
title: '保存失斝欧颉窃蹋!',
icon: 'close',
duration: 2000
})
}
}
})
}
})
} else { // 如果已經(jīng)授權(quán)過卡啰,可以直接保存
savePictureSystem()
}
}
})
}
// 把圖片保存到系統(tǒng)中
const savePictureSystem = () => {
Taro.saveImageToPhotosAlbum({
filePath: saveImage,
success(res) {
Taro.showToast({
title: '保存成功!'
})
},
fail() {
Taro.showToast({
title: '保存失敗!',
icon: 'close',
duration: 2000
})
}
})
}
return (
<View className="share-user-container">
<Canvas style={{width: `${width}px`, height: `${height}px`}} canvasId="shareuser" id="shareuser" className="canvas-wrapper"></Canvas>
<Button onClick={onClickSaveImage}>保存到相冊</Button>
</View>
)
}