当在网页中使用
<img>
标签加载图片并将其下载到本地时,下载的图片文件的宽高是图片的原始尺寸,而 不是<img>
标签在网页中显示的宽高。
当图片被绘制到<canvas>
上并转换成新的图片下载到本地时,下载的图片文件的宽高取决于<canvas>
的尺寸(即canvas.width
和canvas.height
),而不是原始图片的宽高。
图片要想不变形,就需要与原始图片的宽高比保持一致。
图片下载到本地想要是全屏显示,一般来说,只要宽高比能匹配主流手机,下载下来就会全屏显示。
<template>
<!-- <van-overlay> 是 vant 的蒙层 -->
<van-overlay :show="isShow">
<div class="container">
<canvas ref="canvasRef" class="canvas" :width="width" :height="height" />
<img :src="imgUrl" class="img" :width="width" :height="height" />
<div class="button" data-spm="长按保存图片" @click="handleSave">长按图片保存</div>
</div>
</van-overlay>
</template>
<script>
import Vue from 'vue';
import { Overlay } from 'vant';
import QRCode from 'qrcode';
import { getErrMsg } from '@/util/skinUtils';
Vue.use(Overlay);
export default {
name: 'CCBMDPosterPanel',
props: {
salesmanNumber: {
type: [String, Number],
default: '',
},
},
data() {
return {
width: 284,
height: 408,
canvas: null,
isShow: false,
imgUrl: '',
};
},
methods: {
async init() {
const loadingToast = this.$toast.loading({
message: '加载中...',
forbidClick: true,
duration: 0,
});
try {
// 1. 获取 canvas 对象
this.canvas = this.$refs.canvasRef;
// 2. 获取设备像素比
const dpr = window.devicePixelRatio || 1;
// 3. 分别设置 canvas 的实际渲染像素和显示大小,这样是为了解决 canvas 绘制的图像再高清屏下会变得模糊的问题
this.canvas.width = this.width * dpr;
this.canvas.height = this.height * dpr;
this.canvas.style.width = `${this.width}px`;
this.canvas.style.height = `${this.height}px`;
const ctx = this.canvas.getContext('2d');
// 4. 缩放上下文以保持逻辑坐标不变
ctx.scale(dpr, dpr);
// 5. 生成背景图,将其绘制成圆角,绘制到 canvas 上
const bgUrl = 'https://gt-omp.g-town.com.cn/template-market/evt/ccbmd/poster.png';
const bgImg = await this.handleImgLoad(bgUrl, { width: this.width, height: this.height });
this.handleImgRounded(ctx, bgImg, 0, 0, this.width, this.height, 18);
// 6. 生成二维码,绘制到 canvas 上
const qrcodeUrl = await this.getQrcodeUrl();
const qrcodeImg = await this.handleImgLoad(qrcodeUrl, { width: 64, height: 64 });
ctx.drawImage(qrcodeImg, 196, 327, 64, 64);
// 7. 将 canvas 转换成 Base64 URL
this.imgUrl = this.canvas.toDataURL('image/png', 1.0);
loadingToast.clear();
} catch (err) {
loadingToast.clear();
this.$toast(getErrMsg(err));
}
},
// 生成二维码
async getQrcodeUrl() {
const url = `${location.href.replace('ccbSalesman', 'ccbCombo')}&salesmanNumber=${this.salesmanNumber}`;
const qrcodeUrl = await QRCode.toDataURL(url, {
width: 64,
height: 64,
margin: 2,
errorCorrectionLevel: 'H',
});
return qrcodeUrl;
},
// 绘制带圆角的图像,并将其绘制到 canvas 上
handleImgRounded(ctx, img, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.save();
ctx.clip();
ctx.drawImage(img, x, y, width, height);
ctx.restore();
},
// 生成图像
handleImgLoad(url, { width, height }) {
return new Promise((resolve, reject) => {
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.width = width;
img.height = height;
img.src = url;
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('图片绘制出错,请稍后再试'));
});
},
handleSave() {
const link = document.createElement('a');
link.href = this.imgUrl;
link.download = 'poster.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.handlePanelShow(false);
},
handlePanelShow(value) {
if (value) {
this.init();
}
this.isShow = value;
},
},
};
</script>
<style scoped lang="scss">
.container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
.canvas {
display: none;
}
.img {
background: transparent;
}
.button {
box-sizing: border-box;
width: 266px;
height: 40px;
background-image: url('https://gt-omp.g-town.com.cn/template-market/evt/ccbmd/home-btn-bg.png');
background-size: 100% 100%;
border-radius: 20px;
margin-top: 20px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 17px;
font-weight: 400;
text-shadow: 0px 2px 3px 0px rgba(255, 53, 47, 0.3);
}
}
</style>