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

latest log

酩酊状態で書いたエンジニアポエムです。酩酊状態で読んでください。

mm.mfx.js (mass effect) minimum version

mofmof.js の mm.mfx.js を単体で動作可能に切り出してみました。

280行です。ES5対応のブラウザ, Titanium, NGCore, node.js などで動作します。

// mfx.js (minimum version)

// string.js ---

(function(global) { // @param GlobalObject:
                    // @see http://code.google.com/p/mofmof-js/wiki/API

String.prototype.f || (String.prototype.f = String_f);

// --- local vars ---
var _StringForamt = /@@/g;

// String#f - placeholder( "@@" ) replacement
function String_f(/* var_args */) { // @param Mix: values
                                    // @return String: "formatted string"
                                    // @see: String#sprintf
    var i = 0, args = arguments;

    return this.replace(_StringForamt, function() {
        return args[i++];
    });
}

})(this);

// math.easing.js ---

// Math.easing - easing functions
Math.easing = {
        linear: "(c*t/d+b)", // linear(t,b,c,d)
                             //     t:Number - current time (from 0)
                             //     b:Number - beginning value
                             //     c:Number - change in value(delta)(end - begin)
                             //     d:Number - duration(unit: ms)
// Quad ---
        inquad: "(q1=t/d,c*q1*q1+b)",
       outquad: "(q1=t/d,-c*q1*(q1-2)+b)",
     inoutquad: "(q1=t/(d*0.5),q1<1?c*0.5*q1*q1+b:-c*0.5*((--q1)*(q1-2)-1)+b)",
// Cubic ---
       incubic: "(q1=t/d,c*q1*q1*q1+b)",
      outcubic: "(q1=t/d-1,c*(q1*q1*q1+1)+b)",
    inoutcubic: "(q1=t/(d*0.5),q1<1?c*0.5*q1*q1*q1+b:c*0.5*((q1-=2)*q1*q1+2)+b)",
    outincubic: "(q1=t*2,q2=c*0.5,t<d*0.5?(q3=q1/d-1,q2*(q3*q3*q3+1)+b)" +
                                        ":(q3=(q1-d)/d,q2*q3*q3*q3+b+q2))",
// Quart ---
       inquart: "(q1=t/d,c*q1*q1*q1*q1+b)",
      outquart: "(q1=t/d-1,-c*(q1*q1*q1*q1-1)+b)",
    inoutquart: "(q1=t/(d*0.5),q1<1?c*0.5*q1*q1*q1*q1+b" +
                                  ":-c*0.5*((q1-=2)*q1*q1*q1-2)+b)",
    outinquart: "(q1=t*2,q2=c*0.5,t<d*0.5?(q3=q1/d-1,-q2*(q3*q3*q3*q3-1)+b)" +
                                        ":(q4=q1-d,q3=q4/d,q2*q3*q3*q3*q3+b+q2))",
// Back ---
        inback: "(q1=t/d,q2=1.70158,c*q1*q1*((q2+1)*q1-q2)+b)",
       outback: "(q1=t/d-1,q2=1.70158,c*(q1*q1*((q2+1)*q1+q2)+1)+b)",
     inoutback: "(q1=t/(d*0.5),q2=1.525,q3=1.70158," +
                    "q1<1?(c*0.5*(q1*q1*(((q3*=q2)+1)*q1-q3))+b)" +
                        ":(c*0.5*((q1-=2)*q1*(((q3*=q2)+1)*q1+q3)+2)+b))",
     outinback: "(q1=t*2,q2=c*0.5," +
                    "t<d*0.5?(q3=q1/d-1,q4=1.70158,q2*(q3*q3*((q4+1)*q3+q4)+1)+b)" +
                           ":(q3=(q1-d)/d,q4=1.70158,q2*q3*q3*((q4+1)*q3-q4)+b+q2))",
// Bounce ---
      inbounce: "(q1=(d-t)/d,q2=7.5625,q3=2.75,c-(q1<(1/q3)?(c*(q2*q1*q1)+0)" +
                ":(q1<(2/q3))?(c*(q2*(q1-=(1.5/q3))*q1+.75)+0):q1<(2.5/q3)" +
                "?(c*(q2*(q1-=(2.25/q3))*q1+.9375)+0)" +
                ":(c*(q2*(q1-=(2.625/q3))*q1+.984375)+0))+b)",
     outbounce: "(q1=t/d,q2=7.5625,q3=2.75,q1<(1/q3)?(c*(q2*q1*q1)+b)" +
                ":(q1<(2/q3))?(c*(q2*(q1-=(1.5/q3))*q1+.75)+b):q1<(2.5/q3)" +
                "?(c*(q2*(q1-=(2.25/q3))*q1+.9375)+b)" +
                ":(c*(q2*(q1-=(2.625/q3))*q1+.984375)+b))"
};
// create easing functions
(function() {
    for (var key in Math.easing) {
        Math[key] = new Function("t,b,c,d, q1,q2,q3,q4", // q1~q4 is tmp args
                                 "return " + Math.easing[key]);
    }
})();

