Mark Peterson's profile

GEWC Web Ad (Interactive)

For this assignment (Adobe Gen Pro Animation course, May 2015, Class 3), we were to take our previous Edge Animate non-interactive HTML motion graphics sequence (please visit my previous project on Behance for extensive details) and up the ante by making it “responsive and more dynamic.”
 
And so I did. I wanted to:
 
1) Make the concert poster actively glow, and make it clickable so it takes you to the Chorale’s Web site.
 
2) Make the tree stars continuously twinkle. (I use the term “flash” in this project.)
 
3) Make the snowflakes follow the mouse from side to side and up and down, as well as slow their descent as the mouse rises and accelerate as the mouse falls.
 
4) Make the project resize itself depending on the screen size.
 
I succeeded on all points, although it took many hours to research and code the JavaScript and JQuery necessary to make it all work, especially the mouse-Note control.
 
Here is my Google Drive link to this extended project, where you can see it in action: https://googledrive.com/host/0BwIBFbaAk4bufnBrbEZXeGtKYWRfeGxNVzVONXFnRjFwRFVIdm95VXNORVFpMGpOSGZZd2s. Please note that you will get the best results by keeping your mouse out of the Stage area until the music note “snowflakes” start falling at the end. Read on as to why...
I began this project by learning. I worked through all seven built-in Lessons in Edge Animate: I highly recommend these, as they give wonderful examples of how to code simple triggers and event handling. I also read and studied much of the Edge Animate JavaScript API at http://www.adobe.com/devnet-docs/edgeanimate/api/current/index.html, as well as several other Web articles specifically on mouse events. I also downloaded the Edge Animate code files for the Adobe Two Birds project at http://www.adobe.com/devnet/edge-animate/articles/showcase-sample-files.html, which gives a great example of how to control a timeline with the mouse.
 
I then grouped many of my layers into Symbols. For example, I combined all seven of the final tree star “flashing” layers into the Symbol Tree_Flashes_SYM. Symbols give control over a subset of the full timeline, so at the end of that flash sequence I placed a Trigger, which jumps back a few seconds along the timeline and then repeats only the portion containing that flash sequence. Symbols are awesome for that feature alone: They allow you to loop any portion of the timeline by layer or by group of layers.
 
The above image shows my complete set of high-level Elements. Using Symbols, I squished the 50+ layers in my previous project down to 17 high-level layers. I named each Symbol layer with the extension _SYM, to identify them, and grouped up to 26 previous layers in each. This made it much easier to review the “big picture” timeline,  as well as allowing control over specific portions of the full timeline. However, Symbols have one significant disadvantage: The full timeline shows only where each Symbol’s contents end, and for that it uses only hard-to-see arrows. It would be much more helpful if Adobe would instead show a thumbnail graphic of each mini-timeline and enable drill-down.
 
Most of the new project features I wanted required code, which I adapted from many excellent posts by others. The rest of this post shares my own code examples, which I hope will help someone else in need.
Responsive Layout
Almost no code required here. The main thing was to just select the Stage layer and check the Responsive Scaling box in its Properties.

 
However, as the Two Birds example explains, Responsive Scaling will not make mouse events work on mobile devices, so it is best to disable mouse events for touch screens. I copied the Two Birds code to do this, as shown later in this post.
Concert Poster Active Glow and Link
I converted the original poster layer into a Symbol and added a pulsing outer glow effect. I also added a Text layer that pulses along with the glow and made this layer clickable.

 
The glow consists of a white Shadow at 65% Opacity with a 10px Blur. These settings help the glow blend into the background. I grow its Spread from 5px to 13px and back over 2s, which makes the glow “pulse.” To make it continuously pulse, I added a Label (PosterGlowStart) at the beginning of the sequence and a Trigger at the end. The Trigger jumps back to the Label and creates a loop:
 
sym.play("PosterGlowStart");
 
