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>
相关推荐
胡志辉的博客1 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖1 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty1 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
老毛肚2 小时前
软件测试期末考试
vue.js
小二·2 小时前
Next.js 15 全栈开发实战
开发语言·javascript·ecmascript
杨若瑜3 小时前
本地开发环境慢?localhost的锅!
vue.js
Rain5093 小时前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js
拾年2754 小时前
从零手写 Ajax:用原生 XHR 搭建前后端交互全流程
前端·javascript·ajax
拉勾科研工作室4 小时前
区块链工程毕业论文题目【249个】
开发语言·javascript
小林ixn4 小时前
你以为你懂 + 号?看完这篇 Bun + TS 实战,才发现以前全写错了
前端·javascript·typescript