用canvas实现一个大气球送给你

简介: 用canvas实现一个大气球送给你

一、背景


近期在做一个气球挂件的特效需求,值此契机,来跟大家分享一下如何利用canvas以及对应的数学知识构造一个栩栩如生的气球。

640.gif

二、实现



在实现这个看似是圆鼓鼓的气球之前,先了解一下其实现思路,主要分为以下几个部分:


  1. 实现球体部分;
  2. 实现气球口子部分;
  3. 实现气球的线部分;
  4. 进行颜色填充;
  5. 实现动画;


640.png

2.1 球体部分实现


对于这样的气球的球体部分,大家都有什么好的实现思路的?相信大家肯定会有多种多样的实现方案,我也是在看到某位大佬的效果后,感受到了利用四个三次贝塞尔曲线实现这个效果的妙处。为了看懂后续代码,先了解一下三次贝塞尔曲线的原理。(注:引用了CSDN上某位大佬的文章,写的很好,下图引用于此)

640.gif


在上图中P0为起始点、P3为终止点,P1和P2为控制点,其最终的曲线公式如下所示:


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


上述已经列出了三次贝塞尔曲线的效果图和公式,但是通过这个怎么跟我们的气球挂上钩呢?下面通过几张图就理解了:

640.png


如上图所示,就是实现整个气球球体的思路,具体解释如下所示:


  1. A图中起始点为p1,终止点为p2,控制点为c1、c2,让两个控制点重合,绘制出的效果并不是很像气球的一部分,此时就要通过改变控制点来改变其外观;
  2. 改变控制点c1、c2,c1中y值不变,减小x值;c2中x值不变,增大y值(注意canvas中坐标方向即可),改变后就得到了图B的效果,此时就跟气球外观很像了;
  3. 紧接着按照这个方法就可以实现整个的气球球体部分的外观。


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 口子部分实现


口子部分可以简化为一个三角形,效果如下所示:

640.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 线部分实现


线实现的比较简单,就用了一段直线实现


640.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 进行填充


气球部分的填充用了圆形渐变效果,相比于纯色来说更加漂亮一些。


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;
}


640.png

2.5 动画效果及整体代码


上述流程已经将一个静态的气球部分绘制完毕了,要想实现动画效果只需要利用requestAnimationFrame函数不断循环调用即可实现。下面直接抛出整体代码,方便同学们观察效果进行调试,整体代码如下所示:


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月前
win32编程 -- 泡泡跳动
win32编程 -- 泡泡跳动
17 0
|
6月前
|
移动开发 前端开发 JavaScript
用Canvas画一个刮刮乐
用Canvas画一个刮刮乐
35 0
|
8月前
|
前端开发 开发者
|
11月前
|
C++
用C++实现推箱子(小人和推着箱子能过地板版)
用C++实现推箱子(小人和推着箱子能过地板版)
|
11月前
圣诞树的打印
圣诞树的打印
69 0
|
前端开发 JavaScript
教你如何用Canvas绘制整身的哆啦A梦
教你如何用Canvas绘制整身的哆啦A梦 上牌你文章我们说到了如何使用canvas绘制哆啦A梦,但当时只是绘制了哆啦A梦的头部,之后有学员留言说可不可以教大家绘制整身的哆啦A梦,想了想,决定安排一下。如图: 在这里插入图片描述 HTML代码: &lt;canvas id=&quot;my_canvas&quot;&gt;&lt;/canvas&gt; &lt;canvas id=&quot;my_canvas2&quot;&gt;&lt;/canvas&gt; 1 2 CSS代码: * { margin: 0; } #my_canvas {
教你如何用Canvas绘制整身的哆啦A梦
|
前端开发 JavaScript
div在网页中四周留个等宽“包边”的办法
div在网页中四周留个等宽“包边”的办法
56 0
div在网页中四周留个等宽“包边”的办法
|
前端开发 JavaScript
div在网页四周留个等宽“包边”的办法
div在网页四周留个等宽“包边”的办法
69 0
div在网页四周留个等宽“包边”的办法
逐帧动画案例(奔跑的小人)
逐帧动画案例(奔跑的小人)
186 0
逐帧动画案例(奔跑的小人)
|
前端开发
2、CSS动画之行走的米兔、奔跑的小人
2、CSS动画之行走的米兔、奔跑的小人
195 0
2、CSS动画之行走的米兔、奔跑的小人