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.