Frontend Development 11 min read

Drawing the 2022 Winter Olympics Mascot Bing Dwen Dwen with HTML5 Canvas

This tutorial walks you through recreating the 2022 Winter Olympics mascot Bing Dwen Dwen using HTML5 Canvas, covering the drawing of its body, ears, limbs, heart, logo, and facial features with ellipses, arcs, Bezier curves, gradients, and image rendering.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Drawing the 2022 Winter Olympics Mascot Bing Dwen Dwen with HTML5 Canvas

The article begins with a personal note about the excitement of the 2022 Winter Olympics and the popularity of the mascot Bing Dwen Dwen, then proposes to recreate the mascot using Canvas as a way to review basic Canvas methods.

Body : Two half‑ellipses are drawn with ctx.ellipse and filled to form the main torso shape.

ctx.beginPath();
ctx.ellipse(400, 400, 150, 160, 0, 0, Math.PI);
ctx.ellipse(400, 310, 160, 150, 0, 0, Math.PI * 2);
ctx.fillStyle = '#fff';
ctx.fill();
ctx.closePath();

Ears : Each ear consists of a semicircle and a rectangle, drawn before the body so the body covers excess parts.

ctx.beginPath();
ctx.fillStyle = '#000';
ctx.arc(300, 190, 20, Math.PI, Math.PI * 2);
ctx.fillRect(280, 190, 40, 40);
ctx.arc(500, 190, 20, Math.PI, Math.PI * 2);
ctx.fillRect(480, 190, 40, 40);
ctx.fill();
ctx.closePath();

Hands and Feet : Quadratic and cubic Bezier curves are used to create smooth limbs. The code demonstrates drawing a foot, a hand, and the opposite hand with quadraticCurveTo and bezierCurveTo .

// foot
ctx.beginPath();
ctx.moveTo(300, 523);
ctx.bezierCurveTo(315, 523, 290, 583, 300, 603);
ctx.quadraticCurveTo(325, 613, 370, 603);
ctx.quadraticCurveTo(360, 584, 370, 556);
ctx.quadraticCurveTo(325, 523, 300, 520);
ctx.stroke();
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
// left hand
ctx.beginPath();
ctx.moveTo(250, 400);
ctx.quadraticCurveTo(245, 330, 241, 300);
ctx.quadraticCurveTo(200, 320, 160, 420);
ctx.lineTo(230, 420);
ctx.quadraticCurveTo(245, 400, 250, 400);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
// right hand
ctx.beginPath();
ctx.moveTo(559, 300);
ctx.quadraticCurveTo(575, 295, 600, 250);
ctx.quadraticCurveTo(625, 230, 655, 250);
ctx.quadraticCurveTo(680, 330, 550, 400);
ctx.quadraticCurveTo(556, 340, 559, 300);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();

Heart and Logo : A pre‑written heart‑drawing function is reused, and the Olympic logo is added with drawImage .

// heart
let get_arr = function (a, len) {
  let arr = [];
  for (let i = 0; i < len; i++) {
    let step = (i / len) * (Math.PI * 2);
    let vector = {
      x: a * (16 * Math.pow(Math.sin(step), 3)) + 625,
      y: -a * (13 * Math.cos(step) - 5 * Math.cos(2 * step) - 2 * Math.cos(3 * step) - Math.cos(4 * step)) + 275,
    };
    arr.push(vector);
  }
  return arr;
};
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
let len = 50;
let arr = get_arr(1, len);
for (let i = 0; i < len; i++) {
  ctx.lineTo(arr[i].x, arr[i].y);
}
ctx.fillStyle = '#f00';
ctx.fill();
ctx.closePath();
// logo
var img = new Image();
img.onload = () => {
  ctx.drawImage(img, 0, 0, 900, 900, 360, 440, 100, 100);
};
img.src = './logo.jpg';

Face : Simple shapes are combined with linear and radial gradients to create a colorful ring and other facial details.

// rainbow ring
ctx.beginPath();
ctx.ellipse(400, 300, 125, 95, -0.06, 0, Math.PI * 2);
var grd = ctx.createRadialGradient(400, 300, 95, 400, 300, 130);
gradd.addColorStop(0, '#dddddd20');
gradd.addColorStop(1, '#0000FF');
ctx.strokeStyle = grd;
ctx.lineWidth = 6;
ctx.stroke();
ctx.closePath();
// additional rings with different color stops omitted for brevity

Eyes, Nose, Mouth : Ellipses and arcs draw the eyes, radial gradients add depth, and polygons form the nose and mouth.

// eyes
ctx.beginPath();
ctx.ellipse(465, 275, 38, 26, 1, 0, Math.PI * 2);
ctx.ellipse(335, 275, 43, 29, -0.8, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
// nose
ctx.beginPath();
ctx.moveTo(380, 280);
ctx.lineTo(420, 280);
ctx.lineTo(400, 295);
ctx.lineTo(380, 280);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
// mouth
ctx.beginPath();
ctx.moveTo(370, 323);
ctx.lineTo(400, 335);
ctx.lineTo(430, 323);
ctx.quadraticCurveTo(400, 330, 370, 323);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.fillStyle = '#f00';
ctx.fill();
ctx.stroke();
ctx.closePath();

The author concludes by asking for advice on adding a transparent outer coat outline around the mascot.

graphicsJavaScriptTutorialCanvas Drawingbing-dwen-dwenHTML5 Canvas
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.