前言:该插件无需手写canvas所有写法都是HTML就可以轻松实现海报
在一些项目中有些需求会需要分享海报的功能,今天呢就为大家分享一下我在用uniapp开发中使用的一款插件,可以任意的布局。
插件市场地址:https://ext.dcloud.net.cn/plugin?id=22860
附带图片

效果图

步骤一:l-painter是边界容器 这个里面就是构建海报的区域
javascript
<!-- 最好是用position移除屏幕外,使用show、opacity或者visibility可能会出现别的问题 -->
<l-painter v-if="detail && isPainting" ref="painter" @done="onPainterDone" @fail="onPainterFail" style="position: absolute;left: -1000%;top: -1000%;"></l-painter>
onPainterDone:是海报生成成功的回调
onPainterFail:是海报生成失败的回调
l-painter-view:是块级容器类似div和view
l-painter-text:是类似p标签和text用于书写文字
l-painter-image:是image标签和img标签
基本用法:
创建一个新的文件Poster.vue 我已经进行过一个初步的封装 传入需要渲染的对应静态数据即可
父组件使用
javascript
<!-- 分享游戏 -->
<Poster ref="posterRef" :gameInfo="gameTempInfo" />
import Poster from "@/components/Poster.vue";
HTML结构:简单试试在画板上布局排版自己想要的效果,下面代码是我效果图的结构
javascript
<l-painter v-if="detail && isPainting" ref="painter" @done="onPainterDone" @fail="onPainterFail"
style="position: absolute;left: -1000%;top: -1000%;">
<l-painter-view
:css="`position: relative;width: 600rpx;height: 800rpx;object-fit: cover;background-image: url(替换成你的背景图片地址);`">
<l-painter-view
:css="`width: 536rpx;height: 536rpx;border-radius: 32rpx;background: rgba(217, 217, 217, 1);margin: 32rpx;background-size: cover;background-image: url(${detail.coverUrl});`">
<l-painter-view
:css="`width: 148rpx;height: 44rpx;background: rgba(0, 0, 0, 0.60);line-height: 44rpx;text-align: center;position: absolute;top: 12rpx;left: 12rpx;border-radius: 28rpx;`">
<l-painter-text :text="detail.tag"
css="font-size: 24rpx;color: rgba(255, 213, 0, 1)"></l-painter-text>
</l-painter-view>
<l-painter-view
:css="`height: 123rpx;width: 504rpx;border-radius: 0 0 32rpx 32rpx;position: absolute;bottom: 0;left: 0;background: linear-gradient(0deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 100%);padding-top: 45rpx;padding-left: 32rpx;`">
<l-painter-text :text="detail.gameTitle"
css="font-size: 32rpx;color: rgba(255, 255, 255, 1);margin-bottom: 12rpx;"></l-painter-text>
<l-painter-view css="display: flex;">
<l-painter-image
css="width: 32rpx;height: 32rpx;border-radius: 50%;object-fit: cover;margin-right: 12rpx;"
:src="detail.avatarUrl"></l-painter-image>
<l-painter-text css="font-size: 24rpx;color: rgba(255, 255, 255, 0.50);"
:text="detail.nickName"></l-painter-text>
</l-painter-view>
</l-painter-view>
</l-painter-view>
<l-painter-image
css="width: 170rpx;height: 170rpx;object-fit: cover;margin-right: 12rpx;position: absolute;bottom: 32rpx;right: 32rpx;"
:src="detail.code"></l-painter-image>
</l-painter-view>
</l-painter>
JS文件需要进行一定的数据替换
javascript
<script setup>
import { ref, computed } from 'vue';
// 渲染参数
const props = defineProps({
gameInfo: {
type: Object,
default: () => ({})
}
});
// 海报的DOM实例
const painter = ref(null);
// 是否显示
const visible = ref(false);
// 是否在生成中
const isPainting = ref(false);
// 海报地址
const imgSrc = ref('');
// 渲染参数
const detail = ref(null);
// 定时器ID
let timeoutTimer = null;
// 打开弹框
const open = () => {
// 渲染所用数据装配,合并自游戏详情与当前用户态
detail.value = {
tag: props.gameInfo?.gameType || '游戏海报',
gameTitle: props.gameInfo?.gameTitle || '未命名游戏',
avatarUrl: props.gameInfo?.authorAvatar || '默认地址',
nickName: props.gameInfo?.authorNickname|| '探险者',
coverUrl: props.gameInfo?.gameCoverUrl || '默认地址,
code: props.gameInfo?.miniQrCode
};
imgSrc.value = '';
visible.value = true;
renderPoster();
};
// 数据处理
const renderPoster = () => {
imgSrc.value = '';
isPainting.value = true;
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(() => {
if (isPainting.value && !imgSrc.value) {
isPainting.value = false;
uni.showToast({ title: '海报生成超时', icon: 'none' });
}
}, 15000);
};
// 生成海报
const onPainterDone = () => {
setTimeout(() => {
if (painter.value) {
painter.value.canvasToTempFilePathSync({
fileType: "jpg",
pathType: 'url',
quality: 1,
success: (res) => {
imgSrc.value = res.tempFilePath;
isPainting.value = false;
clearTimeout(timeoutTimer);
wx.showShareImageMenu({
path: imgSrc.value,
success: (res) => {
console.log('分享图片菜单成功', res);
},
fail: (err) => {
console.error('分享图片菜单失败', err);
}
});
},
fail: (err) => {
console.error('取图失败', err);
isPainting.value = false;
}
});
}
}, 300);
};
// 失败回调
const onPainterFail = (err) => {
console.error('l-painter fail', err);
isPainting.value = false;
clearTimeout(timeoutTimer);
};
// 暴露方法
defineExpose({ open });
</script>
CSS就是当前文件的一个背景配置了
css
<style lang="scss" scoped>
.poster-container {
width: 100vw;
height: 100vh;
position: absolute;
top: 0;
left: 0;
z-index: 100;
}
.poster-card {
width: 600rpx;
height: 800rpx;
border-radius: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.3);
background-color: transparent;
position: relative;
margin-top: 100rpx;
.close-btn {
width: 48rpx;
height: 48rpx;
position: absolute;
top: -80rpx;
right: 0;
}
}
.poster-img {
width: 100%;
height: 100%;
display: block;
border-radius: 32rpx;
}
.poster-placeholder {
width: 100%;
height: 100%;
background-color: #ffffff;
border: 12rpx solid #16CABB;
border-radius: 32rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
.loading-text {
font-size: 28rpx;
color: #666;
}
}
.action-buttons {
margin-top: 40rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 600rpx;
.btn {
width: 280rpx;
height: 88rpx;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
box-sizing: border-box;
padding: 0;
margin: 0;
&::after {
border: none;
}
}
.btn-friends {
background-color: #16CABB;
/* 主题青色 */
}
.btn-moment {
background-color: #16CABB;
/* 原型图中左右按钮颜色一致 */
}
}
.painter-offscreen {
position: fixed;
left: -9999px;
top: 0;
}
</style>