H5 生成分享海报

当在网页中使用 <img> 标签加载图片并将其下载到本地时,下载的图片文件的宽高是图片的原始尺寸,而 不是 <img> 标签在网页中显示的宽高。
当图片被绘制到 <canvas> 上并转换成新的图片下载到本地时,下载的图片文件的宽高取决于 <canvas> 的尺寸(即 canvas.widthcanvas.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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值