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 动点与动线动画 #4

Open
zhaozy93 opened this issue Apr 6, 2017 · 2 comments
Open

canvas 动点与动线动画 #4

zhaozy93 opened this issue Apr 6, 2017 · 2 comments

Comments

@zhaozy93
Copy link
Contributor

zhaozy93 commented Apr 6, 2017

start

偶然一个机会打开了蓝点网,发现整个网站所有页面都有一个全屏的canvas在页面最底层,里面由动点和动线构成,并且监听鼠标位置去调整页面的点线。示例可以通过打开蓝点网便可查看。通过查看将其canvas源码部分找到。混淆压缩后的代码只有1.3KB,将其拷贝到编译器美化一下 也才97行,代码少到可怜,但效果还不错,于是便想着把它还原一下,再然后就有了这篇文章。

还原后的代码示例在JSFiddle。可以在其中查看页面效果。

源码

源码1

  /**
  * [description]
  * @整段代码被  匿名函数包裹,为匿名函数添加!转为表达式,让其立即执行
  * @立即执行的匿名函数还可以解决全局命名冲突等一些列问题
  */
  !function() {
  // code
  }();

源码2

来看几个辅助性的短小的方法

  /**
   * [o description]
   * @可以猜测出,w应该是一个dom对象可能性非常大,获取w的一个属性,如果不存在就返回一个默认值
   */
  function o(w, v, i) {
      return w.getAttribute(v) || i
  }
  /**
   * [j description]
   * @取名j再看代码就非常类似Jquery
   */
  function j(i) {
      return document.getElementsByTagName(i)
  }
  /**
   * @获取整个文件中最后一个js标签以及上面赋予的参数
   * @这里就利用到了上面两个方法,用于初始化全局的几个属性,这几个属性在后面会用到
   */
  function l() {
      var i = j("script"),
          w = i.length,
          v = i[w - 1];
      return {
          l: w,
          zIndex: o(v, "zIndex", -1),
          opacity: o(v, "opacity", 0.5),
          color: o(v, "color", "0,0,0"),
          count: o(v, "count", 99)
      }
  }
  /**
   * [k description]
   * @获取屏幕大小,并设置刚刚生成的canvas的宽度与高度,并将宽高保存到全局变量
   * @暂时不知道确切的使用位置,但猜测得出方法的目的
   */
  function k() {
      _width = canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
      _height = canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
  }

源码3

  // 这里开始准备绘图了,因为创建了canvas对象
  var canvas = document.createElement("canvas"),
        canvasAttr = l(),
        canvasId = "c_n" + canvasAttr.l,
        context = canvas.getContext("2d"),
        _width,
        _height,
        _requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(i) {
            window.setTimeout(i, 1000 / 45)
        },
        _random = Math.random,
        _current = {
            x: null,
            y: null,
            max: 20000
        };
  canvas.id = canvasId;
  // 前面方法初始化的参数在这里被应用到了刚刚创建的canvas对象上
  canvas.style.cssText = "position:fixed;top:0;left:0;z-index:" + canvasAttr.zIndex + ";opacity:" + canvasAttr.opacity;
  j("body")[0].appendChild(canvas);
  // 刚刚的设置canvas大小的方法也应用上了
  k(),
  // 屏幕尺寸变更时,重新计算整个canvas
  window.onresize = k;
  // 鼠标移动时, 记录鼠标位置
  window.onmousemove = function(i) {
      i = i || window.event,
      _current.x = i.clientX,
      _current.y = i.clientY
  },
  // 鼠标离开时, 鼠标位置置空
  window.onmouseout = function() {
      _current.x = null,
      _current.y = null
  };

至此目前完成了一些初始化工作,但还没有进行真正的绘制。

源码4

  // 初始化所有点的位置
  // 到这里 x y xa ya max的具体含义还只是猜测,但待会就可以验证
  // x y为初始状态的位置
  // xa ya为这个点的xy轴上面每次的偏移量
  // max为假设的最远影响距离
  for (var pointers = [], p = 0; canvasAttr.count > p; p++) {
      var h = _random() * _width,
          g = _random() * _height,
          q = 2 * _random() - 1,
          d = 2 * _random() - 1;
      pointers.push({x: h, y: g, xa: q, ya: d, max: 50000})
  }
  // 这里在js执行后在下一次延迟100ms执行一个函数b
  setTimeout(function() {
      b()
  }, 100)

整个代码到此结束,最后结果是100ms后执行一个函数,目前并没有进行真正的绘制,只是将绘图所需要的绘图板(canvas)、绘图版的一些参数(color等)、以及点的固定位置信息生成(x,y),因此所有的内容都会在b函数内被使用,并生成动画。

