6
头图

1. Background

I'm currently working on a special effects requirement for a balloon pendant. I take this opportunity to share with you how to use canvas and the corresponding mathematical knowledge to construct a lifelike balloon.

balloon1.gif

Second, realize

Before realizing this seemingly bulging balloon, let’s first understand its realization idea, which is mainly divided into the following parts:
  1. Realize the sphere part;
  2. Realize the balloon opening part;
  3. Realize the line part of the balloon;
  4. Perform color filling;
  5. Realize animation;

气球.PNG

2.1 Partial realization of the sphere

For the sphere part of such a balloon, do you have any good ideas for realizing it? I believe that everyone will have a variety of implementation solutions. After seeing the effect of a , I felt the beauty of using four cubic Bezier curves to achieve this effect. In order to understand the subsequent code, first understand the principle of cubic Bezier curve. (Note: I quoted a of a big guy on CSDN, well written, the following picture is quoted here)

三次贝塞尔曲线.gif

In the above figure, P0 is the starting point, P3 is the end point, and P1 and P2 are the control points. The final curve formula is as follows:

B(t)=(1−t)^3 P0​+3t(1−t)^2 P1​+3t ^ 2(1−t) * P2​+t ^ 3P3​, t∈[0,1]

The above has listed the renderings and formulas of the cubic Bezier curve, but how do we hook our balloons through this? Here are a few pictures to understand:

image.png

As shown in the figure above, it is the idea of realizing the entire balloon sphere. The specific explanation is as follows:
  1. In the figure A, the starting point is p1, the ending point is p2, and the control points are c1 and c2. Let the two control points overlap. The drawn effect is not very much like a part of a balloon. At this time, it is necessary to change the control point by changing the control point. Exterior;
  2. Change the control points c1 and c2, the value of y in c1 remains unchanged, and the value of x is reduced; the value of x in c2 remains unchanged, and the value of y is increased (pay attention to the coordinate direction in the canvas). After the change, the effect of Figure B is obtained. Now it looks like a balloon;
  3. Then follow this method to achieve the appearance of the entire balloon sphere.
function draw() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    ctx.translate(250, 250);
    drawCoordiante(ctx);
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(0, -80);
    ctx.bezierCurveTo(45, -80, 80, -45, 80, 0);
    ctx.bezierCurveTo(80, 85, 45, 120, 0, 120);
    ctx.bezierCurveTo(-45, 120, -80, 85, -80, 0);
    ctx.bezierCurveTo(-80, -45, -45, -80, 0, -80);
    ctx.stroke();
    ctx.restore();
}

function drawCoordiante(ctx) {
    ctx.beginPath();
    ctx.moveTo(-120, 0);
    ctx.lineTo(120, 0);
    ctx.moveTo(0, -120);
    ctx.lineTo(0, 120);
    ctx.closePath();
    ctx.stroke();
}

2.2 Partial realization of the opening

The opening part can be simplified to a triangle, and the effect is as follows:

image.png

function draw() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    ……

    ctx.save();
    ctx.beginPath();
    ctx.moveTo(0, 120);
    ctx.lineTo(-5, 130);
    ctx.lineTo(5, 130);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
}

2.3 Line part realization

The realization of the line is relatively simple, and it is realized with a straight line

image.png

function draw() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    ……

    ctx.save();
    ctx.beginPath();
    ctx.moveTo(0, 120);
    ctx.lineTo(0, 300);
    ctx.stroke();
    ctx.restore();
}

2.4 Filling

The filling of the balloon part uses a circular gradient effect, which is more beautiful than a solid color.
function draw() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    ctx.fillStyle = getBalloonGradient(ctx, 0, 0, 80, 210);
    ……
    
}

function getBalloonGradient(ctx, x, y, r, hue) {
    const grd = ctx.createRadialGradient(x, y, 0, x, y, r);
    grd.addColorStop(0, 'hsla(' + hue + ', 100%, 65%, .95)');
    grd.addColorStop(0.4, 'hsla(' + hue + ', 100%, 45%, .85)');
    grd.addColorStop(1, 'hsla(' + hue + ', 100%, 25%, .80)');
    return grd;
}

image.png

2.5 Animation effects and overall code

The above process has finished drawing a static balloon part. If you want to realize the animation effect, you only need to use the requestAnimationFrame function to continuously call it in a loop. The following directly throws out the overall code, which is convenient for students to observe the effect and debug. The overall code is as follows:
let posX = 225;
let posY = 300;
let points = getPoints();
draw();

function draw() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if (posY < -200) {
        posY = 300;
        posX += 300 * (Math.random() - 0.5);
        points = getPoints();
    }
    else {
        posY -= 2;
    }
    ctx.save();
    ctx.translate(posX, posY);
    drawBalloon(ctx, points);
    ctx.restore();

    window.requestAnimationFrame(draw);
}

function drawBalloon(ctx, points) {
    ctx.scale(points.scale, points.scale);
    ctx.save();
    ctx.fillStyle = getBalloonGradient(ctx, 0, 0, points.R, points.hue);
    // 绘制球体部分
    ctx.moveTo(points.p1.x, points.p1.y);
    ctx.bezierCurveTo(points.pC1to2A.x, points.pC1to2A.y, points.pC1to2B.x, points.pC1to2B.y, points.p2.x, points.p2.y);
    ctx.bezierCurveTo(points.pC2to3A.x, points.pC2to3A.y, points.pC2to3B.x, points.pC2to3B.y, points.p3.x, points.p3.y);
    ctx.bezierCurveTo(points.pC3to4A.x, points.pC3to4A.y, points.pC3to4B.x, points.pC3to4B.y, points.p4.x, points.p4.y);
    ctx.bezierCurveTo(points.pC4to1A.x, points.pC4to1A.y, points.pC4to1B.x, points.pC4to1B.y, points.p1.x, points.p1.y);

    // 绘制气球钮部分
    ctx.moveTo(points.p3.x, points.p3.y);
    ctx.lineTo(points.knowA.x, points.knowA.y);
    ctx.lineTo(points.knowB.x, points.knowB.y);
    ctx.fill();
    ctx.restore();

    // 绘制线部分
    ctx.save();
    ctx.strokeStyle = '#000000';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(points.p3.x, points.p3.y);
    ctx.lineTo(points.lineEnd.x, points.lineEnd.y);
    ctx.stroke();
    ctx.restore();
}

function getPoints() {
    const offset = 35;
    return {
        scale: 0.3 + Math.random() / 2,
        hue: Math.random() * 255,
        R: 80,
        p1: {
            x: 0,
            y: -80
        },
        pC1to2A: {
            x: 80 - offset,
            y: -80
        },
        pC1to2B: {
            x: 80,
            y: -80 + offset
        },
        p2: {
            x: 80,
            y: 0
        },
        pC2to3A: {
            x: 80,
            y: 120 - offset
        },
        pC2to3B: {
            x: 80 - offset,
            y: 120
        },
        p3: {
            x: 0,
            y: 120
        },
        pC3to4A: {
            x: -80 + offset,
            y: 120
        },
        pC3to4B: {
            x: -80,
            y: 120 - offset
        },
        p4: {
            x: -80,
            y: 0
        },
        pC4to1A: {
            x: -80,
            y: -80 + offset
        },
        pC4to1B: {
            x: -80 + offset,
            y: -80
        },
        knowA: {
            x: -5,
            y: 130
        },
        knowB: {
            x: 5,
            y: 130
        },
        lineEnd: {
            x: 0,
            y: 250
        }
    };
}

function getBalloonGradient(ctx, x, y, r, hue) {
    const grd = ctx.createRadialGradient(x, y, 0, x, y, r);
    grd.addColorStop(0, 'hsla(' + hue + ', 100%, 65%, .95)');
    grd.addColorStop(0.4, 'hsla(' + hue + ', 100%, 45%, .85)');
    grd.addColorStop(1, 'hsla(' + hue + ', 100%, 25%, .80)');
    return grd;
}

3. Related articles

If only the canvas tag is

canvas from entry to pig's head

1. If you think this article is good, share it, like it, and let more people see it

2. Welcome to pay attention to the front-end point, line and surface open the road to redemption at the front end.


执鸢者
1.7k 声望2.5k 粉丝

摸摸头,编程使我快乐。