小程序 海报

创建canvas绘制图片的组件 - 代码如下

<template>
    <view>
        <canvas canvas-id="canvas" :style="{'width': width + 'px', 'height': height + 'px'}" style="position: fixed; left: -9999px; top: -9999px;"></canvas>
    </view>
</template>
 
<script>
    export default {
        name: "drawImage",
        props: {
            // 绘制图片的尺寸
            imageSize: {
                type: Object,
                default: () => {},
            },
            // canvas绘制的数据
            canvasData: {
                type: Array,
                default: () => [],
            },
            // 是否开始绘制
            isDraw: {
                type: Boolean,
                default: false,
            },
        },
        data() {
            return {
                // 屏幕宽度
                screenWidth: 0,
                // canvas画布的宽度
                width: 0,
                // canvas画布的高度
                height: 0,
                // 当前图片放大倍数 - 清晰度
                count: 2,
            };
        },
        mounted() {
            // 这段代码主要是为了防止uni-app在app端IOS的问题,因为在IOS 画布过大可能会导致绘制空白 - 可以自行调整
            // #ifdef APP-PLUS
            if(uni.getSystemInfoSync().platform === 'ios') {
                this.count = 1.8;
            }
            // #endif
        },
        watch: {
            // 监听是否开始绘制
            isDraw: async function(newVal) {
                if(newVal) {
                    this.getSystemInfo();
                    this.getImageByCanvasData(this.canvasData);
                }
            }
        },
        methods: {
            /** 获取系统信息 */
            getSystemInfo() {
                const { screenWidth } = uni.getSystemInfoSync();
 
                this.width = this.imageSize.width * this.count;
                this.height = this.imageSize.height * this.count;
 
                this.screenWidth = screenWidth;
            },
            
            /**
             * 通过数据绘制图片
             * @param {array} data      canvas绘制的数组
             * 格式:每一项的数据
             * { type: 'rect',  attr: { color: '', x, y, width, height, radian_1, radian_2, radian_3, radian_4 } }
             * { type: 'image', attr: { image: '', x, y, width, height, radian_1, radian_2, radian_3, radian_4 } }
             * { type: 'text',  attr: { text: '', x, y, color, size, weight, writingMode } }
             * */
            async getImageByCanvasData(data) {
                // 获取canvas上下文对象
                const context = uni.createCanvasContext("canvas", this);
                // 清空画布
                context.clearRect(0, 0, this.width * this.count, this.height * this.count);
 
                for(const item of data) {
                    // 判断类型
                    if(item.type === 'rect') {
                        // 绘制圆边矩形
                        this.drawRoundRectangular(
                            context, 
                            item.attr.color, 
                            item.attr.x * this.count, 
                            item.attr.y * this.count, 
                            item.attr.width * this.count, 
                            item.attr.height * this.count, 
                            item.attr.radian_1 ? item.attr.radian_1 * this.count : 0, 
                            item.attr.radian_2 ? item.attr.radian_2 * this.count : -1, 
                            item.attr.radian_3 ? item.attr.radian_3 * this.count : -1, 
                            item.attr.radian_4 ? item.attr.radian_4 * this.count : -1
                        );
                    }
                    else if(item.type === 'image' && item.attr.image) {
                        // 绘制圆边图片
                        await this.drawRoundImageToCanvas(
                            context, 
                            item.attr.image, 
                            item.attr.x * this.count, 
                            item.attr.y * this.count, 
                            item.attr.width * this.count, 
                            item.attr.height * this.count, 
                            item.attr.radian_1 ? item.attr.radian_1 * this.count : 0, 
                            item.attr.radian_2 ? item.attr.radian_2 * this.count : -1, 
                            item.attr.radian_3 ? item.attr.radian_3 * this.count : -1, 
                            item.attr.radian_4 ? item.attr.radian_4 * this.count : -1
                        );
                    }
                    else if(item.type === 'text' && item.attr.text) {
                        // 绘制文本
                        this.drawTextToCanvas(
                            context, 
                            item.attr.text, 
                            item.attr.x * this.count, 
                            item.attr.y * this.count, 
                            item.attr.color, 
                            parseInt(item.attr.size ? item.attr.size * this.count : 16 * this.count), 
                            item.attr.weight,
                            item.attr.writingMode ? item.attr.writingMode : 'initial'
                        );
                    }
                }
                
                // 绘制图片 
                context.draw(false, () => {
                    uni.canvasToTempFilePath({
                        canvasId: 'canvas',
                        x: 0,
                        y: 0,
                        width: this.width,
                        height: this.height,
                        destWidth: this.width,
                        height: this.height,
                        success: res => {
                            this.$emit("generateImageSuccessful", res.tempFilePath);
                        },
                    }, this);
                });
            },
            
            /**
             * 绘制文本
             * @param {string} context      Canvase的实例
             * @param {string} text         文本内容
             * @param {number} x            矩形的x坐标
             * @param {number} y            矩形的y坐标
             * @param {number} color        文本颜色
             * @param {number} size         字体的大小
             * @param {string} weight       字体的粗细
             * @param {string} writingMode  字体的排列方式 - initial 水平  tb 垂直
             * */
            drawTextToCanvas(context, text, x, y, color = '#000', size = 16, weight = '400', writingMode = 'initial') {
                context.fillStyle = color;
                context.font = `normal ${weight} ${size}px sans-serif`;
                if(writingMode === 'tb') {
                    const temp = text.split("");
                    for(let i = 0; i < temp.length; i++) {
                        context.fillText(temp[i], x, i * size + y);
                    }
                }
                else {
                    // 判断是否有换行符
                    const temp = text.split("\n");
                    for(let i = 0; i < temp.length; i++) {
                        context.fillText(temp[i], x, i * size + y + i * size * 0.2);  // i * size * 0.2 增加换行的间距
                    }
                }
            },
            
            /**
             * 绘制圆边矩形
             * @param {string} context  Canvase的实例
             * @param {string} color    填充的颜色
             * @param {number} x        矩形的x坐标
             * @param {number} y        矩形的y坐标
             * @param {number} width    矩形的宽度
             * @param {number} height   矩形的高度
             * @param {number} height   图片的高度
             * @param {number} radian_1 弧度大小 - radian_1 右上 的弧度, 1个参数代表全部
             * @param {number} radian_2 弧度大小 - radian_2 右下 的弧度
             * @param {number} radian_3 弧度大小 - radian_3 左下 的弧度
             * @param {number} radian_4 弧度大小 - radian_4 左上 的弧度
             * */
            drawRoundRectangular(context, color, x, y, width, height, radian_1 = 0, radian_2 = -1, radian_3 = -1, radian_4 = -1) {
                context.save();
                this.drawRoundPath(context, x, y, width, height, radian_1, radian_2, radian_3, radian_4);
                context.setFillStyle(color);
                context.fill();
                context.restore();
            },
            
            /**
             * 绘制圆角图片
             * @param {string} context  Canvase的实例
             * @param {string} image    图片地址
             * @param {number} x        图片的x坐标
             * @param {number} y        图片的y坐标
             * @param {number} width    图片的宽度
             * @param {number} height   图片的高度
             * @param {number} radian_1 弧度大小 - radian_1 右上 的弧度, 1个参数代表全部
             * @param {number} radian_2 弧度大小 - radian_2 右下 的弧度
             * @param {number} radian_3 弧度大小 - radian_3 左下 的弧度
             * @param {number} radian_4 弧度大小 - radian_4 左上 的弧度
             * */
            async drawRoundImageToCanvas(context, image, x, y, width, height, radian_1 = 0, radian_2 = -1, radian_3 = -1, radian_4 = -1) {
                context.save();
                this.drawRoundPath(context, x, y, width, height, radian_1, radian_2, radian_3, radian_4);
                context.drawImage(await this.handleNetworkImgaeTransferTempImage(image), x, y, width, height);
                context.restore();
            },
            
            /**
             * 绘制圆边路径
             * @param {string} context  Canvase的实例
             * @param {number} x        图片的x坐标
             * @param {number} y        图片的y坐标
             * @param {number} width    图片的宽度
             * @param {number} height   图片的高度
             * @param {number} radian_1 弧度大小 - radian_1 右上 的弧度, 1个参数代表全部
             * @param {number} radian_2 弧度大小 - radian_2 右下 的弧度
             * @param {number} radian_3 弧度大小 - radian_3 左下 的弧度
             * @param {number} radian_4 弧度大小 - radian_4 左上 的弧度
             * */
            drawRoundPath(context, x, y, width, height, radian_1 = 0, radian_2 = -1, radian_3 = -1, radian_4 = -1) {
                // 设置弧度
                radian_2 = radian_2 === -1 ? radian_1 : radian_2;
                radian_3 = radian_3 === -1 ? radian_1 : radian_3;
                radian_4 = radian_4 === -1 ? radian_1 : radian_4;
                
                // 创建路径 - 绘制带圆边的矩形
                context.beginPath();                                            
                context.moveTo(x + width / 2, y);
                context.arcTo(x + width, y, x + width, y + height, radian_1);
                context.arcTo(x + width, y + height, x, y + height, radian_2);
                context.arcTo(x, y + height, x, y, radian_3);
                context.arcTo(x, y, x + width, y, radian_4);
                // 关闭路径 - 结束绘制
                context.closePath();
                context.strokeStyle = "transparent";
                context.stroke();
                context.clip();
            },
            
            /** 将网络图片变成临时图片 */
            handleNetworkImgaeTransferTempImage(url) {
                return new Promise(resolve => {
                    if(url.indexOf('http') === 0) {
                        uni.downloadFile({
                            url,
                            success: res => {
                                resolve(res.tempFilePath);
                            }
                        });
                    }
                    else {
                        resolve(url);
                    }
                });
            },
        }
    }
</script>
 
<style scoped lang="scss">
 
</style>

在页面中的使用

<template>
    <view class="sharePoster">
        <view class="content">
            
            <!-- 显示一下绘制完成后的路径 -->
            <image :src="tempImage" style="width: 375px; height: 667px;"></image>
            <Index :isDraw="isDraw" :canvasData="canvasData" :imageSize="{width: 375, height: 667}"
                @generateImageSuccessful="generateImageSuccessful" />
            <view class="btn2" @click="saveAlbum">保存到相册</view>
        </view>

    </view>
</template>

<script>
import Index from "../index/index.vue"
export default {
    components: {
        Index
    },
    data() {
        return {
            option: {
                title: '详情'
            },
            poster: "",
            // 是否开始绘制
            isDraw: false,
            /**
             * 需要绘制的图片数据 - 具体参数需要看组件内的
             * { type: 'rect',  attr: { color: '', x, y, width, height, radian_1, radian_2, radian_3, radian_4 } }
             * { type: 'image', attr: { image: '', x, y, width, height, radian_1, radian_2, radian_3, radian_4 } }
             * { type: 'text',  attr: { text: '', x, y, color, size, weight, writingMode } }
             * */
            canvasData: [],
            // 临时路径
            tempImage: "",
            goodsList:[
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.81"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.82"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.83"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.84"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.85"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.86"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.87"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.88"
                },
                {
                    image:'/static/images/hb.png',
                    text:'商品名字商品',
                    price:"¥12.89"
                },
                
            ]



        };
    },
    onLoad(option) {
        this.option = option
        // this.getCreatPoster(this.option.id)
        
        this.canvasData = [
            // { type: 'rect', attr: { color: '#fff', x: 0, y: 0, width: 375, height: 667 } },
            { type: 'image', attr: { image: '/static/images/hb.png', x: 0, y: 0, width: 375, height: 667, } },
            { type: 'text', attr: { text: this.getString('开心一族漂亮家园推荐语推', 7), x: 110, y: 80, color: 'red', size: 20, weight: '500', } },
            { type: 'text', attr: { text: this.getString('推荐语推荐语推荐语推荐语推荐语推荐语推荐语推荐语', 13), x: 90, y: 108, color: '#000', size: 14, } },
            ...this.getGoodsArr(),
            { type: 'text', attr: { text: '共3个品种', x: 150, y: 515, color: '#000', size: 14, } },
            { type: 'image', attr: { image: '/static/images/hb.png', x: 150, y: 545, width: 75, height: 75, } },
            { type: 'text', attr: { text: '扫码查看海报详情', x: 130, y: 640, color: '#fff', size: 14, } },
            
        ];

        this.isDraw = true;

    },

    methods: {
        getGoodsArr(){
            let data = []
            let canvasData = []
            let text_x=80,img_x=75,img_y=125
        
            for (let i = 0; i < this.goodsList.length; i++) {
                if (data[parseInt(i / 3)] instanceof Array) {
                    data[parseInt(i / 3)].push(this.goodsList[i]);
                } else {
                    let temp = [this.goodsList[i]];
                    data[parseInt(i / 3)] = temp;
                }
            }
            data.forEach((item,index)=>{
                let canvasData_init =[]
                item.forEach((item1,index1)=>{
                    canvasData_init.push(...[
                    
                        { type: 'image', attr: { image: item1.image, x: img_x*(index1+1), y: img_y*(index+1), width: 75, height: 75, } },
                        { type: 'text', attr: { text: this.getString(item1.text, 4), x: text_x*(index1+1), y: img_y*(index+1)+90, color: '#000', size: 14, } },
                        { type: 'text', attr: { text: this.getString(item1.price, 6), x: text_x*(index1+1), y: img_y*(index+1)+110, color: 'red', size: 14, } },
                    
                    ])
                    
                })
                console.log("canvasData_init",canvasData_init)

                canvasData.push(...canvasData_init)
            })
            return canvasData
        },

        getString(string, num) {
            if (string.length > num) {
                string = string.substring(0, num) + "..."
            }
            return string
        },

        /** 绘制成功后的回调 - 返回一个临时路径 */
        generateImageSuccessful(image) {
            this.tempImage = image;
        },


        getCreatPoster(id) {
            this.$u.api.getCreatPoster({ id }).then(res1 => {
                this.poster = res1.poster
            })
        },
        saveImageToPhotosAlbum() {
            uni.saveImageToPhotosAlbum({
                filePath: this.tempImage,
                success: function () {
                    uni.showToast({
                        title: '保存成功,请从相册选择再分享',
                        icon: "none",
                        duration: 2000
                    })
                },
                fail: err => {
                    uni.showToast({
                        title: '保存失败',
                        icon: "none",
                        duration: 1000
                    })
                }
            });
        },
        saveAlbum() {
            uni.getSetting({//获取用户的当前设置
                success: (res) => {
                    if (res.authSetting['scope.writePhotosAlbum']) {//验证用户是否授权可以访问相册
                        this.saveImageToPhotosAlbum();
                    } else {
                        uni.authorize({//如果没有授权,向用户发起请求
                            scope: 'scope.writePhotosAlbum',
                            success: () => {
                                this.saveImageToPhotosAlbum();
                            },
                            fail: () => {
                                uni.showToast({
                                    title: "请打开保存相册权限,再点击保存相册分享",
                                    icon: "none",
                                    duration: 3000
                                });
                                setTimeout(() => {
                                    uni.openSetting({//调起客户端小程序设置界面,让用户开启访问相册
                                        success: (res2) => {
                                        }
                                    });
                                }, 3000);
                            }
                        })
                    }
                }
            })

        },
    },
};
</script>

<style lang="scss">
.sharePoster {
    background: #F5F5F5;
    min-height: 100vh;
    padding-bottom: 30rpx;

    image {
        width: 100%;
    }
}
.btn2 {
    width: 90%;
    text-align: center;
    padding: 18rpx 0;
    background: linear-gradient(270deg, rgba(96, 138, 252, 1) 0%, rgba(96, 186, 252, 1) 100%);
    border-radius: 190rpx;
    color: #fff;
    font-size: 28rpx;
    margin: 30rpx auto;
}
</style>

image.png

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容