在前端生成H5二维码海报

海报图片生成前后端都能实现,个人喜欢在前端生成,主要是前端可以用html+css灵活的实现非常漂亮海报样式,后端比较麻烦,且前端也更便于调试,对于熟悉前端代码的小伙伴来说再好不过。

以下是在vue项目中的实现,非vue前端同理。

思路及步骤:

1. 用html实现海报效果

制作海报模板图,用js二维码库生成二维码,用CSS的绝对定位实现二维码处于模板图上一层

2. 将html转canvas

用到第三方库html2canvas,npm install html2canvas

3. 将canvas转图片

用到第三方库canvas2image,这个库是国人开发的,不过有点坑,它的源代码就是一个函数,并没有实现模块化就提交到了npmjs.com官网,导致install后也无法正常引入到项目中,比较的不规范。我是直接复制他的源代码到项目中使用。

 

html部分

        <el-button type="primary" @click="toShare()" size="small" icon="el-icon-share">生成海报</el-button>

    <!-- 海报生成html模板,为了不让海报显示在页面上,使用绝对定位,且让其漂移到页面外很远的地方 -->
    <div id="posterHtml" class="posterHtml" style="width:390px;height:693px;position:absolute;top:-10000px;left:-10000px;">
      <img
        :src="'https://oss-exam.chanjet.com/ckt_html/poster_template/ec_poster.jpg'"
        crossorigin="anonymous"
        style="width:inherit;height:inherit;position:absolute;z-index: 10;"
      >
      <div style="width:inherit;height:inherit;position:absolute;z-index: 20;display: flex;flex-direction:column;justify-content: flex-start;align-items: center;">
        <div style="font-size:18px;position:absolute;top:30px;width:100%;text-align: left;color:#FFFFCC;text-shadow: 0 8px 10px #FFFFCC;">
          <div style="margin-left: 20px;font-family: 'Arial Black';">我们是【{{teamName}}】团队</div>
          <div style="margin-left: 20px;">正在参加“XX杯”XXXXXX大赛</div>
          <div style="margin-left: 20px;">请为我们的作品投一票吧~</div>
        </div>
        <!-- 二维码 -->
        <img id="posterQRCode" crossorigin="anonymous" style="width:200px;height:200px;position:absolute;top:420px;" />
      </div>
    </div>

    <!-- 分享海报生成后的图片弹窗 -->
    <el-dialog width="350px"   title="作品分享海报" v-if="posterVisible" :visible.sync="posterVisible" append-to-body>
      <div style="margin-top:-30px;display: flex;flex-direction:column;justify-content:center;align-items: center;">
        <div id="myPosterContainer" style="width:100%;display: flex;justify-content: center;align-items: center;"></div>
        <div style="line-height: 30px;">长按图片保存或转发</div>
      </div>
    </el-dialog>

 

js部分

method: {
    toShare() {
      const text = this.getHost() + '/#/2023ec/videoVote?videoInfoId=' + this.videoInfoId
      // 生成二维码
      QRCode.toDataURL(text, {
        width: 200,
        height: 200,
        src: ''
      }).then(url => {
        // <div style="margin:20px 0 20px 0"><img id="qrcode" :src="shortVideoQRCodeURL"/></div>
        document.querySelector('#posterQRCode').src = url
        this.createPoster()
        this.posterVisible = true
      }).catch(err => {
        console.error(err)
      })
    },
    createPoster() {
      // 生成海报
      const vm = this;
      const domObj = document.getElementById('posterHtml');
      var width = parseInt(domObj.style.width);
      var height = parseInt(domObj.style.height);
      var canvas = document.createElement('canvas');
      var scale = 1;
      canvas.width = width * scale;
      canvas.height = height * scale;
      canvas.getContext('2d').scale(scale, scale);
      var opts = {
        scale: scale,
        canvas: canvas,
        width: width,
        height: height,
        useCORS: true,
        allowTaint: false,
        logging: false,
        letterRendering: true
      };
      console.info('准备开始html2canvas')
      html2canvas(domObj, opts).then(function(canvas) {
        var context = canvas.getContext('2d');

        // 重要 关闭抗锯齿
        context.mozImageSmoothingEnabled = false;
        context.webkitImageSmoothingEnabled = false;
        context.msImageSmoothingEnabled = false;
        context.imageSmoothingEnabled = false;
        console.info('准备开始Canvas2Image')
        console.info('canvas2image:', Canvas2Image)
        var img = Canvas2Image.convertToImage(
          canvas,
          canvas.width,
          canvas.height
        );
        vm.postshow = false;
        vm.postcode = false;
        // img.style.width = canvas.width / 2 + 'px';
        img.style.width = '330px';
        // img.style.height = canvas.height / 2 + 'px';
        document.getElementById('myPosterContainer').appendChild(img);
      });
    },
}

 

canvas2image源代码

/**
 * covert canvas to image
 * and save the image file
 */

var Canvas2Image = function () {

  // check if support sth.
  var $support = function () {
    var canvas = document.createElement('canvas'),
      ctx = canvas.getContext('2d');

    return {
      canvas: !!ctx,
      imageData: !!ctx.getImageData,
      dataURL: !!canvas.toDataURL,
      btoa: !!window.btoa
    };
  }();

  var downloadMime = 'image/octet-stream';

  function scaleCanvas (canvas, width, height) {
    var w = canvas.width,
      h = canvas.height;
    if (width == undefined) {
      width = w;
    }
    if (height == undefined) {
      height = h;
    }

    var retCanvas = document.createElement('canvas');
    var retCtx = retCanvas.getContext('2d');
    retCanvas.width = width;
    retCanvas.height = height;
    retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
    return retCanvas;
  }

  function getDataURL (canvas, type, width, height) {
    canvas = scaleCanvas(canvas, width, height);
    return canvas.toDataURL(type);
  }

  function saveFile (strData) {
    document.location.href = strData;
  }

  function genImage(strData) {
    var img = document.createElement('img');
    img.src = strData;
    return img;
  }
  function fixType (type) {
    type = type.toLowerCase().replace(/jpg/i, 'jpeg');
    var r = type.match(/png|jpeg|bmp|gif/)[0];
    return 'image/' + r;
  }
  function encodeData (data) {
    if (!window.btoa) { throw 'btoa undefined' }
    var str = '';
    if (typeof data == 'string') {
      str = data;
    } else {
      for (var i = 0; i < data.length; i ++) {
        str += String.fromCharCode(data[i]);
      }
    }

    return btoa(str);
  }
  function getImageData (canvas) {
    var w = canvas.width,
      h = canvas.height;
    return canvas.getContext('2d').getImageData(0, 0, w, h);
  }
  function makeURI (strData, type) {
    return 'data:' + type + ';base64,' + strData;
  }


  /**
   * create bitmap image
   * 按照规则生成图片响应头和响应体
   */
  var genBitmapImage = function (oData) {

    //
    // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
    // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
    //

    var biWidth  = oData.width;
    var biHeight    = oData.height;
    var biSizeImage = biWidth * biHeight * 3;
    var bfSize  = biSizeImage + 54; // total header size = 54 bytes

    //
    //  typedef struct tagBITMAPFILEHEADER {
    //      WORD bfType;
    //      DWORD bfSize;
    //      WORD bfReserved1;
    //      WORD bfReserved2;
    //      DWORD bfOffBits;
    //  } BITMAPFILEHEADER;
    //
    var BITMAPFILEHEADER = [
      // WORD bfType -- The file type signature; must be "BM"
      0x42, 0x4D,
      // DWORD bfSize -- The size, in bytes, of the bitmap file
      bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
      // WORD bfReserved1 -- Reserved; must be zero
      0, 0,
      // WORD bfReserved2 -- Reserved; must be zero
      0, 0,
      // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
      54, 0, 0, 0
    ];

    //
    //  typedef struct tagBITMAPINFOHEADER {
    //      DWORD biSize;
    //      LONG  biWidth;
    //      LONG  biHeight;
    //      WORD  biPlanes;
    //      WORD  biBitCount;
    //      DWORD biCompression;
    //      DWORD biSizeImage;
    //      LONG  biXPelsPerMeter;
    //      LONG  biYPelsPerMeter;
    //      DWORD biClrUsed;
    //      DWORD biClrImportant;
    //  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
    //
    var BITMAPINFOHEADER = [
      // DWORD biSize -- The number of bytes required by the structure
      40, 0, 0, 0,
      // LONG biWidth -- The width of the bitmap, in pixels
      biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
      // LONG biHeight -- The height of the bitmap, in pixels
      biHeight & 0xff, biHeight >> 8  & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
      // WORD biPlanes -- The number of planes for the target device. This value must be set to 1
      1, 0,
      // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
      // has a maximum of 2^24 colors (16777216, Truecolor)
      24, 0,
      // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
      0, 0, 0, 0,
      // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
      biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
      // LONG biXPelsPerMeter, unused
      0,0,0,0,
      // LONG biYPelsPerMeter, unused
      0,0,0,0,
      // DWORD biClrUsed, the number of color indexes of palette, unused
      0,0,0,0,
      // DWORD biClrImportant, unused
      0,0,0,0
    ];

    var iPadding = (4 - ((biWidth * 3) % 4)) % 4;

    var aImgData = oData.data;

    var strPixelData = '';
    var biWidth4 = biWidth<<2;
    var y = biHeight;
    var fromCharCode = String.fromCharCode;

    do {
      var iOffsetY = biWidth4*(y-1);
      var strPixelRow = '';
      for (var x = 0; x < biWidth; x++) {
        var iOffsetX = x<<2;
        strPixelRow += fromCharCode(aImgData[iOffsetY+iOffsetX+2]) +
          fromCharCode(aImgData[iOffsetY+iOffsetX+1]) +
          fromCharCode(aImgData[iOffsetY+iOffsetX]);
      }

      for (var c = 0; c < iPadding; c++) {
        strPixelRow += String.fromCharCode(0);
      }

      strPixelData += strPixelRow;
    } while (--y);

    var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);

    return strEncoded;
  };

  /**
   * saveAsImage
   * @param canvasElement
   * @param {String} image type
   * @param {Number} [optional] png width
   * @param {Number} [optional] png height
   */
  var saveAsImage = function (canvas, width, height, type) {
    if ($support.canvas && $support.dataURL) {
      if (typeof canvas == "string") { canvas = document.getElementById(canvas); }
      if (type == undefined) { type = 'png'; }
      type = fixType(type);
      if (/bmp/.test(type)) {
        var data = getImageData(scaleCanvas(canvas, width, height));
        var strData = genBitmapImage(data);
        saveFile(makeURI(strData, downloadMime));
      } else {
        var strData = getDataURL(canvas, type, width, height);
        saveFile(strData.replace(type, downloadMime));
      }
    }
  };

  var convertToImage = function (canvas, width, height, type) {
    if ($support.canvas && $support.dataURL) {
      if (typeof canvas == "string") { canvas = document.getElementById(canvas); }
      if (type == undefined) { type = 'png'; }
      type = fixType(type);

      if (/bmp/.test(type)) {
        var data = getImageData(scaleCanvas(canvas, width, height));
        var strData = genBitmapImage(data);
        return genImage(makeURI(strData, 'image/bmp'));
      } else {
        var strData = getDataURL(canvas, type, width, height);
        return genImage(strData);
      }
    }
  };



  return {
    saveAsImage: saveAsImage,
    saveAsPNG: function (canvas, width, height) {
      return saveAsImage(canvas, width, height, 'png');
    },
    saveAsJPEG: function (canvas, width, height) {
      return saveAsImage(canvas, width, height, 'jpeg');
    },
    saveAsGIF: function (canvas, width, height) {
      return saveAsImage(canvas, width, height, 'gif');
    },
    saveAsBMP: function (canvas, width, height) {
      return saveAsImage(canvas, width, height, 'bmp');
    },

    convertToImage: convertToImage,
    convertToPNG: function (canvas, width, height) {
      return convertToImage(canvas, width, height, 'png');
    },
    convertToJPEG: function (canvas, width, height) {
      return convertToImage(canvas, width, height, 'jpeg');
    },
    convertToGIF: function (canvas, width, height) {
      return convertToImage(canvas, width, height, 'gif');
    },
    convertToBMP: function (canvas, width, height) {
      return convertToImage(canvas, width, height, 'bmp');
    }
  };

}();
export default Canvas2Image;

 

posted @ 2023-06-08 14:27  jsper  阅读(385)  评论(0编辑  收藏  举报