Skip to content

andreas-trad/mc-legacy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mc legacy

Disclaimer

This project is discontinuted and it remains here for historical reasons. There is a new, better and more holistic approach on web animations though the newer and better version

=============

Intro

How would you like to decouple and take all the animations logic, of your web applications, completely out of your javascript code?
How would you like to define your animations in a CSS-like syntax on external files and just trigger custom events in order to execute them?
If all these sound interesting read on.

MotorCortex.js aspires to mimic the human brain's motor cortex by providing a new layer of software abstraction that handles and describes the movement of the various elements of an html application in a way that all timed effects doesn't interfere with the logic (decision making) layer of the software and plus each "transition" is treated as a matter of the whole application body and not of each element alone. And all this with an easy and straight forward CSS-like syntax and a js API that respects and matches all the modern MV* javascript frameworks.

License

Released under the WTFPL license. Andreas Trantidis @AndreasTrantidi

Compatibility and Dependencies

Browser support

  • Internet Explorer 8+
  • Mozilla Firefox 1.5+
  • Safari 3+
  • Opera 9.5+ (9.01+ as of 1.10)
  • Google Chrome 1.0+
  • iOS 2.0+
  • Opera Mini (to a certain degree)

Dependencies

Bower

Package name: MotorCortex.js

npm

Name: motor-cortex-js

The basics

MotorCortex.js provides the ability to define / describe animations and (animated) transitions between states through a CSS (or even LESS) - like syntax.
The library is event driven. The developer can define the behavior of DOM elements whenever an event is triggered. This behavior is defined on external MSS (Motor Style Sheet) file(s) (*.mss) which get loaded using a simple load function (loadMSS).
During the mss files load, the library reads and renders the CSS-like code to javascript objects that handle the animations accordingly, whenever an event gets triggered.
Events are triggered using the library's most important function (that's "trigger"). The "trigger" function always takes as its first parameter a string which represents the name of the (custom) event to be triggered. The library, then, uses the proper javascript objects and executes the (sequence) of, the defined on the mss file, animations.
MotorCortex.js uses velocity.js as its underlying animation library. Velocity.js performs better than jQuery animations and is also faster than CSS animation libraries, something that makes MotrorCortex.js animations extremely smooth.

Loading the library

The MotorCortex library depends on jQuery and velocity.js. velocity.js depends on jQuery too, so you should include these two libraries in the jQuery/velocity sequence before including MotorCortex on your page.
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="path/to/jquery.velocity.min.js"></script>
<script src="path/to/MotorCortex.js"></script>

Working with npm and browserify

In order to use MotorCortex with browserify you need to install it through npm npm install motor-cortex-js On any place of your code you need to do the following:
global.jQuery = require('jquery');
global.velocity = require('velocity-animate');
var MotorCortex = require('motor-cortex-js');
/* and proceed normally */
var mc = new MotorCortex();
mc.loadMSS('./path/to/my_mss.mss', function(){
// here you are sure the MotorCortex loaded and rendered the MSS files
});

MotorCortex depends on jQuery and velocity, so make sure you include both on your package.json file or install them manually. The corresponding npm packages are:

  • jquery
  • velocity-animate

Loading the MSS files

All animations are defined in MSS files. Once you've loaded both the library and its dependencies you can load one or more MSS files at once using the "loadMSS" function:
var mc = new MotorCortex();
mc.loadMSS('./path/to/my_mss.mss', function(){
    // here you are sure the MotorCortex loaded and rendered the MSS files
});

loadMSS function takes two parameters. This first parameter can be either a string pointing the one and only MSS file you want to load or an array of strings, each pointing to a different MSS file.
The second parameter is a callback function called when the loading and rendering process finishes.

MSS syntax / Triggering events

Quick example

The following code defines the behavior of the elements of class ".section" and ".section2" when the event with name "myEvent" fires:
.section:myEvent{
    duration:400;
    top:+=300px;
}
.section2:myEvent{
duration:300;
top:-=300px;
}

