canvas实现下雨、下雪特效

昨天下午,学弟突然发过来一个视频,问我怎么实现这样的特效(emmm,不知道咋上传视频,就录个gif吧)

效果也比较简单,于是就自己着手用canvas实现一下。

实现思路

首先,我们需要设计一下小球对象的数据结构,小球的半径设置为r,颜色设置为color,由于其位置是不断移动的,必须要有x和y坐标。其次,小球可以左移,也可以右移,说明小球要有速度vx和vy。最后小球点击可以停止或者继续移动,需要添加一个属性isMoving来记录当前状态。如下:

  x: x轴坐标
  y: y轴坐标
  r: 半径
  vx: x的速度
  vy: y的速度
  color: 颜色
  isMoving: 记录

小球出现

canvas实现这样的特效其实还是比较简单的,绘制图形或者添加文字都有专门的api,很是方便。canvas学习–文本操作 - 掘金 (juejin.cn)和canvas学习–曲线图形 - 掘金 (juejin.cn)这两篇文章是我之前写的学习文章,感兴趣的掘友可以查看。


    let width = 600;
    let height = 600;

    window.onload = function () {
        //写逻辑
        let canvas = document.getElementById('canvas');
        let cxt = canvas.getContext('2d');

        canvas.width = width;
        canvas.height = height;

        let ballCount = 1;  // 小球数量
        let balls = [];   // 存储小球的数组
        let colorArr = ['red', 'orange', 'yellow', 'green', 'teal', 'blue', 'purple'];   //小球颜色
        let radius = Math.floor(Math.random() * 10) + 10;

        for (let i = 0; i < ballCount; i++) {
            balls.push({
                x: Math.floor(Math.random() * (width - radius - width / 3)) + width / 3,  // 小球的x坐标
                y: height - radius,   // 小球的y坐标
                r: radius,     // 小球半径
                vx: Math.floor(Math.random() * 2) - 2,   // 水平偏移量
                vy: Math.floor(Math.random() * 2) + 2,   // 垂直偏移量
                color: colorArr[Math.floor(Math.random() * 7)],
                isMoving: false,   // 小球的状态,false代表移动,true代表停止
            })
        }

        function drawBall() {    // 绘制小球
            cxt.clearRect(0, 0, width, height);  // 清除画布

            for (let i = 0; i < ballCount; i++) {
                cxt.beginPath();

                let text = "Rust";
                let len = cxt.measureText(text).width;

                let ball = balls[i];
                cxt.moveTo(ball.x, ball.y);

                cxt.fillStyle = ball['color'];

                cxt.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, true);
                cxt.fill();

                cxt.font = "bold 15px 楷体";
                cxt.fillStyle = '#000';
                cxt.fillText(text, ball.x - len / 2, ball.y);

                cxt.closePath();

            }


        }

        drawBall();
      
       
    }

canvas实现下雨、下雪特效_第1张图片

小球上浮

小球上浮的话,其实就是x和y坐标的不断变化,我们只需要遍历存储小球的数组,调整x和y就行,这里需要开启一个定时线程。

      function moveBall() {    // 移动小球

            for (let i = 0; i < ballCount; i++) {
                let ball = balls[i];

                if (!ball.isMoving) {
                    ball.x = ball.x + ball.vx;
                    ball.y = ball.y - ball.vy;

                    if (ball.r <= 30) {   // 小球上升逐渐变大
                        ball.r = ball.r + 0.1;
                    }

                    if (ball.x <= ball.r) {    // 到达x轴边界消失
                        balls[i] = {
                            x: ball.x,
                            y: -2 * ball.y,
                            r: ball.r,
                            vx: 0,
                            vy: 0,
                            color: ball.color,
                            isMoving: true,
                        }
                    }

                    if (ball.y <= ball.r) {  // 到达y轴边界消失
                        balls[i] = {
                            x: ball.x,
                            y: -2 * ball.y,
                            r: ball.r,
                            vx: 0,
                            vy: 0,
                            color: ball.color,
                            isMoving: true,
                        }
                    }
                }


            }
        }
        
setInterval(drawBall, 50);

小球点击停止或者继续移动

这里只需要监听点击事件,判断是否点中小球即可

 canvas.onclick = function (e) {

            // 获取点击坐标
            let clickX = e.offsetX;  
            let clickY = e.offsetY;

            for (let i = 0; i < balls.length; i++) {
                if (clickX < balls[i].x + balls[i].r && clickX > balls[i].x - balls[i].r) {   // 判断是否在圆内
                    if (clickY < balls[i].y + balls[i].r && clickY > balls[i].y - balls[i].r) {
                        balls[i].isMoving = !balls[i].isMoving;
                    }
                }
            }
        }

完整代码

DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
head>

<body>
    <canvas id="canvas" width="300" height="300" style="border: 1px dashed gray;">canvas>
body>

