在现代 Web 应用开发中,二维码的应用越来越广泛,从电子票务到信息传递,它都扮演着重要角色。本文将分享如何在 Vue 项目中,结合QRCode
库实现动态二维码的生成、与背景图合成以及图片下载功能,打造一个完整且实用的二维码展示模块,最终效果兼顾美观与交互性。
项目背景与需求分析
在我们的五龙山野生动物园票务项目中,需要为用户提供电子门票二维码展示页面。该页面不仅要展示清晰的门票二维码,还需结合动物园特色背景图,增强视觉体验。同时,为方便用户保存门票,需实现长按二维码保存合成图片的功能。这就涉及到二维码生成、图片合成以及用户交互逻辑的实现。
技术栈选择
本项目基于 Vue.js 框架进行开发,结合QRCode
库用于二维码的生成,利用原生 JavaScript 的 Canvas API 完成图片合成与绘制,确保功能的高效与稳定。
代码实现详解
1. 样式设计(<style>
部分)
css
* {
margin: 0;
padding: 0;
}
.yard_box {
width: 100%;
height: 100vh;
background-image: url('../assets/yard_back.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: relative;
}
.top_title {
width: 100%;
height: 50px;
display: flex;
}
.return_icon {
width: 40px;
height: 40px;
}
.return_box {
width: 40px;
height: 40px;
background-color: rgb(70, 58, 58);
border-radius: 50%;
opacity: 0.7;
margin-left: 2%;
margin-top: 2%;
}
.qr-canvas {
width: 200px;
height: 200px;
position: absolute;
left: 51%;
bottom: 180px;
/* 调整二维码在页面中的位置 */
transform: translateX(-50%);
cursor: pointer;
transition: transform 0.1s;
}
.qr-canvas:active {
transform: translateX(-50%) scale(0.98);
}
.save-hint {
position: absolute;
bottom: 50px;
left: 0;
right: 0;
text-align: center;
color: white;
font-size: 14px;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}
样式部分首先通过*
选择器重置默认样式,消除内外边距。yard_box
类为整个页面设置了全屏背景图,营造沉浸式视觉效果。top_title
与return_box
实现了返回按钮的布局与样式,qr-canvas
类定义了二维码画布的初始位置、大小及交互效果,通过active
伪类实现按压时的缩放动效,提升交互体验。
2. 模板搭建(<template>
部分)
html
<template>
<div class="yard_box">
<div class="top_title" @click="$router.go(-1)">
<div class="return_box">
<img src="../assets/left.svg" alt="" class="return_icon">
</div>
</div>
<!-- 显示二维码的画布 -->
<canvas ref="qrCanvas" @mousedown="startLongPress" @mouseup="cancelLongPress" @mouseleave="cancelLongPress"
@touchstart="startLongPress" @touchend="cancelLongPress" @touchcancel="cancelLongPress"
class="qr-canvas"></canvas>
</div>
</template>
模板中,外层yard_box
包裹页面所有元素,top_title
绑定点击事件实现返回上一页功能。<canvas>
标签用于绘制二维码,同时绑定了鼠标和触摸事件,以兼容 PC 端与移动端的长按操作,为后续的保存功能提供交互基础。
3. 逻辑实现(<script>
部分)
javascript
import QRCode from 'qrcode';
import yardBack from '@/assets/yard_back.png'; // 使用ES模块导入
export default {
data() {
return {
ticketId: '35',
longPressTimer: null,
longPressDuration: 1000, // 长按时间阈值(1秒)
backgroundImage: new Image() // 用于合成图片的背景图
};
},
methods: {
async loadBackgroundImage() {
return new Promise((resolve) => {
this.backgroundImage.src = yardBack; // 使用导入的图片路径
this.backgroundImage.onload = resolve;
});
},
// 生成二维码
generateQRCode() {
const canvas = this.$refs.qrCanvas;
QRCode.toCanvas(canvas, this.ticketId, {
width: 200,
margin: 2
}, (error) => {
if (error) console.error('二维码生成失败:', error);
});
},
// 开始长按
startLongPress() {
this.longPressTimer = setTimeout(() => {
this.saveMergedImage();
}, this.longPressDuration);
},
// 取消长按
cancelLongPress() {
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
},
// 创建并保存合并后的图片
async saveMergedImage() {
// 创建临时Canvas用于合成
const mergedCanvas = document.createElement('canvas');
const ctx = mergedCanvas.getContext('2d');
// 设置Canvas尺寸与背景图一致
mergedCanvas.width = this.backgroundImage.width;
mergedCanvas.height = this.backgroundImage.height;
// 1. 绘制背景图
ctx.drawImage(this.backgroundImage, 0, 0, mergedCanvas.width, mergedCanvas.height);
// 2. 计算白色区域的位置和大小(需要根据实际背景图调整)
const whiteArea = {
x: mergedCanvas.width * 0.2, // 白色区域左侧位置(20%宽度处)
y: mergedCanvas.height * 0.45, // 白色区域顶部位置(60%高度处)
width: mergedCanvas.width * 0.6, // 白色区域宽度(60%宽度)
height: mergedCanvas.height * 0.3 // 白色区域高度(30%高度)
};
// 3. 计算二维码在白色区域中的合适大小和位置
const qrCanvas = this.$refs.qrCanvas;
const qrMaxSize = Math.min(whiteArea.width, whiteArea.height) * 0.8; // 二维码最大尺寸(白色区域的80%)
const qrSize = Math.min(420, qrMaxSize); // 不超过200px
// 计算居中位置
const qrX = whiteArea.x + (whiteArea.width - qrSize) / 2;
const qrY = whiteArea.y + (whiteArea.height - qrSize) / 2;
// 绘制二维码
ctx.drawImage(qrCanvas, qrX, qrY, qrSize, qrSize);
// 4. 添加文字(调整到二维码上方)
const textY = qrY - 30;
ctx.font = 'bold 40px Arial';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.fillText('', mergedCanvas.width / 2, textY);
// 保存图片
const link = document.createElement('a');
link.download = `五龙山野生动物园门票-${this.ticketId}.png`;
link.href = mergedCanvas.toDataURL('image/png');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
},
async mounted() {
await this.loadBackgroundImage();
await this.generateQRCode();
}
};
在JavaScript
逻辑部分,首先导入QRCode
库和背景图片资源。data
中定义了门票 ID、长按计时器、长按阈值以及背景图片对象。
loadBackgroundImage
方法通过 Promise 封装图片加载过程,确保图片加载完成后再进行后续操作。generateQRCode
方法利用QRCode
库将门票 ID 生成到指定的<canvas>
上。
长按相关方法startLongPress
和cancelLongPress
分别用于启动长按计时和取消计时,当长按达到设定阈值时,触发saveMergedImage
方法。
saveMergedImage
方法是核心逻辑,创建新的<canvas>
用于合成图片,先绘制背景图,再计算并绘制白色区域、二维码,最后添加文字。通过创建<a>
标签,利用toDataURL
方法将合成后的图片转换为 URL,实现图片下载功能。mounted
钩子函数确保页面加载时完成背景图加载和二维码生成。
效果展示与优化方向
最终实现的页面,背景图与二维码完美融合,用户长按二维码即可保存包含背景、二维码和文字信息的合成图片。后续优化可以考虑:
- 动态数据绑定 :将
ticketId
改为动态获取,实现不同门票二维码的展示。 - 样式优化:根据实际背景图调整白色区域和文字样式,增强视觉美感。
- 错误处理:完善二维码生成和图片合成过程中的错误提示,提升用户体验。
通过以上步骤,我们成功在 Vue 项目中实现了一个功能完整、交互良好的二维码合成与下载模块。希望本文能为你的前端开发实践带来启发,在实际项目中灵活运用相关技术!