uniapp 手写canvas海报(兼容android/ios/h5/微信小程序)
先上成功图
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/微信小程序)相关推荐
- android小程序源代码_我从 Android 转到微信小程序的思考
大家好,好久不见,我是陈宇明,公众号「码个蛋」主理人. 由于最近工作比较忙,这两年来很少和大家分享自己的收获,期间大部分都是由「码个蛋」运营小组打理. 上个月我参加了腾讯官方举办的<小程序云开发 ...
- 服务器搭建快速入门——适用于Android应用服务器、微信小程序服务器(一)
目录 服务器搭建快速入门--适用于Android应用服务器.微信小程序服务器 适用人群 使用方案 准备工作 硬件方面 软件方面 开始搭建 服务器搭建快速入门--适用于Android应用服务器.微信小程 ...
- 华为Android 10手机微信小程序无法调起的问题解决办法
最近测试小程序发现华为Android 10手机微信小程序无法调起.网上查了一下有很多人都有类似的情况.于是开始排查自己,发现自己的小程序没有问题,是华为手机的一个系统特性. 解决办法: 1. 检查微信 ...
- iOS 唤起微信小程序
最近做了一个新功能.App里面点击按钮,唤起微信小程序. iOS 唤起微信小程序 App配置 微信开发者平台配置 方案1 sharesdk: 方案2:WechatOpenSDK(推荐) App配置 稍 ...
- 【微信小程序】使用uni-app——开发首页搜索框导航栏(可同时兼容APP、H5、小程序)
目录 前言 App.H5效果 小程序效果 一.兼容APP.H5的方式 二.兼容小程序 三.实现同时兼容 前言 首页都会提供一个搜索框给到客户,让客户自己去搜索自己想要的内容,这里就需要导航栏,来实现搜 ...
- uniapp引入自定义组件canvas 不现实,运行到微信小程序端时会报错
问题1 在引入自定义canvas组件时,在微信开发者工具中为空白,和h5中不现实 原因 在微信开发者工具 错误案例,没有加实例化this 在h5中不实现为空白是没有onReady里面实例化canvas ...
- dakai微信小程序 ios_【iOS】微信小程序打开APP到底是怎么回事?
前言 从苹果官方来看,小程序新增了两个功能: 1. 支持打开移动应用 2. 标题栏区域开放自定义 针对第二个功能,就是开发者可以自定义小程序菜单栏的颜色风格,根据需求,对小程序菜单外的标题区域进行自定 ...
- Android新技术——探秘微信小程序
一.什么是微信小程序? 小程序是一种不需要下载安装即可使用的应用,它实现了应用"触手可及"的梦想,用户扫一扫或搜一下即可打开应用.也体现了"用完即走"的理念,用 ...
- uniapp判断当前运行环境 app h5 微信小程序 百度小程序
hbuilderX最新版本现在已经支持在代码中获取当前所处环境 仅3.4.10+版本以上才支持,如果您的hbuilderX版本不是这个版本的需要先升级一下版本 hbuilderx下载 选择3.4.11 ...
最新文章
- SpringBoot第十六篇:用restTemplate消费服务
- 常州一院有全消化道的机器人的_【商务对接】昆山智能机器人及成套装备协会链接京东和智能制造...
- nginx 301重定向带www的https链接配置方法
- 数据中心如何减少人为故障发生率
- 从mysqldump整库备份文件中恢复单表
- linux snap安装redis-desktop-manager
- boost::describe模块和boost::json混合编程的测试程序
- C#实现整数冒泡排序、选择排序
- Python html 代码转成图片、PDF
- R语言利器之ddply
- 【复习】数学分析知识点梳理【思维导图】
- IIC详解之AT24C08
- python统计大写辅音字母_大写
- 计算机打开显示服务器无法运行,电脑中打开WMP播放器提示服务器运行失败如何解决...
- 关于使用Git pull出现冲突“error: Your local changes to the following files would be overwritten by merge”解决方案
- 四.电影/综艺网站(包含搜索经验, 我的经验, 必看) 彻底帮你解决看电影/综艺的问题
- UE4 VR 重置摄像机朝向
- 悉尼大学理学院计算机科学,悉尼大学理学院本科申请
- mysql存储函数中游标报错 No data - zero rows fetched, selected
- 这才是字节跳动(今日头条)面试?