<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
小程式分享海報想必大家都做過,受微信的限制,無法直接分享小程式到朋友圈(雖然微信開發者工具基礎庫從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>
<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>
/** @生成海報 **/ 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的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45