Note that the Label and Trigger are inside the Symbol, not on the main timeline. This shows how it is possible to control a sequence independently of any other sequence on the timeline.
 
The Text layer invites the viewer to click on the poster for more information about the concert. I wanted to make this message noticeable without overpowering the poster itself. To do this, I positioned the text in the least busy area of the poster and set it to phase in, ghost-like, from 0% to only 70% Opacity and back, in time with the poster’s outer glow effect.
 
I also coded a Click event, which opens a new browser window for the Chorale’s Web site, and changed the Text layer’s Cursor (in the Properties panel) to a pointer. However, I discovered that adding a Click event of any kind makes that area of the screen active throughout a timeline. It would therefore be possible to click the poster even before it turns visible. To solve this issue, I added another Label at 21s (2s before the outer glow starts), and coded the Click event to check for this:
 
var startLabel = sym.getLabelPosition("PosterClickStart");
if (sym.getPosition() >= startLabel) {
    window.open("http://www.gewchorale.com", "_blank");
}

 
sym.getLabelPosition() gets the timeline position, in ms, of the identified Label.
 
sym.getPosition() gets the current timeline playhead position, in ms.
 
This code ensures that the clickable link does not become active until the sequence plays through to that point.
Continuously Flashing Tree Stars
For this, I combined all of the previous “Flash” layers into one Symbol, added a Label where I wanted to start the loop, and added a Trigger at the end to point back to that Label:

 
sym.play("Flashback");
 
Similar to the continuous loop for the poster glow, this sequence, with its Symbol, Label, and Trigger, is self-contained and runs independently of all other timeline elements.
Music Note Snowflakes with Mouse Control
This was by far the most difficult part of the project. Making the snowflakes fall continuously was easy: Just make Labels and Triggers, as with the concert poster glow and the tree star flashes. Making the snowflakes follow the mouse required complex code.
 
Another Edge Animate sin of omission is that it does not show how your code runs from inside the program. You have to open your project in a browser to see if your code even works. Since my falling musical snowflakes happen at the end of the entire sequence, that means I would have to wait 20s every time I wanted to see if what I was doing was even correct.
 
Instead of wasting so much time, I set up a test project with only a pair of Note elements. I then ran test after test after test, incorporating tips and tricks learned from the resources I mentioned earlier.
 
One very important thing I learned is that in order to make an element follow the mouse, that element has to be a Symbol. You can in fact code mouse events on elements that are not Symbols, but the results will not be what you expect. Specifically, to make mouse-follow work, you have to know the X/Y coordinates of the element and of the mouse pointer. You can get these coordinates by coding calls for .css(), .position(), or .offset(), all of which give different results for different reasons. Many articles debate the virtues of one or the other. In Edge Animate, I found through experimentation that .css() gives the most accurate results, especially since that is how you set things up on the Stage.
 
For example, if you place a Text box 50px from the Left and 25px from the Top, .css() gives you Left: 50 and Top: 25, relative to the Stage, which is exactly what you see in the Edge Animate Properties panel. Position() gives you coordinates relative to the parent object, but if you are coding for the Stage itself, it is the parent object. Offset() gives you coordinates relative to the browser window, but Responsive Scaling allows the window size to change.
 
That said, if the element is a Symbol, .css() gives the correct screen position relative to the Stage. If the element is not a Symbol, .css() gives its current position as 0...
 
Since I wanted each snowflake to fall independently of the others, I had to make each a separate Symbol with only one layer and its own Label and Trigger. Doing this gave additional advantages: I could vary the start time of each Note, how long it takes to fall, and how far back its Trigger restarts the loop. This all helps give a random feel to the overall effect.
The original project used eight Notes in five columns, with some columns holding multiple notes. In this project, I stretched them out across eight columns, one for each Note.
 
I made Notes 1, 3, 5, and 7 static, that is they fall independently but without mouse control.
 
