读万卷书行万里路

canvas文字粒子特效(含运动)

发布时间:2018/9/6 11:29:47
    全文约1.7k
    预计需要9分钟

最近在学习canvas,对于canvas我本是是不太会的,仅会画个图片文字的,尴尬!!所以下个功夫学习下。

先看一个简单的例子:

左边是通过canvas fillText API画出来的字,左边是根据右边的canvas用例子组成的。这种原理是过canvas的getImageData()方法获取canvas的像素点,再通过 Uint32Array 这个对象转成数组方便操作。

Uint32Array表示一个由基于平台字节序的32位无符号字节组成的数组.如果需要对字节顺序进行控制(译者注:即 littleEndian 或 bigEndian),请使用DataView代替.数组中每个元素的初始值都是0.一旦创建,你可以用对象的方法引用数组里的元素,或者使用标准的数组索引语法(即,使用中括号)。

Uint32Array MDN

通过上述方法获取有效的像素点,用来生成例子文字

代码实现

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title></title>
  <style media="screen">
    * &#123;
      padding: 0;
      margin: 0;
    &#125;
    .inp_text_a &#123;
      height: 44px;
      line-height: 44px;
      border-radius: 4px;
      border: 1px solid #dddd;
      width: 400px;
      padding: 0 5px;
      outline: none;
    &#125;
    .inp_btn_a &#123;
      width: 80px;
      height: 44px;
      line-height: 44px;
      background: #5fdf7e;
      border-radius: 4px;
      border: none;
      color: #fff;
    &#125;
  </style>
</head>

<body style="text-align: center">
  <div class="" style="width: 800px; margin: 100px auto;">
    <input type="text" name="" value="" class="inp_text_a" id="inp_text" placeholder="请输入文字"/>
    <input type="button" name="" value="美化" class="inp_btn_a" id="inp_btn" />
  </div>
  <script type="text/javascript">

    document.querySelector("#inp_btn").onclick = function () &#123;
      let words = document.querySelector("#inp_text").value

      let wordsArr = words.split('')

      let canvas = document.querySelectorAll('canvas')
      let body = document.querySelector('body')
      for(let  i = 0; i < canvas.length; i ++) &#123;
        body.removeChild(canvas[i])
      &#125;

      for (let i = 0; i < wordsArr.length; i++) &#123;
        draw(wordsArr[i])
      &#125;
    &#125;

    function draw (words) &#123;
      let canvas = document.createElement('canvas')
      let ctx = canvas.getContext('2d')

      canvas.width = 400 / 2
      canvas.height = 400 / 2

      ctx.textAlign = "center"
      ctx.font = "200px arial"
      ctx.fillText(words, 200 / 2, 350 / 2 - 5)
      drawColors(ctx, canvas)
    &#125;

    function drawColors (ctx, canvas) &#123;
      let gridY = 7,
        gridX = 7;


      var idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
      var buffer32 = new Uint32Array(idata.data.buffer);

      var canvas_new = document.createElement('canvas')
      var context = canvas_new.getContext('2d')
      canvas_new.width = canvas.width
      canvas_new.height = canvas.height

      for (var j = 0; j < canvas.height; j += gridY)
      &#123;
        for (var i = 0; i < canvas.width; i += gridX)
        &#123;
          if (buffer32[j * canvas.width + i])
          &#123;
            var particle = drawArc(context, i, j);
          &#125;
        &#125;
      &#125;
      document.querySelectorAll('body')[0].appendChild(canvas)
      document.querySelectorAll('body')[0].appendChild(canvas_new)
    &#125;

    function drawArc(context, x, y)
    &#123;
      let colors = ["#feea00", "#a9df85", "#5dc0ad", "#ff9a00", "#fa3f20", "#e03636", "#25c6fc", "#ff534d", "#b78261", "#b78261", "#eaf048", "#9ff048", "#2a5200", "#f6d6ff"]
      context.beginPath();
      context.arc(x, y, 2, 0, 2 * Math.PI);
      context.fillStyle = colors[getRandom(0, colors.length - 1)];
      context.fill();
      context.closePath();
    &#125;

    function getRandom(min, max)
    &#123;
      return Math.floor((Math.random() * (max - min + 1) + min))
    &#125;
  </script>
