Html2canvas is the first choice to realize page screenshots or a series of DOM operations by canvas. However, in the actual application process, it will be found that there will be style problems in some models, and the picture drawing will be fuzzy. Therefore, the problems encountered in generating a poster with native canvas and common solutions will be briefly recorded here.

Image not displayed

img.onload

This is an old cliche. When drawing with context. DrawImage, if your image doesn’t draw, it’s probably img.onload. The main reason is that JS is asynchronous in retrieving these images and video resources. It is recommended to encapsulate it in the form of a promise, with direct async/await

function createImg(url) {
    const img = new Image();
    // Cross-domain images can be cropped properly (images are not converted to base64)
    / / : https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
    img.crossOrigin = "Anonymous";
    img.src = url;
    return new Promise((resolve) = > {
      img.onload = () = > {
        resolve(img);
      };
    });
  }
 
async function draw() {
    const img = await createImg("url");
    // do something
  }
Copy the code

The fuzzy

imageSmoothingEnabled

CanvasRenderingContext2D imageSmoothingEnabled is Canvas 2 d API is used to set the picture whether smooth the properties of the true image smoothing (default), false said the picture is not smooth.

This property is easy to understand and will not be described here. If you use this property, it is recommended to add a browser prefix for compatibility

const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// set imageSmoothingEnabled
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
Copy the code

devicePixelRatio

This property simply means that the better the screen, the sharper the display, especially on a retina screen, and the image below clearly shows the difference

The Canvas element looks like this on a screen with devicePixelRatio of 2

The Canvas element looks like this on a screen with devicePixelRatio of 1

As for why this is the case, let’s look at the main code first

function createHIDPICanvas(w = 200, h = 200) {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");

    // Set display size (css pixels).
    canvas.style.width = w + "px";
    canvas.style.height = h + "px";

    // Set actual size in memory (scaled to account for extra pixel density).
    const scale = window.devicePixelRatio || 1; // Change to 1 on retina screens to see blurry canvas.
    canvas.width = Math.floor(w * scale);
    canvas.height = Math.floor(h * scale);

    // Normalize coordinate system to use css pixels.
    ctx.scale(scale, scale);
  }
Copy the code

As you can see, we pass Canvas.width = math.floor (w * scale); And CTX. Scale (scale, scale); Width = w + “px”; The size control of its style is equivalent to enlarging the width and height of its canvas. The canvas element is still the size we want, creating a magnifying glass effect.

Commonly used method

ellipsics

const text = "Core selling point according to the product user requirements, content labels, cooperate with the planning live content based on the core product selling point collation and user requirements, content labels, cooperate with the planning live content based on the core product selling point collation and user requirements, content labels, cooperate with the planning live content based on the core product selling point collation and user requirements, content, Cooperate with the planning to sort out the live broadcast content.";
function getEllipsisShowText(ctx, text, maxWidth, maxLine = 1) {
    const ellipsisText = "...";
    const ellipsisTextWidth = ctx.measureText(ellipsisText).width;
    // Calculate the maximum remaining text width that can be drawn
    const restTextWidth = maxWidth * maxLine - ellipsisTextWidth;
    let textLine = "";
    for (let n = 0; n < text.length; n++) {
      textLine += text[n];
      const textLineWidth = ctx.measureText(textLine).width;
      // Then iterate over the accumulated text to compare the remaining width
      if (textLineWidth > restTextWidth && n > 0) {
        return textLine.substring(0, textLine.length - 1) + ellipsisText; }}return text;
  }
 
function fillWrapText({ text, x, y, maxOpt = {}, type }) {
    // createHiDPICanvas can be implemented as described above
    const canvas = createHiDPICanvas(300.500);
    const ctx = canvas.getContext("2d");
    // You can set the text size, color, font and so on
    ctx.font = "14px";
    const { maxWidth = 300, lineHeight = 20, maxLine = 1 } = maxOpt;
    let showText = text;
    if (type === "ellipsis") {
      showText = getEllipsisShowText(ctx, text, maxWidth, maxLine);
    }
    let line = "";
    for (let n = 0; n < showText.length; n++) {
      const textLine = line + showText[n];
      const textWidth = ctx.measureText(textLine).width;
      if (textWidth > maxWidth && n > 0) {
        ctx.fillText(line, x, y);
        line = showText[n];
        y += lineHeight;
      } else {
        line = textLine;
      }
    }
    ctx.fillText(line, x, y);
    document.body.appendChild(canvas);
  }
 // If ellipsis needs to be set, type must be passed, otherwise maxLine is invalid. MaxWidth is always valid
 fillWrapText({ text, x: 30.y: 30.maxOpt: { maxWidth: 240.maxLine: 3 }, type: "ellipsis" });
