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 ...
随机推荐
- 广州2022CCPC补题
I Infection 知识点: 树上背包 第一次写树上背包的题目,没想到就是在区域赛中 神奇的是树上背包的复杂度,看起来是\(O(n^3)\),但是实际计算只有\(O(n^2)\) 学会树上背包后可 ...
- i春秋Do you know upload?
打开题目是一个文件上传,就先写了一个一句话木马的php文件,直接提交显示文件类型不允许.于是乎将其改为jpeg格式上传,成功了,但是没用,菜刀连不上.再次上传jpg格式的一句话木马(写好php木马后将 ...
- vulnhub靶场渗透实战11-Deathnote
网络模式,怎么方便怎么来. 靶场地址:https://download.vulnhub.com/deathnote/Deathnote.ova 这个靶机很简单.vbox装好. 1:主机发现,和端口 ...
- 安装mySql 出现 one more product requirements have not been satisified
安装mySql 出现 one more product requirements have not been satisified 原因是缺少一些依赖环境. 在弹出的对话框中点击 否. 然后点击执行, ...
- 【微服务架构设计实施】第一部分:架构篇-1:微服务架构与Spring Cloud介绍
〇.概述 一.微服务架构与Spring Cloud (一)概念 不同说法:细粒度的.清凉组件化的小型SOA(面向服务架构) 统一说法:小型应用程序(服务组件),使用轻量级设计方法和HTTP协议通信 理 ...
- 一个小时,200行代码,手写Spring的IOC、DI、MVC
一.概述 配置阶段:主要是完成application.xml配置和Annotation配置. 初始化阶段:主要是加载并解析配置信息,然后,初始化IOC容器,完成容器的DI操作,已经完成HandlerM ...
- 过压保护芯片,高输入电压(OVP)
PW2606是一种前端过电压和过电流保护装置.它实现了广泛的输入电压范围从2.5V到40V.过电压阈值可在外部编程或设置为内部默认设置.集成功率路径nFET开关的超低电阻确保了更好的性能电池充电系统应 ...
- 为什么推荐Kestrel作为网络开发框架
为什么推荐Kestrel 网络框架千千万万,在dotnet平台,我们可以直接手撸Socket,也可以基于dotnetty来开发,或者选择某些第三方类似于dotnetty的网络库,为何我要推荐Kestr ...
- 利用云服务提供商的免费证书,在服务器上发布https前端应用和WebAPI的应用
我们如果要在服务器上发布https前端应用和WebAPI的应用,那么我们就需要用到https证书了.我们一般发布的应用的云服务器上,都会提供一定量的相关的免费证书(一般为20个)供我们使用,每个一年期 ...
- 基于人人框架--本地项目部署流程(前后端+IIS上传功能)
基于人人框架--本地项目部署流程(前后端+IIS上传功能) 一.环境要求 JAVA环境 JDK:1.8 IIS 本地电脑必须要有IIS服务 MySQL 数据库采用MySQL数据库,安装版本为 5.7. ...