uniapp 分享 绘制海报
小程序分享海报,由于微信的限制,暂时无法直接分享小程序到朋友圈(虽然微信开发者工具基础库从2.11.3开始支持分享小程序到朋友圈,但目前仍处于Beta中)所以生成海报仍然还是主流方式
主体思路:将设计稿通过canvas生成图片,然后保存到用户相册,用户再通过图片分享小程序
| 属性 | 说明 | 可选值 |
|---|---|---|
| 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 分享 绘制海报的更多相关文章
- uniapp分享功能-系统分享
uni-app分享 uniapp官网地址:https://uniapp.dcloud.io/api/plugins/share?id=sharewithsystem 调用系统分享组件发送分享消息,不需 ...
- 微信小程序之canvas绘制海报分享到朋友圈
绘制canvas内容 首先,需要写一个canvas标签,给canvas-id命名为shareBox <canvas canvas-id="shareBox"></ ...
- 利用Python快速绘制海报级别地图
1 简介 基于Python中诸如matplotlib等功能丰富.自由度极高的绘图库,我们可以完成各种极富艺术感的可视化作品,关于这一点我在系列文章在模仿中精进数据可视化中已经带大家学习过很多案例了. ...
- 使用Canvas绘制分享海报
这几天接到一个需求,需要将一个邀请链接转换为一个带有二维码并且能够分享出去的海报图,网上找了很多的方法,也踩了不少的坑,希望大家遇到类似的需求能够少走弯路.. 具体效果图如下: 效果图 首先我采用了 ...
- 开源)嗨,Java,你可以生成金山词霸的二维码分享海报吗?
As long as you can still grab a breath, you fight.只要一息尚存,就不得不战. 有那么一段时间,我特别迷恋金山词霸的每日一句分享海报.因为不仅海报上的图 ...
- 微信小程序利用canvas生成海报分享图片
一 . 效果 这是借用女神照生成的分享的海报,图片来自网络. 新增了poster组件和更新图片自适应 二 . 准备 准备两张图片连接,最好是自己开发账号验证的https图片链接. 三 . 实现思路 其 ...
- Java生成微信分享海报【基础设计】
前言 微信后台生成海报一般都是一个模板写死,然后就完事了,过了不久让修改个模板,就又要看半天,还要考虑是否重新复制一份改一改,越来越多的重复代码,全在一个图片类里,然后就越来越乱.这两天用设计模式处理 ...
- 关于canvas合成分享图
最近在uni-app项目中遇到一个合成分享图的需求,其实最开始是用原生写法来做的,后台发现在PC端测试是可以的,但在APP模拟器中会出现问题,可能是因为两者的js环境不同吧,uni-app官网也说了这 ...
- fastposter发布1.4.2 跨语言的海报生成器
fastposter发布1.4.2 跨语言的海报生成器 fastposter发布1.4.2 跨语言的海报生成器,一分钟完成海报开发 future: 完善docker镜像 引入异步asyncio 升级p ...
- java画海报
package demotest; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; impor ...
随机推荐
- oracle 内置函数(一)数值函数
oracle内置函数主要分为四类: 数值函数 字符函数 日期函数 转换函数 本分析数值函数: 一.四舍五入round round(double,m) double:我们要处理的小数. m: defau ...
- Crane如何做到利用率提升3倍稳定性还不受损?
作为云平台用户,我们都希望购买的服务器物尽其用,能够达到最大利用率.然而要达到理论上的节点负载目标是很的,计算节点总是存在一些装箱碎片和低负载导致的闲置资源.下图展示了某个生产系统的CPU资源现状,从 ...
- frp内网穿透(已验证)
# 云服务器frp内网穿透教程 软件源码在https://github.com/fatedier/frp/releases上,根据系统类型,按需下载 教程术语描述 > A 是有固定IP的云服务器 ...
- 教你铁威马NAS中如何进行阵列升级
磁盘阵列 (RAID) 是磁盘阵列的管理工具.当TNAS 中安装的硬盘多于1个时,组建适当的磁盘阵列能提高硬盘的存储效率,提高数据的安全性. 磁盘阵列升级,比如,将原来是RAID 0 或者RAID 1 ...
- GitHub车牌检测识别项目调研
一,EasyOCR 1.1,仓库介绍 1.2,使用记录 二,HyperLPR 2.1,HyperLPR 概述 2.3,使用记录 2.3,使用建议 三,simple-car-plate-recognit ...
- 升级csproj文件为vs2017工程格式(SDK样式)
csproj文件在VS2017后格式变更为SDK样式.framework项目如果需要源链接等等功能,需要进行手动升级. 升级过程 升级需要Project2015To2017Nuget包. > d ...
- 琐碎的想法(五)for 的前世今生
for 起因 记得大学上C语言的课,第一次遇到的问题就是循环结构里面的 for. 选择结构的 if 非常易懂,和日常生活的判断没有区别. 循环结构的 while 同样比较好理解. 本质上是一个判断 如 ...
- 《STL源码剖析》STL的双层配置器
SGI STL第一级配置器: template<int inst> class __malloc_alloc_template{...}; 其中: 1.allocate()直接使用mall ...
- 学习记录C
学了这么久,终于开始实训项目了....... 奥里给 !!! 压力好大,好喜欢什么也不想的时候 记录学习的代码 分享一下 /* system函数:( #include<stdlib.h> ...
- CF803G Periodic RMQ Problem
简要题意 你需要维护一个序列 \(a\),有 \(q\) 个操作,支持: 1 l r x 将 \([l,r]\) 赋值为 \(x\). 2 l r 询问 \([l,r]\) 的最小值. 为了加大难度, ...