Copy the code

The rich text

In this example, the value obtained is a simple tag form. It is recommended to use SVG + XML. The advantage of this form is that it does not need to deal with the tag style alone, and the content can be restored perfectly.

const text = '

     

      Business analysis in the field of shared services; 2. Combined with the overall planning, make a detailed analysis of business requirements, and develop the design of solutions and system prototypes; 3. Carry out project & project management, coordinate team resources of all parties to jointly promote the smooth development of system construction, and timely warn and manage project risks;

`
; const div = document.createElement("div"); // This step is to escape < > &... div.innerHTML = text; const data = ` < SVG XMLNS = "http://www.w3.org/2000/svg" width = "500" height = "1000" > here / / can use style style, if the style of the rich text can be enumerated, <style> //. Ql-center {// text-align: center //} //. Ql-right {// text-align: center: right // } // </style> <foreignObject width='100%' height='100%'> <div xmlns='http://www.w3.org/1999/xhtml' style='font-size:14px; color:rgba(41, 44, 50, 1); white-space:pre-wrap'>${div.innerHTML} </div> </foreignObject> </svg>`; / / XML < br > no recognition, need to switch to < br / > https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/br const url = `data:image/svg+xml,${data .replace(/#/g."% 23") .replace(/  /g."") .replace(/<br>/g."<br />")}`; const img = document.createElement("img"); img.src = url; img.style.width = "500"; document.body.appendChild(img); Copy the code

Look at it. It’s perfect

Disadvantages: XML does not have much support for special characters, so you need to escape manually, without escaping the image will hang.

In addition, the tag with class can use the regular matching, class enumeration can also use the above SVG + XML form, the following post the regular code, can be modified according to their own needs

const str = ` < p > try to help you recommend < / p > < p class = \ "ql - align - center \" > legal third prize < / p > < p class = \ \ "" ql - align - right > yes spiritual motive < / p > `

function patternClassAndContent(str = "") {
    const res = [];
    if(! str) {return res;
    }
    str.match(/. *? <\/p>/g).forEach((item) = > {
      const patternResult = /class=['|"]? ql-align-([\w+]+)['|"]? /g.exec(item);
      if(patternResult? .length >0) {
        const content = patternResult.input.replace(/<[^>]*>|/g."");
        res.push({ align: patternResult[1].text: content });
      } else {
        const content = item.replace(/<[^>]*>|/g."");
        res.push({ align: "left".text: content }); }});return res;
  }
// patternClassAndContent(desc) results in:
/ / /
// {align: "left", text: "left"},
// {align: "center", text: "law third prize"},
// {align: "right", text: "right"},
// ];
Copy the code

Just take the array above and iterate over the drawing according to the style

The rounded rectangle

/** * Draw rounded rectangle *@param {number} X the x-coordinate star@param {number} Y y coordinate *@param {number} W Width of rectangle *@param {number} H Rectangle height *@param {number} The radius rounded corners *@param {number} [tl] top left
 * @param {number} [tr] top right
 * @param {number} [br] bottom right
 * @param {number} [bl] bottom left
 * @param {number} [our lineWidth] line width *@param {string} [color] line color */
function drawRectRadius({ x, y, w, h, radius, tl, tr, br, bl, lineWidth = 2, color = '#dddfe3' }) {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  let r = x + w;
  let b = y + h;
  tl = tl || radius;
  tr = tr || radius;
  br = br || radius;
  bl = bl || radius;
  ctx.strokeStyle = color || "black";
  ctx.lineWidth = lineWidth || 2;
  ctx.beginPath();
  ctx.moveTo(x + tl, y);
  ctx.lineTo(r - tr, y);
  ctx.quadraticCurveTo(r, y, r, y + tr);
  ctx.lineTo(r, b - br);
  ctx.quadraticCurveTo(r, b, r - br, b);
  ctx.lineTo(x + bl, b);
  ctx.quadraticCurveTo(x, b, x, b - bl);
  ctx.lineTo(x, y + tl);
  ctx.quadraticCurveTo(x, y, x + tl, y);
  ctx.stroke();
}
drawRectRadius({ x: 10.y: 10.w: 100.h: 50.radius: 5.color: "red" });
Copy the code

Non-stick pan tip: the above code is based on my shallow understanding of the code to write, not guarantee completely correct.