源码5

  /**
   * [b description]
   * @一个巨大的绘图函数,核心代码
   * @同时是一个无限自循环的函数,利用_requestAnimationFrame实现页面的刷新,从而实现动画
   */
  function b() {
    // 每一帧动画都先清空整个画板
    context.clearRect(0, 0, _width, _height);
    // 生成一个带鼠标位置点的点数组
    var _pointersWithCurrent = [_current].concat(pointers);
    var x,
        i,
        _distance,
        _distancePercentage,
        _xDiff,
        _yDiff;
    // 遍历pointers, 不包含鼠标位置的点数组
    pointers.forEach(function(point) {
        // 上一次点的位置加上偏移量来确定这一次点的位置
        // 这样才能让所有点都动起来,每一帧绘制时 看起来都是连贯的运动轨迹
        point.x += point.xa;
        point.y += point.ya;
        // 当超出边界时就需要将偏移量改变符号,以改变它的移动方向
        point.xa *= point.x > _width || point.x < 0 ? -1 : 1;
        point.ya *= point.y > _height || point.y < 0 ? -1 : 1;
        
        // 先把所有点都绘制在屏幕上面
        context.fillRect(point.x - 0.5, point.y - 0.5, 1, 1);
        
        // 再绘制线
        // 因为要判断点与点之间的距离来决定是不是绘制线
        // 所以又遍历了一边_pointersWithCurrent 包含鼠标位置的点数组
        for (i = 0; i < _pointersWithCurrent.length; i++) {
          // 用一个临时变量保存一下内层for遍历的点信息, 提高效率 不用后面每次都去数组里面取
          // point表示当前点, x表示对比点, 
          x = _pointersWithCurrent[i];
          // 当内层for遍历的点与外层大forEach遍历不是同一个点 且当前内层for的点真实存在
          // 因为内层for遍历中也就是对比点 是包含 鼠标点的数组, 鼠标有可能被置空,防止错误
          if (point !== x && null !== x.x && null !== x.y) {
            // 计算了两点之间的距离 _distance表示
            _xDiff = point.x - x.x,
            _yDiff = point.y - x.y,
            _distance= _xDiff * _xDiff + _yDiff * _yDiff;
            // 下面被注释的巨长的这行代码被下面这个if语句块分割表示,以方便理解。但生产代码这样一行书写是很好的格式范本
            // _distance < x.max && (x === _current && _distance >= x.max / 2 && (point.x -= 0.03 * _xDiff, point.y -= 0.03 * _yDiff), A = (x.max - _distance) / x.max, context.beginPath(), context.lineWidth = A / 2, context.strokeStyle = "rgba(" + canvasAttr.color + "," + (A + 0.2) + ")", context.moveTo(point.x, point.y), context.lineTo(x.x, x.y), context.stroke())
            // 这个if语句块是对上面一行进行分割得到的
            // 只有在距离小于最大距离情况下才考虑绘制线
            if (_distance < x.max){  
              // 如果对比点是鼠标 则需要单独处理一下 以达到点向鼠标靠拢的效果
              if ( x === _current){   
                // 与鼠标点距离大于最大距离的一半,直接更改点的位置,影响下一次绘制的结果
                if (_distance >= (x.max / 2)){
                  // 这里有一个问题需要理解清晰,就是当前点与鼠标点的距离恰好大于最大距离的一半,又小于最大距离的时候才会去更改当前点的位置,在下一次绘制时就会生效,因为这一次所有点已经绘制过了
                  // 符合条件的点并没有更新它的偏移属性,也就是说它还是会照原方向进行偏移。只是它每一次都会直接被更改xy坐标。
                  // 最开始符合这个条件的时候 它的_xDiff 和 _yDiff 差不多是最大的时候,会出现一个小加速 向鼠标方向靠拢,但是速度又很快降下去
                  // 速度降下去之后继续按照原偏移方向移动,当移动到圆的另一个点(因为影响半径是一定的,也就是鼠标点可以画一个圆)边缘,就会出现这抖动状况
                  // 抖动原因 是因为 偏移量决定了点继续向前走,但下面两行代码决定了点开始向后走,恰好两个值大小差不多。也不会出圆的范围,所以纠结的抖动起来
                  point.x -= 0.03 * _xDiff;
                  point.y -= 0.03 * _yDiff;
                }
              }

              // 只要是距离满足要求 不管对比点是不是鼠标都绘制线
              // 线的宽度、透明度都是根据距离计算出来的,增加层次感,美化效果吧
              _distancePercentage = (x.max - _distance) / x.max;
              context.beginPath();
              context.lineWidth = _distancePercentage / 2;
              context.strokeStyle = "rgba(" + canvasAttr.color + "," + (_distancePercentage + 0.2) + ")";
              context.moveTo(point.x, point.y);
              context.lineTo(x.x, x.y);
              context.stroke();
            }
          }
        }
        // 每次循环完毕就将鼠标点剔除出去
        _pointersWithCurrent.splice(_pointersWithCurrent.indexOf(point), 1);
    }),
    // 请求浏览器按帧绘制
    _requestAnimationFrame(b)
  }

conclusion

1、代码不多,主要是利用requestAnimationFrame来实现高效按帧绘制屏幕,实现动画效果。

2、利用偏移量来让点自己动起来

3、通过给script标签添加属性来配置动画,值得学习

4、匿名函数的使用方法也值得学习

5、通过距离对线条的粗细、颜色等进行动态设置,也较固定值增加层次感。

@id7368
Copy link

id7368 commented May 9, 2018

你好我是蓝点网站长,这个动画在蓝点网已经删除,原因在Mac平台可能会引起屏幕闪烁。需要预览的网友可以访问https://www.lovestory.hk/ 这个页面还没有移除。

@zhaozy93
Copy link
Contributor Author

你好我是蓝点网站长,这个动画在蓝点网已经删除,原因在Mac平台可能会引起屏幕闪烁。需要预览的网友可以访问https://www.lovestory.hk/ 这个页面还没有移除。

感谢通知 谢谢。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants