uni生成海报并保存
需求:通过不访问服务器,生成海报,解决服务访问压力和带宽问题,在本地生成海报图片
<template>
<!--页面属性配置节点,设置页面跟字体大小 -->
<!-- <page-meta :root-font-size="fontSize + 'px'"></page-meta>-->
<view class="posPoster">
<view class="percard">
<canvas
canvas-id="myCanvas"
:style="{
width: state.canvasWidth,
height: state.canvasHeight,
transform: 'scale(' + scale + ') translate(-' + translate + '%, -' + translate + '%)'
}"
></canvas>
</view>
<!-- 底部按钮占位,防止底部按钮挡住海报 -->
<view class="bottomPosHeight"></view>
</view>
<uv-modal
ref="modalRef"
title="长按保存到本地"
:showCancelButton="false"
:showConfirmButton="false"
:textStyle="{ color: '#c22632' }"
>
<uv-image width="650rpx" mode="widthFix" v-if="isShow" :src="state.imgs"></uv-image>
</uv-modal>
<view class="sureEvaluate" @click="saveImg()">下载到本地</view>
</template>
<script setup>
import { onLoad, onReady } from '@dcloudio/uni-app'
import html2canvas from 'html2canvas'
const scale = ref(0.5)
const picWidth = reactive(750)
const picHeight = reactive(1080)
const translate = computed(() => {
console.log(state.screenWidth, state.ratio)
return ((picWidth - state.screenWidth) / (state.screenWidth * 2)) * 100
})
const isShow = ref(false)
const modalRef = ref()
const state = reactive({
fontSize: '',
isShowBtn: false,
myObj: {
number: '',
bgImg: '', //背景图
name: ''
},
canvasWidth: picWidth, //画布宽度
canvasHeight: picHeight, //画布高度
ratio: window.devicePixelRatio || 1, //计算UI设计稿和你手机的屏幕宽度比例(例如UI设计稿是750宽度 你手机是350宽度 比例就是2 那么你画布画图时候 所有的尺寸大小、宽高、位置、定位左右上下都需要除以 / 比例2 )
widths: '',
heights: '',
imgs: ''
})
const saveImg = () => {
isShow.value = true
console.log(modalRef)
modalRef.value.open()
alert('长按保存到本地')
}
/**
* 计算海报尺寸,适配各种手机,因为我的项目没有使用rpx用的rem所以需要换算,后来为了提出单独组件,在scss里用了rpx。
*/
onLoad((option) => {
const pop = getCurrentPages().pop()
const eventChannel = pop.$vm.getOpenerEventChannel()
eventChannel.on('acceptDataFromOpenerPage', function (data) {
console.log(1111, data)
state.myObj.name = data.name
})
uni.getSystemInfo({
success: (res) => {
console.log(res.screenWidth)
console.log(state.ratio)
state.screenWidth = res.screenWidth
scale.value = res.screenWidth / picWidth
state.screenHeight = res.screenHeight
state.canvasWidth = picWidth + 'px'
state.canvasHeight = picHeight + 'px'
state.ratio = 750 / res.screenWidth
state.widths = (res.screenWidth / 750) * 750
state.heights = (res.screenWidth / 750) * 1080
state.fontSize = res.screenWidth / 750
downImgUrl()
}
})
uni.showLoading({
title: '生成中...'
})
})
const downImgUrl = () => {
uni.getImageInfo({
src: state.myObj.bgImg,
success: function (res) {
console.log(1234, res)
state.myObj.bgImg = res.path
drawPageImg()
state.isShowBtn = true
}
})
}
//画一个带圆角矩形
const ctxCircular = (ctx, img, x, y, width, height, r, shadow) => {
ctx.beginPath() //开始绘制
ctx.save() //保存(canvas)状态
ctx.moveTo(x + r, y)
ctx.lineTo(x + width - r, y)
ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
ctx.lineTo(x + width, y + height - r)
ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5)
ctx.lineTo(x + r, y + height)
ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI)
ctx.lineTo(x, y + r)
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
if (shadow == 1) {
ctx.shadowBlur = 20 // 模糊效果程度的
ctx.shadowColor = 'red' // 阴影颜色
}
ctx.fill() //对当前路径中的内容进行填充
ctx.clip() //从原始画布中剪切任意形状和尺寸
ctx.closePath() //关闭一个路径
ctx.drawImage(img, x, y, width, height)
ctx.restore() //恢复(canvas)状态
ctx.globalCompositeOperation = 'source-over'
}
//画一个矩形也就是整个海报的背景
const ctxRectangle = (ctx, x, y, width, height, r, gnt) => {
ctx.beginPath()
ctx.save() //保存状态
ctx.moveTo(x + r, y)
ctx.lineTo(x + width - r, y)
ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
ctx.lineTo(x + width, y + height - r)
ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5)
ctx.lineTo(x + r, y + height)
ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI)
ctx.lineTo(x, y + r)
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
ctx.fillStyle = gnt
ctx.fill() //对当前路径中的内容进行填充
ctx.closePath() //关闭一个路径
}
// 文字
const ctxText = (ctx, textFont, textAlign, textFillStyle, textName, x, y, w) => {
ctx.beginPath()
ctx.save() //保存状态
//字体
;(ctx.font = textFont),
//字体样式
(ctx.textAlign = textAlign)
//字体颜色
ctx.fillStyle = textFillStyle
//填充字体
var temp = ''
var row = []
let gxqm = ''
if (textName) {
gxqm = textName
} else {
gxqm = '失败'
}
let gexingqianming = gxqm.split('')
for (var a = 0; a < gexingqianming.length; a++) {
if (ctx.measureText(temp).width < w) {
} else {
row.push(temp)
temp = ''
}
temp += gexingqianming[a]
}
row.push(temp)
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
}
// ctx.fillText(textName || '', x, y)
ctx.globalCompositeOperation = 'source-over'
}
// 文字
const ctxTextWrap = (ctx, text, x, y, w, size) => {
//自动换行介绍
var temp = ''
var row = []
let gxqm = ''
if (text) {
gxqm = text
} else {
gxqm = ''
}
let gexingqianming = gxqm.split('')
for (var a = 0; a < gexingqianming.length; a++) {
if (ctx.measureText(temp).width < w) {
} else {
row.push(temp)
temp = ''
}
temp += gexingqianming[a]
}
row.push(temp)
ctx.font = `${size}px arail`
ctx.textAlign = 'left'
ctx.fillStyle = '#333'
ctx.globalCompositeOperation = 'source-over'
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
}
}
// 文字
const ctxTextWrapA = (ctx, text, x, y, w, size, color) => {
//自动换行介绍
var temp = ''
var row = []
let gxqm = ''
if (text) {
gxqm = text
} else {
gxqm = ''
}
let gexingqianming = gxqm.split('')
for (var a = 0; a < gexingqianming.length; a++) {
if (ctx.measureText(temp).width < w) {
} else {
row.push(temp)
temp = ''
}
temp += gexingqianming[a]
}
row.push(temp)
ctx.font = `${size}px arail`
ctx.textAlign = 'left'
ctx.fillStyle = `#${color}`
ctx.globalCompositeOperation = 'source-over'
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
}
}
// 文字
const ctxTextWrapB = (ctx, text, x, y, w, size, color) => {
//自动换行介绍
var temp = ''
var row = []
let gxqm = ''
if (text) {
gxqm = text
} else {
gxqm = ''
}
let gexingqianming = gxqm.split('')
for (var a = 0; a < gexingqianming.length; a++) {
if (ctx.measureText(temp).width < w) {
} else {
row.push(temp)
temp = ''
}
temp += gexingqianming[a]
}
row.push(temp)
// ctx.font = `arail normal bold ${size}px sans-serif`
// ctx.textAlign = 'left'
// ctx.fillStyle = `#${color}`
// ctx.globalCompositeOperation = 'source-over'
for (var b = 0; b < row.length; b++) {
ctx.fillText(row[b] || '', x, y + (b + 1) * 20)
}
}
// 使用画布绘制页面
const drawPageImg = () => {
// 生成画布
const ctx = uni.createCanvasContext('myCanvas')
// 绘制背景
ctx.drawImage(
state.myObj.bgImg, //图像资源
0 / state.ratio, //图像的左上角在目标canvas上 X 轴的位置
0 / state.ratio, //图像的左上角在目标canvas上 Y 轴的位置
picWidth, //在目标画布上绘制图像的宽度
picHeight //在目标画布上绘制图像的高度
)
let fontBase = Math.round(picWidth * 0.02580645)
let fontBase2 = Math.round(fontBase * 0.84375)
let fontBase3 = Math.round(fontBase * 0.875)
let fontSizeStyle = fontBase + 'px 思源宋体'
const date = state.myObj.arrivalDate.split('-')
//编号
ctxText(
ctx,
fontSizeStyle,
'right',
'',
state.myObj.number,
picWidth * 0.871,
picHeight * 0.361,
picWidth * 0.242
)
//姓名
ctxText(
ctx,
fontSizeStyle,
'right',
'',
state.myObj.name.length == 2 ? state.myObj.name.split('').join(' ') : state.myObj.name,
picWidth * 0.2218,
picHeight * 0.411,
600 / state.ratio
)
ctx.draw(false, () => {
// alert('draw')
setTimeout(() => {
// 将canvas 变成图片方便发送给好友或者保存
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
fileType: 'jpg',
success: (res) => {
// alert(JSON.stringify(res))
isShow.value = true
uni.hideLoading()
// alert(res.tempFilePath)
state.imgs = res.tempFilePath
},
fail: function (error) {
// alert(JSON.stringify(error))
uni.hideLoading()
uni.showToast({
icon: 'none',
position: 'bottom',
title: '绘制图片失败'
})
}
})
}, 100)
})
}
</script>
<style scoped>
.posPoster {
width: 750rpx;
height: 1338rpx;
position: relative;
}
.percard {
width: 750rpx;
height: 1080rpx;
overflow: hidden;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
}
.sureEvaluate {
width: 690rpx;
height: 88rpx;
line-height: 88rpx;
border-radius: 53rpx;
background: #e6e8ff;
position: fixed;
bottom: 50rpx;
left: 30rpx;
border: none;
outline: none;
box-shadow: none;
z-index: 999;
text-align: center;
}
.bottomPosHeight {
width: 750rpx;
height: 138rpx;
}
</style>