I made Notes 2, 4, 6, and 8 such that if you scrub sideways, these Notes will all follow the mouse, but they will always stay within the L/R borders of the Stage. If you scrub up, they will follow but will continue pressing downward. If you scrub down, they will accelerate downward. Even though these Notes all follow simultaneously, they are still independent and vary in drop timing, and depending on what pretty patterns you make with your mouse they may or may not return in the same relative positions on their next pass. Furthermore, I built in positioning and delay code so that as you move your mouse across the Stage--and even if you leave the Stage and reenter it--these Notes will follow smoothly, without jerking over to the new mouse position.
 
The only caveat to this (a caveat that I could not wholly overcome despite many hours of trying) is that if your mouse is inside the Stage area when this sequence starts up, or if you very quickly leap the mouse into the Stage area at that time, these Notes will freeze up. (If you then move your mouse out of the Stage and wait a bit, they will eventually start up.) This is because the mouse events that control entry into (mouseenter) and hovering over (mouseover) an object get confused. Normally, these events capture the mouse coordinates at any given time. However, if you are too quick on the draw, these events cannot keep up and return NaN (not a number) as coordinates. I built in error-checking to deal with such failures, but it is not bullet-proof. The best solution is just to wait until the Note animation starts before you even let your mouse into the Stage area, and even then do not jerk it in.
For the Note animation, I coded three events: mouseenter, mousemove, and compositionReady.
 
mouseenter
This event captures the mouse coordinates as it enters the Stage, whether from outside of the Stage or on first movement if hovering over the Stage. The mouseover event does likewise. Some articles say to use mouseover rather than mouseenter, but Adobe (in its API link at the beginning of this post) says that to avoid conflict with other elements that may be running at the same time, use mouseenter.

 
NOTE: As discussed earlier, if you move the mouse too quickly, this event will fail to capture the mouse coordinates and will instead return NaN. This causes problems with mousemove, as it does not understand what numbers it is supposed to use to move the Notes in order to follow the mouse. However, if mouseenter first captures actual coordinates, you can then move the mouse in, out, and around as much as you like and all will be well.
 
As with the clickable image, problems could arise if the timeline deals with mouse events before their time. The following code prevents the timeline from even attempting to detect the mouse until it reaches the correct Label:
 
var startLabel = sym.getLabelPosition("NoteStart");
if (sym.getPosition() >= startLabel) {
    this.onMouseEnter( e.pageX, e.pageY );
}

 
When the timeline does reach the correct label, this code sends the mouse coordinates to a function defined in compositionReady.
 
mousemove
This event captures the mouse coordinates as it moves across the Stage. It has similar error-checking to mouseenter and likewise sends its results to a function defined in compositionready.

 
var startLabel = sym.getLabelPosition("NoteStart");
if (sym.getPosition() >= startLabel) {
    this.onMove( e.pageX, e.pageY );
}

 
compositionReady
This event runs when the project is fully loaded. For this project, it defines three functions: onMouseEnter(mouseX, mouseY), onMove(mouseX, mouseY), and kill(type).

 
onMouseEnter receives the mouse coordinates as it enters the Stage. It:
 
1) Gets the Stage width and subtracts the width of a Note object. This value acts to offset the right Stage margin in order to keep the moveable Notes in bounds on the right side.
 
2) Gets the Stage height. This value helps determine a Note’s current playback time relative to its physical position on the Stage. This then helps adjust how slowly the Note reacts to the mouse’s up/down movements.
 
3) Gets the initial mouse X/Y values and the initial Note_2 X value, to help set up the first time the Notes follow the mouse.
 
onMove receives the mouse coordinates every time the mouse moves across the Stage. It:
 
1) Loads the initial X values from onMouseEnter.
 
2) Calculates the X distance from where the mouse entered the Stage to where the mouse is now. Calculates how far L-R each Note should move relative to that difference. (At the current setting, each Note moves 1/3 as far as the mouse.) Assigns new X values to each Note. This all turns the mouse entry point into a “magnet”: The farther you move the mouse away from it, the slower the Notes will follow, and vice versa. Moving the mouse off and then back on the Stage resets the “magnet.” This makes it so that you have to move in and out of the Stage several times in order to move the Notes completely from one side of the Stage to the other. However, this also makes it very smooth and more interesting.
 