// core.js ---
window.mm || (window.mm = {});

// mm.fx.js ---
(function(global, mm) {

mm.fx = {};
mm.fx.requestAnimationFrame = mm_fx_requestAnimationFrame;
mm.mfx = mm_mfx;
mm.mfx.kill = mm_mfx_kill;

var _uniqueCount = 0,
    _mfxTicket = [],
    _mfxKillingTicket = [],
    _requestAnimationFrame = global.requestAnimationFrame ||
                             global.oRequestAnimationFrame ||
                             global.msRequestAnimationFrame ||
                             global.mozRequestAnimationFrame ||
                             global.webkitRequestAnimationFrame;

// mm.fx.requestAnimationFrame
function mm_fx_requestAnimationFrame(tick,    // @param Function:
                                     delay) { // @param Number(= 4): setTimeout delay, msec
                                              // @return Mix:
    return _requestAnimationFrame ? _requestAnimationFrame(tick)
                                  : setTimeout(tick, delay == null ? 4 : delay);
}

// mm.mfx.kill - killing animation
function mm_mfx_kill(killingTicket) { // @param Number: mm.mfx() result killingTicket
    // addif
    if (_mfxKillingTicket.indexOf(killingTicket) < 0) {
        _mfxKillingTicket.push(killingTicket);
    }
}

// mm.mfx - mass effect
function mm_mfx(param,           // @param Hash: { id: spec, ... }
                tickCallback,    // @param Function: tick callback(result, param)
                stateCallback) { // @param Function(= null): change state callback(build, param)
                                 // @return Number: killingTicket for killing animation

    function tick() { // @lookup: param, ticket, past, mfxdb,
                      //          tickCallback, stateCallback
        var now = Date.now(), curt, spec, result = {}, update = false,
            i = 0, iz = mfxdb.length, j, jz, remain = iz;

        if (_mfxKillingTicket.length &&
            _mfxKillingTicket.indexOf(ticket) >= 0) {
            kill();
        }
        for (; i < iz; ++i) {
            curt = null;
            spec = mfxdb[i];

            switch (spec.state) {
            case COMPLETED:
                --remain;
                break;
            case WAIT:
                spec.past || (spec.past = now);
                if (now >= spec.past + spec.delay) {
                    spec.state = RUNNING;
                    curt = spec.a.concat();
                    update = true;
                }
                break;
            case RUNNING:
                update = true;
                if (now >= spec.past + spec.delay + spec.time) { // timeout?
                    spec.state = COMPLETED; // change state(RUNNING -> COMPLETED)
                    curt = spec.b.concat();
                    --remain;
                } else {
                    for (curt = [], j = 0, jz = spec.a.length; j < jz; ++j) {
                        curt.push(spec.easing(now - spec.past - spec.delay, // -> current time
                                              spec.a[j], spec.b[j] - spec.a[j],
                                              spec.time));
                    }
                    spec.c = curt.concat();
                }
                break;
            case FREEZE:
                update = true;
                spec.state = COMPLETED; // change state(FREEZE -> COMPLETED)
                curt = (spec.freeze ? spec.c : spec.b).concat();
                --remain;
            }

            if (curt !== null) {
                if (spec.isArray) {
                    result[spec.id] = [];
                    for (j = 0, jz = spec.a.length; j < jz; ++j) {
                        result[spec.id].push(curt[j]);
                    }
                } else {
                    result[spec.id] = curt[0];
                }
            }
        }
        if (update) {
            // tickCallback() -> false is killing animation
            if (tickCallback(result, now - spec.past, param) === false) {
                kill();
            }
        }
        if (remain > 0) {
            _requestAnimationFrame ? _requestAnimationFrame(tick)
                                   : setTimeout(tick, 4);
        } else {
            // removeif
            var index = _mfxTicket.indexOf(ticket);
            if (index >= 0) {
                _mfxTicket.splice(index, 1);
            }

            stateCallback && stateCallback(false, param); // destruct
        }
    }

    function kill() { // @lookup: _mfxKillingTicket, ticket, mfxdb
        var i = 0, iz = mfxdb.length;

        // removeif
        var index = _mfxKillingTicket.indexOf(ticket);
        if (index >= 0) {
            _mfxKillingTicket.splice(index, 1);
        }

        for (i = 0; i < iz; ++i) {
            if (mfxdb[i].state !== COMPLETED) {
                mfxdb[i].state = FREEZE;
            }
        }
    }

    function _buildMassEffectDB(param) { // @lookup:
        function ora() {
            var undef, args = arguments, i = 0, iz = args.length;

            for (; i < iz; ++i) {
                if (args[i] !== undef) {
                    return args[i];
                }
            }
            return args[iz - 1]; // last
        }

        var rv = [], id, spec, remain = {},
            defs = { _time: 200, _delay: 0,
                     _easing: Math.inoutquad, _freeze: false };

        // --- pickup reserved words ---
        for (id in param) {
            id in defs ? (defs[id]   = param[id])
                       : (remain[id] = param[id]);
        }
        // --- build mfx db ---
        // mfxdb = [spec, ...]
        // spec = { id, a, b, c, time, delay, easing, freeze, isArray, state, past }
        for (id in remain) {
            spec = remain[id];
            if (spec.a === void 0 || spec.b === void 0) {
                ; // ignore id
            } else {
                rv.push({
                    id:     id,   // id. eg: "x", "y"
                    a:      Array.isArray(spec.a) ? spec.a : [spec.a], // point A
                    b:      Array.isArray(spec.b) ? spec.b : [spec.b], // point B
                    c:      Array.isArray(spec.a) ? spec.a : [spec.a], // Current value
                    time:   ora(spec.time,   spec.t, defs._time),
                    delay:  ora(spec.delay,  spec.d, defs._delay),
                    easing: ora(spec.easing, spec.e, defs._easing),
                    freeze: ora(spec.freeze, spec.f, defs._freeze),
                    isArray: Array.isArray(spec.a),
                    state:  WAIT,
                    past:   0
                });
            }
        }
        return rv;
    }

    var ticket = ++_uniqueCount, mfxdb,
        WAIT = 0, RUNNING = 1, FREEZE = 2, COMPLETED = 4;

    mfxdb = _buildMassEffectDB(param);

    // addif
    if (_mfxTicket.indexOf(ticket) < 0) {
        _mfxTicket.push(ticket);
    }

    stateCallback && stateCallback(true, param); // build

    _requestAnimationFrame ? _requestAnimationFrame(tick)
                           : setTimeout(tick, 4);
    return ticket;
}

})(this, this.mm || this);