uniapp使用Canvas生成电子名片
工作中有生成电子名片的一个需求,刚刚好弄了发一下分享分享
文章目录
前言
先看效果
一、上代码?
不对不对应该是上才艺,哈哈哈
html
<template>
<view class="container">
<view class="head" style="height: 7rem;">
<view style="color:#fff;position: fixed;top: 4rem;left: 1rem;">
<br>
<text style="font-size: 11px;"></text>
</view>
</view>
<view style="width: 100%; height: 7.5rem;"></view>
<scroll-view scroll-y style="height: 100vh;" class="content">
<view style="padding-top: 30rpx;">
<!-- 立体名片卡片 -->
<view class="card-container" id="cardContainer">
<view class="business-card">
<!-- 顶部彩色边缘标签 -->
<view class="card-edge"></view>
<view class="card-content">
<view class="card-title">裤架工厂</view>
<view class="card-subtitle">专做软体沙发</view>
<view class="card-details">
<view class="detail-item">
<view>高级产品设计师</view>
</view>
<view class="detail-item">
<view>创新事业部 | ABC科技</view>
</view>
<view class="detail-item">
<view>工厂地址:上海浦东新区三林东地铁左边大厦23</view>
</view>
</view>
</view>
<!-- 双二维码容器 -->
<view class="qrcode-column">
<view class="qrcode-item">
<view class="card-image-container">
<image class="card-image"
src="https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png"
mode="aspectFit"></image>
</view>
<view class="qrcode-label">微信联系</view>
</view>
<view class="qrcode-item">
<view class="card-image-container">
<image class="card-image"
src="https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png"
mode="aspectFit"></image>
</view>
<view class="qrcode-label">官方网站</view>
</view>
</view>
<!-- 角落装饰 -->
<view class="card-corner corner-tl"></view>
<view class="card-corner corner-br"></view>
</view>
</view>
<view class="save-btn" @click="saveCardImage">
<text>保存名片图片</text>
</view>
</view>
</scroll-view>
<canvas canvas-id="cardCanvas"
:style="{ position: 'absolute', left: '-9999px', width: canvasWidth + 'px', height: canvasHeight + 'px' }"></canvas>
</view>
</template>
<script>
export default {
data() {
return {
canvasWidth: 1100,
canvasHeight: 630,
dpr: 1,
qrcode1: 'https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png',
qrcode2: 'https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/app_20230925_20250529092745.png',
backgroundImage: 'https://linda-uni.oss-cn-guangzhou.aliyuncs.com/images/and/550e8a1a-e450-4880-9441-33608476b32a.jpg'
}
},
mounted() {
},
methods: {
// 保存名片为图片
async saveCardImage() {
uni.showLoading({
title: '生成中...',
mask: true
});
try {
// 1. 绘制Canvas
await this.drawCardCanvas();
// 2. 生成临时图片路径
const tempPath = await new Promise((resolve, reject) => {
uni.canvasToTempFilePath({
canvasId: 'cardCanvas',
quality: 1,
fileType: 'png',
width: this.canvasWidth * this.dpr,
height: this.canvasHeight * this.dpr,
destWidth: this.canvasWidth * this.dpr,
destHeight: this.canvasHeight * this.dpr,
success: res => resolve(res.tempFilePath),
fail: err => reject(err)
});
});
// 3. 保存到相册
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({
filePath: tempPath,
success: () => resolve(),
fail: err => reject(err)
});
});
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
} catch (err) {
uni.hideLoading();
uni.showToast({
title: '保存失败: ' + (err.errMsg || err),
icon: 'none'
});
console.error('保存失败:', err);
}
},
// 绘制名片到Canvas
async drawCardCanvas() {
const ctx = uni.createCanvasContext('cardCanvas', this);
const dpr = this.dpr;
const width = this.canvasWidth;
const height = this.canvasHeight;
const padding = 30 * dpr;
// 清除画布
ctx.clearRect(0, 0, width, height);
// 1. 绘制背景
// ctx.setFillStyle('#1F1F1F');
// ctx.fillRect(0, 0, width, height);
// 2. 绘制名片背景
const cardWidth = width - padding * 2;
const cardHeight = height - padding * 2;
// 绘制名片背景
if (this.backgroundImage) {
// 使用图片背景
try {
await this.drawRoundedImage(
ctx,
this.backgroundImage,
padding,
padding,
cardWidth,
cardHeight,
20 * dpr
);
} catch (e) {
console.error('背景图片加载失败:', e);
// 回退到默认背景
this.drawDefaultBackground(ctx, padding, cardWidth, cardHeight);
}
} else {
// 使用颜色背景
this.drawDefaultBackground(ctx, padding, cardWidth, cardHeight);
}
// // 渐变背景
// const gradient = ctx.createLinearGradient(0, 0, cardWidth, cardHeight);
// gradient.addColorStop(0, '#302f30');
// gradient.addColorStop(1, '#282728');
// ctx.setFillStyle(gradient);
// // 圆角矩形
// this.drawRoundedRect(ctx, padding, padding, cardWidth, cardHeight, 20 * dpr);
// ctx.fill();
// 3. 顶部彩色边缘(带圆角)
const topHeight = 16 * dpr; // 边缘高度
const topRadius = 20 * dpr; // 圆角半径
// 创建渐变
const edgeGradient = ctx.createLinearGradient(padding, padding, padding + cardWidth, padding);
edgeGradient.addColorStop(0, '#ff9a9e');
edgeGradient.addColorStop(0.3, '#fad0c4');
edgeGradient.addColorStop(0.6, '#a18cd1');
edgeGradient.addColorStop(1, '#fbc2eb');
ctx.setFillStyle(edgeGradient);
// 绘制带圆角的顶部边缘(仅左上和右上圆角)
ctx.beginPath();
// 从左上圆角结束点开始
ctx.moveTo(padding + topRadius, padding);
// 绘制上边缘线
ctx.lineTo(padding + cardWidth - topRadius, padding);
// 绘制右上圆角
ctx.arcTo(
padding + cardWidth, padding,
padding + cardWidth, padding + topHeight,
topRadius
);
// 绘制右边缘
ctx.lineTo(padding + cardWidth, padding + topHeight);
// 绘制下边缘(向左)
ctx.lineTo(padding, padding + topHeight);
// 绘制左边缘(向上)
ctx.lineTo(padding, padding + topRadius);
// 绘制左上圆角
ctx.arcTo(
padding, padding,
padding + topRadius, padding,
topRadius
);
ctx.closePath();
ctx.fill();
// 4. 设置字体
ctx.setFontSize(40 * dpr);
ctx.setFillStyle('#f0f0f0');
ctx.fillText('裤架工厂', padding + 40 * dpr, padding + 80 * dpr);
ctx.setFontSize(28 * dpr);
ctx.setFillStyle('#CCCCCC');
ctx.fillText('专做软体沙发', padding + 40 * dpr, padding + 130 * dpr);
// 绘制下划线
ctx.setStrokeStyle('#ff9a9e');
ctx.setLineWidth(6 * dpr);
ctx.beginPath();
ctx.moveTo(padding + 40 * dpr, padding + 90 * dpr);
ctx.lineTo(padding + 120 * dpr, padding + 90 * dpr);
ctx.stroke();
// 5. 绘制详情信息
const details = [
'高级产品设计师',
'创新事业部 | ABC科技',
'工厂地址:上海浦东新区三林东地铁左边大厦23'
];
ctx.setFontSize(28 * dpr);
ctx.setFillStyle('#b0b0b0');
details.forEach((text, i) => {
ctx.fillText(text, padding + 40 * dpr, padding + 200 * dpr + i * 50 * dpr);
});
// 6. 绘制二维码 (修改部分开始)
const qrSize = cardWidth * 0.12; // 缩小二维码尺寸
const qrY = padding + 350 * dpr;
const qrSpacing = 30 * dpr; // 二维码间距
const qrRadius = 20 * dpr; // 圆角半径
const wxX = padding + 40 * dpr;
await this.drawRoundedImage(
ctx,
this.qrcode1,
wxX,
qrY,
qrSize,
qrSize,
qrRadius
);
// 官网二维码 (靠左,在微信二维码右侧)
const webX = wxX + qrSize + qrSpacing;
await this.drawRoundedImage(
ctx,
this.qrcode2,
webX,
qrY,
qrSize,
qrSize,
qrRadius
);
ctx.setFontSize(24 * dpr);
ctx.setFillStyle('#e0e0e0');
// 微信文字居中
ctx.setTextAlign('center');
ctx.fillText('客户群', wxX + qrSize / 2, qrY + qrSize + 30 * dpr);
// 官网文字居中
ctx.setTextAlign('center');
ctx.fillText('国内站', webX + qrSize / 2, qrY + qrSize + 30 * dpr);
// 恢复对齐方式
ctx.setTextAlign('left');
// 执行绘制
ctx.draw();
// 返回绘制完成的Promise
return new Promise(resolve => {
setTimeout(resolve, 500);
});
},
// 新增方法:绘制圆角图片
async drawRoundedImage(ctx, src, x, y, width, height, radius) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: src,
success: (res) => {
// 保存当前状态
ctx.save();
// 创建圆角矩形路径
this.drawRoundedRect(ctx, x, y, width, height, radius);
ctx.clip(); // 裁剪为圆角矩形
// 绘制图片
ctx.drawImage(res.path, x, y, width, height);
// 恢复状态
ctx.restore();
resolve();
},
fail: reject
});
});
},
// 工具方法:绘制圆角矩形
drawRoundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.arcTo(x + width, y, x + width, y + radius, radius);
ctx.lineTo(x + width, y + height - radius);
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
ctx.lineTo(x + radius, y + height);
ctx.arcTo(x, y + height, x, y + height - radius, radius);
ctx.lineTo(x, y + radius);
ctx.arcTo(x, y, x + radius, y, radius);
ctx.closePath();
},
drawDefaultBackground(ctx, padding, cardWidth, cardHeight) {
const gradient = ctx.createLinearGradient(0, 0, cardWidth, cardHeight);
gradient.addColorStop(0, '#302f30');
gradient.addColorStop(1, '#282728');
ctx.setFillStyle(gradient);
this.drawRoundedRect(ctx, padding, padding, cardWidth, cardHeight, 20);
ctx.fill();
},
// 工具方法:绘制装饰角
drawCorner(ctx, x, y, size, position) {
ctx.setStrokeStyle('#ff9a9e');
ctx.setLineWidth(3 * this.dpr);
ctx.beginPath();
// 圆角半径
const radius = 12 * this.dpr;
if (position === 'tl') {
// 左上角 - 包围式圆角
ctx.moveTo(x - size, y);
ctx.lineTo(x, y);
ctx.lineTo(x, y - size);
// 添加圆角效果
ctx.moveTo(x, y - radius);
ctx.arc(x + radius, y - radius, radius, Math.PI, 1.5 * Math.PI);
} else if (position === 'br') {
// 右下角 - 包围式圆角
ctx.moveTo(x + size, y);
ctx.lineTo(x, y);
ctx.lineTo(x, y + size);
// 添加圆角效果
ctx.moveTo(x, y + radius);
ctx.arc(x - radius, y + radius, radius, 0.5 * Math.PI, Math.PI);
}
ctx.stroke();
},
// 工具方法:绘制图片(支持网络图片)
drawImage(ctx, src, x, y, width, height) {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: src,
success: res => {
ctx.drawImage(res.path, x, y, width, height);
resolve();
},
fail: (err) => {
console.error('图片加载失败:', err);
reject(err);
}
});
});
}
},
}
</script>
<style>
page {
background-color: #1F1F1F;
}
.container {
position: relative;
height: 100vh;
width: 100vw;
overflow: hidden;
}
.head {
width: 100%;
position: fixed;
top: 0px;
left: 0%;
z-index: 99999;
}
/* 新增样式 */
.card-container {
position: relative;
width: 88%;
margin: 0 auto 40rpx;
perspective: 1000px;
}
.business-card {
background: linear-gradient(145deg, #302f30, #282728);
border-radius: 20rpx;
padding: 30rpx;
/* display: flex; */
justify-content: space-between;
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.4);
position: relative;
overflow: hidden;
transform: translateY(0);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.business-card:active {
transform: translateY(-10rpx);
box-shadow: 0 15rpx 40rpx rgba(0, 0, 0, 0.6);
}
/* 边缘装饰效果 */
.card-edge {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 16rpx;
background: linear-gradient(90deg, #ff9a9e, #fad0c4, #a18cd1, #fbc2eb);
border-radius: 20rpx 20rpx 0 0;
}
.card-corner {
position: absolute;
width: 40rpx;
height: 40rpx;
border: 3rpx solid #ff9a9e;
}
.corner-tl {
top: 20rpx;
left: 20rpx;
border-right: none;
border-bottom: none;
border-top-left-radius: 12rpx;
}
.corner-br {
bottom: 20rpx;
right: 20rpx;
border-left: none;
border-top: none;
border-bottom-right-radius: 12rpx;
}
.card-content {
flex: 1;
padding-right: 20rpx;
}
.card-title {
font-size: 40rpx;
font-weight: bold;
color: #f0f0f0;
margin-bottom: 16rpx;
position: relative;
display: inline-block;
}
.card-title::after {
content: '';
position: absolute;
bottom: -8rpx;
left: 0;
width: 80rpx;
height: 6rpx;
background: linear-gradient(90deg, #ff9a9e, #fad0c4);
border-radius: 4rpx;
}
.card-subtitle {
font-size: 28rpx;
color: #CCCCCC;
margin: 30rpx 0 20rpx;
line-height: 1.5;
}
.card-details {
display: flex;
flex-direction: column;
gap: 20rpx;
margin-top: 30rpx;
}
.detail-item {
display: flex;
align-items: center;
font-size: 28rpx;
color: #b0b0b0;
}
.detail-icon {
width: 50rpx;
color: #ff9a9e;
font-size: 32rpx;
margin-right: 15rpx;
}
.card-image-container {
flex-shrink: 0;
width: 180rpx;
height: 180rpx;
border-radius: 15rpx;
overflow: hidden;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.3);
border: 2rpx solid rgba(255, 255, 255, 0.1);
position: relative;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.card-image-container:active {
transform: scale(1.05);
box-shadow: 0 12rpx 25rpx rgba(0, 0, 0, 0.5);
}
.card-image {
width: 90%;
height: 90%;
display: block;
}
/* 双二维码样式 */
.qrcode-column {
display: flex;
/* flex-direction: column; */
/* align-items: center; */
/* justify-content: center; */
flex-shrink: 0;
width: 100%;
gap: 25rpx;
margin-top: 20rpx;
}
.qrcode-item {
display: flex;
flex-direction: column;
align-items: center;
}
.qrcode-label {
color: #e0e0e0;
font-size: 24rpx;
text-align: center;
margin-top: 10rpx;
font-weight: 500;
}
.save-btn {
margin: 40rpx auto;
padding: 20rpx 40rpx;
background: linear-gradient(90deg, #ff9a9e, #a18cd1);
color: white;
border-radius: 50rpx;
width: 60%;
text-align: center;
font-weight: bold;
box-shadow: 0 8rpx 20rpx rgba(161, 140, 209, 0.4);
}
</style>
总结
这里面就js 里面的有用,其他的我也没有删除,要用的可以自己删除就可以了,go,卷起来