Flash / Browser Navigation Integration

by Stewart Haines (email the author)
2009-12-02

Goals

One doesn't often see it in the wild, but it is easy to integrate a Flash site's navigation with the browser. (See Tumbalina's Empire for an example.) I'm calling it 'browser-integrated' to distinguish it from 'browser-ignorant' embedding of Flash movies where your browser history records nothing of what you're seeing on the site.

The aim is to integrate navigation of your Flash site's content with the web browser. This enables bookmarking and forward/back navigation through your Flash movie. It requires two-way communication between the Flash player and the browser.

With just a few well-placed function calls you can make your Flash site work like the rest of the web. Hooray! Some features of a browser-integrated Flash site;

  1. browser window title reflects site navigation
  2. browser url reflects site navigation
  3. ability to bookmark content within the Flash site
  4. site navigation using browser forward/back buttons
  5. manually entered url changes site navigation

Some of the assumptions I make about the web environment you're building for. (I may or may not have tested these browsers.)

Technology

To understand this document a reading familiarity with Javascript and Actionscript 3 is necessary.

I don't describe markup for embedding the Flash movie but assume you're using a combination of static <object> and <embed> rather than the ActiveContent javascripty injection (that Flash likes to publish).

1. Browser window title reflects site navigation

document.title

The first goal is achieved by defining a javascript function and calling it from Flash. Here's the javascript function that will update the browser window title;

// javascript
var set_page = function(new_title) {
  document.title = new_title;
};

ExternalInterface.call()

In Flash, in a function (or on a frame, or wherever the content navigation happens within your movie) call ExternalInterface.call() passing the javascript function name and arguments;

import flash.external.ExternalInterface;

ExternalInterface.call("set_page", "Tumbalina's Empire: About");

Refer to the Adobe documentation for ExternalInterface.call()

2. Browser url reflects site navigation

In addition to changing the title we want to see the updated URL. The solution here is to modify the hash property of the javascript window.location object. This corresponds to the '#' fragment of the URL which is more often used for in-page navigation within long documents.

Page urls will look like this;

window.location.hash

Modify the javascript function to accept a second parameter which is the 'hash fragment' representing the current page within the Flash movie. (Additions in bold)

// javascript
var set_page = function(new_title, page) {
  document.title = new_title;
  window.location.hash = page;
};

In Flash add the page name to be used as the hash fragment in the url;

// actionscript
ExternalInterface.call("set_page", "Tumbalina's Empire: About", "about");

This will make a URL something like this; http://tumbalinasempire.com/#about

3. Ability to bookmark content within the Flash site

We get bookmark creation for free. Adding a bookmark will record the URL (with the hash fragment) and the document title that we've set using set_page(). However, we don't yet have the ability to revisit the bookmarked page.

We want the Flash movie to navigate to a content page based on the hash fragment in the URL. There are various possibilities for passing this fragment to the Flash movie. I've chosen to add a javascript function that returns the hash fragment, and call it explicitly from the Flash movie when it loads.

// javascript
var get_hash = function() {
  return window.location.hash;
};

When the Flash movie loads, check if there is a page specified in the URL and navigate directly to it;

// actionscript frame 1
var initial_page = ExternalInterface.call("get_hash");

// ... confirm that 'initial_page' is a page in your movie

if (initial_page) {
  TransitionManager.start(pages[initial_page], {type:Fade, direction:Transition:IN});
  // or gotoAndStop(initial_page) or whatever you're doing for movie navigation
}

Note: I'm simplifying here. The value of window.location.hash might be NULL, '', '#' or '#pagename' (or possibly even 'pagename', depending on the browser) so you'll need to check that it corresponds to the page name within your Flash movie before using it. You'll also want to identify URLs that are invalid and provide the equivalent of a 404 error, or something.

4. Site navigation using browser forward/back buttons

We've already set up forward/backward navigation with hash fragments such that the URL is updated. But the Flash movie doesn't yet know about changing hash fragments. The browser doesn't refresh the page (and therefore doesn't reload the Flash movie) when the hash fragment changes, so our existing call to get_hash() doesn't fire for forward/back navigation.

We use get_hash() to pull the hash fragment into the Flash movie when it's ready. We also want a way for the browser to push the Flash movie to a specific page. Here's a javascript function that calls an actionscript function on the Flash movie based on the current hash fragment.

// javascript
var current_hash;
val check_hash = function() {
  if (window.location.hash != current_hash) {
    document['movie_name'].navigate_to_page(window.location.hash);
  }
  current_hash = window.location.hash;
};

Here 'movie_name' is the name of the Flash movie object in the DOM (or window). It corresponds to the 'name' parameter of the <object> or the 'name' attribute of the <embed> tag. Optionally, IE refers to it as window['movie_name'].

We store current_hash so that we can repeatedly call this function and have the hash fragment pushed to the Flash movie when it changes (see below).

ExternalInterface.addCallback()

We still need to define the actionscript callback that we refer to above as navigate_to_page. We do that in Flash, and export it using ExternalInterface as a javascript-callable function on the movie object.

// actionscript
function navigate_to_page(page:String) {
  // ... check that 'page' is valid, then...
  TransitionManager.start(pages[page], {type:Fade, direction:Transition:IN});
  // or gotoAndStop(page) or whatever...
}

ExternalInterface.addCallback("navigate_to_page", navigate_to_page);

Refer to the Adobe documentation for ExternalInterface.addCallback()

Additionally, we want to repeatedly poll the window.location.hash and notify the Flash movie when it has changed;

// javascript
var loaded = function() {
  current_hash = window.location.hash;
  var interval = setInterval("check_hash();", 500);
};
window.addEventListener("load", loaded, false);

Here we add a timer using setInterval() which will call check_hash() every 500 milliseconds. When it detects that the hash fragment has changed it will call the Flash movie's navigate_to_page() function to update the displayed page. It's set up in the window's 'load' event handler which will be called when the html document has finished loading.

5. Manually entered url changes site navigation

We get this from the changes for #4 above. Manual changes to the URL by editing the hash fragment will be picked up by the window.location.hash polling in check_hash().

So that's it.

Summary

Here are the fragments of javascript that we've constructed;

// javascript

var set_page = function(new_title, page) {
  document.title = new_title;
  window.location.hash = page;
};

var get_hash = function() {
  return window.location.hash;
};  var current_hash;

val check_hash = function() {
  if (window.location.hash != current_hash) {
    document['movie_name'].navigate_to_page(window.location.hash);
  }
  current_hash = window.location.hash;
};

var loaded = function() {
  current_hash = window.location.hash;
  var interval = setInterval("check_hash();", 500);
};

window.addEventListener("load", loaded, false);

The actionscript is highly dependent on the way you handle navigation within your Flash movie, so it doesn't make a lot of sense to summarize it. But here it is;

// actionscript
  
import flash.external.ExternalInterface;

ExternalInterface.call("set_page", "Tumbalina's Empire: About", "about");

// actionscript frame 1
var initial_page = ExternalInterface.call("get_hash");

// ... confirm that 'initial_page' is a page in your movie

if (initial_page) {
  TransitionManager.start(pages[initial_page], {type:Fade, direction:Transition:IN});
  // or gotoAndStop(initial_page) or whatever you're doing for movie navigation
}

function navigate_to_page(page:String) {
  // ... check that 'page' is valid, then...
  TransitionManager.start(pages[page], {type:Fade, direction:Transition:IN});
  // or gotoAndStop(page) or whatever...
}

ExternalInterface.addCallback("navigate_to_page", navigate_to_page);