Tag Archives for actionscript

Brightcove videoplayer accessibility plugin

Q2 2014
Javascript/Actionscript solution to Brightcove player accessibility problems

DOWNLOAD SRC

When I wrote this, Brightcove did have an “Accessible” template – which was not even remotely accessible. Accessible means I should be able to navigate the web page/application with my eyes closed, using only the keyboard, and access the same content in as smooth and efficient an experience as a visually-equipped user.

This solution isn’t perfect – I still received complaints from users about tab-accessibility inconsistency and video control button labels not changing on toggle, which I couldn’t control without access to the video src. Ultimately, we decided to move off of Brightcove as a video solution, but if you’re stuck with it, this will help significantly.

Tabbing into and out of Flash objects embedded on an HTML page is a common problem — if you can’t tab into an embedded application, it doesn’t exist to any user using a screenreader. The solution I stitched together uses a Class from Adobe to setup the tab ordering of clickable elements within the swf, in combination with some javascript by Richard England to handle focus into and out of the swf.

1. Create the swf that will become your custom Brightcove plugin. This is an empty .swf which, upon being added to the stage, will explore the heirarchy of the swf into which is has been imported, find the video controls, will pass those to the SWFFocus class. Below isthe Document class of your .fla.

package {
	import com.adobe.swffocus.SWFFocus;

	import flash.accessibility.AccessibilityProperties;
	import flash.accessibility.Accessibility;
	import flash.display.Sprite;
	import flash.events.Event;
//	import com.richardengland.utils.Tabbing;
	import flash.display.DisplayObjectContainer;
	import flash.display.DisplayObject;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;


	public class SWFFocusFix extends Sprite {
		public function SWFFocusFix() {
			if (stage) {
				init();
			} else {
				addEventListener(Event.ADDED_TO_STAGE, init);
			}
		}

		private function init(event:Event = null):void {
			trace("SWFFocusFix.init");

			var player:Object = parent;
			var counter:Number = 0;

			var buttonContainer:Object;

			while (player.parent) {
				for (var i:uint = 0; i < player.numChildren; i++) {
					var child:DisplayObject = player.getChildAt(i);
					var childClass = getQualifiedClassName(child);

					if (childClass.indexOf("BEMLContainer") != -1) {
						//then this is where we want the tabbing focus to be
						buttonContainer = child;
					}

				}

				player = player.parent;
				counter++;

				if (buttonContainer) {
					break;
				}
			}

			traceDisplayList(DisplayObjectContainer(player));

			if (player) {
				trace("Found player.stage! | " + player.stage);
				if (player.stage) {
					if (hasEventListener(Event.ADDED_TO_STAGE)) {
						removeEventListener(Event.ADDED_TO_STAGE, init);
					}
					/*
					if (buttonContainer) {
					var tabbing = new Tabbing(DisplayObjectContainer(buttonContainer));
					}
					*/
					if (buttonContainer) {
						
							SWFFocus.init(buttonContainer.stage);
					}
				}
			} else {
				if (stage) {
					trace("Using local stage.");
					SWFFocus.init(stage);
				}
			}
		}

		private function traceDisplayList(container:DisplayObjectContainer, indentString:String = ""):void {
			var child:DisplayObject;
			for (var i:uint=0; i < container.numChildren; i++) {
				child = container.getChildAt(i);
				if (child.accessibilityProperties) {
					trace(child.accessibilityProperties.name, child.accessibilityProperties.description );
				}
				trace(indentString, child, child.name);


				if (container.getChildAt(i) is DisplayObjectContainer) {
					traceDisplayList(DisplayObjectContainer(child), indentString + "    ");
				}
				
				
				var childClass = getQualifiedClassName(child);
				//adjustment for the closedCaptions BG
				//if (child.name == "instance137" && childClass.indexOf("GlowingButton") != -1) {
					if (childClass.indexOf("GlowingButton") != -1){
						if (getQualifiedClassName(child.parent.parent.parent).indexOf("CaptionControls") != -1){
							trace("******* removed child " + child.name);
							//child.parent.removeChild(child);
							child.alpha = 0;
							
						}
				}

			}
		}

	}
}

2. In Brightcove VideoCloud, select the player you are embedding, click “Edit” and check the option “Enable Javascript/Actionscript API’s”:

Upload the swf to your host, and include a reference in your Brightcove BEML template.


<SWFLoader depth="2" width="10" height="10" source="https://www.yourdomain.com/swf/SWFFocusFix.swf"/>

