アニメのエンディングのようなトランジションを目指しました。
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; } }