VSB and Sequencer

Knockout で逐次実行 visual effect を実装。 Visual State Binding と sequencer を考案・利用。

by sukobuto

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>
<h1>Sequencer と<br/>Visual State Binding で<br/>逐次 jQuery アニメーション</h1>
<p><a href="http://slide.sukobuto.com/article/ko-effect#/8/1" target="_blank">Visual State Binding</a> で逐次アニメーションをバインドするデモです。</p>
<p>jQuery promise だとやりづらかったので、車輪の再開発かもしれませんが Sequencer という関数逐次実行のためのオブジェクトを作っています。Express.js のミドルウェアと同じようなノリです。</p>
<label>
    <input type="checkbox" data-bind="checked: opened"/>
    オープン/クローズ
</label>

<div class="wrapper" id="a" data-bind="visualState: { opened: opened }">
    <div class="container" id="b">
        <div class="item" id="c"></div>
        <div class="item" id="d"></div>
        <div class="item" id="e"></div>
    </div>
</div>

CSS

body {
    background: #578;
    color: #ccc;
}

.wrapper {
    padding: 20px;
    background: #79a;
    overflow: hidden;
    border-radius: 10px;
}

.container {
    padding: 20px;
    background: #9bc;
    overflow: hidden;
}

.item {
    background: #cef;
    width: 160px;
    height: 40px;
    margin: 20px;
}

JavaScript

/////////////////// Sequencer /////////////////////
(function() {
	var sequencers = {};
	/**
	 * 関数配列を逐次実行するためのシーケンサ
	 * 呼び出される関数では引数 next を受け取り、
	 * 処理が終わった時点で next を呼び出してください。
	 * @constructor
	 * @param {[callback]} func_array 逐次実行する関数配列
	 * @param {string} [group_id] 指定した場合、start したときに同じ id に属するシーケンサを停止します。
	 */
	function Sequencer(func_array, group_id) {
		var self = this
			, i = 0
			, alive = true
			;
		function next() {
			if (alive && func_array[i]) {
				func_array[i++](arguments.callee);
			}
		}

		/**
		 * このシーケンサを中断します。
		 */
		self.stop = function() {
			alive = false;
		};

		/**
		 * このシーケンサを、中断したところから再開します。
		 */
		self.resume = function() {
			alive = true;
			next();
		};

		/**
		 * このシーケンサを最初から実行します。
		 */
		self.start = function() {
			if (group_id) {
				sequencers[group_id].forEach(function(seq) {
					seq.stop();
				});
			}
			i = 0;
			alive = true;
			next();
		};
		
		if (group_id) {
			sequencers[group_id] = sequencers[group_id] || [];
			sequencers[group_id].push(self);
		}
	}
	window.Sequencer = Sequencer;
})();

//////////////// Viaual Effects ///////////////////
var $a = $('#a')
  , $b = $('#b')
  , $c = $('#c')
  , $d = $('#d')
  , $e = $('#e')
  , base_duration = 'fast'
  , wrapper_style = {
      shown: {
        width: 500,
        height: 300,
        padding: 20
      },
      hidden: {
        width: 0,
        height: 0,
        padding: 0
      }
    }
  , seq_open = new Sequencer([
      function(next) { $a.stop().animate(wrapper_style.shown, {complete: next}) },
      function(next) { $b.stop().slideDown(base_duration, next) },
      function(next) { $c.stop().fadeIn(base_duration, next) },
      function(next) { $d.stop().animate({ height: 40 }, {complete: next}) },
      function(next) { $d.stop().animate({ width: 160 }, {complete: next}) },
      function(next) { $e.stop().slideDown(base_duration, next) }
  ], 'seq_group_a')
  , seq_close = new Sequencer([
      function(next) { $e.stop().slideUp(base_duration,...