This code indicates that when the event with the name "myEvent" fires all elements of class "section" will animate top by 300 pixels in 400ms and all elements of class "section2" by 300 pixels in 300ms.
Triggering an event is easy. After loading the library and the mss files we call the MotorCortex's "trigger" method. At the specific example this is as easy as this:

var mc = new MotorCortex();
mc.loadMSS('./path/to/my_mss.mss', function(){
    mc.trigger('myEvent');
});

The trigger function

The trigger function takes from 1 up to 4 parameters:
  • eventName: the name of the event to trigger
  • e [optional]: a javascript event object. This is passed when we want to know the target of an action that caused an event to fire. We'll see more later on about this
  • params [optional]: an object containing animation parameters. These parameters (with the right MSS syntax) can be used on run time in order to assign custom values to animation parameters
  • callback [optional]: a callback function to call when the animation of the event finishes
A full example, using all the parameters is the following:
var mc = new MotorCortex();
mc.loadMSS('./path/to/my_mss.mss', function(){
    $('button').click(function(e){
        mc.trigger('myEvent', e, {top:'300px'}, function(){
            console.log('event animation finished!');
        });
    });
});

All the following method invocations are valid:
mc.trigger(eventName);
mc.trigger(eventName, e);
mc.trigger(eventName, e, params);
mc.trigger(eventName, e, params, callback);
mc.trigger(eventName, e, callback);
mc.trigger(eventName, params);
mc.trigger(eventName, params, callback);
mc.trigger(eventName, callback);

Animation parameters and characteristics

There are two kinds of parameters that we can define for an animation:
  • The animation parameters: These parameters define the CSS attributes that we want to alter during an animation
  • The animation characteristics: These options define the animation characteristics

Animation parameters

The "animation parameters" define the CSS attributes of the selected elements that are going to get animated during an animation.
MotorCortex.js uses velocity.js as its underlying animation mechanism. The animation parameters supported by MotorCortex are identical with the ones supported by velocity.js. In general, these parameters are identical with any valid CSS parameter (e.g. width, height, top, left, etc).
Though, there are some major differences when it comes to transforms and colors. You can check these two links for more information:
Supported transforms
Dealing with colors
Animation parameters can be expressed in absolute values on your MSS files (such as):
.section:myEvent{
    duration:400;
    top:300px;
}
, can be followed by any valid unit (such as):
  • %
  • px
  • deg
  • etc
, can be negative (by using the "-" operator)
and can, also be relative by using the proper operators (such as):
  • -=
  • +=

Animation characteristics

The "animation characteristics" define the characteristics of an animation. The list of the available characteristics is:
  • duration: expressed in integer number. Defines the milliseconds that the animation will last
  • delay: expressed in integer number. Defines the milliseconds that the animation will delay before starting
  • easing: defines the easing of the animation. You can use any value from this list, pass in a four-item array of bezier points. (Refer to Cubic-Bezier.com for crafing custom bezier curves.) or use CSS3 named easings ("ease", "ease-in", "ease-out", and "ease-in-out").
Here is an example:
.section:myEvent{
    duration:400; /* the animation will last 400ms */
    delay:300; /* the animation will delay 300ms before executing */
    easing:easeInElastic
    top:-=300px;
}

Globals and nested elements

Just like in LESS, MotorCortex supports global variables and nested elements.

Globals

Global variables are defined in the top of each MSS document and can be used all over it in this manner:
@myGlobalDuration:300;
@myGlobalTop:300px;
.section:myEvent{
duration:@globals.myGlobalDuration;
top:@globals.myGlobalTop;
}
As shown on the example, in order to define a global variable you just define its value using the "@" character as the first of the definition string.
You can use any of the defined global variables within the MSS file by using the expression:
@globals. followed by the global variable name (this time without the precedent "@".

Nested elements

There are occasions that we want to define animations for an element and also, different animation behavior for its nested elements. For example, let's suppose that we want to animate the top value of all elements of class "section" and also to rotate the images contained to these elements by 90deg.
We could just write:
.section:myEvent{
    duration:300;
    top:+=300px;
}
.section img:myEvent{
duration:300;
rotateZ:90deg;
}

With the nested elements capability provided by MotorCortex you can just write:

.section:myEvent{
    duration:300;
    top:+=300px;

    img{
        /* no need to redefine the duration property. It is inherited from the parent */
        rotateZ:90deg;
    }
}

As you can see we've not included the "duration" option on the img scope. Nested elements automatically inherit the animation characteristics from their parents. Though you can always change / overwrite these options if you want. Using the following syntax the img elements will animate in 400ms and not in 300:

.section:myEvent{
    duration:300;
    top:+=300px;

    img{
        duration:400;
        rotateZ:90deg;
    }
}

Runtime parameters

As mentioned, we can pass run-time variables through the "trigger" function. These parameters dynamically define (on invocation time) the value of selected animation parameters and characteristics.
The following MSS code has been written in a way that expects for these dynamic params to be based on "trigger" invocation:
.section:myEvent{
    duration:@params.duration;
    top:+=(@params.top)px; /* always put in parenthesis dynamic variables (both @globals.xxx and @params.xxx) when you want to follow them by the "px" units */
}

As you can see in the example we've put in parenthesis the @params.top variable. This is because without the parenthesis the code would look like this:
wrong syntax!!
.section:myEvent{
    duration:@params.duration;
    top:+=@params.toppx; /* no way to make clear where the param name ends end if the "px" is part of it or represents the units */
}

As you can see there is no way to separate the @params.top from the "px" units. This is why and when you should use parentheses when working with parametric values.
Going back to our example, we've prepared our MSS to accept dynamic parameters from the trigger function. It's time to see how this is implemented from the javascript side. A proper way to call the trigger function for this example is the following:
var mc = new MotorCortex();
mc.loadMSS('./path/to/my_mss.mss', function(){
    mc.trigger('myEvent', {duration:300, top:250});
});
By this invocation the transition will execute in 300ms making the .section elements top equals to 250px.
Not passing in an expected, from the MSS code, parameter will cause MotorCortex to complete ignore the directive stated on the MSS.

DOM elements attributes

There are occasions that we want each DOM element participating in an animation, to animate according to a specific (own) value. This is totally feasible with MotorCortex by using DOM element attributes.
Let's consider the following example:
MSS
.section:myEvent{
    delay:@domel.data-delay;
    top:+=(@domel.data-top)px; /* always put in parenthesis dynamic variables (both @globals.xxx and @params.xxx) when you want to follow them by the "px" units */
}

As you can see in the MSS code we've used the @domel.delay and @domel.top directives for the value assignment to the delay and top variables. This syntax defines the delay equal to the "data-delay" value of each element of class "section" and that the "top" attribute's value will be equal to the "data-top" attribute of each.
The javascript code is very simple in such an occasion. A simple trigger call is enough.
What matters though is the HTML. As mentioned the delay and top values are going to be retrieved from the animated DOM elements' attributes. So, each of them should have this attributes on its tag.
A proper HTML for the specific example is the following:
HTML
    <div class="section" data-delay="50" data-top="150"></div>
    <div class="section" data-delay="100" data-top="200"></div>
    <div class="section" data-delay="150" data-top="250"></div>
    <div class="section" data-delay="200" data-top="300"></div>
    <div class="section" data-delay="250" data-top="350"></div>

Selectors

As shown, each MSS node starts with the selection part. The selection part defines two things:
  • The elements that are going to be animated
  • The name of the event that this animation is going to be triggered
For example on the following code:
.section:myEvent{
    delay:@domel.data-delay;
    top:+=(@domel.data-top)px;
}

the selection part is the expression: .section:myEvent.
Selections are executed on nested elements too. For example, consider this MSS code:
.section:myEvent{
    duration:300;
    top:+=300px;
    img{
        rotateZ:90deg;
    }
}

The selector parts are:
  • .section:myEvent
  • img

A major difference between nested and top selectors is that on top selectors we always should define the event name. On nested selectors we never define the event name. Just the actual selection part.
A top selection part has the following format:
CSS selection:extra filter:extra filter:....:eventName
A nested selection part has the followng format:
CSS selection:extra filter:extra filter:....:extra filter
By "extra filters" we mean some extra selection methods of the MSS syntax. There are two categories of extra filters that MSS provides:
The @index selection
MotorCortex provides the ability to select (~filter) elements according to their index. By "index" we mean the number of the indexed position of the element within the dom compared to the rest of the selected items. The indexing starts from 0, so the first element on a group of elements has the @index=0, the second @index=1 and so on.
The provided @index filters are the following [X means integer number throughout the scope of the whole table]:
# Operation Syntax Example
1 index greater than @index>X .section:@index>3
2 index less than @index .section:@index<3
3 index less or equal to @index<=X .section:@index<=3
4 index greater or equal to @index>=X .section:@index>=3
5 index equals to @index==X .section:@index==3
6 index odd @index odd .section:@index odd
7 index even @index even .section:@index even

You can use the @params.xxx as the second argument of all selections. For example the following syntax is valid:
.section:@index>[email protected]

The triggeringElement selection Let's suppose that we have a number of list items. We want each item gets clicked to execute an animation where all the rest of the items scale down to 0.3 and the clicked one up to 1.5. On MotorCortex we call the element that triggered an event as "triggering element" and we refer to it by the keyword "triggeringElement". We can either refer to it on selection this way:
.section:triggeringElement:myEvent{
    duration:300;
    top:+=300px;
}

or either exclude it from the selection using the following syntax:
.section:not(triggeringElement):myEvent{
    duration:300;
    top:+=300px;
}

MotorCortex doesn't actually "know" which the triggering element was for each event. We must inform it by passing the "e" variable on the "trigger" method. Continuing the same example, the following javascript code would be correct:
var mc = new MotorCortex();
mc.loadMSS('./path/to/my_mss.mss', function(){
    $('.section').click(function(e){
        mc.trigger('myEvent', e);
    });
});

From the variable e, that represents the event, MotorCortex will find out and filter accordingly the animations.

The "complete" keyword

You can define sequences of animations using the "complete" keyword. For example, let's assume that we want all elements of class "section" to animate left by 200px and then fade out in 300ms when the event with name "myEvent" gets triggered. This can easily be done using the "complete" keyword on our MSS syntax:
.section:myEvent{
    duration:200;
    left:+=200px;
    complete{
        opacity:0;
        duration:300;
    }
}

You can nest as many completes within completes as you want. Also, exactly as on nested elements, the animation characteristics are inherited from the parent (and can be overwritten).
In cases that you use @domel attributes the complete process runs after ALL the elements have completed the animation. The attributes that we define within the complete scope refer to the elements of the parent node. In our example the elements of class "section" are the ones that will execute the animation defined on the complete section.
So, on the complete section we are still working with the selected items, picked form the parent node selector. A LESS-like nested elements definition still applies within the complete section.
Example: let's assume we want the elements of class "section" to animate moving to 200px left in 300ms and then rotate all the images that each contains by 90 degrees in 100ms. We can do that using the following syntax:
.section:myEvent{
    duration:300;
    left:200px;
    complete{
        img{
            rotateZ:90deg;
            duration:100;
        }
    }
}

Looping

MotorCortex MSS syntax provides the "loop" keyword. By assigning a value to the loop attribute of a section, the animation loops as many times as the assigned value.
Loops only have true meaning for sequences (when there are "complete" directives). For example, consider the following MSS code:
.section:myEvent{
    duration:200;
    left:200px;
    loop:5;
}

The first time that the animation will be executed the elements of class "section" will be animated left to 200px. If we repeat the same animation step nothing will really happen as the elements will start and end their movement from the exact same spot.
Loops apply to sections where callbacks have been defined through the "complete" keyword. On our example the whole process (including the callback) will be executed 4 times:
.section:myEvent{
    duration:200;
    left:200px;
    complete{
        left:0;
        duration:200;
    }
    loop:4;
}

Stop

You can easily stop the animation of any animating element at any time. The only thing that you need to do is to create an event and after picking the elements you want to stop on this event just pass "stop:true" on the body of the selection.
Let's suppose that on the event "stopAnimation" you want to stop animating all elements of class ".item". The only thing you need to do is to define on your mss file the corresponding event, set your selector and pass the command:
.item:stopEvent{
    stop:true;
}

This way all items with class ".item" will stop exactly where they are once the "stopEvent" event gets triggered.
On a block that includes the stop command all other properties will be ignored except the "complete". That means that after stopping animation of any element in the page you can proceed by any other action by using the "complete" keyword, as regular.

Reverse

MotorCortex supports reverse of any animation. If you want to reverse the last animation of any element in your DOM just pass the "reverse:true" parameter within the body of your selection. All properties (such as delay, duration, etc) apply as usual on blocks that execute reverse. You can either use parametric values as usual.
It's good to always stop the animation of any element before reversing otherwise the behaviour might be unpredictable. That's easy to do by creating a block that executes the stop command and on the complete block has the command reverse. Here's an example that demonstrate how to stop and reverse the animation of all elements of class ".item":
.item:stopAndReverse{
    stop:true;
    complete{
        reverse:true;
        duration:200;
    }
}

Events sequencing

MotorCortex lets you define sequences of events (sequencial execution of events). You can define which event you want to trigger when an event ends by entering to the global (top) scope of any MSS file the directive:

theNameOfTheAnimationThatEnds:callback{
    eventName:theNameOfTheAnimationToFollow;
}

You can even trigger multiple events after an event completes by entering multiple "eventName" attributes within the callback node:
theNameOfTheAnimationThatEnds:callback{
    eventName:theNameOfTheAnimationToFollow;
    eventName:theNameOfASecondAnimationToFollow;
    eventName:theNameOfAThirdAnimationToFollow;
}

Random values

Anywhere in your MSS file you can use the @rand() function provided. The rand function has the following syntax:
@rand(min, max)

and returns a random number that's equal or greater than min and less or equal than max.
An example of @rand usage is the following:
.section:myEvent{
    duration:@rand(250,350);
    left:(@rand(100,200))px;
}

Adding and removing classes

Sometimes during an animation it's useful to either add or remove classes from the animated elements. This not only applies alterations on the elements' appearance but also provides a way to change the groups of elements to animate on following animations dynamically.
MSS provides the ability to dynamically add and remove classes to elements by the keywords:
-. /* remove class */
+. /* add class */


Example:
.section:myEvent{
    duration:@rand(250,350);
    left:(@rand(100,200))px;
    complete{
        -.:classA;
        +.:classB;
        duration:250;
        left:-=200;
    }
}

The process of adding or removing classes to elements is always executed BEFORE the actual animation process. In our example the elements of class "section", when entering the "complete" section of the animation, they will first remove the class "classA" and add the class "classB" and THEN they will animate left by -200 px.

Scroll

As defined on velocity's website the scroll command is supported so you can easily scroll the window to the top of any element of the webpage.
In order to do that just put "scroll:true" to the animation attributes of any element.
Example:
#section:myEvent{
    duration:300;
    scroll:true;
}

This way, whenever the "myEvent" gets fired by MotorCortex the window will scroll to the top of the "#section" element of the webpage.
Make sure the selection ("#section" on this example) refers to just one element on the webpage to avoid strange behaviour of the animation. Also, keep in mind that the scroll command gets executed separately (in parallel) with any other animation defined for a section. If on a section there's only the "scroll" command the complete keyword / functionality is not supported.