Canvas is a great element for H5 and can create a lot of beautiful effects. Now, in one of its simplest ways, draw a cool bear (yeah, cool because kumamoto is a cool creature).

results

Take a look at the results of this article. With the following several packaged functions plus a little other small functions, can be made into a generation of kumamoto bear pictures of the small web application. (Currently only PNG images are recommended)

Click here to see the effect: Draw Kumamoto bear on canvas

The preparatory work

Modern browsers that support Canvas, HTML pages, CSS files, JS files.

HTML & CSS

Write structure and style first:



kumamon
Copy the code

To see the location of the canvas clearly, let’s add a black border to the canvas:

/* CSS file */. Kumamon {border: 1px solid #000; }Copy the code

JS

JavaScript scripts are an important part of drawing Kumamoto. Before we start, let’s find a finished image to imitate:

The game is divided into three parts: the background, the bear in the ring, and the text.

The framework

The function that draws the whole picture:

function drawKumamon() { var ku = document.getElementById("kumamon"); if (ku.getContext) { var ctx = ku.getContext("2d"); }}Copy the code

Document after loading (to prevent getElementById() from null) :


window.onload = function() {
    drawKumamon();
}
Copy the code

Cross-browser event-listening methods (only addHandler will be used in this example) :

var EventUtil = { addHandler: function(ele, type, handler) { if (ele.addEventListener) { ele.addEventListener(type, handler, false); } else if (ele.attachEvent) { ele.attachEvent("on" + type, handler); } else { ele["on" + type] = handler; } }, removeHandler: function(ele, type, handler) { if (ele.removeEventListener) { ele.removeEventListener(type, handler, false); } else if (ele.detachEvent) { ele.detachEvent("on" + type, handler); } else { ele["on" + type] = null; }}};Copy the code

background

To make the following white circles and so on more obvious, we first paint the background. Here, the background is a solid color, temporarily a deep red, and the color value is RGB (174,0,0).

We wrap the background color code into a function and call it in the main function. This tool drawing class is called paintXXX.

function paintBackground(ctx, sColor) { v_color = sColor; ctx.fillStyle = sColor; ctx.fillRect(0, 0, 500, 500); } var color = "RGB (50, 50, 50);Copy the code

Call from the main function drawKumamon() :


paintBackground(ctx, v_color);
Copy the code

The circle

Kumamoto lives in a white circle, and so does the blush on his face. So again, we’ve wrapped the circle drawing code into a function.


function paintCircle(ctx, x, y, r, i, sColor) {
    ctx.beginPath();
    ctx.moveTo(x - r, y);
    ctx.arc(x, y, r, 0, i * Math.PI, false);
    ctx.fillStyle = sColor;
    ctx.fill();
}
Copy the code

Call in the main function:


paintCircle(ctx, 250, 224, 191, 2, "#fff");
Copy the code

So, in the upper center of the canvas, there is a circle where kumamoto lives.

Current results:

Order of organs in Kumamoto bear

Because the default covering effect of canvas is source-over, that is, the newly drawn source image covers the previously drawn target image. If you look at Kumamoto, you see that his face is above his ears, his facial features are above his face, and his head is above his body. Therefore, if kumamoto is divided into these pieces, the drawing sequence should be body, ears, face, and facial features.

However, the width of the body should be determined according to the position of the five features on the head, so first draw the head, draw kumamoto each position of the code into a function, pay attention to the order when calling.

ear

Kumamoto’s ears are black circles with a smaller white circle on them. The center of the two circles is the same point.

// Left ear paintCircle(CTX, 138, 120, 28, 2, "#000"); paintCircle(ctx, 138, 120, 15, 2, "#fff"); // Right ear paintCircle(CTX, 365, 120, 28, 2, "#000"); paintCircle(ctx, 365, 120, 15, 2, "#fff");Copy the code

face

Kumamoto’s face is oval. Let’s start by creating a utility function that draws an ellipse.


function paintOval(ctx, x, y, a, b, i, sColor) {
    ctx.save();
    var r = (a > b) ? a : b;
    var ratioX = a / r;
    var ratioY = b / r;
    ctx.scale(ratioX, ratioY);
    ctx.beginPath();
    ctx.arc(x / ratioX, y / ratioY, r, 0, i * Math.PI, false);
    ctx.closePath();
    ctx.restore();
    ctx.fillStyle = sColor;
    ctx.fill();
}
Copy the code

The parameters (x,y) form the coordinates of the center of the ellipse, and a, b are the radius of the ellipse. If I is 2, I can draw an ellipse; SColor is a string representation of the color to color the ellipse.

Call it like this:


paintOval(ctx, 250, 210, 140, 118, 2, "#000");
Copy the code

Current results:

His eyebrows

Eyebrow is a closed graph formed by quadratic Bezier curves with the same linear distance between two starting and ending points (called base) and different distance between the highest point of the arc and the bottom edge.


function bearEyebrow(ctx, h1, h2, x0, y0, y1, d, sColor) {
    ctx.beginPath();
    ctx.moveTo(x0, y0);
    var x1 = x0 + d;
    var cp1x = x0 + d / 2;
    var cp1y = y0 - h1;
    var cp2y = y0 - h2;
    ctx.quadraticCurveTo(cp1x, cp1y, x1, y1);
    ctx.quadraticCurveTo(cp1x, cp2y, x0, y0);
    ctx.fillStyle = sColor;
    ctx.fill();
}
Copy the code

Call:


bearEyebrow(ctx, 20, 10, 168, 133, 130, 32, "#fff");
bearEyebrow(ctx, 20, 10, 298, 130, 133, 32, "#fff");
Copy the code

The parameters passed in are not set to be symmetric because the width and position of the eyebrows are a bit nifty.

eyes

Black eyes and white eyes are standing ovals. I need to write a function that rotates the ellipse.


function paintRotatedOval(ctx, x, y, a, b, i, sColor, ang) {
    ctx.save();
    var r = (a > b) ? a : b;
    var ratioX = a / r;
    var ratioY = b / r;
    ctx.translate(x / ratioX, y / ratioY);
    ctx.rotate(ang * Math.PI / 180);
    ctx.scale(ratioX, ratioY);
    ctx.beginPath();
    ctx.arc(0, 0, r, 0, i * Math.PI, false);
    ctx.closePath();
    ctx.restore();
    ctx.fillStyle = sColor;
    ctx.fill();
}
Copy the code

The last parameter of ↑ is the rotation Angle of the ellipse.

Call:

// Ctx.moveto (160, 170); paintRotatedOval(ctx, 186, 170, 26, 27, 2, "#fff", 90); ctx.moveTo(182, 170); paintOval(ctx, 192, 170, 4, 10, 2, "#000"); // Right eye CTx.moveto (273, 170); paintRotatedOval(ctx, 300, 170, 26, 27, 2, "#fff", 90); ctx.moveTo(308, 170); paintOval(ctx, 312, 170, 4, 10, 2, "#000");Copy the code

mouth

The mouth has an oval white area and a black mouth with two closed quadratic Bezier curves.

To color the closed region enclosed by a quadratic Bessel:


function paintQuadratic(ctx, cpy, x0, y0, d, sColor) {
    ctx.beginPath();
    ctx.moveTo(x0, y0);
    var x1 = x0 + d;
    var cpx = x0 + d / 2;
    ctx.quadraticCurveTo(cpx, cpy, x1, y0);
    ctx.closePath();
    ctx.fillStyle = sColor;
    ctx.fill();
}
Copy the code

Call:

// Mouth area ctx.moveto (186, 243); paintOval(ctx, 251, 243, 65, 52, 2, "#fff"); // Mouth paintQuadratic(CTX, 240, 196, 253, 110, "#000"); paintQuadratic(ctx, 290, 196, 253, 110, "#000");Copy the code

nose

The nose can be split in half and is a semi-ellipse of short radius.

// Ctx.moveto (228, 217); paintOval(ctx, 248, 217, 20, 17, 1, "#000"); ctx.moveTo(228, 142); paintRotatedOval(ctx, 248, 142, 20, 13, 1, "#000", 180);Copy the code

Cheek is red

Blush is a red circle.

// Ctx.moveto (99, 227); PaintCircle (CTX, 133, 227, 34, 2, "RGB (255,0,2)"); PaintCircle (CTX, 366, 227, 34, 2, "RGB (255,0,2)");Copy the code

To complete the above steps, the header looks like this:

The body

In order to distinguish kumamoto’s head from its body, its body is constructed in a straight line.

Place a rectangle at the chin to give the impression of kumamoto’s (perhaps) short neck, and then an isosceles trapezoid with the same top and bottom as the rectangle.


function bearBody(ctx, x0, y0, rectW, rectH, trapW, trapH, sColor) {
    var x1 = x0 - (trapW - rectW) / 2;
    var y1 = y0 + rectH + trapH;
    ctx.beginPath();
    ctx.moveTo(x0, y0 + rectH);
    ctx.lineTo(x1, y1);
    ctx.lineTo(x1 + trapW, y1);
    ctx.lineTo(x0 + rectW, y0 + rectH);
    ctx.closePath();
    ctx.fillStyle = sColor;
    ctx.globalCompositeOperation = "source-atop";
    ctx.fill();
    ctx.moveTo(x0, y0);
    ctx.lineTo(x0 + rectW, y0);
    ctx.lineTo(x0 + rectW, y0 + rectH);
    ctx.lineTo(x0, y0 + rectH);
    ctx.lineTo(x0, y0);
    ctx.fill();
}
Copy the code

Call:


bearBody(ctx, 128, 241, 245, 40, 334, 140, "#000");
Copy the code

Now Kumamoto looks like this:

Change the call position of the bearBody before drawing the bear head:

In the draw background function paintBackground () with such a CTX. GlobalCompositeOperation = “destination – over”; And put the function last in the main function call.

Results as follows:

The text

function paintText(ctx, txt,sColor) { inTxt = txt; sColor = txtColor; ctx.font = "bold 36px Arial"; ctx.textAlign = "center"; ctx.textBaseLine = "middle"; ctx.fillStyle = sColor; ctx.fillText(txt, 250, 462); } var inTxt = "Why don't you study? !" ; var txtColor = "#fff";Copy the code

Call:


paintText(ctx, inTxt,txtColor);
Copy the code

Since the globalCompositeOperation was changed to source-atop when the bearBody function was called earlier, that is, the new drawing on the canvas will display the source image at the top of the target image. Parts of the source image that lie outside the target image are not visible. The rest of the canvas is invisible because of the white circle kumamoto is in first. To see text, change the globalCompositeOperation value to source-over before calling paintText.

drawKumamon.js

function drawKumamon() { var ku = document.getElementById("kumamon"); if (ku.getContext) { var ctx = ku.getContext("2d"); // paintCircle(CTX, 250, 224, 191, 2, "# FFF "); bearBody(ctx, 128, 241, 245, 40, 334, 140, "#000"); // Left ear paintCircle(CTX, 138, 120, 28, 2, "#000"); paintCircle(ctx, 138, 120, 15, 2, "#fff"); // Right ear paintCircle(CTX, 365, 120, 28, 2, "#000"); paintCircle(ctx, 365, 120, 15, 2, "#fff"); // paintOval(CTX, 250, 210, 140, 118, 2, "#000"); // brow(CTX, 20, 10, 168, 133, 130, 32, "# FFF "); bearEyebrow(ctx, 20, 10, 298, 130, 133, 32, "#fff"); // Ctx.moveto (160, 170); paintRotatedOval(ctx, 186, 170, 26, 27, 2, "#fff", 90); ctx.moveTo(182, 170); paintOval(ctx, 192, 170, 4, 10, 2, "#000"); // Right eye CTx.moveto (273, 170); paintRotatedOval(ctx, 300, 170, 26, 27, 2, "#fff", 90); ctx.moveTo(308, 170); paintOval(ctx, 312, 170, 4, 10, 2, "#000"); // Mouth area ctx.moveto (186, 243); paintOval(ctx, 251, 243, 65, 52, 2, "#fff"); // Mouth paintQuadratic(CTX, 240, 196, 253, 110, "#000"); paintQuadratic(ctx, 290, 196, 253, 110, "#000"); // Ctx.moveto (228, 217); paintOval(ctx, 248, 217, 20, 17, 1, "#000"); ctx.moveTo(228, 142); paintRotatedOval(ctx, 248, 142, 20, 13, 1, "#000", 180); // Ctx.moveto (99, 227); PaintCircle (CTX, 133, 227, 34, 2, "RGB (255,0,2)"); PaintCircle (CTX, 366, 227, 34, 2, "RGB (255,0,2)"); / / text CTX. GlobalCompositeOperation = "source - over"; paintText(ctx, inTxt,txtColor); paintBackground(ctx, v_color); } } function paintBackground(ctx, sColor) { v_color = sColor; ctx.globalCompositeOperation = "destination-over"; ctx.fillStyle = sColor; ctx.fillRect(0, 0, 500, 500); } function paintCircle(ctx, x, y, r, i, sColor) { ctx.beginPath(); ctx.moveTo(x - r, y); ctx.arc(x, y, r, 0, i * Math.PI, false); ctx.fillStyle = sColor; ctx.fill(); } function paintOval(ctx, x, y, a, b, i, sColor) { ctx.save(); var r = (a > b) ? a : b; var ratioX = a / r; var ratioY = b / r; ctx.scale(ratioX, ratioY); ctx.beginPath(); ctx.arc(x / ratioX, y / ratioY, r, 0, i * Math.PI, false); ctx.closePath(); ctx.restore(); ctx.fillStyle = sColor; ctx.fill(); } function bearEyebrow(ctx, h1, h2, x0, y0, y1, d, sColor) { ctx.beginPath(); ctx.moveTo(x0, y0); var x1 = x0 + d; var cp1x = x0 + d / 2; var cp1y = y0 - h1; var cp2y = y0 - h2; ctx.quadraticCurveTo(cp1x, cp1y, x1, y1); ctx.quadraticCurveTo(cp1x, cp2y, x0, y0); ctx.fillStyle = sColor; ctx.fill(); } function paintRotatedOval(ctx, x, y, a, b, i, sColor, ang) { ctx.save(); var r = (a > b) ? a : b; var ratioX = a / r; var ratioY = b / r; ctx.translate(x / ratioX, y / ratioY); ctx.rotate(ang * Math.PI / 180); ctx.scale(ratioX, ratioY); ctx.beginPath(); ctx.arc(0, 0, r, 0, i * Math.PI, false); ctx.closePath(); ctx.restore(); ctx.fillStyle = sColor; ctx.fill(); } function paintQuadratic(ctx, cpy, x0, y0, d, sColor) { ctx.beginPath(); ctx.moveTo(x0, y0); var x1 = x0 + d; var cpx = x0 + d / 2; ctx.quadraticCurveTo(cpx, cpy, x1, y0); ctx.closePath(); ctx.fillStyle = sColor; ctx.fill(); } function bearBody(ctx, x0, y0, rectW, rectH, trapW, trapH, sColor) { var x1 = x0 - (trapW - rectW) / 2; var y1 = y0 + rectH + trapH; ctx.beginPath(); ctx.moveTo(x0, y0 + rectH); ctx.lineTo(x1, y1); ctx.lineTo(x1 + trapW, y1); ctx.lineTo(x0 + rectW, y0 + rectH); ctx.closePath(); ctx.fillStyle = sColor; ctx.globalCompositeOperation = "source-atop"; ctx.fill(); ctx.moveTo(x0, y0); ctx.lineTo(x0 + rectW, y0); ctx.lineTo(x0 + rectW, y0 + rectH); ctx.lineTo(x0, y0 + rectH); ctx.lineTo(x0, y0); ctx.fill(); } function paintText(ctx, txt,sColor) { inTxt = txt; sColor = txtColor; ctx.font = "bold 36px Arial"; ctx.textAlign = "center"; ctx.textBaseLine = "middle"; ctx.fillStyle = sColor; ctx.fillText(txt, 250, 462); } var color = "RGB (50, 50, 50); Var inTxt = "Why don't you study? !" ; var txtColor = "#fff"; window.onload = function() { drawKumamon(); }Copy the code

Overall effect:

Done! It was now enough to accept Kumamoto’s soulful question: “Why don’t you study? !”