読者です 読者をやめる 読者になる 読者になる

みかづきブログ その3

3ヶ月つづけてみました。

今日のCSSアニメーション その4

アニメのエンディングのようなトランジションを目指しました。


DEMO

See the Pen Transition like the ending by kimmy (@kimmy) on CodePen.


Jade

.btn.adjust(data-color="black")
  .hole
    .inner GO

#mask-wrapper
  #mask

JavaScript

(function(win) {
  
  "use strict";
  
  win.App = {};
  
})(this);

(function(win, doc, ns) {

  "use strict";

  ns.Util = ns.Util || {}

  function Lottery(length) {
    this._length = length;
    this._pod = [];
    
    this.reset(length);
  }

  Lottery.prototype.reset = function(length) {
    var i = 0;

    for (; i < length; i++) {
      this._pod.push(i);
    }
  };    

  Lottery.prototype.select = function() {
    var pod = this._pod,
        num = pod.splice(Math.random() * pod.length | 0, 1)[0];
    
    if (!pod.length) {
      this.reset(this._length);
    }
    
    return num;
  };

  ns.Util.Lottery = Lottery;

})(this, document, App);

(function(win, doc, ns) {

  "use strict";

  ns.Util = ns.Util || {};
  
  function Deferred() {
    var success = new SimpleDeferred(),
        miss    = new SimpleDeferred();

    function done(callback) {
      success.done.call(this, callback);
    }

    function fail(callback) {
      miss.done.call(this, callback);
    }

    function resolve(opt_org) {
      success.resolve.call(this, opt_org);
      miss.reject();
    }

    function reject(opt_org) {
      success.reject();
      miss.resolve.call(this, opt_org);
    }

    function SimpleDeferred() {
      var queue    = [],
          arg      = null,
          isReject = false;

      function done(callback) {
        if (isReject) {
          return;
        }

        if (!!queue) {
          queue.push(callback);
        } else {
          callback.apply(this, arg);
        }
      }

      function resolve(opt_arg) {
        var length = queue.length,
            i;

        if (isReject) {
          return;
        }

        arg = !!opt_arg ? arguments : null;

        for (i = 0; i < length; i++) {
          queue[i].apply(this, arg);
        }
              
        queue = null;
      }

      function reject() {
        if (!!queue) {
          isReject = true;
        }
      }

      return {
        done    : done,
        resolve : resolve,
        reject  : reject
      };
    }

    return {
      done    : done,
      fail    : fail,
      resolve : resolve,
      reject  : reject
    };
  }

  ns.Util.Deferred = Deferred;

})(this, document, App);

(function(win, doc, ns) {

  "use strict";

  function Sprite(id) {
    var that = this;

    _init();

    function _init() {
      that.duration = 0;
      that.$elm     = $("#" + id);
      that.$sec     = $("<div>");

      that.$sec.addClass("data-sec").css({
        display : "none",
        width   : 0,
        height  : 0
      });

      that.$elm.append(that.$sec);
    }
  }

  Sprite.CONST = {
    START_KLASS : "anim"
  };

  Sprite.EVENT = {
    ANIM_END : "animend"
  };

  Sprite.prototype.start = function() {
    var that = this,
        dfd  = new ns.Util.Deferred(),
        zoom;

    that.$elm.addClass(Sprite.CONST.START_KLASS);

    if (!that.duration) {
      zoom = that.$sec.css("zoom");
  
      if (/\%/.test(zoom)) {
          that.duration = parseFloat(that.$sec.css("zoom"), 10) * 10;
      } else {
          that.duration = parseFloat(that.$sec.css("zoom"), 10) * 1000;
      }
    }

    setTimeout(function() {
      var param = {
        duration : that.duration
      };

        dfd.resolve(param);
    }, that.duration);

    return dfd;
  };

  Sprite.prototype.reset = function() {
    var that = this;

    that.$elm.removeClass(Sprite.CONST.START_KLASS);
  };

  ns.Sprite = Sprite;

})(this, document, App);

(function(win, doc, ns) {
  
  "use strict";
  
  $(function() {
    var mask   = new ns.Sprite("mask"),
        $btn   = $(".btn"),
        colors = [
          "black",
          "red",
          "purple",
          "blue",
          "green",
          "yellow"
        ],
        lottery = new ns.Util.Lottery(colors.length);

    $btn.on("click", transition).trigger("click");

    function transition() {
      mask.start().done(function() {
        var DELAY = 200,
            i     = lottery.select();

        $btn.attr("data-color", colors[i]);        
        setTimeout(mask.reset.bind(mask), DELAY);
      });
    }
  });
  
})(this, document, App);

SCSS

@import "compass/reset";

html, body {
  height: 100%;
}

body {
  background: #e3e3e3;
}

#mask-wrapper {
  $duration: 1;

  position: fixed;
  top: 0; bottom: 0; left: 0; right: 0;
  pointer-events: none;
  z-index: 10;

  #mask {
    position: absolute;
    top: 50%; left: 50%;
    margin: -1500px;
    border: solid 1000px rgba(0, 0, 0, 1);
    border-radius: 50%;
    width: 1000px; height: 1000px;
    opacity: 0;
    // box-shadow: 0 0 5px inset;
    transition: opacity #{$duration}s ease-in, margin #{$duration}s ease-out, width #{$duration}s ease-out, height #{$duration}s ease-out;
    transform: translate3d(0, 0, 0);
    backface-visibility: hidden;

    &.anim {
      margin: -1000px;
      width: 0; height: 0;
      opacity: 1;
      transition: opacity .1s ease-in, margin #{$duration}s ease-out, width #{$duration}s ease-out, height #{$duration}s ease-out;
    }

    .data-sec {
      zoom: $duration;
    }
  }
}

.adjust {
  position: absolute;
  top: 0; bottom: 0;
  left: 0; right: 0;
  margin: auto;
}

.btn {
  $size: 80px;
  
  border-radius: 50%;
  width: $size; height: $size;
  cursor: pointer;
  user-select: none;

  .hole {
    position: relative;
    border-radius: 50%;
    width: $size; height: $size;
    box-shadow: 0 4px 4px rgba(0, 0, 0, .25);
  }
  
  .inner {
    position: relative;
    border-radius: 50%;
    width: $size; height: $size;
    color: rgba(255, 255, 255, .9);
    font: 20px "AvenirNext-Heavy" bold;
    line-height: $size;
    text-align: center;
    cursor: pointer;
    transition: opacity .6s ease;
    pointer-events: none;
    overflow: hidden;
    
    &:after {
      display: block;
      position: absolute;
      top: 0; left: 0;
      width: 100%; height: 100%;
      content: "";
      background: linear-gradient(rgba(255, 255, 255, .1), rgba(255, 255, 255, 0));
    }
  }
  
  &:hover {
    .hole {
      box-shadow: none;
    }
    
    .inner {
      top: 4px;
      box-shadow: none !important;
      
      &:after {
        background: rgba(255, 255, 255, 0);
      }
    }
  }
  
  &.push {
    .hole {
      top: 4px;
      box-shadow: 0 4px 4px rgba(0, 0, 0, .25) inset;
      overflow: hidden;
    }
    
    .inner {
      &:after {
        background: linear-gradient(rgba(0, 0, 0, .1), rgba(0, 0, 0, 0));
      }
    }
  }
  
  &[data-color="black"] .inner {
    background: #424242;
    box-shadow: 0 2px 0 #212121;
  }
  
  &[data-color="red"] .inner {
    background: #F50057;
    box-shadow: 0 2px 0 #880E4F;
  }
  
  &[data-color="purple"] .inner {
    background: #D500F9;
    box-shadow: 0 2px 0 #4A148C;
  }

  &[data-color="blue"] .inner {
    background: #00B0FF;
    box-shadow: 0 2px 0 #01579B;
  }

  &[data-color="green"] .inner {
    background: #00E676;
    box-shadow: 0 2px 0 #1B5E20;
  }
  
  &[data-color="yellow"] .inner {
    background: #FFC400;
    box-shadow: 0 2px 0 #FF6F00;
  }
}