IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]使用requestAnimationFrame和Canvas给按钮添加绕边动画

    iefreer发表于 2016-03-30 23:42:39
    love 0

    要给按钮添加酷炫的绕边动画,可以使用Canvas来实现。

    基本的思路是创建一个和按钮大小相同的Canvas元素,内置在按钮元素中。然后在Canvas上实现边线环绕的动画。


    requestAnimationFrame接口

    我们使用requestAnimationFrame接口来实现动画帧的绘制,该接口告诉浏览器在重画(repaint)之前先执行一个动画回调函数。

    通过这样的回调机制,浏览器可以把多个动画函数执行完成后再统一绘制,提高动画渲染性能。

    (function() {
        for (var d = 0, a = ["webkit", "moz"], b = 0; b < a.length && !window.requestAnimationFrame; ++b) window.requestAnimationFrame = window[a[b] + "RequestAnimationFrame"], window.cancelAnimationFrame = window[a[b] + "CancelAnimationFrame"] || window[a[b] + "CancelRequestAnimationFrame"];
        window.requestAnimationFrame || (window.requestAnimationFrame = function(b) {
            var a = (new Date).getTime(),
                c = Math.max(0, 16 - (a - d)),
                e = window.setTimeout(function() {
                    b(a + c)
                }, c);
            d = a + c;
            return e
        });
        window.cancelAnimationFrame || (window.cancelAnimationFrame =
            function(a) {
                clearTimeout(a)
            })
    })();
    上面实现的是一个兼容各浏览器并可以安全降级的requestAnimationFrame版本,当浏览器不支持时,回退到setTimeout/clearTimeout定时函数。

    Border对象

    现在我们需要创建一个Border对象,用来表示绕边的那个动画对象。该Border对象的宽高和按钮元素接近,并包含一个canvas对象。

    function Border(opt) { // 参数opt即为传入的button元素
        this.elem = opt.elem;
        this.active = false;
        this.canvas = document.createElement('canvas');//创建画布
        this.ctx = this.canvas.getContext('2d');//获取画布上下文
        this.width = this.canvas.width = this.elem.outerWidth();//设置宽
        this.height = this.canvas.height = this.elem.outerHeight();//设置高
        this.borderSize = parseInt(this.elem.css('border-left-width'), 10);//设置边宽
        this.waypoints = [
            [0, 0],
            [this.width - this.borderSize, 0],
            [this.width - this.borderSize, this.height - this.borderSize],
            [0, this.height - this.borderSize]
        ];//设置路标数组
        this.tracer = {
            x: 0,
            y: 0,
            color: opt.color,
            speed: opt.speed,
            waypoint: 0
        };//设置路径对象
        this.canvas.style.top = -this.borderSize + 'px';
        this.canvas.style.left = -this.borderSize + 'px';
        this.elem.append($(this.canvas));//把canvas内置到button元素中。
    }

    然后我们实现循环绕边的函数,由于这是Border对象的一个行为,我们把它实现为其原型对象的一个方法:

    Border.prototype.loop = function() {
      requestAnimationFrame($.proxy(this.loop, this));//定时调用自身
      this.ctx.globalCompositeOperation = 'source-over';//源覆盖目标的合成模式
      this.ctx.fillStyle = this.tracer.color;
      this.ctx.fillRect(this.tracer.x, this.tracer.y, this.borderSize, this.borderSize);//在当前路径上绘制小方块(本例是4*4px)
       //下面这段代码是通用算法,用来计算某段路径上x/y方向上的速度分量,同时判断下一步是否需要拐弯(在路标处)
      var previousWaypoint = (this.tracer.waypoint == 0) ? this.waypoints[this.waypoints.length - 1] : this.waypoints[this.tracer.waypoint - 1],
          dxTotal = previousWaypoint[0] - this.waypoints[this.tracer.waypoint][0],
          dyTotal = previousWaypoint[1] - this.waypoints[this.tracer.waypoint][1],
          distanceTotal = Math.sqrt(dxTotal * dxTotal + dyTotal * dyTotal),//该段路径总长度
          angle = Math.atan2(this.waypoints[this.tracer.waypoint][1] - this.tracer.y, this.waypoints[this.tracer.waypoint][0] - this.tracer.x),
          vx = Math.cos(angle) * this.tracer.speed,
          vy = Math.sin(angle) * this.tracer.speed,
          dxFuture = previousWaypoint[0] - (this.tracer.x + vx),
          dyFuture = previousWaypoint[1] - (this.tracer.y + vy),
          distanceFuture = Math.sqrt(dxFuture * dxFuture + dyFuture * dyFuture);//在该路径上的总移动距离
    
    
      if (distanceFuture >= distanceTotal) {//已移动距离超过路径长度,则需要拐弯,即更新路标
          this.tracer.x = this.waypoints[this.tracer.waypoint][0];
          this.tracer.y = this.waypoints[this.tracer.waypoint][1];
          this.tracer.waypoint = (this.tracer.waypoint == this.waypoints.length - 1) ? 0 : this.tracer.waypoint + 1;
      } else {//否则,在当前路径上移动位置
          this.tracer.x += vx;
          this.tracer.y += vy;
      }
    }
    

    有了Border对象后,我们来实际使用它。(在C++等面向对象语言中,我们通常称上面定义了一个类Class,在实际使用时,需要创建类的实例,即对象Object)。

    var button = $("#your_button_id")[0];
    $this = $(button);
    var border = new Border({
        elem: $this,
        color: $this.data('color'),
        speed: $this.data('speed')
    });
    
    $(border.canvas).stop(true).animate({
        'opacity': 1
    }, 400);
    
    border.loop();
    这样我们就实现了一个按钮的绕边动画。

    在线实例

    你可以通过在线实例自己试试看,还可以基于这个简化版本添加渐变效果和鼠标悬停交互处理,乃至实现不规则形状按钮的描边动画。

    by iefreer


沪ICP备19023445号-2号
友情链接