2022-10-26uniapp 小程序页面 生成图片后保存手机相册

一、整体步骤流程:

1、用小程序的页面把想要的海报格式渲染出来(有海报设计稿的话此处可以不用)。

我的海报组成:上面一张背景图,背景图的左下方是说明文字,说明文字可以随意编辑,右下方是一张固定的二维码图片,这三部分组成


image.png

2、在第一步的页面上放一个按钮“生成海报”,加一个点击事件,点击按钮后跳转到海报页面

第一步渲染的页面上,我需要把图片地址和录入的文字信息传到海报的页面,所以跳转时带了两个参数:一个地址,一个文字标题

   saveImage() {
            //跳转到海报页面
            uni.navigateTo({
                url: '/pages/canTest/canTest?url=' + this.url + '&title=' + this.title
            })
        },

3、新的海报页面,通过canvas把海报画出来。

4、点击新生成的海报链接,预览海报,再把海报保存到手机相册

难点:canvas不会用,不知道怎么画。

二、操作过程:

1、海报页面写一个canvas组件

上面第2步点击“生成海报”,跳转到画海报的页面,页面上写一个canvas组件,然后给canvas一个点击事件 ,点击后可以预览画布画的图片。
style里面设置的是画布的宽度和高度

<template>
<view class="demo">
    <canvas :style="{ width: canvasW + 'px', height: canvasH + 'px' }" canvas-id="myCanvas" id="myCanvas"
        @click="saveHB"></canvas>
</view>
</template>

2、获取设备的信息和照片的信息,

(我的背景图用的是网络图片:this.url)

2.1获得屏幕的宽和高

 //获取设备信息
  this.SystemInfo = await this.getSystemInfo();    

获得了设备的宽和高就是获得了画布的宽和高,如果画布高度不需要整个屏幕高,则可以减掉一部分,至于具体减掉多少自己调试就行,我减掉了150高度。

          this.canvasW = this.SystemInfo.windowWidth; // 画布宽度
          this.canvasH = this.SystemInfo.windowHeight - 150; //画布高度

2.2获得背景图片的实际宽和实际高

      this.goodsImg = await this.getImageInfo(this.url);   //获取照片信信息

2.3计算背景图片画在画布上时应该画的宽度和高度

这个在画布上需要画的背景图片的宽度,我的设计稿左右padding :20rpx,所以实际画的宽度是屏幕的宽度减掉两边一起20的宽度

         this.picWidth = this.canvasW - 20     

背景图片的在画布上的宽度确认后,要保证原图的缩放比,所以高度需要计算下:
原图宽 / 原图高=实际画的宽 / 实际画的高,
原图的宽和高通过getImageInfo得到了,新需要画的宽在上面一步得到了,所以实际需要的高为:实际画的宽*原图的高/原图的宽

 this.picHight = this.picWidth * this.goodsImg.height / this.goodsImg.width

3.准备二维码图片

我没有自动生成二维码,直接准备了一张二维码图片

   //二维码的信息
        this.ewmImg = '/static/erweima.png';

4.画背景图

// 如果主图,二维码图片,设备信息都获取成功,开始绘制海报,这里需要用setTimeout延时绘制,否则可能会出现图片不显示。

        //检查系统信息,图片信息都正常返回了
        if (this.goodsImg.errMsg == 'getImageInfo:ok' && this
            .SystemInfo.errMsg == 'getSystemInfo:ok') {
            // console.log('ok')
            //弹出提示,海报绘制中
            uni.showToast({
                icon: 'loading',
                mask: true,
                // duration: 5000,
                title: '海报绘制中',
            });
            setTimeout(() => {
                //准备ctx画布
                var ctx = uni.createCanvasContext('myCanvas', this);
                // 填充画布的背景色为白色
                ctx.setFillStyle('#fff'); // 默认白色
                //设置的画布的高度与宽度和2.1章节的屏幕宽高保持一致
                ctx.fillRect(0, 0, this.canvasW, this.canvasH) // fillRect(x,y,宽度,高度)

   // 绘制主背景图,因为有20rpx的padding,所以起始坐标是10,10,
                //后面两个参数是图片需要画的宽度和高度
                ctx.drawImage(this.goodsImg.path, 10, 10, this.picWidth, this.picHight)

5、绘制二维码

image.png

//如果有设计图,直接量设计图即可,
//没有设计图就慢慢调试
//我的x坐标是设备的宽度减掉前面文字组件的大概宽度:this.canvasW - 110
//我的Y坐标是上面背景图的高度加上中间隔离的高度:this.picHight + 45
//后面两个参数就是需要画的二维码的宽和高: this.ewmW

ctx.drawImage(this.ewmImg, this.canvasW - 110, this.picHight + 45, this.ewmW, this.ewmW)

// 6、绘制文字标题,多余文字自动换行

                ctx.setFontSize(18); // setFontSize() 设置字体字号
                ctx.setFillStyle('#000000'); // setFillStyle() 设置字体颜色
                //canvas不能自动换行,需要自行计算 ,直接copy过去用
                let _strlineW = 0;
                let _strLastIndex = 0; //每次开始截取的字符串的索引
                // let _strHeight = this.canvasW * rant + 30; //绘制字体距离canvas顶部的初始高度
                let _strHeight = this.picHight + 70;
                let _num = 1;
                for (let i = 0; i < this.title.length; i++) {
                    _strlineW += ctx.measureText(this.title[i]).width;
                    // if (_strlineW > this.canvasW * rant - 155) {
                    if (_strlineW > this.canvasW - 155) {
                        if (_num == 4 && 4) {
                            //文字换行数量大于4进行省略号处理,想几行就改下上面的数字
                            ctx.fillText(this.title.substring(_strLastIndex, i - 5) + '...', 10,
                                _strHeight);
                            _strlineW = 0;
                            _strLastIndex = i;
                            _num++;
                            break;
                        } else {
                            ctx.fillText(this.title.substring(_strLastIndex, i), 20, _strHeight);
                            _strlineW = 0;
                            _strHeight += 20;
                            _strLastIndex = i;
                            _num++;
                        }
                    } else if (i == this.title.length - 1) {
                        ctx.fillText(this.title.substring(_strLastIndex, i + 1), 20, _strHeight);
                        _strlineW = 0;
                    }
                }
                /* end */

以下内容就比较容易了,直接照抄即可

                ctx.draw(true, (ret) => { // draw方法 把以上内容画到 canvas 中。
                    uni.showToast({
                        icon: 'success',
                        mask: true,
                        title: '绘制完成',
                    });
                    uni.canvasToTempFilePath({ // 保存canvas为图片
                        canvasId: 'myCanvas',
                        quality: 1,
                        complete: (res) => {
                            this.hbUrl.push(res.tempFilePath)
                        },
                    })
                });
            }, 1000)
        } else {
            console.log('err')
        }

7.给canvas绑定一个点击事件,点击后,可以预览图片,预览时可以长按图片进行转发和保存了

//预览图片接口,urls参数为数组,上面绘图得到的地址push到 this.hbUrl,就可以预览了

saveHB() {
            // console.log('点击了图片')
            uni.previewImage({
                urls: this.hbUrl
            })
        },

下面时获取图片信息和获取系统信息的方法

 // 获取图片信息
        getImageInfo(image) {
            return new Promise((req, rej) => {
                uni.getImageInfo({
                    src: image,
                    success: (res) => {
                        req(res)
                    },
                });
            })
        },

        // 获取设备信息
        getSystemInfo() {
            return new Promise((req, rej) => {
                uni.getSystemInfo({
                    success: function(res) {
                        req(res)
                    }
                });
            })
        },

通过以上的方式,就可以生成海报图片了,点击海报也能预览,在预览页面长按就能把图片转发或者保存到电脑了,后面的操作无需任何方法。

整体页面的代码如下,直接复制过去,稍微调试下就能使用。

    <template>
<view class="demo">
    <canvas :style="{ width: canvasW + 'px', height: canvasH + 'px' }" canvas-id="myCanvas" id="myCanvas"
        @click="saveHB"></canvas>
