文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-12-14 00:08

关注

二、实现

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

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

气球.PNG

2.1 球体部分实现

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

三次贝塞尔曲线.gif

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

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

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

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

  1. A图中起始点为p1,终止点为p2,控制点为c1、c2,让两个控制点重合,绘制出的效果并不是很像气球的一部分,此时就要通过改变控制点来改变其外观;
  2. 改变控制点c1、c2,c1中y值不变,减小x值;c2中x值不变,增大y值(注意canvas中坐标方向即可),改变后就得到了图B的效果,此时就跟气球外观很像了;
  3. 紧接着按照这个方法就可以实现整个的气球球体部分的外观。
  1. function draw() { 
  2.     const canvas = document.getElementById('canvas'); 
  3.     const ctx = canvas.getContext('2d'); 
  4.  
  5.     ctx.translate(250, 250); 
  6.     drawCoordiante(ctx); 
  7.     ctx.save(); 
  8.     ctx.beginPath(); 
  9.     ctx.moveTo(0, -80); 
  10.     ctx.bezierCurveTo(45, -80, 80, -45, 80, 0); 
  11.     ctx.bezierCurveTo(80, 85, 45, 120, 0, 120); 
  12.     ctx.bezierCurveTo(-45, 120, -80, 85, -80, 0); 
  13.     ctx.bezierCurveTo(-80, -45, -45, -80, 0, -80); 
  14.     ctx.stroke(); 
  15.     ctx.restore(); 
  16.  
  17. function drawCoordiante(ctx) { 
  18.     ctx.beginPath(); 
  19.     ctx.moveTo(-120, 0); 
  20.     ctx.lineTo(120, 0); 
  21.     ctx.moveTo(0, -120); 
  22.     ctx.lineTo(0, 120); 
  23.     ctx.closePath(); 
  24.     ctx.stroke(); 

2.2 口子部分实现

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

  1. function draw() { 
  2.     const canvas = document.getElementById('canvas'); 
  3.     const ctx = canvas.getContext('2d'); 
  4.  
  5.     …… 
  6.  
  7.     ctx.save(); 
  8.     ctx.beginPath(); 
  9.     ctx.moveTo(0, 120); 
  10.     ctx.lineTo(-5, 130); 
  11.     ctx.lineTo(5, 130); 
  12.     ctx.closePath(); 
  13.     ctx.stroke(); 
  14.     ctx.restore(); 

2.3 线部分实现

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

  1. function draw() { 
  2.     const canvas = document.getElementById('canvas'); 
  3.     const ctx = canvas.getContext('2d'); 
  4.  
  5.     …… 
  6.  
  7.     ctx.save(); 
  8.     ctx.beginPath(); 
  9.     ctx.moveTo(0, 120); 
  10.     ctx.lineTo(0, 300); 
  11.     ctx.stroke(); 
  12.     ctx.restore(); 

2.4 进行填充

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

  1. function draw() { 
  2.     const canvas = document.getElementById('canvas'); 
  3.     const ctx = canvas.getContext('2d'); 
  4.  
  5.     ctx.fillStyle = getBalloonGradient(ctx, 0, 0, 80, 210); 
  6.     …… 
  7.      
  8.  
  9. function getBalloonGradient(ctx, x, y, r, hue) { 
  10.     const grd = ctx.createRadialGradient(x, y, 0, x, y, r); 
  11.     grd.addColorStop(0, 'hsla(' + hue + ', 100%, 65%, .95)'); 
  12.     grd.addColorStop(0.4, 'hsla(' + hue + ', 100%, 45%, .85)'); 
  13.     grd.addColorStop(1, 'hsla(' + hue + ', 100%, 25%, .80)'); 
  14.     return grd; 

2.5 动画效果及整体代码

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

  1. let posX = 225; 
  2. let posY = 300; 
  3. let points = getPoints(); 
  4. draw(); 
  5.  
  6. function draw() { 
  7.     const canvas = document.getElementById('canvas'); 
  8.     const ctx = canvas.getContext('2d'); 
  9.     ctx.clearRect(0, 0, canvas.width, canvas.height); 
  10.     if (posY < -200) { 
  11.         posY = 300; 
  12.         posX += 300 * (Math.random() - 0.5); 
  13.         points = getPoints(); 
  14.     } 
  15.     else { 
  16.         posY -= 2; 
  17.     } 
  18.     ctx.save(); 
  19.     ctx.translate(posX, posY); 
  20.     drawBalloon(ctx, points); 
  21.     ctx.restore(); 
  22.  
  23.     window.requestAnimationFrame(draw); 
  24.  
  25. function drawBalloon(ctx, points) { 
  26.     ctx.scale(points.scale, points.scale); 
  27.     ctx.save(); 
  28.     ctx.fillStyle = getBalloonGradient(ctx, 0, 0, points.R, points.hue); 
  29.     // 绘制球体部分 
  30.     ctx.moveTo(points.p1.x, points.p1.y); 
  31.     ctx.bezierCurveTo(points.pC1to2A.x, points.pC1to2A.y, points.pC1to2B.x, points.pC1to2B.y, points.p2.x, points.p2.y); 
  32.     ctx.bezierCurveTo(points.pC2to3A.x, points.pC2to3A.y, points.pC2to3B.x, points.pC2to3B.y, points.p3.x, points.p3.y); 
  33.     ctx.bezierCurveTo(points.pC3to4A.x, points.pC3to4A.y, points.pC3to4B.x, points.pC3to4B.y, points.p4.x, points.p4.y); 
  34.     ctx.bezierCurveTo(points.pC4to1A.x, points.pC4to1A.y, points.pC4to1B.x, points.pC4to1B.y, points.p1.x, points.p1.y); 
  35.  
  36.     // 绘制气球钮部分 
  37.     ctx.moveTo(points.p3.x, points.p3.y); 
  38.     ctx.lineTo(points.knowA.x, points.knowA.y); 
  39.     ctx.lineTo(points.knowB.x, points.knowB.y); 
  40.     ctx.fill(); 
  41.     ctx.restore(); 
  42.  
  43.     // 绘制线部分 
  44.     ctx.save(); 
  45.     ctx.strokeStyle = '#000000'
  46.     ctx.lineWidth = 1; 
  47.     ctx.beginPath(); 
  48.     ctx.moveTo(points.p3.x, points.p3.y); 
  49.     ctx.lineTo(points.lineEnd.x, points.lineEnd.y); 
  50.     ctx.stroke(); 
  51.     ctx.restore(); 
  52.  
  53. function getPoints() { 
  54.     const offset = 35; 
  55.     return { 
  56.         scale: 0.3 + Math.random() / 2, 
  57.         hue: Math.random() * 255, 
  58.         R: 80, 
  59.         p1: { 
  60.             x: 0, 
  61.             y: -80 
  62.         }, 
  63.         pC1to2A: { 
  64.             x: 80 - offset, 
  65.             y: -80 
  66.         }, 
  67.         pC1to2B: { 
  68.             x: 80, 
  69.             y: -80 + offset 
  70.         }, 
  71.         p2: { 
  72.             x: 80, 
  73.             y: 0 
  74.         }, 
  75.         pC2to3A: { 
  76.             x: 80, 
  77.             y: 120 - offset 
  78.         }, 
  79.         pC2to3B: { 
  80.             x: 80 - offset, 
  81.             y: 120 
  82.         }, 
  83.         p3: { 
  84.             x: 0, 
  85.             y: 120 
  86.         }, 
  87.         pC3to4A: { 
  88.             x: -80 + offset, 
  89.             y: 120 
  90.         }, 
  91.         pC3to4B: { 
  92.             x: -80, 
  93.             y: 120 - offset 
  94.         }, 
  95.         p4: { 
  96.             x: -80, 
  97.             y: 0 
  98.         }, 
  99.         pC4to1A: { 
  100.             x: -80, 
  101.             y: -80 + offset 
  102.         }, 
  103.         pC4to1B: { 
  104.             x: -80 + offset, 
  105.             y: -80 
  106.         }, 
  107.         knowA: { 
  108.             x: -5, 
  109.             y: 130 
  110.         }, 
  111.         knowB: { 
  112.             x: 5, 
  113.             y: 130 
  114.         }, 
  115.         lineEnd: { 
  116.             x: 0, 
  117.             y: 250 
  118.         } 
  119.     }; 
  120.  
  121. function getBalloonGradient(ctx, x, y, r, hue) { 
  122.     const grd = ctx.createRadialGradient(x, y, 0, x, y, r); 
  123.     grd.addColorStop(0, 'hsla(' + hue + ', 100%, 65%, .95)'); 
  124.     grd.addColorStop(0.4, 'hsla(' + hue + ', 100%, 45%, .85)'); 
  125.     grd.addColorStop(1, 'hsla(' + hue + ', 100%, 25%, .80)'); 
  126.     return grd; 

 

来源:前端点线面内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