3) Checks that Note 2’s X value is at least 0 to keep all even Notes inside the left Stage boundary. Checks that Note 8’s X value is no more than the modified Stage width calculated earlier, to keep all even Notes inside the right Stage boundary. Moves the Notes along the X axis.
 
4) Loads the initial Y values from onMouseEnter.
 
5) Calculates the current timeline positions of each of the even Notes. Checks if any of them are NaN and if so, assigns 0 (start of timeline). Gets the Y difference from where the mouse was last to where it is now. (Normally, this will be either 0 (no Y movement) or 1px.) Uses that difference, the Stage height, and a hard-coded value of 3000 (representing the minimum number of ms it takes each Note to fall) to calculate how far to move the Note along its own timeline from its current timeline position. (At the current setting, each Note moves back and forth along its timeline, which is the same as up and down physically, 1/3 as far as the mouse.) Moves each Note back or forth along its timeline, i.e. the Y-axis, and restarts playback of each Note from that respective point.
 
6) Saves the new mouse Y position for the next round.
 
kill(type) discovers if the device the viewer is using is touch-enabled, and if so prevents either mouseenter or mousemove from firing.
 
Here is the code for compositionReady:
 
compositionReady
// insert code to be run when the composition is fully loaded here
// Control the timeline with the mouse cursor - Get the tutorial here http://tinyurl.com/cxws32a
 
// Per http://www.adobe.com/devnet-docs/edgeanimate/api/current/index.html
//   "When using mouseover on a symbol, child elements of the symbol
//    may interrupt the mouse event. Use mouseenter instead to avoid
//    the conflict."
this.onMouseEnter = function( mouseX, mouseY ){
 
    // Get initial Stage width, modified by Note width.
    // Use to prevent right-most Note from going out of bounds,
    //   regardless of Responsive Scaling adjustments.
    var note_2W = parseInt(sym.$("Note_2_SYM").css("width"), 10);
    var stageW = parseInt(sym.$("Stage").css("width"), 10) - note_2W;
    sym.setVariable("stageW_G", stageW);
 
    // Get initial Stage height.
    // Use to calculate a Note's current animation time relative to
    // its physical fall position down the Stage.
    var stageH = parseInt(sym.$("Stage").css("height"), 10);
    sym.setVariable("stageH_G", stageH);
 
    // Get initial mouse X/Y positions for Note drag control.
    // X-pos is used as a constant.
    // Y-pos is set first here and then reused.
    sym.setVariable("mouse_InitX_G", mouseX);
    sym.setVariable("mouse_SaveY", mouseY);
 
    // Get initial Note_2 X-position for L-R drag control.
    var note_2_InitX = parseInt(sym.$("Note_2_SYM").css("left"), 10);
    sym.setVariable("note_2_InitX_G", note_2_InitX);
}
 
