uniapp实现生成海报功能 (开箱即用)

前言:该插件无需手写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>
相关推荐
PedroQue9911 分钟前
@meng-xi/create-uni-app v1.0.0 正式发布
前端·uni-app
肥仔哥哥193020 分钟前
uniapp开发App一键登录:从技术选型到前后端落地实战--2秒登录,流失率减半:在uniapp里接入一键登录的全过程
uni-app·一键登录设计选型
snow@li29 分钟前
服务器运维:Alibaba Cloud Linux 4 LTS・Vue前端 Java 后端 K3S 部署 CICD 目录规范清单
linux·前端·vue.js
mONESY32 分钟前
从零到上线:全栈项目部署全流程实战
javascript
honkun638 分钟前
vue 表格组件 vxe-table 实实现专业记账凭证编辑表格与自动汇总
前端·javascript·vue.js
蛋先生DX1 小时前
明明都是源码到CPU,各语言中间原理咋不是一个套路?
java·javascript·go
JackieDYH2 小时前
uniapp-幸运抽奖转盘组件-案例
uni-app
andrsted3 小时前
老师可以用小程序在线整理题库、课堂检测和错题集
学习·微信小程序·小程序·学习方法
show4333 小时前
2026小程序视频转文字多语种识别引擎选型:22方言25外语
小程序·音视频