</body>

</html>

当然这只是静态的,没加任何效果的。但是原理都是一样的,知道了原理,都是万变不离其宗的。接下来只是在原来的基础上加上动画了。

代码实现如下,大部分代码都有注释的:

/**
 * canvas 粒子文字
 */

class WordsCanvas &#123;
  constructor (canvas, options = &#123;&#125;) &#123;
    this.canvas = canvas ? (typeof canvas == 'object' ? canvas : document.querySelector(canvas)) : document.createElement('canvas')

    this.data = &#123;&#125;
    this.ctx = this.canvas.getContext('2d')
    this.particles = []
    this.init(options)
    this.lock = true
  &#125;

  init (options) &#123;
    let defaultConfig = &#123;
      colors: ["#feea00", "#a9df85", "#5dc0ad", "#ff9a00", "#fa3f20", "#e03636", "#25c6fc", "#ff534d", "#b78261", "#b78261", "#eaf048", "#9ff048", "#2a5200", "#f6d6ff"],
      r: 2,
      wordWidth: 0,
      gridY: 7, // 间隔像素点
      gridX: 7, //间隔像素点
    &#125;

    this.data = Object.assign(&#123;&#125;, defaultConfig, options)
  &#125;

  render (words) &#123;

    this.ctx.textAlign = "center"
    this.ctx.textBaseline = "middle";
    this.ctx.font = "200px Arial"
    let wordWidth = this.ctx.measureText(words).width

    if (wordWidth > window.innerWidth) &#123;
      let rows = Math.ceil(wordWidth / window.innerWidth)
      this.canvas.width = window.innerWidth
      this.canvas.height = 200 * rows
    &#125; else&#123;
      this.canvas.width = 200 * words.length
      this.canvas.height = 200
    &#125;

    this.cw = this.canvas.width
    this.ch = this.canvas.height

    this.ctx.clearRect(0 , 0, this.cw, this.ch)
    this.ctx.textAlign = "center"
    this.ctx.textBaseline = "middle"
    this.ctx.font = "200px Arial"
    // this.ctx.fillText(words, (this.cw - wordWidth ) / 2 + wordWidth / 2, 200 / 2, this.cw)
    this.renderWords(words, (this.cw - wordWidth ) / 2 + wordWidth / 2, 200 / 2, this.cw)
    let imgData = this.ctx.getImageData(0, 0, this.cw, this.ch)

    this.ctx.clearRect(0 , 0, this.cw, this.ch)
    this.createParticle(imgData)
  &#125;

  /**
   * 随机返回一个颜色
   * @return &#123;[type]&#125; [description]
   */
  getRandomColor () &#123;
    return this.data.colors[this.getRandom(0, this.data.colors.length - 1)]
  &#125;

  /**
   * 创建粒子集合
   * @param  &#123;[type]&#125; imgData [description]
   * @return &#123;[type]&#125;         [description]
   */
  createParticle (imgData) &#123;
    let buffer32 = new Uint32Array(imgData.data.buffer)

    for (var j = 0; j < this.ch; j += this.data.gridY)
    &#123;
      for (var i = 0; i < this.cw; i += this.data.gridX)
      &#123;
        if (buffer32[j * this.cw + i])
        &#123;
          var part = this.particle(i, j, this.data.r, this.getRandomColor(), this.cw, this.ch)
          this.renderParticle(part)
          this.particles.push(part)
        &#125;
      &#125;
    &#125;
    this.animation()
  &#125;

  /**
   * 动画执行函数
   * @return &#123;[type]&#125; [description]
   */
  animation () &#123;
    let that = this
    let thisTIme = new Date()
    let animTime = null
    doAnimation()
    let pause = false

    function doAnimation(time) &#123;
      that.ctx.clearRect(0, 0, that.cw, that.ch)
      that.particles.forEach(item => &#123;
        if(that.lock)&#123;
          if(Math.abs(item.targetX-item.x) < 0.01 && Math.abs(item.targetY-item.y) < 0.01)&#123;
            item.x = item.targetX
            item.y = item.targetY
            pause = true
            if(thisTIme - animTime > 300) that.lock = false
          &#125; else&#123;
            item.x += (item.targetX - item.x) * 0.2
            item.y += (item.targetY - item.y) * 0.2
            animTime = new Date()
          &#125;
        &#125;else&#123;
          console.log(2);
          if(Math.abs(item.initX-item.x) < 0.01 && Math.abs(item.initY-item.y) < 0.01)&#123;
            item.x = item.initX
            item.y = item.initY
            pause = true
            console.log("执行完毕!")
          &#125; else&#123;
            item.x += (item.initX-item.x) * 0.01
            item.y += (item.initY-item.y) * 0.01
            pause = false
          &#125;
        &#125;
        that.renderParticle(item)
      &#125;)
      if(!pause) &#123;
        window.requestAnimationFrame(doAnimation)
      &#125;
    &#125;
  &#125;

  /**
   * 画文字
   * @param  &#123;[type]&#125; t [description]
   * @param  &#123;[type]&#125; x [description]
   * @param  &#123;[type]&#125; y [description]
   * @param  &#123;[type]&#125; w [description]
   * @return &#123;[type]&#125;   [description]
   */
  renderWords (t, x, y, w) &#123;
    t = t.replace(/\s/g,"");
    let textArr = t.split('')
    let cw = this.cw
    let texts = []
    // 一行的字数
    let temp = []
    let textNumber = Math.floor(cw / 200)
    textArr.forEach((v, i) => &#123;
      let row = Math.floor(i / textNumber)
      if (texts[row]) &#123;
        texts[row].push(v)
      &#125; else &#123;
        texts[row] = []
        texts[row].push(v)
      &#125;
    &#125;)

    texts.forEach((v, i) => &#123;
      this.ctx.fillText(v.join(''), x, y + (i * 200), cw)
    &#125;)
  &#125;
  /**
   * 渲染单个粒子
   * @param  &#123;[type]&#125; particle [description]
   * @return &#123;[type]&#125;          [description]
   */
  renderParticle (particle) &#123;
    this.ctx.save()
    this.ctx.fillStyle = particle.color
    this.ctx.beginPath()
    this.ctx.arc(particle.x, particle.y, particle.r, 0, 2 * Math.PI, true)
    this.ctx.closePath()
    this.ctx.fill()
    this.ctx.restore()
  &#125;

  getRandom(min, max) &#123;
    return Math.floor((Math.random() * (max - min + 1) + min))
  &#125;

  /**
   * 粒子构造函数
   * @param  &#123;Number&#125; x  [x坐标]
   * @param  &#123;Number&#125; y  [y坐标]
   * @param  &#123;Number&#125; r  [半径]
   * @param  &#123;Number&#125; cw [canvas宽]
   * @param  &#123;Number&#125; ch [canvas高]
   * @return &#123;Object&#125;    [粒子对象]
   */
  particle (x, y, r, color, cw, ch) &#123;
    let initx = Math.random() * cw
    let inity = Math.random() * ch
    return &#123;
      targetX: x,
      targetY: y,
      color: color,
      initX: initx,
      initY: inity,
      x: initx,
      y: inity,
      r: r,
    &#125;
  &#125;
&#125;
module.exports = WordsCanvas;

调用方式

let a = new WordsCanvas(document.querySelector("#canvasWords"), &#123;&#125;)
  a.render('A')

最终实现的效果如下:

最后奉上github地址: https://github.com/CavinHuang/canvas-study-demo/tree/master/app/templates/canvas-word

目录
文章作者:CavinHuang
文章链接:http://blog.zukmb.cn/posts/6b730681/
版权声明:转载请注明来自CavinHuangのBlog
linux操作记录(持续记录)
canvas圆形进度盘