</view>
</template>
<script>
export default {
    components: {},
    data() {
        return {
            canvasW: 0, // 画布宽
            canvasH: 0, // 画布高
            picWidth: '', //图片宽
            picHight: 260, //图片高
            SystemInfo: {}, // 设备信息
            goodsImg: {}, // 主图
            ewmImg: {}, // 二维码图片
            ewmW: 100, // 二维码大小
            title: '营销是做一切的事情,让客户来找我;销售是做一切的事情,我去找客户。',
            name: '周希奇', // 推荐人
            hbUrl: [], //储存生成的图片地址
        }
    },
    async onLoad(params) {
        //获取从上衣页面传过来的图片参数
        this.title = params.title
        this.url = params.url
        // 获取设备信息,获取设备的宽高,画布做一样的高度
        this.SystemInfo = await this.getSystemInfo();
        console.log('this.SystemInfo', this.SystemInfo)
        this.canvasW = this.SystemInfo.windowWidth; // 画布宽度
        this.canvasH = this.SystemInfo.windowHeight - 150; //画布高度

        //获取背景图片的长与宽,按画布的比例进行缩放
        this.goodsImg = await this.getImageInfo(this.url);
        console.log('this.goodsImg', this.goodsImg)
        //图片的宽度等于整个宽度减掉两边的padding
        this.picWidth = this.canvasW - 20
        //图片的高度= this.picWidth * 实际的宽度/原来的高度
        this.picHight = this.picWidth * this.goodsImg.height / this.goodsImg.width
        console.log('画的图片宽度为:', this.picWidth)
        console.log('画的图片高度为:', this.picHight)

        //二维码的信息
        this.ewmImg = '/static/erweima.png';

        // 如果主图,二维码图片,设备信息都获取成功,开始绘制海报,这里需要用setTimeout延时绘制,否则可能会出现图片不显示。
        if (this.goodsImg.errMsg == 'getImageInfo:ok' && this
            .SystemInfo.errMsg == 'getSystemInfo:ok') {
            // console.log('ok')
            uni.showToast({
                icon: 'loading',
                mask: true,
                // duration: 5000,
                title: '海报绘制中',
            });
            setTimeout(() => {
                var ctx = uni.createCanvasContext('myCanvas', this);
                // 填充背景色,白色
                ctx.setFillStyle('#fff'); // 默认白色
                ctx.fillRect(0, 0, this.canvasW, this.canvasH) // fillRect(x,y,宽度,高度)

                // 绘制商品主图,二维码
                ctx.drawImage(this.goodsImg.path, 10, 10, this.picWidth, this.picHight)
                ctx.drawImage(this.ewmImg, this.canvasW - 110, this.picHight + 45, this.ewmW, this.ewmW)
                // 3、绘制商品标题,多余文字自动换行
                ctx.setFontSize(18); // setFontSize() 设置字体字号
                ctx.setFillStyle('#000000'); // setFillStyle() 设置字体颜色
                /* str 这段代码是我百度找的,参考别人的。canvas不能自动换行,需要自行计算 */
                let _strlineW = 0;
                let _strLastIndex = 0; //每次开始截取的字符串的索引
                // let _strHeight = this.canvasW * rant + 30; //绘制字体距离canvas顶部的初始高度
                let _strHeight = this.picHight + 70;
                let _num = 1;
                for (let i = 0; i < this.title.length; i++) {
                    _strlineW += ctx.measureText(this.title[i]).width;
                    // if (_strlineW > this.canvasW * rant - 155) {
                    if (_strlineW > this.canvasW - 155) {
                        if (_num == 4 && 4) {
                            //文字换行数量大于二进行省略号处理
                            ctx.fillText(this.title.substring(_strLastIndex, i - 5) + '...', 10,
                                _strHeight);
                            _strlineW = 0;
                            _strLastIndex = i;
                            _num++;
                            break;
                        } else {
                            ctx.fillText(this.title.substring(_strLastIndex, i), 20, _strHeight);
                            _strlineW = 0;
                            _strHeight += 20;
                            _strLastIndex = i;
                            _num++;
                        }
                    } else if (i == this.title.length - 1) {
                        ctx.fillText(this.title.substring(_strLastIndex, i + 1), 20, _strHeight);
                        _strlineW = 0;
                    }
                }
                /* end */

                ctx.draw(true, (ret) => { // draw方法 把以上内容画到 canvas 中。
                    uni.showToast({
                        icon: 'success',
                        mask: true,
                        title: '绘制完成',
                    });
                    uni.canvasToTempFilePath({ // 保存canvas为图片
                        canvasId: 'myCanvas',
                        quality: 1,
                        complete: (res) => {
                            this.hbUrl.push(res.tempFilePath)
                        },
                    })
                });
            }, 1000)
        } else {
            console.log('err')
        }

        uni.showShareMenu({
            withShareTicket: true,
            menus: ["shareAppMessage", "shareTimeline"]
        })
    },
    methods: {
        saveHB() {
            // console.log('点击了图片')
            uni.previewImage({
                urls: this.hbUrl
            })
        },

        // 获取图片信息
        getImageInfo(image) {
            return new Promise((req, rej) => {
                uni.getImageInfo({
                    src: image,
                    success: (res) => {
                        req(res)
                    },
                });
            })
        },

        // 获取设备信息
        getSystemInfo() {
            return new Promise((req, rej) => {
                uni.getSystemInfo({
                    success: function(res) {
                        req(res)
                    }
                });
            })
        },

        //保存相片到本地
        savePicture() {
            uni.saveImageToPhotosAlbum({
                filePath: this.hbUrl,
            })
        },
        //分享相片
        sharePicture() {
            uni.share({
                provider: 'weixin',
                imageUrl: this.hbUrl,
                type: 2,
                scene: 'WXSceneTimeline'
            })
        },

    },
}
</script>
<style lang="scss">
.button-container {
    display: flex;
    justify-content: center;
    position: fixed;
    top: 80%;
    height: 100rpx;
    width: 100%;

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

推荐阅读更多精彩内容