uniapp封裝canvas組件無(wú)腦繪制保存小程序分享海報(bào)
正文
小程序分享海報(bào)想必大家都做過(guò),受微信的限制,無(wú)法直接分享小程序到朋友圈(雖然微信開(kāi)發(fā)者工具基礎(chǔ)庫(kù)從2.11.3開(kāi)始支持分享小程序到朋友圈,但目前仍處于Beta中),所以生成海報(bào)仍然還是主流方式,通常是將設(shè)計(jì)稿通過(guò)canvas繪制成圖片,然后保存到用戶(hù)相冊(cè),用戶(hù)通過(guò)圖片分享小程序
但是,如果不是對(duì)canvas很熟悉的話(huà),每次都要去學(xué)習(xí)canvas的Api,挺麻煩的。我只想“無(wú)腦”的完成海報(bào)的繪制,就像是把每一個(gè)元素固定定位一樣,告訴它你要插入的是圖片、還是文字,然后再傳入坐標(biāo)、寬高就能把在canvas繪制出內(nèi)容。
怎么做呢?接著往下看(注:本文是基于uniapp Vue3搭建的小程序?qū)崿F(xiàn)的海報(bào)功能)
配置項(xiàng)
屬性 | 說(shuō)明 | 可選值 |
---|---|---|
type | 元素類(lèi)型 | image、text、border、block(一般用于設(shè)置背景色塊) |
left | 元素距離canvas左側(cè)的距離 | 數(shù)字或者center,center表示水平居中,比如10、'center' |
right | 元素距離canvas右側(cè)的距離 | 數(shù)字,比如10 |
top | 元素距離canvas頂部的距離 | 數(shù)字,比如10 |
bottom | 元素距離canvas底部的距離 | 數(shù)字,比如10 |
width | 元素寬度 | 數(shù)字,比如20 |
height | 元素高度 | 數(shù)字,比如20 |
url | type為image時(shí)的圖片地址 | 字符串 |
color | type為text、border、block時(shí)的顏色 | 字符串,比如#333333 |
content | type為text時(shí)的文本內(nèi)容 | 字符串 |
fontSize | type為text時(shí)的字體大小 | 數(shù)字,比如16 |
radius | type為image、block時(shí)圓角,200表示圓形 | 數(shù)字,比如10 |
maxLine | type為text時(shí)限制最大行數(shù),超出以…結(jié)尾 | 數(shù)字,比如2 |
lineHeight | type為text時(shí)的行高,倍數(shù) | 數(shù)字,比如1.5,默認(rèn)1.3 |
一、使用
<template> <m-canvas ref="myCanvasRef" :width="470" :height="690" /> <button @click="createPoster">生成海報(bào)</button> </template> <script setup> import { ref } from 'vue' const myCanvasRef = ref() function createPoster() { // 配置項(xiàng) const options = [ // 背景圖 { type: 'image', url: '自行替換', left: 0, top: 0, width: 470, height: 690 }, // 長(zhǎng)按掃碼 > 瀏覽臻品 > 獲取權(quán)益 { type: 'text', content: '長(zhǎng)按掃碼 > 瀏覽臻品 > 獲取權(quán)益', color: '#333', fontSize: 20, left: 'center', top: 240 }, // 小程序碼白色背景 { type: 'block', color: '#fff', radius: 30, left: 'center', top: 275, width: 245, height: 245 }, // 小程序碼 { type: 'image', url: '自行替換', left: 'center', top: 310, width: 180, height: 180 }, // 頭像 { type: 'image', url: '自行替換', radius: '50%', left: 'center', top: 545, width: 50, height: 50 }, // 昵稱(chēng) { type: 'text', content: 'Jerry', color: '#333', fontSize: 20, left: 'center', top: 625 } ] // 調(diào)用myCanvas的onDraw方法,繪制并保存 myCanvasRef.value.onDraw(options, url => { console.log(url) }) } </script> <style lang="scss" scoped></style>
二、封裝m-canvas組件
<template> <canvas class="myCanvas" canvas-id="myCanvas" /> </template> <script setup> import { getCurrentInstance } from 'vue' // 引入canvas方法 import { createPoster } from './canvas' const { proxy } = getCurrentInstance() // 寬高需要傳哦~ const props = defineProps({ width: { type: Number, required: true }, height: { type: Number, required: true } }) // 導(dǎo)出方法給父組件用 defineExpose({ onDraw(options, callback) { createPoster.call( // 當(dāng)前上下文 proxy, // canvas相關(guān)信息 { id: 'myCanvas', width: props.width, height: props.height }, // 元素集合 options, // 回調(diào)函數(shù) callback ) } }) </script> <style lang="scss" scoped> // 隱藏canvas .myCanvas { left: -9999px; bottom: -9999px; position: fixed; // canvas寬度 width: calc(1px * v-bind(width)); // canvas高度 height: calc(1px * v-bind(height)); } </style>
三、聲明canvas.js,封裝方法
/** @生成海報(bào) **/ export function createPoster(canvasInfo, options, callback) { uni.showLoading({ title: '海報(bào)生成中…', mask: true }) const myCanvas = uni.createCanvasContext(canvasInfo.id, this) var index = 0 drawCanvas(myCanvas, canvasInfo, options, index, () => { myCanvas.draw(true, () => { // 延遲,等canvas畫(huà)完 const timer = setTimeout(() => { savePoster.call(this, canvasInfo.id, callback) clearTimeout(timer) }, 1000) }) }) } // 繪制中 async function drawCanvas(myCanvas, canvasInfo, options, index, drawComplete) { let item = options[index] // 最大行數(shù):maxLine 字體大?。篺ontSize 行高:lineHeight // 類(lèi)型 顏色 left right top bottom 寬 高 圓角 圖片 文本內(nèi)容 let { type, color, left, right, top, bottom, width, height, radius, url, content, fontSize } = item radius = radius || 0 const { width: canvasWidth, height: canvasHeight } = canvasInfo switch (type) { /** @文本 **/ case 'text': if (!content) break // 根據(jù)字體大小計(jì)算出寬度 myCanvas.setFontSize(fontSize) // 內(nèi)容寬度:傳了寬度就去寬度,否則取字體本身寬度 item.width = width || myCanvas.measureText(content).width console.log(myCanvas.measureText(content)) // left位置 if (right !== undefined) { item.left = canvasWidth - right - item.width } else if (left === 'center') { item.left = canvasWidth / 2 - item.width / 2 } // top位置 if (bottom !== undefined) { item.top = canvasHeight - bottom - fontSize } drawText(myCanvas, item) break /** @圖片 **/ case 'image': if (!url) break var imageTempPath = await getImageTempPath(url) // left位置 if (right !== undefined) { left = canvasWidth - right - width } else if (left === 'center') { left = canvasWidth / 2 - width / 2 } // top位置 if (bottom !== undefined) { top = canvasHeight - bottom - height } // 帶圓角 if (radius) { myCanvas.save() myCanvas.beginPath() // 圓形圖片 if (radius === '50%') { myCanvas.arc(left + width / 2, top + height / 2, width / 2, 0, Math.PI * 2, false) } else { if (width < 2 * radius) radius = width / 2 if (height < 2 * radius) radius = height / 2 myCanvas.beginPath() myCanvas.moveTo(left + radius, top) myCanvas.arcTo(left + width, top, left + width, top + height, radius) myCanvas.arcTo(left + width, top + height, left, top + height, radius) myCanvas.arcTo(left, top + height, left, top, radius) myCanvas.arcTo(left, top, left + width, top, radius) myCanvas.closePath() } myCanvas.clip() } myCanvas.drawImage(imageTempPath, left, top, width, height) myCanvas.restore() break /** @盒子 **/ case 'block': // left位置 if (right !== undefined) { left = canvasWidth - right - width } else if (left === 'center') { left = canvasWidth / 2 - width / 2 } // top位置 if (bottom !== undefined) { top = canvasHeight - bottom - height } if (width < 2 * radius) { radius = width / 2 } if (height < 2 * radius) { radius = height / 2 } myCanvas.beginPath() myCanvas.fillStyle = color myCanvas.strokeStyle = color myCanvas.moveTo(left + radius, top) myCanvas.arcTo(left + width, top, left + width, top + height, radius) myCanvas.arcTo(left + width, top + height, left, top + height, radius) myCanvas.arcTo(left, top + height, left, top, radius) myCanvas.arcTo(left, top, left + width, top, radius) myCanvas.stroke() myCanvas.fill() myCanvas.closePath() break /** @邊框 **/ case 'border': // left位置 if (right !== undefined) { left = canvasWidth - right - width } // top位置 if (bottom !== undefined) { top = canvasHeight - bottom - height } myCanvas.beginPath() myCanvas.moveTo(left, top) myCanvas.lineTo(left + width, top + height) myCanvas.strokeStyle = color myCanvas.lineWidth = width myCanvas.stroke() break } // 遞歸邊解析圖片邊畫(huà) if (index === options.length - 1) { drawComplete() } else { index++ drawCanvas(myCanvas, canvasInfo, options, index, drawComplete) } } // 下載并保存 function savePoster(canvasId, callback) { uni.showLoading({ title: '保存中…', mask: true }) uni.canvasToTempFilePath( { canvasId, success(res) { callback && callback(res.tempFilePath) uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success() { uni.showToast({ icon: 'success', title: '保存成功!' }) }, fail() { uni.showToast({ icon: 'none', title: '保存失敗,請(qǐng)稍后再試~' }) }, complete() { uni.hideLoading() } }) }, fail(res) { console.log('圖片保存失敗:', res.errMsg) uni.showToast({ icon: 'none', title: '保存失敗,請(qǐng)稍后再試~' }) } }, this ) } // 繪制文字(帶換行超出省略…功能) function drawText(ctx, item) { let { content, width, maxLine, left, top, lineHeight, color, fontSize } = item content = String(content) lineHeight = (lineHeight || 1.3) * fontSize // 字體 ctx.setFontSize(fontSize) // 顏色 ctx.setFillStyle(color) // 文本處理 let strArr = content.split('') let row = [] let temp = '' for (let i = 0; i < strArr.length; i++) { if (ctx.measureText(temp).width < width) { temp += strArr[i] } else { i-- //這里添加了i-- 是為了防止字符丟失,效果圖中有對(duì)比 row.push(temp) temp = '' } } row.push(temp) // row有多少項(xiàng)則就有多少行 //如果數(shù)組長(zhǎng)度大于2,現(xiàn)在只需要顯示兩行則只截取前兩項(xiàng),把第二行結(jié)尾設(shè)置成'...' if (row.length > maxLine) { let rowCut = row.slice(0, maxLine) let rowPart = rowCut[1] let text = '' let empty = [] for (let i = 0; i < rowPart.length; i++) { if (ctx.measureText(text).width < width) { text += rowPart[i] } else { break } } empty.push(text) let group = empty[0] + '...' //這里只顯示兩行,超出的用...表示 rowCut.splice(1, 1, group) row = rowCut } // 把文本繪制到畫(huà)布中 for (let i = 0; i < row.length; i++) { // 一次渲染一行 ctx.fillText(row[i], left, top + i * lineHeight, width) } } // 獲取圖片信息 function getImageTempPath(url) { return new Promise((resolve) => { if (url.includes('http')) { uni.downloadFile({ url, success: (res) => { uni.getImageInfo({ src: res.tempFilePath, success: (res) => { resolve(res.path) } }) }, fail: (res) => { console.log('圖片下載失敗:', res.errMsg) } }) } else { resolve(url) } }) }
以上就是uniapp封裝canvas組件無(wú)腦繪制保存小程序分享海報(bào)的詳細(xì)內(nèi)容,更多關(guān)于uniapp封裝canvas的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS數(shù)據(jù)分析數(shù)據(jù)去重及參數(shù)序列化示例
這篇文章主要為大家介紹了JS數(shù)據(jù)分析數(shù)據(jù)去重及參數(shù)序列化示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08TypeScript中Module使用區(qū)別及模塊路徑解析規(guī)則
這篇文章主要為大家介紹了TypeScript中Module使用區(qū)別及模塊路徑解析規(guī)則,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06前端JavaScript徹底弄懂函數(shù)柯里化curry
隨著主流JavaScript中函數(shù)式編程的迅速發(fā)展, 函數(shù)柯里化在許多應(yīng)用程序中已經(jīng)變得很普遍。 了解它們是什么,它們?nèi)绾喂ぷ饕约叭绾纬浞掷盟鼈兎浅V匾?。本篇文章小編九向大家詳?xì)介紹JavaScript函數(shù)柯里化,需要的小伙伴可以參考下面文字內(nèi)容2021-09-09THREE.js添加多個(gè)castShadow光源報(bào)錯(cuò)解決及原因分析
這篇文章主要介紹了THREE.js添加多個(gè)castShadow的光源報(bào)錯(cuò)解決及原因分析2023-06-06微信小程序 頁(yè)面滑動(dòng)事件的實(shí)例詳解
這篇文章主要介紹了微信小程序 頁(yè)面滑動(dòng)事件的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10Server-sent?events實(shí)時(shí)獲取服務(wù)端數(shù)據(jù)技術(shù)詳解
這篇文章主要為大家介紹了Server-sent?events實(shí)時(shí)獲取服務(wù)端數(shù)據(jù)技術(shù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02