preface

Recently, I received a demand to make a poster with words and pictures, which can be used to share the ending page of the activity with long press on wechat. As soon as I received the demand, I thought of using canvas to draw, but when I saw the tedious drawing process of canvas, I couldn’t help feeling overwhelmed. After several searches, I found that wheels had been made. This article mainly records the implementation process, and the problems encountered.

Rely on

QRCode this dependency is mainly used for mobile terminals to generate the url QRCode, note the name of qrcodejs2 do not install the wrong

npm install qrcodejs2 --save 

import QRCode from "qrcodejs2"
Copy the code

html2canvas

This dependency mainly converts the current HTML structure and CSS styles to canvas. It’s a lot easier to draw than using the API yourself

Canvas2Image Github

In fact, canvas2image.js is also based on the encapsulation of Canvas. toDataURL. Compared with the native API, canvas2image.js is more specific in the function of converting images (the uncompressed package size is 7.4KB), which is suitable for project use.

The main idea

All the poster structures are written in a parent structure, and then html2Canvas is called to transform into pictures and create images. Through CSS hierarchy and positioning, image is set as the top layer to achieve long-click sharing

code

HTML part

<! -- HTML structure, specific CSS does not write --> <! -- Poster HTML element --> <div id="posterHtml"
      :style="{backgroundImage: 'url('+posterHtmlBg+')'}"
    >
      <div class="posterHtml">
    
        <div class="posterklass"> I am {{name}}, invite you :</div> <! <div id="qrcodeImg" :if="postcode"></div> </div> </div> <! - the location of the image is to be inserted, this structure processing, cooperate with method of CSS is automatically set to the top - > < div id ="myCanvas"></div> <! -- Prompting the user to write the text, actually can be written in the poster HTML structure, through html2Canvas ignore generation --> <span class="tip"< p style = "max-width: 100%; clear: both; min-height: 1em; </span>Copy the code

Js part

CreateQrcode (text) {const qrcodeImgEl = document.getelementById (const qrcodeImgEl = document.getelementById ("qrcodeImg");
      qrcodeImgEl.innerHTML = "";
      letwidth = document.documentElement.clientWidth; Width = width *0.32;let qrcode = new QRCode(qrcodeImgEl, {
        width: width,
        height: width,
        colorDark: "# 000000",
        colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); //text is the generated URL qrcode.makecode (text); This.createposter (); Html2canvas (element[, options]) will return a promise containing a <canvas> element: // Generate a postercreatePoster() {
    
      const vm = this;
      const domObj = document.getElementById("posterHtml");

      var width = document.documentElement.clientWidth;
      var height = document.documentElement.clientHeight;
      var canvas = document.createElement("canvas"); Html2canvas var opts = {canvas: canvas, logging:true,
        width: width,
        height: height,
        useCORS: true,
        allowTaint: false,
        logging: false,
        letterRendering: true}; html2canvas(domObj, opts).then(function(canvas) {
        var context = canvas.getContext("2d");
        var img = Canvas2Image.convertToImage(
          canvas,
          canvas.width,
          canvas.height
        );
        
        img.style.width = canvas.width;

        img.style.height = canvas.height;
        img.style.position = "absolute";
        img.style.top = "0px";
 
        document.getElementById("myCanvas").appendChild(img); // Hide the HTML structure after inserting the image to avoid pulling up the wechat image to display the HTML structure vm under the image.$nextTick(() = > {let e =  document.querySelector("#posterHtml");
             e.style.display = "none"; })} //data() {
      return {
        name:' ', // posterHtmlBg: require('.. /.. /assets/images/poster/invite_poster_bg.jpg'), // Background}},Copy the code

Problems and solutions

In theory, the requirement should be implemented at this time, but after running, you will find the converted image, clarity touching. Definitely not, so I did some research and found the following solutions.

First, the background image cannot be referenced by background, so we need to write a separate image structure


  <div class="main"> <! -- Poster HTML element --> <div id="posterHtml" >
      <div class="posterHtml">
        <img
          :src="posterHtmlBg"
          style="width:100%; height:100%"
        >
        <div class="posterklass"> I am {{name}}, invite you :</div> <! <div id="qrcodeImg" :if="postcode"></div>

      </div>
    </div>
    
    <div id="myCanvas"></div>
    <span class="tip"< p style = "max-width: 100%; clear: both; min-height: 1em; </span> </div>Copy the code

Canvas size can also affect the quality of the conversion. The code above sets the size, but it is not enough, because the browser draws the Canvas and renders it to the screen in two steps:

Drawing process:

WebkitBackingStorePixelRatio webkitBackingStorePixelRatio said browser in the drawing Canvas to the buffer zone of draw ratio, high if the image width is 200 px. WebkitBackingStorePixelRatio is 2, then the Canvas to draw the picture to the cache area wide job into a 400 px

Rendering process: devicePixelRatio

Canvas display to the screen also requires a rendering process, which will scale and render the Canvas in the cache to the screen according to the devicePixelRatio parameter

The reason why Canvas drawing will be blurred can be inferred: 1, devicePixelRatio = Device Pixel/CSS Pixel If devicePixelRatio = 2 then for 200px by 200px images to draw on the screen, The corresponding screen pixels (physical pixels) are 400px by 400px

2, in most Gao Qingbing webkitBackingStorePixelRatio devicePixelRatio = 1 = 2 will be 100 px x 100 px picture drawing to the screen goes through the following process:

WebkitBackingStorePixelRatio = 1 size for the mapped to the cache: 200 px x 200 px devicePixelRatio = 2

The 200px * 200px image corresponds to 400px * 400px pixels on the screen, and devicePixelRatio = 2. The browser will double the width and height of the 200px * 200px cache and render it on the screen, so it will cause blur

So our solution is to set the canvas width and height to twice that of the screen

In addition, canvas will enable anti-aliasing by default, so we need to turn it off manually to achieve sharpening of the picture

MDN: imageSmoothingEnabled

context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;

Copy the code

The final code looks like this

HTML part

  <div class="main"> <! -- Poster HTML element --> <div id="posterHtml" >
      <div class="posterHtml">
        <img
          :src="posterHtmlBg"
          style="width:100%; height:100%"
        >
        <div class="posterklass"> I am {{name}}, invite you :</div> <! <div id="qrcodeImg" :if="postcode"></div>

      </div>
    </div>
    <div id="myCanvas"></div>
    <span class="tip"< p style = "max-width: 100%; clear: both; min-height: 1em; </span> </div>Copy the code

Js part


createQrcode: functionConst qrcodeImgEl = document.getelementById (const qrcodeImgEl = document.getelementById ("qrcodeImg");
     qrcodeImgEl.innerHTML = "";
     let width = document.documentElement.clientWidth;
     width = width * 0.32;

     let qrcode = new QRCode(qrcodeImgEl, {
       width: width,
       height: width,
       colorDark: "# 000000",
       colorLight: "#ffffff",
       correctLevel: QRCode.CorrectLevel.H
     });
     qrcode.makeCode(text);
     this.createPoster();
   },

   createPosterConst vm = this; const domObj = document.getElementById("posterHtml");

     var width = document.documentElement.clientWidth;
     var height = document.documentElement.clientHeight;
     var canvas = document.createElement("canvas");
     var scale = 2;

     canvas.width = width * scale;
     canvas.height = height * scale;
     canvas.getContext("2d").scale(scale, scale);

     var opts = {
       scale: scale,
       canvas: canvas,
       logging: true,
       width: width,
       height: height,
       useCORS: true,
       allowTaint: false,
       logging: false,
       letterRendering: true}; html2canvas(domObj, opts).then(function(canvas) {
       var context = canvas.getContext("2d"); / / important close anti-aliasing context. MozImageSmoothingEnabled =false;
       context.webkitImageSmoothingEnabled = false;
       context.msImageSmoothingEnabled = false;
       context.imageSmoothingEnabled = false;

       var img = Canvas2Image.convertToImage(
         canvas,
         canvas.width,
         canvas.height
       );
        vm.postshow = false;
        vm.postcode = false;
       img.style.width = canvas.width / 2 + "px";

       img.style.height = canvas.height / 2 + "px";
       img.style.position = "absolute";
       img.style.top = "0px";
      
       document.getElementById("myCanvas").appendChild(img);
     
       vm.$nextTick(() = > {let e =  document.querySelector("#posterHtml");
            e.style.display = "none"; })}); }Copy the code

At this point, the requirement is perfectly fulfilled. Let me know if you have a better idea.

My github my blog reference article 1 Reference article 2