草庐IT

uniapp 手写canvas海报(兼容android/ios/h5/微信小程序)

爱学习的蒋大奘 2023-04-04 原文

先上成功图

1.在父组件里面定义弹出层,并且调用子组件制作海报的方法

2.点击显示二维码调用子组件海报方法

showPoster(customerPostId) {
            // console.log(this.$refs.positionPoster)
            this.$refs.positionPoster.createPoster(customerPostId);
        }

3.mounted方法里面获取手机屏幕的宽度,并且设置canvas的大小

 uni.getSystemInfo({
            success: res => {
                this.canvasStyle.width = res.screenWidth * 0.9 + 'px';
                this.screenWidth = res.screenWidth * 0.9;
            }
        });

4.调用createPoster获取小程序二维码(小程序地址必须为https,并且需要在小程序管理后台配置白名单,不然downloadFile无效)

5.使用的uview2.0组件库的弹出层,写了二个弹出层,一个是新生成的海报,另一个是生成之后不需要再次生成,直接显示,减少用户等待时间

<template>
    <view>
        <u-popup :show="showCanvas" :customStyle="{ width: canvasStyle.width, 'padding-bottom': '20rpx' }" mode="center" round="10" class="position_r">
            <view class="position_a close-icon"><u-icon name="close-circle" color="#ffffff" size="25" @click="showCanvas = false"></u-icon></view>
            <canvas :style="{ height: canvasStyle.height, width: canvasStyle.width }" class="whiteBg" canvas-id="myCanvas" @longpress="saveImage"></canvas>
            <u-button type="primary" size="mini" icon="photo" color="rgb(38,78,203)" :customStyle="{ margin: 'auto', width: '200rpx', height: '60rpx' }" @click="saveImage">
                生成图片
            </u-button>
        </u-popup>
        <u-popup :show="showCanvasImg" :customStyle="{ width: canvasStyle.width, 'padding-bottom': '20rpx' }" mode="center" round="10" class="position_r">
            <view class="position_a close-icon"><u-icon name="close-circle" color="#ffffff" size="25" @click="showCanvasImg = false"></u-icon></view>
            <image :src="posterImage" :style="{ height: canvasStyle.height, width: canvasStyle.width }" style="display: block;" @longpress="saveImage"></image>
            <u-button type="primary" size="mini" icon="photo" color="rgb(38,78,203)" :customStyle="{ margin: 'auto', width: '200rpx', height: '60rpx' }" @click="saveImage">
                生成图片
            </u-button>
        </u-popup>
    </view>