According to their documentation, it seems the XML node to import the plugin can be placed anywhere. I placed it within the video controls HBox and this is where it worked in my project, see complete BEML src:




<Runtime>
  <Labels>
    <label key="controls play">PLAY button labeled via BEML</label>
    <label key="controls pause">PAUSE button labeled via BEML</label>
  </Labels>
  <Theme name="Deluxe" style="Light"></Theme>
  <Layout boxType="vbox">
    <ChromelessVideoPlayer id="videoPlayer" useOverlayMenu="false">
      <ChromelessControls boxType="vbox" visible="{!videoPlayer.menu.open}" backgroundImage="https://www.yourdomain.com/images/bc_chromelessBG.png">
        <Spacer width="1"/>
        <HBox hAlign="center" height="10">
          <Playhead id="playhead" height="10" mediaController="{videoPlayer}" useTimeToolTip="true" enabled="{videoPlayer.video}"/>
        </HBox>
        <HBox hAlign="center" height="50">
          <HBox id="defaultView" gutter="0" padding="0" vAlign="middle" maxWidth="820">
            <ToggleButton id="playButton" showBack="false" tabIndex="0" width="65" iconName="play" toggledIconName="pause" accessibleName="acc play pause name" tooltip="controls play tooltip" toggledTooltip="controls pause tooltip" click="{videoPlayer.play()}" toggledClick="{videoPlayer.pause()}" toggled="{videoPlayer.playing}" enabled="{videoPlayer.video}"/>
            <Canvas height="50">
              <HBox gutter="4" vAlign="middle" padding="10">
                <Canvas width="40">
                  <Label id="durationLabel" y="-1" style="font-weight:bold;" tabIndex="1" text="{format(videoPlayer.mediaDuration, SecondsTimecodeFormatter)}" vAlign="middle" hAlign="left" alpha=".75"/>
                </Canvas>
              </HBox>
            </Canvas>
            <CaptionControls hoverMenu="true" id="captionsButton" showBack="false" tabIndex="2" width="50" height="50" accessibleName="Closed captions button. Use spacebar to toggle " iconName="cc-on" toggledIconName="cc-off" tooltip="acc cc showcaptions" toggledTooltip="no CC" click="{videoPlayer.enableCaptions(true)}" toggledClick="{videoPlayer.enableCaptions(false)}" toggled="{videoPlayer.captionsEnabled}" includeInLayout="{videoPlayer.captionsAvailable}"/>
            <VolumeControl id="volumeButton" showBack="false" tabIndex="3" width="50" height="50" accessibleName="acc volume name" mediaController="{videoPlayer}" useOverlayLayer="false" iconName="volume" mutedIconName="muted" tooltip="mute tooltip" mutedTooltip="unmute tooltip" useZeroWidth="true" horizontalPadding="10" popupGutter="3" popupHeight="100" sliderHeight="20"/>
            <ToggleButton id="fullscreenButton" showBack="false" tabIndex="4" width="50" height="50" accessibleName="acc fullscreen name" iconName="maximize" toggledIconName="minimize" tooltip="controls maximize tooltip" toggledTooltip="controls minimize tooltip" click="{videoPlayer.goFullScreen(true)}" toggledClick="{videoPlayer.goFullScreen(false)}" toggled="{videoPlayer.fullscreen}"/>
            <SWFLoader depth="2" width="10" height="10" source="https://www.yourdomain.com/swf/SWFFocusFix.swf"/>
          </HBox>
        </HBox>
      </ChromelessControls>
    </ChromelessVideoPlayer>
  </Layout>
</Runtime>



3. Add a crossdomain.xml policy file to the directory where you are hosting the swf.


<cross-domain-policy>
  <allow-access-from domain="*.yourdomain.com"/>
<allow-access-from domain="admin.brightcove.com" />
<allow-access-from domain="sadmin.brightcove.com" />
</cross-domain-policy>

4. Add a reference to the Javascript to your HTML page. Use <script> or ajax, whatever you please, as long as it is loaded by the time the user needs to tab into and out of the Brightcove player. stealFocus(){} is the function you must modify in order to determine where focus should go when the user has tabbed out of your flash application.




/**
* Cross browser tabbing solution/engine for Flash projects
* 
* Released under MIT license:
* http://www.opensource.org/licenses/mit-license.php
* 
* @author Richard England 2011
* @see http://www.richardengland.co.uk
* @version 0.1
*/


var lastAction = ""; //store last "click" selection (useful for toggle states for example - e.g. play/pause - remembers focus)
var index = 0;
var flashObj; // the flash player object
var firstInput; // the first input object
var shifted = false;

function playerReady( obj ){
	flashObj = obj;
	//get the flash object's "top" CSS value
	var topV = $("#flashplayer").offset().top;
	//add a form to the document and places it at same top offset as flash player object
	$("body").prepend('<form name="form1" id="form1" style="position:absolute; top:'+topV+'px; left:-1500px;"></form>');

	//flashObj returning no name for some reason?
	t = thisMovie("flashplayer");
	t.refreshTabbingList();
	
}

doOnce = false;
function stealFocus( shiftKeyDown ){
	//INSERT YOUR JS FUNCTIONS, or bind to window event:
	
	//are we tabbing backwards or forwards?
	if (shiftKeyDown){
		$(window).trigger("VIDEO_SHIFT_TAB");
	}else{
		$(window).trigger("VIDEO_TAB");
	}
	
	if(!doOnce){
		//alert("steal the focus");
		doOnce = true;
	}
	
}

function addFormField( name, accessName, accessDesc, tabIndex, theTop) {
	//trace("tabbing: addFormField");

	var id = document.getElementById("id").value;
	var accesskey = "";
	if(accessDesc == "") accessDesc = accessName;
	if(index == 0) accesskey = " accesskey='q' "; //for first item only - keyboard shortcut
	var field = $("#form1").append("<div class='temp' id='row" + id + "'><label for='txt" + id + "'>" + accessDesc + " <input tabindex='"+tabIndex+"' type='button' size='20' name='" + name + "' id='"+ name + "' value='"+accessName+"' alt='"+accessName+"' title='"+accessName+"' onfocus='sendFocus( this ); ' onclick='doAction( this );' " + accesskey + "></label></div>");
	
	//set the position so scroll will work properly with tabbing
	$("#row" + id).css( 
              { 
			  "position"	: "absolute",
              "top"			: theTop + "px" ,
              "display"		:"block" 
              }
	)
	id = (id - 1) + 2;

	document.getElementById("id").value = id;
	
}

function addTextField( name, accessName, accessDesc, tabIndex, theTop ) {
	//trace("tabbing: addTextField");

	var id = document.getElementById("id").value;
	if(accessDesc == "") accessDesc = accessName;
	$("#form1").append("<div class='temp' id='row" + id + "' style='position:absolute; top:" + theTop + "px; left:0px;'><label for='txt" + id + "'>" + accessDesc + " <input tabindex='"+tabIndex+"' type='text' size='20' name='" + name + "' id='"+ name + "' value='' alt='"+accessName+"' title='"+accessName+"' onfocus='sendFocus( this ); ' onclick='doAction( this );' onkeyup='doTextUpdate(this)' style='position:absolute;' ></div>");

	id = (id - 1) + 2;

	document.getElementById("id").value = id;
	
}

function removeAllFormFields() {
	//trace("tabbing: removeAllFormFields");
	$(".temp").remove();
	
	document.getElementById('form1').innerHTML = '<input type="hidden" id="id" value="1">';
}

function removeFormField(id) {

	$(id).remove();

}


//receives a new tabbing list object/array from Flash and generates the form by looping through the list
function newTabbingList( obj ){
	//trace("tabbing: newTabbingList");
//alert("newTabbingList");
	removeAllFormFields();

	var i = 0;
	var highestTabIndex = 0;
	var isLastActionStillAvailable = false;

	var lowestTabIndex = 10000;
	while(i < obj.length ) {
		if(obj[i].tabIndex < lowestTabIndex) {
			firstInput = obj[i].name;
			lowestTabIndex = obj[i].tabIndex;
		}
		if(lastAction == obj[i].name)  isLastActionStillAvailable = true;
		if(obj[i].isText){
			addTextField(obj[i].name, obj[i].accessibilityName, obj[i].accessibilityDesc, obj[i].tabIndex, obj[i].theY);
		} else {
			addFormField(obj[i].name, obj[i].accessibilityName, obj[i].accessibilityDesc, obj[i].tabIndex, obj[i].theY);
		}
		if(obj[i].tabIndex > highestTabIndex) highestTabIndex = obj[i].tabIndex;
		i++
	}
	
	
	if(lastAction != "" && isLastActionStillAvailable){
		
		$("#" + lastAction).focus();
	} else {
		
		$("#" + firstInput).focus();
	}

	addFormField("blank", "blank",  "blank", highestTabIndex + 1, 0);
	
	
	index = i;
}

function doPlayFocus(){
	//trace("tabbing: doPlayFocus");
	t = thisMovie("flashplayer");
	t.setFocusTo("playButton", false);
}

//sends text input back to text input in Flash object
function doTextUpdate( inputFocus ){
	
	id = inputFocus.id;
	t = thisMovie("flashplayer");
	t.sendTextUpdate (id , {txt: inputFocus.value});
	lastAction = id;
	
	//alert("doTextUpdate " + inputFocus.value);
}


function doAction( inputFocus ){
	//trace("tabbing: doAction");	
	id = inputFocus.id;
	t = thisMovie("flashplayer");
	t.sendTabAction (id , false);
	lastAction = id;
	
	//alert("doAction " + inputFocus.id);
	//$("#" + lastAction).focus();
}

function thisMovie(movieName) {
	 if (navigator.appName.indexOf("Microsoft") != -1) {
		 return window[movieName];
	 } else {
		 return document.getElementById(movieName);
	 }
 }
 
function sendFocus( inputFocus ) {
	//trace("tabbing: sendFocus");	
	id = inputFocus.id;
	t = thisMovie("flashplayer");
	if(id == "blank") {
		//fix for IE focus
		$("#" + firstInput).focus();
	} else {
		t.setFocusTo(id, false);
	}
	
	
}



16. July 2014 by admin
Tags: , , , , , | Leave a comment

Chevron ECOnomics Touchscreen

2009
Touchscreen Kiosk application for ECO:nomics conference, Santa Barbara, CA
Continuity Worldwide
AS3, HTML, XML


This was a touchscreen kiosk developed for Chevron using Helios’ projector & touchscreen system. 4 projectors each projecting a swf, I was responsible for the content – 3 different content-type loaders which react & load appropriate swfs/flvs/imgs based on what they find in their associated xml. Another dev did the larger projected background swf & the scrolling drag-n-drop ‘shell’ into which content loads. Video is probably going to be slow here – was compressed for local loading.

23. June 2014 by admin
Tags: , , , | Leave a comment

Crossborders – custom cms with flash front-end

2009
crossborders.tv (now rain)
website/custom cms

PHP/MySQL backend w/custom DB, XML output for interpretation by flash front-end, Brightcove for video service, though the client insisted on implementing Brightcove in a way tha bypassed the API’s and was never intended (oh if I had a nickel…).







23. June 2014 by admin
Tags: , , , , , , , , , | Leave a comment

Fedex Sustainability splash animation

2010
Atmosphere BBDO
Flash Splash & Navigation Header
HTML, CSS, AS3, MVC Framework, XML Driven; No Papervision – 3D done with native AS3 3D transforms

Clouds on this one were interesting. Started with a Flint particle system, which, while lovely, bogged down the animation everywhere else.Same issue with Perlin noise clouds. Switched to video. better. Browsed a few hundred cloud clips without finding anything the CD liked as much as the flint clouds. SO, rendered the Flint particles from Flash to Quicktime, converted to .flv to be played via netstream. In retrospect another solution (which might prove quicker) might be going into AE and pinch-distorting a couple cloudscape video layers.

23. June 2014 by admin
Tags: , , | Leave a comment

flash & CSS paragraph spacing

I still can’t believe margin-top and margin-bottom aren’t supported, but whatever. Here’s a hack.

DOWNLOAD SRC

How it works:
The method you’ll need to modify is setCSS(_css : StyleSheet); For each paragraph style in your XML, call setLineSpacing(_pTag : String, _pSpacing : Number = 0), passing the opening tag of your xml paragraph, and the amount of space, in points, you want above it. The actionscript runs through your xml and prepends a <spacer> tag with fontSize styled to the points specified.

MyTextField.as

package com {

import flash.text.AntiAliasType;

import flash.text.StyleSheet;

import flash.text.TextField;

import flash.text.TextFieldAutoSize;

import flash.text.TextFormat;

public class MyTextField extends TextField {

private var initCopy : String;

private var modifiedCopy : String;

private var tags : Array;

public function MyTextField(_s : String, _w : Number = 0, _h : Number = 0) {

//trace(‘MyTextField: + (s));

initCopy = _s;

modifiedCopy = (_s);

if(_w) {

width = _w;

wordWrap = true;

}

if(_h) {

height = _h;

wordWrap = true;

}

antiAliasType = AntiAliasType.ADVANCED;

selectable = false;

autoSize = TextFieldAutoSize.LEFT;

embedFonts = true;

multiline = true;

htmlText = _s;

mouseEnabled = false;

//border = true;

}

override public function setTextFormat(_format : TextFormat, _beginIndex : int = -1, _endIndex : int = -1) : void {

//trace(‘setTextFormat: + (format.font));

if(_format.italic && !embedFonts) {

htmlText = text + ” “;

}

super.setTextFormat(_format, _beginIndex, _endIndex);

defaultTextFormat = _format;

}

public function setCSS(_css : StyleSheet) : void {

styleSheet = _css;

setLineSpacing(“<pa>”, 5);

setLineSpacing(“<pa class=\”style1\”>”, 10);

setLineSpacing(“<pa class=\”style2\”>”, 20);

htmlText = modifiedCopy;

}

private function setLineSpacing(_pTag : String, _pSpacing : Number = 0) : void {

if (!tags) {

tags = [];

}

tags.push(_pTag);

var _copyElem : Array = modifiedCopy.split(_pTag);

var _newCopy : String;

if (!styleSheet) {

styleSheet = new StyleSheet;

}

var _spacerID:String = “spacer” + tags.length;

var _sTag : String = (“<“ + _spacerID + “> </” + _spacerID + “>” + _pTag);

_newCopy = _copyElem.join(_sTag);

styleSheet.setStyle(_spacerID, {fontSize:String(_pSpacing + “px”), fontFamily:“Arial”});

modifiedCopy = _newCopy;

}

public function shrinkToFit(_w : Number) : void {

while(width > _w) {

scaleX -= 0.1;

scaleY = scaleX;

}

}

public function truncate(_maxWidth : Number = 200, _maxLines : Number = 1, _ellipsis : Boolean = false) : String {

multiline = true;

wordWrap = true;

width = _maxWidth;

//for some reason it does not work unless width gets (evaluated)… weird!

width = (width);

var _newCopy : String = (htmlText);

while(numLines > _maxLines) {

_newCopy = _newCopy.slice(0, _newCopy.length – 2);

htmlText = _newCopy;

setTextFormat(getTextFormat());

}

if (_ellipsis) {

_newCopy = _newCopy.slice(0, _newCopy.length – 3);

_newCopy += “…”;

htmlText = _newCopy;

setTextFormat(getTextFormat());

}

return _newCopy;

}

public function toTitleCase() : String {

var _words : Array = text.split(” “);

for (var i : int = 0;i < _words.length; i++) {

var _word : String = _words[i];

var _newWord : String = _word.charAt(0).toUpperCase() + _word.substr(1, _word.length – 1).toLowerCase();

_words[i] = _newWord;

}

text = _words.join(” “);

setTextFormat(getTextFormat());

return (text);

}

}

}

the flash
the xml

<?xml version=“1.0” encoding=“UTF-8”?>

<content>

<page><![CDATA[

<pa>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam malesuada elit nec elit luctus aliquam. Nullam a purus ante, quis feugiat leo. Vivamus a quam suscipit tellus vehicula posuere at eu augue.</pa>

<pa class=”style1″>Suspendisse varius fermentum tellus, pharetra luctus velit imperdiet id. Vestibulum fermentum, lorem a facilisis fringilla, turpis est aliquam massa, vel accumsan urna augue vel orci.</pa>

<pa class=”style2″><span class=”ital”>Curabitur tellus quam</span>, pretium vitae commodo quis, ultricies ut velit. Praesent id quam non ante bibendum mattis. Aenean ut sem vitae arcu tincidunt dapibus at at arcu.</pa>

<pa>Integer ut erat nulla, placerat blandit quam. Vivamus id molestie ligula.</pa>

<pa>Curabitur tellus quam, pretium vitae commodo quis, ultricies ut velit. Praesent id quam non ante bibendum mattis. Aenean ut sem vitae arcu tincidunt dapibus at at arcu.</pa>

<pa class=”style2″><span class=”strong”>Fusce scelerisque </span><br/>turpis ac mauris sodales id dignissim enim lobortis. Sed aliquet, ligula non laoreet adipiscing, velit quam placerat quam, sit amet ultrices massa ante nec augue.</pa>

]]>

</page>

</content>

27. March 2013 by admin
Tags: , | Leave a comment

Happy Holidays from Team Chemistry – greeting card service

2010
www.happyholidaysfromchemistry.com
GHG – Grey Healthcare

MVC AS3 Framework
WCF back-end, XML static data content
Role: front-end – AS3/AJAX Development

Users are invited to create a snowflake with thew drawing tool, attach a message, and send to a friend/colleague. The recipient receives and email bringing them to the site, where their unique snowflake and message are displayed. Snowflakes are public and users can rollover to see other messages that have been sent.

I think the most interesting aspect of this was how we went about storing the snowflake data; Initially I created a test that saved out a PNG bitmap of the snowflake to be downlaodded later. The back-end folks didn’t like the number of potential database calls & http requests (rolls eyes) so what I ended up doing was defining each snowflake as a XML list of shapes with scale, rotation, x-loc & y-loc attributes. This got passed to the REST service via flash and stored in the DB. When retrieving the user’s snowflake later, flash was setup to parse the XML and recreate the snowflake with library vector shapes

23. June 2014 by admin
Tags: , , , , , | Leave a comment

Lexus – The New IS microsite

2005
www.theNewIS.com
Venice Consulting Group
Developed using Adobe Flash & Zoomifier plug-in by Zoomify



This was a viral marketing campaign launched in September 2005 for Lexus’ latest luxury model, the IS. While providing users with product information via an interactive flash interface, the site offered users the opportunity to upload their own photos to be incorporated into a mosaic image of the IS. If desired, the images could also be displayed on the Reuter’s Jumbotron in Times Square, and a photo of the displayed image taken at the time of broadcast to be emailed to the user. Developed by a team of 6 developers.

23. June 2014 by admin
Tags: , , | Leave a comment

Martha Stewart – Products microsite

2008
products.marthastewart.com
Continuity Worldwide
Flash microsite

AS3, pureMVC framework, url routing with swfAddress, Omniture for tracking. Team of 3 developers

The most interesting aspect of this one was the initial animation populating the product pages, using Flash’s drawing API. Simply using trig equation easing on the tweening of the points that made up the corners of each white-outlined square resulted in a very elegant but simply achieved 3D effect.

23. June 2014 by admin
Tags: , , , , | Leave a comment

rotatable draggable MCs

2007

Click and drag the polaroid to move it, click and drag a corner to
rotate it.


High Strung – Photo courtesy
of Patricia
Bower
& Rodolfo
Baras
)

DOWNLOAD SRC

1. _root Setup>
On the _root level we have a polaroid of the band High Strung,
which we want the user to be able to move & rotate. In the
.fla, the name of this movieclip is "polaroid." Also
on the _root level is a small transparent movieclip, called
"mouseTarget" which will assist in the rotation functions.

2. polaroid>
Within the "polaroid" mc is the mc "dragElements"
which contains the Actionscript and Shapes used for ‘hotspots.’
If you’ve multiple objects you want the user to drag and rotate,
simply duplicate this clip and place it within each MC on the
top layer & level.

3. dragElements>
There are plenty of ways one can approach defining one’s interactive
areas; in this case. when the user clicks and drags the corner shapes,
which are contained within their own MC, the rotating functions will
be called. When they click and drag over the central square, they’ll
be able to drag the polaroid about on the stage with a startDrag().

Below right is an explanation of the Functions.

 
NUM
//make
dragElements elements transparent

center._alpha
= 0;
allCorners._alpha
= 0;
//define the area
over which the mc can be dragged

stageLeft = 0;
stageRight = _root._width;
stageTop = 0;
stageBottom = _root._height;
margin = 4;
//dragging actions
– due to rotation, limits must be redefined with each
new startDrag();

center.onPress
= function()
{
     leftLimit = stageLeft+_parent._width/2+margin;
     rightLimit = stageRight-_parent._width/2-margin;
     topLimit = stageTop+_parent._height/2+margin;
     bottomLimit = stageBottom-_parent._height/2-margin;
     startDrag(_parent,
false, leftLimit,
topLimit, rightLimit, bottomLimit);
};
center.onRelease
= center.onReleaseOutside=function
() {
     stopDrag();
};
//calculates degrees
rotation from y-axis based on target & origin point
coordinates

function getCurrentAngle(target_x, target_y, origin_x,
origin_y) {
     xChange = target_x-origin_x;
     yChange = target_y-origin_y;
     if
(xChange<0) {
          xChange_pos
= Number(-1*xChange);
     } else
{
          xChange_pos
= Number(xChange);
     }
     if
(yChange<0) {
          yChange_pos
= Number(-1*yChange);
     } else
{
          yChange_pos
= Number(yChange);
     }
     convert = 57.2957795;
     if
(xChange>0 && yChange>0) {
          angle
= (convert*Math.atan(xChange_pos/yChange_pos));
     }
else if
(xChange>0 && yChange<0)
{
          angle
= (convert*Math.atan(yChange_pos/xChange_pos))+90;
     } else
if
(xChange<0 && yChange<0) {
          angle
= (convert*Math.atan(xChange_pos/yChange_pos))+180;
     } else
if
(xChange<0 && yChange>0) {
          angle
= (convert*Math.atan(yChange_pos/xChange_pos))+270;
     }
     return
angle;
}
//to find the x &
y edges of an object, regardless of shape or rotation

function getEdges()
{
     leftEdge = _parent._x-Number(_parent._width)/2;
     rightEdge = _parent._x+Number(_parent._width)/2;
     topEdge = _parent._yNumber(_parent._height)/2;
     bottomEdge =
_parent._y
+Number(_parent._height)/2;
}
//mouseTarget mc
gets dragged by the user’s cursor, x & y coordinates
will be used to calculate rotation

allCorners.onRollOver
= function()
{
     startDrag(_parent._parent.mouseTarget,
true);
};
//rotates the mc
allCorners.onPress
= function()
{
     startAngle = getCurrentAngle(_parent._parent.mouseTarget._x,
_parent._parent.mouseTarget._y,
_parent._x,
_parent._y);
     onEnterFrame
= function
() {
          newAngle
= getCurrentAngle(_parent._parent.mouseTarget._x,
_parent._parent.mouseTarget._y,
_parent._x,
_parent._y);
          changeAngle
= startAngle-newAngle;
          getEdges();
          if
(leftEdge>0 && rightEdge<stageRight &&
topEdge>0 && bottomEdge<stageBottom) {
               _parent._rotation
= _parent._rotation+changeAngle;
          }
else {
               if
(leftEdge<0 || rightEdge>stageRight || topEdge<0
|| bottomEdge>stageBottom) {
                    _parent._rotation
= _parent._rotation-changeAngle;
          }
     }
          startAngle
= Number(newAngle);
     };
};
//stops rotating
the mc, and ensures that the edges of the clip are within
the limits of the draggable area

allCorners.onRelease
= allCorners.onReleaseOutside=function
() {
     onEnterFrame
= null;
     onEnterFrame
= function
() {
          getEdges();
          if
(leftEdge>0 && rightEdge<stageRight &&
topEdge>0 && bottomEdge<stageBottom) {
               onEnterFrame
= null;
          }
else {
               _parent._rotation
= _parent._rotation-changeAngle;
          }
     };
};
//nullifies the drag
action on the mouseTarget clip

allCorners.onRollOut
= function()
{
     stopDrag();
};

Dragging
A typical startDrag(), but because the object can be rotated,
its absolute width and height are constantly changing,
which is why the startDrag limits must be defined each
onPress()

Rotating
In order to rotate the object based on the user’s cursor
movement, we need to find the starting angle between the
cursor and object origin when the user first clicks, and
then calculate the change in that angle as the user moves
the cursor clockwise or counter-clockwise. This is accomplished
with the function getCurrentAngle().

Dust off your trig

If
the angle we’re trying to define can be considered
1 of the non-right angles in a right triangle, we
can use the difference in x and y to find its value.
The change in x & y are initially set to positive
in getCurrentAngle() for the purpose of the equation.

SOCAHTOA!

 
sin A = 
opposite
hypotenuse
cos A = 
adjacent
hypotenuse
tan A = 
opposite
adjacent
 

Because the difference in x and y provide us with the
opposite and adjacent legs, we’ll go with an inverse tangent
function.

A =tan(-1) 
opposite
adjacent



Knowing which value to plug into the opposite or
adjacent variables is dependent on which quadrant
the triangle resides. There are 4 quadrants, thus
4 possible orientations of this triangle, thus 4
conditional cases in the function determined by
the positivity or negativity of the change in x
or y. The result is converted from radians to degrees
(1 radian = 57.2957795 degrees) before the appropriate
multiple of 90 is added to get the total rotation.

getEdges()
The edges of the parent object change with rotation, so
to keep it within the borders of the stage this function
will be used in a couple places to grab the x & y
values of those edges.

Start Rotating!
When the user rolls over the corners, a startDrag() is
applied to the the mouseTarget mc on the _root level,
who’s x & y location will be used in the getCurrentAngle()
function.

When pressed, the initial angle from cursor to center
is calculated with getCurrentAngle(), and an onEnterFrame
is set up to do so continuously, then rotate the object
appropriately based on the difference, while ensuring
with a conditional statement that the objects edges are
within the boundaries of the initially defined stage.

onRelease, the onEnterFrame is nullified, and a new one
set up to take care of any remaining overlap (in case
the user yanks a corner of the object to outside of the
stage limits and lets go of the mouse).

onRollout, the mouseTarget on the _root.level no longer
gets dragged, until the user rolls over another corner.

27. March 2013 by admin
Tags: , , | Leave a comment

Sprite – Spark Movie Director

2010
AKQA
Facebook Application

AS3 development, team of 4 devs
MVC Framework
XML Driven except for dynamic content using FB API

Facebook users can select characters, settings and plots to create their own animated short films (which in actuality meant selecting one of a few hundred pre-rendered permutations, alas). Users can manage & share films, create playlists, watch the films of others, view behind the project scenes. The sister app was a mixer that allowed users to create their own music from a selection of samples.

Something I recall as rather neat about this one was having to come up with smart textfields that would stretch/grow/re-center/truncate/append to handle user-input of unknown length in various languages.

23. June 2014 by admin
Tags: , , , , | Leave a comment

Symbion – Holiday card

2007
Symbion Power
Animated Holiday Greeting Card
Role: concept dev, character animation, scripting for motion & interactivity, XML/CSS, Flash CS3, Illustrator.

I think the most interesting aspect of this one was shooting myself climbing up a ladder in order to get a rotoscoping reference.

23. June 2014 by admin
Tags: , , , | Leave a comment

wrapping dynamic text to a curve

Short and sweet, but I’ve seen it come up on a couple of projects to stump others and myself. And really, it’s not that daunting when armed with a few right triangles and the good ol’ Pythagorean theorum.

DOWNLOAD SRC

If you want to wrap a block of text to a curve, this isn’t quite the tutorial you’re looking for, though the same principles apply – you’ll have to figure out the maximum width of each line, split the copy into an array, and loop through it creating textfields and checking their textWidth to determine where the linebreaks should occur. Additionally, you might also check out gskinner’s textflowpro.




The trick is thinking of the curve as part of a circle, and then looking at the x/y location of the TextField or Object as 2 legs of a triangle whose hypotenuse is the radius of that circle.

Which means that if we know the radius to be a constant, and we know the y-location which presumably updates incrementally, then we can easily solve for the x-location using:

a2 + b 2 = h2

b = √h2 – a 2


import gs.TweenLite;

var list:Array = new Array("alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india", "juliet", "kilo", "lima", "mike", "november", "oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform", "whiskey", "x-ray", "yankee", "zulu");
var curveCategories:Array = new Array();

var textForm = new TextFormat;
textForm.font = "Helvetica";
textForm.size = 10;
textForm.align = "left";
textForm.color = 0x777777;

//set init location vars
var xLoc : Number = 0;
var yLoc : Number = 15;
var yIncrement : Number = 17;

for (var i : Number = 0; i < list.length; i++) {

     //create textfield
     curveCategories[i] = new TextField;
     curveCategories[i].htmlText = list[i];
     curveCategories[i].embedFonts = true;
     curveCategories[i].autoSize = "left";
     curveCategories[i].setTextFormat(textForm);

     //calculate x based on y and radius of circle
     var r : Number = 250;
     var originY : Number = 260;
     var originX : Number = 20;
     var legVert : Number = Math.round(Math.sqrt(Math.pow(originY – yLoc, 2)));
     var legHoriz : Number = Math.round(Math.sqrt(Math.pow(r, 2) – Math.pow(legVert, 2)) + originX);

     curveCategories[i].y = yLoc;
     curveCategories[i].x = legHoriz;
     curveCategories[i].alpha = 1;

     //update y
     yLoc += yIncrement;

     //animate/add to stage
     TweenLite.from(curveCategories[i], .4, {alpha:0, x:legHoriz+30, delay:.1 * i});
     addChild(curveCategories[i]);
}

27. March 2013 by admin
Tags: , , | Leave a comment

viagra.com interactive video modules

2007
viagra.com

CDMi Connect
Interactive video modules
Flash 8, Hitbox Tracking; Poll database by Pointroll

One of several interactive modules I worked on for Viagra.com; 4 possible different video/polling experiences using the same SWF, dependent on variables passed in via SWFObject.

23. June 2014 by admin
Tags: , , , | Leave a comment