需求:通过不访问服务器,生成海报,解决服务访问压力和带宽问题,在本地生成海报图片
<template><!--页面属性配置节点,设置页面跟字体大小 --><!-- <page-meta :root-font-size="fontSize + 'px'"></page-meta>--><view class="posPoster"><view class="percard"><canvascanvas-id="myCanvas":style="{width: state.canvasWidth,height: state.canvasHeight,transform: 'scale(' + scale + ') translate(-' + translate + '%, -' + translate + '%)'}"></canvas></view><!-- 底部按钮占位,防止底部按钮挡住海报 --><view class="bottomPosHeight"></view></view><uv-modalref="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 = trueconsole.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.screenWidthscale.value = res.screenWidth / picWidthstate.screenHeight = res.screenHeightstate.canvasWidth = picWidth + 'px'state.canvasHeight = picHeight + 'px'state.ratio = 750 / res.screenWidthstate.widths = (res.screenWidth / 750) * 750state.heights = (res.screenWidth / 750) * 1080state.fontSize = res.screenWidth / 750downImgUrl()}})uni.showLoading({title: '生成中...'})})const downImgUrl = () => {uni.getImageInfo({src: state.myObj.bgImg,success: function (res) {console.log(1234, res)state.myObj.bgImg = res.pathdrawPageImg()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 = gntctx.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 = trueuni.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>