Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

canvas粒子 #1

Open
tinet-jutt opened this issue Apr 20, 2021 · 0 comments
Open

canvas粒子 #1

tinet-jutt opened this issue Apr 20, 2021 · 0 comments
Labels

Comments

@tinet-jutt
Copy link
Owner

tinet-jutt commented Apr 20, 2021

canvas粒子动画

先看效果

1.定义个粒子类 Particle

class Parcicle {
  constructor(props) {

    // 粒子的横坐标初始位置
    this.x = 0; 

    // 粒子的纵坐标初始位置
    this.y = 0;

    // 粒子的横向速度
    this.vx = 0;

    // 粒子的纵向速度
    this.vy = 0;

    // 粒子的运动终点横坐标
    this.finalX = 0;

    // 粒子的运动终点纵坐标
    this.finalY = 0;

    // 粒子的半径
    this.r = 1;

    // 粒子的填充颜色
    this.fillStyle = '#000';

    // 粒子的描边颜色
    this.strokeStyle = '#000';
    Object.assign(this, props);
  }
  render(ctx) {
    const { x, y, r, fillStyle, strokeStyle } = this;
    ctx.save();
    ctx.translate(x, y);
    ctx.fillStyle = fillStyle;
    ctx.strokeStyle = strokeStyle;
    ctx.beginPath();
    ctx.arc(0, 0, r, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.fill();
    ctx.restore();
    return this;
  }
}

2.定义粒子系统类

先确定下实例化参数

let fontList = [
  {
    // 要显示的文字
    font: '3',

    // 字体颜色
    color: 'green',

    // 字体显示时间
    lifetime: 2000,

    // 字体占比画布大小比例
    size: 0.8,

    // 粒子密度
    density: 5
  }
];
new ParticleSystem({
  fontDataList: fontList,

  // 循环显示
  loop: true,

  // canvas宽度
  width: 200,

  // canvas 高度
  height: 200,

  // 字体占比画布大小比例
  size: 0.8,

  // 画布容器
  container
});

3.设置默认值

constructor(props) {
  // 显示的字体样式
  this.font = '💕';

  // 当前显示字体的颜色
  this.color = 'red';

  //  canvas容器
  this.container = null;

  // canvas 的宽度
  this.width = 200;

  // canvas 的高度
  this.height = 200;

  this.size = 0.8;

  //粒子未扩散之前的范围
  this.range = 0.5;

  //粒子密度
  this.density = 4;
  Object.assign(this, props);
  this.init();
  return this;
}

4.初始化调用

init() {
  // 设置画布尺寸
  this.setCanvasSize();

  // 执行动画
  this.animate();

  // 添加画布到页面元素
  this.appendCanvas();
}

5.动画执行方法

animate() {
  const _this = this;
  // 获取当前时间戳
  let startTime = +new Date();

  (function move() {
    const { currentFontIndex, fontDataList } = _this;

    // 获取执行动画时的时间
    let endTime = +new Date();
    let interTime = endTime - startTime;
    let lifetime = fontDataList[currentFontIndex] && fontDataList[currentFontIndex].lifetime;

    // 判断当前时间差是否达到预设的lifeTime值来判断是否执行下一个文字
    if (interTime >= lifetime) {
      _this.nextFont();
      startTime = +new Date();
    }
    _this.drawParticle();
    requestAnimationFrame(move);
  })();
}

6.文字切换

nextFont() {
  const { fontDataList, loop } = this;
  const _this = this;

  // 设置当前字体的配置
  Object.entries(fontDataList[this.currentFontIndex]).forEach(([ key, value ]) => {
    _this[key] = value || _this.defaultValue(key);
  });
  if (typeof _this.font == 'string') _this.setFont();
  this.currentFontIndex += 1;
  if (fontDataList.length <= this.currentFontIndex) {
    if (!loop) return false;
    this.currentFontIndex = 0;
  }
}

setFont() {
  const { height, width, size, font, ctx } = this;
  ctx.clearRect(0, 0, width, height);
  ctx.fillStyle = '#000';

  // 预设10px的字体
  ctx.font = 'blod 10px Arial';

  // 测量10px字体的宽高
  const measure = ctx.measureText(font);

  // 10px的字体除行高7得到字体的高度
  const lineHeight = 7;

  // 通过画布的尺寸计算出最大的字体尺寸
  const fSize = Math.min(height * size * 10 / lineHeight, width * size * 10 / measure.width);
  ctx.save();
  ctx.font = `bold ${fSize}px Arial`;
  const measureResize = ctx.measureText(font);
  const left = (width - measureResize.width) / 2;
  const bottom = (height + fSize / 10 * lineHeight) / 2;
  ctx.fillText(font, left, bottom);

  // 获取回执好的图片信息
  this.getImageData();
  ctx.restore();
}

7.获取图片文字信息

getImageData() {
  const { ctx, color, range, width, height, density } = this;

  // 获取图片信息
  const data = ctx.getImageData(0, 0, width, height);
  const particleList = [];
  for (let x = 0, width = data.width, height = data.height; x < width; x += density) {
    for (let y = 0; y < height; y += density) {

      // 获取粒子的初始位置
      const currentX = (width - width * range) / 2 + Math.random() * width * range;
      const currentY = (height - height * range) / 2 + Math.random() * height * range;
      const i = (y * width + x) * 4;

      // 如果当前的像素点有色值则push当前的像素点到particleList
      if (data.data[i + 3]) {
        particleList.push(
          new Particle({
            x: currentX,
            y: currentY,
            finalX: x,
            finalY: y,
            fillStyle: color,
            strokeStyle: color
          })
        );
      }
    }
  }
  this.particleList = particleList;
  this.drawParticle();
}

8.绘制粒子执行动画

drawParticle() {
  const { particleList, ctx, width, height } = this;
  if (!particleList) return false;
  ctx.clearRect(0, 0, width, height);

  // 设置加速度系数,参数越大动画执行速度越快
  const spring = 0.01;

  // 设置摩擦系数,让粒子最后能停下来(偷了个懒没做终点位置校正)
  const FRICTION = 0.88;

  // 让所有的粒子都动起来吧
  particleList.forEach((item, index) => {
    item.vx += (item.finalX - item.x) * spring;
    item.vy += (item.finalY - item.y) * spring;
    item.x += item.vx;
    item.y += item.vy;
    item.vx *= FRICTION;
    item.vy *= FRICTION;
    item.render(ctx);
  });
}

至此粒子已经完成了动画

8.添加图片支持

图片转化为粒子

setImage() {
  const { height, width, font, ctx } = this;
  ctx.clearRect(0, 0, width, height);
  ctx.drawImage(font, 0, 0, width, height);
  this.getImageData();
}

判断是否为图片

if (typeof _this.font == 'object') _this.setImage();

加载图片配合 es7 的async,await解决异步加载。

function loadImg(src) {
  return new Promise((res, rej) => {
    let oImg = new Image();
    oImg.src = src;
    oImg.onload = () => res(oImg);
    oImg.onerror = (err) => rej(err);
  });
}

(async () => {
  const oImg4 = await loadImg('./1.png');
  let fontList = [
    {
      font: '3',
      color: 'green',
      lifetime: 2000,
      size: 0.8,
      density: 5
    },
    {
      font: oImg4,
      lifetime: 2000,
      density: 5,
      color: "red"
    }
  ];
  const container = document.getElementById('canvasContainer');
  new ParticleSystem({
    fontDataList: fontList,
    loop: true,
    width: 200,
    height: 200,
    size: 0.8,
    container
  });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant