uniapp封裝canvas組件無腦繪制保存小程序分享海報
正文
小程序分享海報想必大傢都做過,受微信的限制,無法直接分享小程序到朋友圈(雖然微信開發者工具基礎庫從2.11.3開始支持分享小程序到朋友圈,但目前仍處於Beta中),所以生成海報仍然還是主流方式,通常是將設計稿通過canvas繪制成圖片,然後保存到用戶相冊,用戶通過圖片分享小程序
但是,如果不是對canvas很熟悉的話,每次都要去學習canvas的Api,挺麻煩的。我隻想“無腦”的完成海報的繪制,就像是把每一個元素固定定位一樣,告訴它你要插入的是圖片、還是文字,然後再傳入坐標、寬高就能把在canvas繪制出內容。
怎麼做呢?接著往下看(註:本文是基於uniapp Vue3搭建的小程序實現的海報功能)
配置項
屬性 | 說明 | 可選值 |
---|---|---|
type | 元素類型 | image、text、border、block(一般用於設置背景色塊) |
left | 元素距離canvas左側的距離 | 數字或者center,center表示水平居中,比如10、'center' |
right | 元素距離canvas右側的距離 | 數字,比如10 |
top | 元素距離canvas頂部的距離 | 數字,比如10 |
bottom | 元素距離canvas底部的距離 | 數字,比如10 |
width | 元素寬度 | 數字,比如20 |
height | 元素高度 | 數字,比如20 |
url | type為image時的圖片地址 | 字符串 |
color | type為text、border、block時的顏色 | 字符串,比如#333333 |
content | type為text時的文本內容 | 字符串 |
fontSize | type為text時的字體大小 | 數字,比如16 |
radius | type為image、block時圓角,200表示圓形 | 數字,比如10 |
maxLine | type為text時限制最大行數,超出以…結尾 | 數字,比如2 |
lineHeight | type為text時的行高,倍數 | 數字,比如1.5,默認1.3 |
一、使用
<template> <m-canvas ref="myCanvasRef" :width="470" :height="690" /> <button @click="createPoster">生成海報</button> </template> <script setup> import { ref } from 'vue' const myCanvasRef = ref() function createPoster() { // 配置項 const options = [ // 背景圖 { type: 'image', url: '自行替換', left: 0, top: 0, width: 470, height: 690 }, // 長按掃碼 > 瀏覽臻品 > 獲取權益 { type: 'text', content: '長按掃碼 > 瀏覽臻品 > 獲取權益', 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 }, // 昵稱 { type: 'text', content: 'Jerry', color: '#333', fontSize: 20, left: 'center', top: 625 } ] // 調用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 } }) // 導出方法給父組件用 defineExpose({ onDraw(options, callback) { createPoster.call( // 當前上下文 proxy, // canvas相關信息 { id: 'myCanvas', width: props.width, height: props.height }, // 元素集合 options, // 回調函數 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,封裝方法
/** @生成海報 **/ export function createPoster(canvasInfo, options, callback) { uni.showLoading({ title: '海報生成中…', mask: true }) const myCanvas = uni.createCanvasContext(canvasInfo.id, this) var index = 0 drawCanvas(myCanvas, canvasInfo, options, index, () => { myCanvas.draw(true, () => { // 延遲,等canvas畫完 const timer = setTimeout(() => { savePoster.call(this, canvasInfo.id, callback) clearTimeout(timer) }, 1000) }) }) } // 繪制中 async function drawCanvas(myCanvas, canvasInfo, options, index, drawComplete) { let item = options[index] // 最大行數:maxLine 字體大小:fontSize 行高:lineHeight // 類型 顏色 left right top bottom 寬 高 圓角 圖片 文本內容 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 // 根據字體大小計算出寬度 myCanvas.setFontSize(fontSize) // 內容寬度:傳瞭寬度就去寬度,否則取字體本身寬度 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 } // 遞歸邊解析圖片邊畫 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: '保存失敗,請稍後再試~' }) }, complete() { uni.hideLoading() } }) }, fail(res) { console.log('圖片保存失敗:', res.errMsg) uni.showToast({ icon: 'none', title: '保存失敗,請稍後再試~' }) } }, 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-- 是為瞭防止字符丟失,效果圖中有對比 row.push(temp) temp = '' } } row.push(temp) // row有多少項則就有多少行 //如果數組長度大於2,現在隻需要顯示兩行則隻截取前兩項,把第二行結尾設置成'...' 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 } // 把文本繪制到畫佈中 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組件無腦繪制保存小程序分享海報的詳細內容,更多關於uniapp封裝canvas的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- JavaScript canvas實現九宮格切圖效果
- JavaScript+Canvas實現帶跳動效果的粒子動畫
- 微信小程序使用canvas繪制鐘表
- JavaScript實現搜索的數據顯示
- JavaScript Canvas繪制六邊形網格