canvas烟雾效果学习

一、学习背景与效果

前些天翻大神blog,看到了张鑫旭大神写的一篇《canvas图形绘制之星空、噪点与烟雾效果》,深受启发。详细了研究了一下烟雾效果的代码,效果如下: 动态效果请点击。(!-- 去掉了原文中的背景图 --!)

效果图: image

代码如下:

<!DOCTYPE html>
<html>

<head>
  <title>canvas实现的烟雾缭绕效果</title>
  <style>
    .smoke {
      height: 500px;
      background: #fff;
      background-size: cover;
      position: relative;
    }
    
    .smoke canvas {
      height: 100%;
      width: 100%;
    }
  </style>
</head>

<body>
  <div id="main">
    <h1>canvas实现的烟雾缭绕效果实例页面</h1>
    <div id="body" class="light">
      <div id="content" class="show">
        <h3>展示</h3>
        <div class="smoke">
          <canvas id="smokeCanvas"></canvas>
        </div>
      </div>
    </div>
  </div>
  <script>
    // canvas烟雾缭绕效果
    var canvasSmoke = function (canvas, options) {
      var defaults = {
        count: 30,
        velocity: 2,
        fps: 30,
        url: 'smoke.png'
      };

      options = options || {};

      // 参数合并
      var params = {};

      for (var key in defaults) {
        params[key] = options[key] || defaults[key];
      }

      // 创建存储粒子的数组
      var particles = [];

      // 渲染的粒子数目
      var particleCount = params.count;

      // 每个方向的最大速度
      var maxVelocity = params.velocity;

      // 每秒多少帧
      var targetFPS = params.fps;

      // canvas元素
      var eleCanvas = canvas;

      if (!eleCanvas) {
        return this;
      }

      // 画布的尺寸
      var canvasWidth = eleCanvas.clientWidth;
      var canvasHeight = eleCanvas.clientHeight;

      eleCanvas.width = canvasWidth;
      eleCanvas.height = canvasHeight;

      // 创建图片对象
      var imageObj = new Image();

      // 一旦图像被下载,然后在所有的颗粒上设置图像
      imageObj.onload = function () {
        particles.forEach(function (particle) {
          particle.setImage(imageObj);
        });
      };

      // 烟雾图片地址
      imageObj.src = params.url;

      // 粒子实例方法
      function Particle(context) {

        // 设置初始位置
        this.x = 0;
        this.y = 0;

        // 纵横速度
        this.xVelocity = 0;
        this.yVelocity = 0;

        // 圆角大小
        this.radius = 2;

        // 存储上下文,绘制的时候需要
        this.context = context;

        // 绘制粒子的具体方法
        this.draw = function () {
          // 如果图片,则绘制
          if (this.image) {
            this.context.globalAlpha = this.alpha;
            // 烟雾缭绕就看这里了
            // 这是宽度,是动态的
            var fillWidth = canvasWidth / 2,
              fillHeight = fillWidth - fillWidth * (this.x / canvasWidth * this.y / canvasHeight);

            this.context.drawImage(this.image, 0, 0, this.imageWidth, this.imageHeight, this.x, this.y, fillWidth,
              fillHeight);
          }
        };

        // 刷新粒子
        this.update = function () {
          // 改变粒子的
          this.x += this.xVelocity;
          this.y += this.yVelocity;

          // 如果到了右边缘
          if (this.x >= canvasWidth) {
            this.xVelocity = -this.xVelocity;
            this.x = canvasWidth;
          }
          // 检测是否到了左边缘
          else if (this.x <= 0) {
            this.xVelocity = -this.xVelocity;
            this.x = 0;
          }

          // 底边缘
          if (this.y >= canvasHeight) {
            this.yVelocity = -this.yVelocity;
            this.y = canvasHeight;
          }

          // 是否上边缘
          else if (this.y <= 0) {
            this.yVelocity = -this.yVelocity;
            this.y = 0;
          }

          // 越靠近边缘,透明度越低
          // 纵向透明度变化要比横向的明显
          this.alpha = (1 - Math.abs(canvasWidth * 0.5 - this.x) / canvasWidth) * (0.7 - Math.abs(canvasHeight *
            0.5 - this.y) / canvasHeight);
        };

        // 设置粒子位置方法
        this.setPosition = function (x, y) {
          this.x = x;
          this.y = y;
        };

        // 设置速度方法
        this.setVelocity = function (x, y) {
          this.xVelocity = x;
          this.yVelocity = y;
        };

        this.setImage = function (image) {
          this.imageWidth = image.width;
          this.imageHeight = image.height;
          this.image = image;
        }
      }

      // 生成一个min,max大小之间的随机数
      function generateRandom(min, max) {
        return Math.random() * (max - min) + min;
      }

      // canvas上下文
      var context;

      // 初始化常见
      function init() {
        var canvas = eleCanvas;
        if (canvas.getContext) {

          // 绘图都需要这条语句
          context = canvas.getContext('2d');

          // 创建粒子,并设置他们的位置什么的,当然都是随机的
          for (var i = 0; i < particleCount; ++i) {
            var particle = new Particle(context);

            // 随机位置
            particle.setPosition(generateRandom(0, canvasWidth), generateRandom(0, canvasHeight));

            // 设置随机速度
            particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
            particles.push(particle);
          }
        }
      }

      // 初始化
      init();

      // 绘制方法
      function draw() {
        // 清除绘制
        //context.fillStyle = "rgba(0, 0, 0, 0)";
        context.clearRect(0, 0, canvasWidth, canvasHeight);

        // 绘制所有粒子
        particles.forEach(function (particle) {
          particle.draw();
        });
      }

      // 刷新
      function update() {
        particles.forEach(function (particle) {
          particle.update();
        });
      }

      // 开始绘制
      if (context) {
        setInterval(function () {
          // 绘制前先更新位置什么的
          update();

          // 绘制
          draw();
        }, 1000 / targetFPS);
      }
    };

    // IE9+烟雾效果走起
    if ([].map) {
      canvasSmoke(document.querySelector('#smokeCanvas'));
    }
  </script>
</body>

</html>

效果实现的好不好,关键的代码注释里面已经标出来了,就是这行:
fillHeight = fillWidth - fillWidth * (this.x / canvasWidth * this.y / canvasHeight);

大家进行试验的时候,可以试着修改一下fillHeight的生成函数,产生的效果会很不同。为什么函数这样取值可以实现好的效果呢?

在坐标系中根据函数取点画图就可以明白,这个函数可以让烟雾图片的实例在面上更加连续的去展示。因为每个实例的取点都是伪随机的,所以很容易点都随机到一条线上,导致动画看起来不真实,即烟雾不是那么飘逸。按这个函数取值,可以大概率的保证烟雾实例的变化,在面上保证实例初始化后的连续性,让效果看着更加飘逸。

大神blog中的几个例子充分说明了,canvas绘图去实现动画效果好与差,与想象力紧密相关呀,数学具象化的能力很重要啊(努力提升中)。大家有时间可以多试试其他不同函数出现的效果,来加深影响。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值