</template>
<script>
import { queryCustomerPostInfoApi } from '@/apis/customer-position.js';
import { getSalaryType, getEducationType, getWorkYearType } from '@/filter/filer.js';
import { base64ToPath, savePicture } from '@/utils/file-utils.js';
export default {
    data() {
        return {
            positionInfo: {},
            canvasStyle: {
                height: '0rpx',
                width: '0rpx'
            },
            imgUrl: 'https://bjbztest.oss-cn-qingdao.aliyuncs.com/master-su-core/image/position-bg.png',
            wxCodeUrl: '',
            screenWidth: null,
            posterImage: null,
            customerPostId: 0,
            showCanvas: false,
            showCanvasImg: false
        };
    },

 6.makePoster方法里面获取背景图片的长宽,算出长宽比,设置canvas的高度,创建canvas的content,this必须添加,且指向vue实列(注意指向问题),不然canvas出不来,ctx.rect设置整体画布大小,ctx.setFillStyle设置背景色,并且填充ctx.fill(),drawImage可以使用本地图片,但是h5对于本地大的图片显示不出来我的超过1M就显示不出来,这边用的是网络图片(网络图片小程序需要配置白名单)

makePoster(customerPostId) {
            uni.showLoading({
                title: '海报生成中'
            });
            let that = this;
            uni.getImageInfo({
                src: this.imgUrl,
                success: image => {
                    // console.log(image);
                    const prop = image.width / image.height;
                    // 算出底部需要的高度
                    if (this.positionInfo.content.length > Math.ceil((this.screenWidth - 40) / 16) * 3) {
                        this.canvasStyle.height = this.screenWidth / prop + 250 + 'px';
                        var ctx = uni.createCanvasContext('myCanvas', this);
                        ctx.rect(0, 0, this.screenWidth, this.screenWidth / prop + 250);
                    } else {
                        let ceil_integer = Math.ceil(this.positionInfo.content.length / Math.ceil((this.screenWidth - 40) / 16));
                        this.canvasStyle.height = this.screenWidth / prop + 190 + ceil_integer * 20 + 'px';
                        var ctx = uni.createCanvasContext('myCanvas', this);
                        ctx.rect(0, 0, this.screenWidth, this.screenWidth / prop + 190 + ceil_integer * 20);
                    }

                    ctx.setFillStyle('white');
                    ctx.fill();
                    // 画布尺寸
                    // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
                    // 背景
                    ctx.drawImage(image.path, 0, 0, this.screenWidth, this.screenWidth / prop);

                    ctx.font = '25px Arial';
                    ctx.fillStyle = '#ffffff';
                    // ctx.fillStyle = '#000000';
                    ctx.fillText('优质岗位', this.screenWidth / prop / 5, this.screenWidth / prop / 2 - 2.5);
                    ctx.fillText('职等你来', this.screenWidth / prop / 5, this.screenWidth / prop / 2 + 27.5);
                    ctx.drawImage(
                        this.wxCodeUrl,
                        this.screenWidth / 2 + this.screenWidth / prop / 8,
                        this.screenWidth / prop / 2 - this.screenWidth / 8,
                        this.screenWidth / 4,
                        this.screenWidth / 4
                    );

7.设置你想生成的canvas图

ctx.font = 'bold 18px Arial';
                    ctx.fillStyle = '#000000';
                    ctx.fillText(this.positionInfo.postName, 20, this.screenWidth / prop + 36);

                    ctx.font = 'bold 20px Arial';
                    ctx.fillStyle = 'red';
                    ctx.fillText(
                        this.positionInfo.salaryValueMinWithUnit + '~' + this.positionInfo.salaryValueMaxWithUnit + '/' + this.positionInfo.salaryTypeValue,
                        20,
                        this.screenWidth / prop + 74
                    );

                    ctx.font = '14px Arial';
                    ctx.fillStyle = '#666666';
                    ctx.fillText(
                        this.positionInfo.age + ' | ' + this.positionInfo.educationTypeValue + ' | ' + this.positionInfo.workYearTypeValue,
                        20,
                        this.screenWidth / prop + 106
                    );

                    ctx.drawImage('/static/address.png', 20, this.screenWidth / prop + 134, 13, 16);

                    ctx.font = '14px Arial';
                    ctx.fillStyle = '#666666';
                    ctx.fillText(this.positionInfo.province + '/' + this.positionInfo.city + '/' + this.positionInfo.area, 43, this.screenWidth / prop + 138);

                    ctx.fillText(this.positionInfo.address, 43, this.screenWidth / prop + 156);

                    // console.log(this.screenWidth);
                    // console.log(this.screenWidth - 40);
                    // console.log(Math.trunc((this.screenWidth - 40) / 16) * 3 - 1);

 8.这边因为无法判断岗位的content,需要手动算他的高度和位置(这个和开始的if判断一样,判断长度,然后设置cavans的高度),这边我向上取整,多留一个字

ctx.font = '16px Arial';
                    ctx.fillStyle = '#666666';
                    let str;
                    // 超过三行需要截取
                    if (this.positionInfo.content.length > Math.ceil((this.screenWidth - 40) / 16) * 3) {
                        str = this.positionInfo.content.substring(0, Math.ceil((this.screenWidth - 40) / 16) * 3 - 1) + '...';
                        for (let i = 0; i < 3; i++) {
                            let newStr;
                            if (i == 2) {
                                newStr = str.substring(Math.ceil((this.screenWidth - 40) / 16) * i, str.length);
                            } else {
                                newStr = str.substring(Math.ceil((this.screenWidth - 40) / 16) * i, Math.ceil((this.screenWidth - 40) / 16) * (i + 1));
                            }
                            ctx.fillText(newStr, 20, this.screenWidth / prop + 190 + i * 20);
                        }
                    } else {
                        // 未超三行自动截取换行
                        str = this.positionInfo.content;
                        let ceil_integer = Math.ceil(str.length / Math.ceil((this.screenWidth - 40) / 16));
                        for (let i = 0; i < ceil_integer; i++) {
                            let newStr = str.substring(Math.ceil((this.screenWidth - 40) / 16) * i, Math.ceil((this.screenWidth - 40) / 16) * (i + 1));
                            ctx.fillText(newStr, 20, this.screenWidth / prop + 190 + i * 20);
                        }
                    }

 9.这边画完,显示弹出层,这边一定要加定时器,因为有的还没画上去加个延时的功能,反正有loading没事,draw方法设为fasle,这样他会覆盖之前的canvas,之后canvas转path,保存下来

this.showCanvas = true;
                    // 开始绘画,必须调用这一步,才会把之前的一些操作实施
                    setTimeout(() => {
                        ctx.draw(false, ret => {
                            uni.hideLoading();
                            // setTimeout(() => {
                            uni.canvasToTempFilePath(
                                {
                                    canvasId: 'myCanvas',
                                    success: res => {
                                        // console.log(res);
                                        this.posterImage = res.tempFilePath;
                                        this.customerPostId = customerPostId;
                                    },
                                    fail: err => {
                                        // console.log(err);
                                        uni.showToast({
                                            title: '名片加载失败',
                                            icon: 'error'
                                        });
                                    }
                                },
                                this
                            );
                            // }, 500);
                        });
                    }, 500);
                }
            });
        },

10.点击保存图片的时候,H5的uni.saveImageToPhotosAlbum是不支持的,你的写个自己的下载(canvas生成的是base64的)

saveImage() {
            if (this.posterImage) {
                // #ifdef H5
                // console.log(this.posterImage);
                savePicture(this.posterImage);
                // let picUrl = base64ToPath(this.posterImage);
                // console.log(picUrl);
                // #endif
                // #ifndef H5
                uni.saveImageToPhotosAlbum({
                    filePath: this.posterImage,
                    success: res => {
                        // console.log(res);
                        this.showCanvas = false;
                        this.showCanvasImg = false;
                        uni.showToast({
                            title: '海报已保存,快去分享给好友吧。',
                            icon: 'none'
                        });
                    },
                    fail: err => {
                        // console.log(err);
                        if (
                            err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' ||
                            err.errMsg === 'saveImageToPhotosAlbum:fail auth deny' ||
                            err.errMsg === 'saveImageToPhotosAlbum:fail authorize no response'
                        ) {
                            uni.showModal({
                                title: '提示',
                                content: '需要您授权保存相册',
                                showCancel: false,
                                success: modalSuccess => {
                                    // #ifndef APP-PLUS||H5
                                    uni.openSetting({
                                        success(settingdata) {
                                            // console.log('settingdata', settingdata);
                                            if (settingdata.authSetting['scope.writePhotosAlbum']) {
                                                uni.showModal({
                                                    title: '提示',
                                                    content: '获取权限成功,再次点击图片即可保存',
                                                    showCancel: false
                                                });
                                            } else {
                                                uni.showModal({
                                                    title: '提示',
                                                    content: '获取权限失败,将无法保存到相册哦~',
                                                    showCancel: false
                                                });
                                            }
                                        }
                                    });
                                    // #endif
                                }
                            });
                        }
                    }
                });
                // #endif
            } else {
                uni.showToast({
                    title: '海报生成有误!',
                    icon: 'error'
                });
            }
        }

export function savePicture(base64) {
    var arr = base64.split(',');
    var bytes = atob(arr[1]);
    let ab = new ArrayBuffer(bytes.length);
    let ia = new Uint8Array(ab);
    for (let i = 0; i < bytes.length; i++) {
        ia[i] = bytes.charCodeAt(i);
    }
    var blob = new Blob([ab], { type: 'application/octet-stream' });
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = new Date().valueOf() + ".png";
    var e = document.createEvent('MouseEvents');
    e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    a.dispatchEvent(e);
    URL.revokeObjectURL(url);
}

11.微信小程序因为canvas原生层级的问题,导致之前定位的关闭图表总是被覆盖住,我写个条件编译改下定位的位置(使用了cover-view标签包裹,好像没有生效,就写了这个过渡方案,产品认可那就是没问题了,哈哈哈)

 

.close-icon {
    /* #ifdef APP-PLUS||H5 */
    right: 20rpx;
    top: 20rpx;
    /* #endif */
    /* #ifndef APP-PLUS||H5 */
    right: 0;
    top: -60rpx;
    /* #endif */
    z-index: 9;
}
.safe-button {
    background-color: rgb(38, 78, 203);
    border-radius: 6rpx;
    padding: 4rpx 16rpx;
}

 12.下班

 

 

 

 

有关uniapp 手写canvas海报(兼容android/ios/h5/微信小程序)的更多相关文章

  1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  2. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  3. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  4. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  5. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  6. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  7. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  8. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  9. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  10. ruby-on-rails - 不兼容的库版本 : nokogiri. bundle 需要 8.0.0 或更高版本,但 libiconv.2.dylib 提供 7.0.0 版本 - 2

    为了在我的mac上为一个rails项目安装mysql,我遵循了安装Homebrew软件和删除mac端口的在线建议。这是问题开始的地方。rails项目不会构建,我得到这个:[rake--prereqs]rakeaborted!dlopen(/Users/Parker/.rvm/gems/ruby-1.9.3-p448/gems/nokogiri-1.6.0/lib/nokogiri/nokogiri.bundle,9):Librarynotloaded:/opt/local/lib/libiconv.2.dylibReferencedfrom:/Users/Parker/.rvm/gem

随机推荐