this.onMove = function( mouseX, mouseY ){
    // Get initial values of X-variables as set when mouse entered Stage.
    var stageW = sym.getVariable("stageW_G");
    var mouse_InitX = sym.getVariable("mouse_InitX_G");
    var note_2_InitX = sym.getVariable("note_2_InitX_G");
 
    // Make Notes follow mouse L-R, but more slowly than mouse.
    //   - Get distance between initial & current mouse positions.
    //   - Set Notes to move at a fraction (1/3) of that distance.
    //       Ex. If mouse enters Stage @ 100px w/Note_4 @ 200px, and
    //       mouse moves left to 80px (20px diff), Notes will also
    //       move left but only to 195px (5px diff).
    //   - Also, enforce Note separations as set up in project.
    //       Otherwise, every Note will lock onto the same X value
    //       when the mouse moves.
    var mouse_DiffX = mouse_InitX - mouseX;
    var note_2_DiffX = mouse_DiffX / 3;
    var note_2X = note_2_InitX - note_2_DiffX;
    var note_4X = note_2X + 180;
    var note_6X = note_4X + 180;
    var note_8X = note_6X + 180;
 
    // Move the notes, but keep the outer Notes in bounds.
    if (note_2X >= 0 && note_8X <= stageW){
        sym.$("Note_2_SYM").css({"left": note_2X});
        sym.$("Note_4_SYM").css({"left": note_4X});
        sym.$("Note_6_SYM").css({"left": note_6X});
        sym.$("Note_8_SYM").css({"left": note_8X});
    }
 
    // Get initial values of Y-variables as set when mouse entered Stage.
    var stageH = sym.getVariable("stageH_G");
    var mouse_OldY = sym.getVariable("mouse_SaveY");
 
    // Make Notes follow mouse T-B, but more slowly than mouse.
    //   - Get current Timeline position.
    //       Will use this to calculate the Y-follow-mouse position.
    //   - If the mouse is INSIDE the Stage when the animation
    //       starts, animation MAY lock up when mouse is first
    //       moved if mouse if moved too quickly. The Timeline
    //       position ends up as NaN. If so, set Note animation
    //       to 0 (beginning of timeline).
    var note_2_TimelineY = sym.getSymbol("Note_2_SYM").getPosition();
    var note_4_TimelineY = sym.getSymbol("Note_4_SYM").getPosition();
    var note_6_TimelineY = sym.getSymbol("Note_6_SYM").getPosition();
    var note_8_TimelineY = sym.getSymbol("Note_8_SYM").getPosition();
    if (isNaN(note_2_TimelineY)){
        note_2_TimelineY = 0;
    }
    if (isNaN(note_4_TimelineY)){
        note_4_TimelineY = 0;
    }
    if (isNaN(note_6_TimelineY)){
        note_6_TimelineY = 0;
    }
    if (isNaN(note_8_TimelineY)){
        note_8_TimelineY = 0;
    }
 
    //   - Get distance between previous & current mouse positions.
    //   - Set Notes to move at a fraction (1/3) of the mouse distance.
    //       Get ratio of animation (each Note takes at least 3000ms
    //       to fall) to Stage height, multiply by difference in mouse
    //       positions to move Notes by the same relative amount, but
    //       a fraction as much (1/3) to make it slower.
    //   - Restart the Note's Timeline at that point.
    //       Effect is as though the Note follows the mouse but
    //       continues playing on its merry way.
    var mouse_DiffY = mouseY - mouse_OldY;
    var note_2_DiffY = ((3000 / stageH) * mouse_DiffY / 3);
    sym.getSymbol("Note_2_SYM").play(note_2_TimelineY + note_2_DiffY);
    sym.getSymbol("Note_4_SYM").play(note_4_TimelineY + note_2_DiffY);
    sym.getSymbol("Note_6_SYM").play(note_6_TimelineY + note_2_DiffY);
    sym.getSymbol("Note_8_SYM").play(note_8_TimelineY + note_2_DiffY);
 
    // Save the new mouse Y position.
    sym.setVariable("mouse_SaveY", mouseY);
}
 
// Disable mousemove event for mobile.
// From the Two Birds project at:
//   http://html.adobe.com/edge/animate/showcase/birds/
var isTouch = ('ontouchstart' in window);

 
   function kill(type){
     window.document.body.addEventListener(type, function(e){
       e.preventDefault();
       e.stopPropagation();
       return false;
     }, true);
   }
 
   if( isTouch ){
     kill('mouseenter');
     kill('mousemove');
   }
GEWC Web Ad (Interactive)
Published:

GEWC Web Ad (Interactive)

This is an interactive version of my first Edge Animate project. It adds a clickable link to the Chorale’s Web site, a continuously glowing conce Read More

Published: