angularjs-gsTimelines

Creation of animation DSL to build sophisticated UX transitions using animation timelines.

Homepage

Angular Core Dependency: 1.3.x

Module License: MIT

Added by: Sam Deering

GitHub

Repository

thomasburleson/angularjs-gstimelines

Stats

Stars: 36

Watchers: 36

Forks: 2

Module Description

Designing an AngularJS Animation DSL

Summary

The goal is the development of a next-generation Animation layer for AngularJS with functionality and power to easily develop complex, rich user experiences. A new Animation Timeline API and a easy-to-use DSL will be derived based on experiments and explorations of real-world animation design samples; samples with UX as those as demonstrated in Material Design and the Polymer Topeka Quiz application.

The following three (3) Animation libraries will be considered:

For these experiments, several real-world UX applications were selected from Material Design: dsl_ideas

revealexplode

Each application will be implemented with the three (3) Animation libraries show above. These implementations will be used to identify core animation APIs and features. And that API will, in turn, be used to derive a XML-based Animation DSL.

Background

Meaningful transitions establish visual continuity with and during transitions between two visual states. Carefully choreographed motion design can effectively guide the user’s attention and focus through multiple steps of a process or procedure, avoid confusion when layouts change or elements are rearranged, and improve the overall beauty of the experience

Highlighted in Material Design is a user experience (UX) that is achieved using the 'Reveal' pattern. Reveal animations provide users visual continuity when you show or hide a group of UI elements... Reveal animations often involve complex choreographies with hierarchical timing; where elements are removed, added, or shared between the animation states.

The Koda application developed below uses the Explode-Reveal effect and provides a UX composed of complex motions.

Using Greensock (GSAP) Timelines

Leveraging the power of the Greensock Animation Platform (GSAP) and the timeline features provided in TimelineLite, we can implement choreographed animations in JavaScript and in our Timeline DSL.

DSL: is an acronym for 'domain specific language'. In our case the DSL is intended to be used in HTML markup as a designer-like facade to Timeline features; features that are themselves layered on top of AngularJS ngAnimate features.

From Javascript to DSL

Consider the Koda JavaScript use of the Greensock TimeLine API:

var zoom = new TimelineLite({paused:true}),
    unzoom = new TimelineLite({paused:true});

// Do zoom to show Kodaline details...

zoom.timeScale(1)
    .set($("#mask"),                               { zIndex:90, className:""})
    .set($("#details"),                            { height:162, opacity:0, width:162 } )
    .set($("#details"),                            { className:"" })
    .to( $("#details"),                       0.2, { opacity:1} )
    .to( $("#details"),                       0.3, { left:0, height:210, width:323 } )
    .addLabel("fullWdith")
    .to( $("#mask"),                          0.5, { opacity:0.80 },        "fullWidth-=0.3" )
    .to( $("#details"),                       0.3, { top:18, height:512 },  "fullWidth-=0.05" )
    .addLabel("slideIn")
    .set($("#details > #green"),                   { zIndex:92, opacity:1.0, top:21, className:"" })
    .to( $("#details > #green"),              0.2, { top:0 },                "slideIn" )
    .to( $("#details > #tile"),               0.6, { height:131 },           "fullWdith")
    .to( $("#details > #info"),               0.5, { height:56 },            "fullWdith+=0.2")
    .to( $("#details > #title > div.content"),0.8, { opacity:1 },            "fullWdith+=0.3")
    .to( $("#details > #pause"),              0.4, { opacity:1, scale:1.0 }, "fullWidth+=0.4")
    .to( $("#details > #info > div.content"), 1.0, { opacity:1 },            "fullWdith+=0.6");

We can this express this same transition as an HTML-based DSL:

<!-- AngularJS Koda SPA  -->

<body ng-app="kodaline" ng-controller="TimelineController" >

 <!-- Animation DSL -->
 <timeline state="zoom" time-scale="1" resolve="preloadImages(source)" >
    <timeline>
      <step target="#mask"                           style="z-index:90;" />
      <step target="#mask"                           style="opacity:0.8;" position="300" duration="0.5"/>
    </timeline>
    <timeline>
      <step target="#details"                        style="opacity:1; bounds:{{source.from}};"/>
      <step target="#details"                        style="left:0; height:210; width:323;" duration="0.3"  />
      <step mark-position="fullWidth"/>
      <step target="#details"                        style="top:18; height:512" duration="300" position="fullWidth-=0.3"/>
      <step mark-position="slideIn"/>
      <step target="#details > #green"               style="z-index:92; opacity:1; top:21;" />
      <step target="#details > #green"               style="top:0;" />
      <step target="#details > #title"               style="height:131;"  duration="200" position="fullWidth" />
      <step target="#details > #info"                style="height:56;"   duration="0.6" position="fullWidth+=0.2" />
      <step target="#details > #title > div.content" style="opacity:1.0;" duration="500" position="fullWidth+=0.3" />
      <step target="#details > #pause"               style="opacity:0.8;" duration="800" position="fullWidth+=0.4" />
      <step target="#details > #info > div.content"  style="opacity:0;"   duration="0.4" position="fullWidth+=0.6" />
    </timeline>
 </timeline>

 <!-- UI View Elements --> 

 <div id="stage">

    <!-- Tile Grid View -->
    <div id="status" class="status"></div>
    <div id="header"></div>

    <div class="tile1" ng-click="showDetails(0)" ></div>
    <div class="tile2" ng-click="showDetails(1)" ></div>
    <div class="tile3" ng-click="showDetails(2)" ></div>
    <div class="tile4" ng-click="showDetails(3)" ></div>

    <div id="other"></div>
    <div id="footer"></div>

    <!-- Tile Grid Mask -->
    <div id="mask" class="hidden"></div>

    <!-- Tile Details View -->
    <div id="green_status" class="hidden"></div>
    <div id="details" class="hidden">


This DSL is much more expressive and intuitive. For web designers tasked with animation and choreographic considerations (instead of Javascript implementataions), the DSL is especially suitable as the transition definitions are embedded within the HTML markup in close proximity to the DOM elements that will animated.

Instead of the typical separation of HTML layouts from animation logic (and element manipulation) in JavaScript, we can express both the UI and the UX transitions within the same UI layers of the client.

Note: some of the parameters (eg. Line #69) support AngularJS interpolation symbols and data-binding. This is powerful feature over the javascript-approach... timelines will be automatically updated when scope variables are modified. This, in turn, means that the timeline can be applied to 1..n targets. In the case of Koda, the timeline is applied to any of the gridlist tiles.


Animation States

Leveraging the concept of associating views and view layouts with states of the application, transitions of states can be defined that animate elements and their properties. Changes between states can trigger animations...

Such solutions can be seen in the:

With the features provided by gsTimeline library, animation states can be consider as specific groupings of transitions.

The Koda #4 sample uses Animation states to define a zoom state that shows the tile details zoomed full screen. Simply set the $scope.state = 'zoom' to trigger the animations associated with that state.

$timeline( "zoom", {
    onUpdate          : makeNotify("zoom", "updating..."),
    onComplete        : makeNotify("zoom", "complete.")
});

// Perform animation via state change
$scope.state        = "zoom";
$scope.selectedTile = selectedTile;

The Koda Application

Koda with GreenSock GSAP

Use Greensock's (GSAP) TimelineMax within a Gridlist application and demonstrate the use of animation timelines to build complex transitions.

Samples

Here are some quick links to source or demos for the experiments:

| Description | HTML | Javascript | Live Demos | |--------|--------|--------|--------| | Use ngAnimate with TweenMax | block_1.html | block_1.js | Plunkr #1 | | Use custom Animation services layer | block_2.html | block_2.js | CodePen #2 | | Use gsTimeline $timeline() with DSL | block_3.html | block_3.js | CodePen #3 |

Block Animations

| Description | HTML | Javascript | Live Demos | |--------|--------|--------|--------| | jQuery app with click animation | koda_1.html | koda_1.js | CodePen #1 | | AngularJS app with Timeline slider controls | koda_2.html | koda_2.js | CodePen #2 | | AngularJS app with DSL | koda_3.html | koda_3.js | | | AngularJS app with DSL & States | koda_4.html | koda_4.js | CodePen #4 | | DSL for Explode-Reveal | reveal_1.html | reveal_1.js | CodePen #5 | dsl_codepen_2


Functional Considerations

While exploring the animation API requirements, the implemenation should also consider other functional requirements:

Koda #1

  • Load images in background so zoom works quickly
  • Use promises to delay transitions until the images are ready
  • Dynamically modify timescale so unzoom is faster
  • Use of global keypress to unzoom/reverse the timeline
  • Use data model to dynamically define tile zoom from/to transitions

Koda #2 Only

  • Plugin use of Timeline Slider controls; independent of TimelineController
    • Sync Slider to transition timeline
    • Use slider to manually sequence through transition frames

Koda #3 Only

  • Use of AngularJS-GSAP $timeline service to parse animation DSL (in HTML) and build animations with databindings to scope and data models. Uses programmatic approach to trigger animations:
$timeline( "zoom", {

  onComplete        : makeNotify("zoom"),
  onReverseComplete : makeNotify("unzoom"),
  onUpdate          : makeNotify("zoom", "update")

}).then( function(animation){
    animation.restart();
});

Koda #4 Only

  • Use of AngularJS-GSAP $timeline service (again) but with feature support for Animation States. Now uses state name changes to trigger animations:
$timeline( "zoom", {
    onUpdate          : makeNotify("zoom", "updating..."),
    onComplete        : makeNotify("zoom", "complete.")
});

// Perform animation via state change
$scope.state        = "zoom";
$scope.selectedTile = selectedTile;

Tips to Run Locally

Open Terminal console in the project directory.

bower update
http-server -d ./

Open Browser and navigate to URL http://localhost:8080/


Debugging

By default the $timeline() service and process outputs to the console:

>> TimelineBuilder::makeTimeline() invoked by $timeline('0')
TimelineStates::watchState( state = 'zoom' )
loading $( #backgroundLoader ).src = http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_kodaline.png
 $('#backgroundLoader').loaded() 
loading $( #backgroundLoader ).src = http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_moby_v2.png
 $('#backgroundLoader').loaded() 
>> TimelineBuilder::makeTimeline() invoked by $timeline('zoom')
timeline.set( '#mask', 0.001,  {"zIndex":"-10","className":""}, '' )
timeline.set( '#details', 0.001,  {"zIndex":"-11","className":""}, '' )
timeline.set( '#green_status', 0.001,  {"zIndex":"-13","className":""}, '' )
timeline.set( '#other', 0.001,  {"top":"481","left":"88","opacity":"1.0"}, '' )
timeline.set( '#mask', 0.001,  {"zIndex":"90"}, '' )
timeline.set( '#details', 0.01,  {"zIndex":"92","opacity":"0.01","top":"-1"}, '' )
timeline.set( '#details', 0.3,  {"opacity":"1.0"}, '' )
addLabel( 'fullThumb' )
timeline.set( '#other', 0.2,  {"top":"532","left":"324"}, '' )
timeline.set( '#other', 0.1,  {"opacity":"0"}, 'fullThumb+=0.1' )
timeline.set( '#details', 0.5,  {"delay":"0.3","left":"0","width":"329"}, '' )
addLabel( 'fullWidth' )
timeline.set( '#mask', 0.5,  {"opacity":"0.80"}, 'fullWidth-=0.3' )
timeline.set( '#details', 0.3,  {"opacity":"1","top":"18","height":"512"}, 'fullWidth+=0.1' )
addLabel( 'slideIn' )
timeline.set( '#green_status', 0.001,  {"zIndex":"91","opacity":"1","top":"21"}, 'slideIn' )
timeline.set( '#green_status', 0.2,  {"top":"1"}, 'slideIn' )
timeline.set( '#details > #title', 0.6,  {"height":"131"}, 'fullWidth' )
timeline.set( '#details > #info', 0.5,  {"height":"56"}, 'fullWidth+=0.2' )
timeline.set( '#details > #title > div.content', 0.8,  {"opacity":"1.0"}, 'fullWidth+=0.3' )
timeline.set( '#details > #info > div.content', 0.4,  {"opacity":"1"}, 'fullWidth+=0.6' )
timeline.set( '#details > #pause', 0.4,  {"opacity":"1","scale":"1.0"}, 'fullWidth+=0.4' )
loading $( #backgroundLoader ).src = http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_supermodel.png
 $('#backgroundLoader').loaded() 
loading $( #backgroundLoader ).src = http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_goulding.png
 $('#backgroundLoader').loaded() 
loading $( #backgroundLoader ).src = http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_goyte.png
 $('#backgroundLoader').loaded() 
loading $( #backgroundLoader ).src = http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_pharrell.png
 $('#backgroundLoader').loaded() 
updating $(#details > img).src = 'http://solutionoptimist-bucket.s3.amazonaws.com/kodaline/album_kodaline.png'
>> TimelineStates::triggerTimeline( state = 'zoom' )
tl('zoom') updating...
>> TimelineBuilder::makeTimeline() invoked by $timeline('zoom')
timeline.set( '#mask', 0.001,  {"zIndex":"-10","className":""}, '' )
timeline.set( '#details', 0.001,  {"zIndex":"-11","className":""}, '' )
timeline.set( '#green_status', 0.001,  {"zIndex":"-13","className":""}, '' )
timeline.set( '#other', 0.001,  {"top":"481","left":"88","opacity":"1.0"}, '' )
timeline.set( '#mask', 0.001,  {"zIndex":"90"}, '' )
timeline.set( '#details', 0.01,  {"zIndex":"92","opacity":"0.01","left":"0","top":"73","width":"162","height":"164"}, '' )
timeline.set( '#details', 0.3,  {"opacity":"1.0"}, '' )
addLabel( 'fullThumb' )
timeline.set( '#other', 0.2,  {"top":"532","left":"324"}, '' )
timeline.set( '#other', 0.1,  {"opacity":"0"}, 'fullThumb+=0.1' )
timeline.set( '#details', 0.5,  {"delay":"0.3","left":"0","height":"216","width":"329"}, '' )
addLabel( 'fullWidth' )
timeline.set( '#mask', 0.5,  {"opacity":"0.80"}, 'fullWidth-=0.3' )
timeline.set( '#details', 0.3,  {"opacity":"1","top":"18","height":"512"}, 'fullWidth+=0.1' )
addLabel( 'slideIn' )
timeline.set( '#green_status', 0.001,  {"zIndex":"91","opacity":"1","top":"21"}, 'slideIn' )
timeline.set( '#green_status', 0.2,  {"top":"1"}, 'slideIn' )
timeline.set( '#details > #title', 0.6,  {"height":"131"}, 'fullWidth' )
timeline.set( '#details > #info', 0.5,  {"height":"56"}, 'fullWidth+=0.2' )
timeline.set( '#details > #title > div.content', 0.8,  {"opacity":"1.0"}, 'fullWidth+=0.3' )
timeline.set( '#details > #info > div.content', 0.4,  {"opacity":"1"}, 'fullWidth+=0.6' )
timeline.set( '#details > #pause', 0.4,  {"opacity":"1","scale":"1.0"}, 'fullWidth+=0.4' )
tl('zoom') updating...
tl('zoom') complete.

Module stats last updated: 2017-10-23 16:00:06

Disclaimer: Some data on this page may have been gathered from the authors GitHub respository. If you see any mistakes or outdated information please let us know. Thanks!