页面绘制 canvas 并转换成图片实现长按保存

前言

基于 uni-app 进行m端 app 开发的时候,碰上一个需求是将卡片弹窗生成图片,之前工作有用过 html2image 等插件,虽然简单便捷,但是经常会碰见一些奇奇怪怪的兼容性问题,因此此次需求采用手写 canvas 然后转成 base64 图片,杜绝一切 bug,直接无敌。

实现代码如下:

html:

页面添加一个 canvas 容器以及生成图片的 img 容器




css:

没啥好说的,就是 canvas 和 img 的定位和宽高,隐藏 canvas,图片放置在页面中间

#cardCanvas {
	width: 654rpx;
	height: 400rpx;
	position: absolute;
	// top: -99999rpx;
	// left: -99999rpx;
	// z-index: 9999;
}

.cardImage {
	width: 654rpx;
	height: 400rpx;
	// position: absolute;
	// top: 50%;
	// left: 50%;
	// transform: translate(-50%, -50%);
	top: 0;
	left: 0;
}

js:

data 里面为一些假数据和存放 base64 图片路径的变量(base64)

在 mounted 钩子中调用函数绘制 canvas

data() {
    return {
		showDiaCard: true,
		category: '服务类',
		amount: '10,200.00',
		cardNum: '0522 0522 0522 0522',
		base64: null
	}
},
mounted() {
    // 渲染 canvas 
	this.renderCanvas();
},

methods 方法中共用到三个函数方法

渲染 canvas 函数

Tips: 因为是 m 端,所以考虑适配兼容问题,所有单位都乘以 w,w 为获取的设备宽度除以 750,以此达到类似于 rem, rpx 的适配兼容效果:

// canvas 单位换算为 rpx

const system = uni.getSystemInfoSync()
const w = system.windowWidth / 750

console.log(w)

// 渲染 canvas
renderCanvas() {
	let imgW, imgH;
	const query = uni.createSelectorQuery().in(this);
	// canvas 单位换算为 rpx
	const system = uni.getSystemInfoSync()
	const w = system.windowWidth / 750
	console.log(w)
	// 获取canvas
	query.select('#cardCanvas').boundingClientRect(data => {
		imgW = data.width;
		imgH = data.height;
		//绑定画布
		var ctx = uni.createCanvasContext('myCanvas');
		// 填充图片
		ctx.drawImage('https://doc.shoufusheji.com/img/temp/20221128/qlcu3_dia-card-bg.png', 0, 0,	imgW, imgH);
		// 画出 icon 图标
		ctx.drawImage('https://doc.shoufusheji.com/img/temp/20221205/siz7o_dia-card-icon.png', 32 * w, 48 * w, 40 * w, 40 * w);
		// 菜花卡类别
		ctx.font = "normal bold 34px PingFang SC-Bold"
		ctx.setFillStyle('#fff');
		ctx.setFontSize(34 * w);
		ctx.fillText('菜花卡-' + this.category, 88 * w, 80 * w);
		// 菜花卡金额
		ctx.font = "normal bold 48px Inter-Bold"
		ctx.setFillStyle('#fff');
		ctx.setFontSize(48 * w);
		ctx.fillText(this.amount, 32 * w, 224 * w);
		// 画出二维码
		this.drawRoundRect(ctx, 20 * w, 468 * w, 134 * w, 146 * w, 146 * w, "https://doc.shoufusheji.com/img/temp/20221128/loogd_dia-card-ewm.png")
		// 卡密
		ctx.font = "normal bold 28px Inter-Bold"
		ctx.setFillStyle('#fff');
		ctx.setFontSize(28 * w);
		ctx.fillText("卡密:" + this.cardNum, 32 * w, 374 * w);
		//输出到画布中
		ctx.draw();
		// 显示 loading
		uni.showLoading({
			mask: true
		})
		// 不加延迟的话,base64有时候会赋予undefined
		setTimeout(() => {
			uni.canvasToTempFilePath({
				canvasId: 'myCanvas',
				success: (res) => {
					this.base64 = res.tempFilePath
				}
			})
			uni.hideLoading();
		}, 1200)
	}).exec();
},

保存 canvas 为 base64 图片函数

// 保存图片
saveImage() {
	uni.saveImageToPhotosAlbum({
		filePath: this.base64,
		success: (res) => {
			uni.showToast({
				title: '保存成功',
			})
		}
	})
},

因为二维码需要绘制圆角,所以加了个图片绘制圆角函数,在上面渲染 canvas 函数中有用到

// 二维码绘制圆角
/**
 * @param {Object} ctx: canvas实例
 * @param {Object} r: 圆弧的半径
 * @param {Object} x: 图片 x 轴坐标
 * @param {Object} y: 图片 y 轴坐标
 * @param {Object} w: 图片宽度
 * @param {Object} h: 图片高度
 * @param {Object} img: 图片路径
 */
drawRoundRect(ctx, r, x, y, w, h, img) {
	ctx.save();
	if (w < 2 * r) r = w / 2;
	if (h < 2 * r) r = h / 2;
	ctx.beginPath();
	ctx.moveTo(x + r, y);
    ctx.arcTo(x + w, y, x + w, y + h, r);
	ctx.arcTo(x + w, y + h, x, y + h, r);
	ctx.arcTo(x, y + h, x, y, r);
	ctx.arcTo(x, y, x + w, y, r);
	ctx.closePath();
	ctx.clip();
	ctx.drawImage(img, x, y, w, h);
	ctx.restore(); // 返回上一状态
},

你可能感兴趣的:(solve,uni-app,前端,uni-app)