用canvas实现节日孔明灯放飞功能

1,819 阅读4分钟

做一个在线的孔明灯许愿,既环保又绿化,又可以寄托许愿者的美好愿望,先看效果:

1.gif

制作思路:

孔明灯有二种:第一种,背景飘动的一群假孔明灯,第二种,是一个图片,用户实际放飞的孔明灯

制作背景孔明灯思路

2.png

1、轮廓制作思路:画一个孔明灯,计算每个点的坐标值,划线,最下面的线是有一点弧度的,用了赛贝尔曲线,整体颜色使用渐变填充

2、文字制作思路:给孔明灯加上随机文字,文字我用了一个数组,里面存了几个默认的祝福语,随机赋值给不同的孔明灯

3、飞行思路:根据屏幕宽度,计算需要多少孔明灯,给这些孔明灯定制:随机大小,随机轨迹,随机速度,和随机透明度,遇到屏幕方向变化的处理

制作点击放飞的前景孔明灯思路

3.png

1、图片:在网上找一个喜欢的孔明灯图片,photoshop抠图切割,以canvas加载图片方式加入

2、输入框:制作屏幕左下角的输入框和放飞按钮,输入文字字数限制

3、dom点击:输入框许愿文字赋值给孔明灯,调整文字在孔明灯上的大小和位置调整

4、飞行轨迹:起始位置高度在屏幕最下方,宽度中间,缓慢上升,左右方向随机,飞行速度随机,为了实现飞远效果,设置了上升过程逐渐变小,设置了最小值,达到最小值就不会再变小了,设置遇到屏幕边界处理方法。

剩余细节处理

1、背景夜色图片

2、飘动的小光点

github地址

代码我用vue写的,下载后,npm i 一下,就可以运行,直属代码在Test.vue里面了

github.com/alian1128/k…

代码解析

1、画背景孔明灯

分析孔明灯的形状:设置点坐标,最下面的边是弧度的,中间有一条分割线,标明了坐标的位置

4.png

代码如下:

moveto 是把画笔移动某个坐标,lineto是划线,赛贝尔曲线三个参数的意义我就不说了,有在线测试赛贝尔曲线的,可以搜一下在上面感受学习。

        //---------画孔明灯外形 begin
        this.ctx.beginPath();
        this.ctx.moveTo(x + 10 * scale, y + 10 * scale); //左上
        this.ctx.lineTo(x + 20 * scale, y + 38 * scale); //左下
        
        this.ctx.quadraticCurveTo(
          x + 25 * scale,
          y + 41 * scale,
          x + 30 * scale,
          y + 38 * scale
        ); //赛贝尔曲线给最下面边,增加弧度
        this.ctx.lineTo(x + 30 * scale, y + 38 * scale); //右下
        this.ctx.lineTo(x + 40 * scale, y + 10 * scale); //右上
        this.ctx.lineTo(x + 25 * scale, y); //顶部
        this.ctx.closePath();
        this.ctx.fill();

        //画中间的那一条分割线,
        this.ctx.moveTo(x + 25 * scale, y); //顶部
        this.ctx.lineTo(x + 25 * scale, y + 38 * scale); //顶部中间

        var lingrad2 = this.ctx.createLinearGradient(
          x + 25 * scale,
          y + 0 * scale,
          x + 40 * scale,
          y + 30 * scale
        );
        lingrad2.addColorStop(0, "#000");
        lingrad2.addColorStop(1, "#ff8d1a");
        this.ctx.strokeStyle = lingrad2; //增加渐变
        this.ctx.lineWidth = 0.3;
        this.ctx.stroke();
        //---------孔明灯外形 end

2、给孔明灯加文字:使用filltext,四个参数,分别是文字内容,x轴位置,y轴位置,文字大小, 文字内容从数组下标0-10随机拿,文字大小我使用了0-1之间随机数乘以19

        const textarr = ["股票", "回本","祖国","统一","国庆","快乐", "安康", "涨薪","平安", "如意", "暴富", "不卷"];
        const randomtext = Math.ceil(p.scaleText * 10); //随机取一个词
        
        //赋值给孔明灯文字
        this.ctx.fillText(
          textarr[randomtext],
          x + 15 * scale,
          y + 22 * scale,
          scale * 19
        );

3、循环:循环出很多灯,并且让这些灯动起来,这就需要requestAnimationFrame函数,用于实现动画效果。

requestAnimationFrame 我是这样理解的,每秒钟大概执行60次左右它的回调函数,只要我们随机加减x和y的位置,就看起来像是在动移动,实际上不是真的在移动,而是像电影一样一帧一帧的刷新

前景孔明灯

先声明好image,然后用drawImage 来画图,


   mounted() {
    this.imageObj = new Image();
    this.imageObj.src = "/44.png";
    requestAnimationFrame(this.drawOneLine); // 在此调用requestAnimationFrame回调单个大孔明灯的函数
 }
 
 methods: {
  
   fly() {
      const one ={} //存放灯的变量
      this.arrclickFly.push(one); //点击一次存入数组一个变量
    },
    drawOneLine(timer) {
      this.arrclickFly.forEach((item) => {
      //循环许愿灯的数组,画图
        this.ctx.drawImage(
          this.imageObj,
          item.p.x,
          item.p.y,
          item.size,
          item.size
        );       
    },
 
}

点击放飞按钮的时候,调用fly方法,给数组增加arrclickFly孔明灯的数量。在drawOneLine回调函数中回循环这个数组,然后给每个孔明灯计算位置,速度,缩放大小。


   mounted() {
    this.imageObj = new Image();
    this.imageObj.src = "/44.png";
    requestAnimationFrame(this.drawOneLine); // 在此调用requestAnimationFrame回调单个大孔明灯的函数
 }
 
 methods: {
  
   fly() {
      const text = this.$refs.text.value;
      if (text.length > 10) {
        this.message = "请输入10个字符以内的祝福";
        return;
      }
      const rany = this.binRandom(0.5);
      const one = {
        p: { x: this.width / 2, y: this.height - 200 }, 
        v: {
          x: this.binRandom(0.5) ? this.random(1) : this.random(-1),
          y: rany < 0.2 ? 0.2 : rany,
        },
        o: this.random(1) + 0.3, 
        scale: 5, 
        scaleText: text,
        size: 200,
      };
      this.arrclickFly.push(one);
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    },
    drawOneLine(timer) {
      this.arrclickFly.forEach((item) => {
        this.ctx.drawImage(
          this.imageObj,
          item.p.x,
          item.p.y,
          item.size,
          item.size
        );
        item.p.x += item.v.x;
        item.p.y -= item.v.y;
        item.size = item.size - 0.05;  //孔明灯越飞越远,
        if (item.p.x > this.width + 20 || item.p.x < -60) item.v.x *= -1;  //x轴的位置乘以速度,碰到浏览器边缘返回
        if (item.p.y > this.height || item.p.y < -60) item.v.y *= -1;  //y轴的位置乘以速度,碰到浏览器边缘返回
        if (item.size <= 10) item.size = 10; 
        if (item.scaleText) {
          //在孔明灯上写字
          this.ctx.fillStyle = "#ffcc00"; //轮廓颜色
          this.ctx.font = `${item.size / 18}px serif`;
          let x = item.p.x + item.size / 2;
          let y = item.p.y + item.size / 4;
          this.ctx.fillTextVertical(item.scaleText, x, y, 10);
        }
      });
      requestAnimationFrame(this.drawOneLine, timer);
    },
 
}

如果需要代码的去git上自行下载吧。