<script>
    let width = 600;
    let height = 600;

    window.onload = function () {
        //写逻辑
        let canvas = document.getElementById('canvas');
        let cxt = canvas.getContext('2d');

        canvas.width = width;
        canvas.height = height;

        let ballCount = 5;  // 小球数量
        let balls = [];   // 存储小球的数组
        let colorArr = ['red', 'orange', 'yellow', 'green', 'teal', 'blue', 'purple'];   //小球颜色
        let radius = Math.floor(Math.random() * 10) + 10;

        for (let i = 0; i < ballCount; i++) {
            balls.push({
                x: Math.floor(Math.random() * (width - radius - width / 3)) + width / 3,  // 小球的x坐标
                y: height - radius,   // 小球的y坐标
                r: radius,     // 小球半径
                vx: Math.floor(Math.random() * 2) - 2,   // 水平偏移量
                vy: Math.floor(Math.random() * 2) + 2,   // 垂直偏移量
                color: colorArr[Math.floor(Math.random() * 7)],
                isMoving: false,   // 小球的状态,false代表移动,true代表停止
            })
        }

        //console.log(balls);

        canvas.onclick = function (e) {

            // 获取点击坐标
            let clickX = e.offsetX;  
            let clickY = e.offsetY;

            for (let i = 0; i < balls.length; i++) {
                if (clickX < balls[i].x + balls[i].r && clickX > balls[i].x - balls[i].r) {   // 判断是否在圆内
                    if (clickY < balls[i].y + balls[i].r && clickY > balls[i].y - balls[i].r) {
                        balls[i].isMoving = !balls[i].isMoving;
                    }
                }
            }
        }

        function drawBall() {    // 绘制小球
            cxt.clearRect(0, 0, width, height);  // 清楚画布

            for (let i = 0; i < ballCount; i++) {
                cxt.beginPath();

                let text = "Rust";
                let len = cxt.measureText(text).width;

                let ball = balls[i];
                cxt.moveTo(ball.x, ball.y);

                cxt.fillStyle = ball['color'];

                cxt.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, true);
                cxt.fill();

                cxt.font = "bold 15px 楷体";
                cxt.fillStyle = '#000';
                cxt.fillText(text, ball.x - len / 2, ball.y);

                cxt.closePath();

            }

            moveBall();

        }

        function moveBall() {    // 移动小球

            for (let i = 0; i < ballCount; i++) {
                let ball = balls[i];

                if (!ball.isMoving) {
                    ball.x = ball.x + ball.vx;
                    ball.y = ball.y - ball.vy;

                    if (ball.r <= 30) {   // 小球上升逐渐变大
                        ball.r = ball.r + 0.1;
                    }

                    if (ball.x <= ball.r) {    // 到达x轴边界消失
                        balls[i] = {
                            x: ball.x,
                            y: -2 * ball.y,
                            r: ball.r,
                            vx: 0,
                            vy: 0,
                            color: ball.color,
                            isMoving: true,
                        }
                    }

                    if (ball.y <= ball.r) {  // 到达y轴边界消失
                        balls[i] = {
                            x: ball.x,
                            y: -2 * ball.y,
                            r: ball.r,
                            vx: 0,
                            vy: 0,
                            color: ball.color,
                            isMoving: true,
                        }
                    }
                }


            }
        }

        setInterval(drawBall, 50);
    }
script>

html>

效果图如下:

下雨或者下雪

export function snow(canvas) {
  var ctx = canvas.getContext("2d");

  var W = window.innerWidth;
  var H = window.innerHeight;
  canvas.width = W;
  canvas.height = H * 0.12;

  var flakesCount = 100;
  var flakes = []; // flake instances

  for (var i = 0; i < flakesCount; i++) {
    flakes.push({
      x: Math.random() * W,
      y: Math.random() * H,
      r: Math.random() * 5 + 2, // 2px - 7px
      d: Math.random() + 1
    });
  }

  function drawFlakes() {
    ctx.clearRect(0, 0, W, H);
    ctx.fillStyle = "#fff";
    ctx.beginPath();
    for (var i = 0; i < flakesCount; i++) {
      var flake = flakes[i];
      ctx.moveTo(flake.x, flake.y);
      ctx.arc(flake.x, flake.y, flake.r, 0, Math.PI * 2, true);
    }
    ctx.fill();
    moveFlakes();
  }

  var angle = 0;

  function moveFlakes() {
    angle += 0.01;
    for (var i = 0; i < flakesCount; i++) {
      var flake = flakes[i];
      flake.y += Math.pow(flake.d, 2) + 1;
      flake.x += Math.sin(angle) * 2;

      if (flake.y > H) {
        flakes[i] = { x: Math.random() * W, y: 0, r: flake.r, d: flake.d };
      }
    }
  }

  setInterval(drawFlakes, 50);
}

export function rain(canvas) {
  var ctx = canvas.getContext("2d");

  var w = window.innerWidth;
  var h = window.innerHeight;
  canvas.width = w;
  canvas.height = h * 0.12;

  ctx.strokeStyle = "rgba(174,194,224,0.5)";
  ctx.lineWidth = 1;
  ctx.lineCap = "round";

  var init = [];
  var maxParts = 200;
  for (var a = 0; a < maxParts; a++) {
    init.push({
      x: Math.random() * w,
      y: Math.random() * h,
      l: Math.random() * 1,
      xs: -4 + Math.random() * 4 + 2,
      ys: Math.random() * 10 + 10
    });
  }

  var particles = [];
  for (var b = 0; b < maxParts; b++) {
    particles[b] = init[b];
  }

  function draw() {
    ctx.clearRect(0, 0, w, h);
    for (var c = 0; c < particles.length; c++) {
      var p = particles[c];
      ctx.beginPath();
      ctx.moveTo(p.x, p.y);
      ctx.lineTo(p.x + p.l * p.xs, p.y + p.l * p.ys);
      ctx.stroke();
    }
    move();
  }

  function move() {
    for (var b = 0; b < particles.length; b++) {
      var p = particles[b];
      p.x += p.xs;
      p.y += p.ys;
      if (p.x > w || p.y > h) {
        p.x = Math.random() * w;
        p.y = -20;
      }
    }
  }

  setInterval(draw, 50);
}

下雨和小球上浮原理基本上都是一样的,需要的掘友可以阅读一下上面的代码或者导入到自己的工程中。

你可能感兴趣的:(前端,javascript,html5)