задача: добиться на флэш-сайте функциональности, привычной для хтмл-сайтов:
  • чтобы правильно работали кнопки back и forward в броузере
  • чтобы в любой момент ссылка в адресной строке броузера отражала состояние флэшового сайта
Статья рассказывает об истории вопроса и о том, в каких броузерах и в какой степени удаётся решить данную проблему. Также, в статье расказывается о всех известных вариантах организации взаимодействия Flash и Javascript, и об их практической полезности.

Коротко о главном.
  • в сети полно решений, к чему еще одно?
    вот тут об этом сказано подробно.
  • удалось решить проблему?
    — да.
  • легко и просто?
    — нет. трудно и сложно.
  • а работает везде?
    — в разумных пределах.
    под управлением Flash Player 7: IE 5/6 (pc), Mozilla/Firefox (pc/mac), Opera 7.54/8.53/9.0 (pc), Netscape 8 (pc).
    под управлением Flash Player 8: IE 5/6 (pc), Mozilla/Firefox (pc/mac), Opera 9 (pc), Netscape 8 (pc), Safari (mac, кнопки back/forward не работают).

    можно сделать версию, работающую под всеми версиями Оперы, но это не входило в задачи этого материала.
Если научная часть вас не интересует, можно сразу перейти к практике. :)

назад к списку уроков и рецептов

1. описание проблемы

2. известные методы решения

    2.1 именованные якоря во флэше
    2.2 frameset

3. общая схема решения и подходы к реализации

    3.1 JavaScript -> Flash: при загрузке страницы

    3.2 Flash -> JavaScript: при переходе между разделами
          вариант 1: fscommand()
          вариант 2: getURL("javascript:...")
          вариант 3: ExternalInterface

    3.3 JavaScript: отреагировать на команду от флэша
          при использовании fscommand()
          при использовании getURL("javascript:...")
          при использовании ExternalInterface

    3.4 JavaScript -> Flash
          вариант 1: при помощи SetVariable()
          вариант 2: при помощи прокси-флэшки и LocalConnection
          вариант 3: при помощи ExternalInterface

    3.5 JavaScript: реакция на переходы back/forward в броузере

4. что же делать?

    4.1 сужаем задачу

    4.2 упрощаем систему

    4.3 изменения в адресной строке
          магия Internet Explorer: location.hash и getURL("javascript:...")

    4.4 кнопки back/forward
          магия Internet Explorer: location.hash и document.title

5. результат / исходник

6. тесты, задействованные в статье



"Когда-нибудь у нас будет Идеальный Броузер... Он будет храниться в парижской палате мер и весов, а работать в нём будут шарообразные сайты радиусом 1 метр и весом 1 байт."

1. Описание проблемы

a) Флэш-сайт представляет собой для броузера черный ящик, коробочку с секретами. Зайдя на флэш сайт и погуляв по его разделам, в избранное обычно вы можете добавить только ссылку на страницу, на которой расположен флэш-сайт, но почти никогда не можете сохранить ссылку на конретную "страницу" этого сайта, потому что для броузера не существует самих "страниц" внутри флэша.

b) Аналогично, если вы нашли на флэш-сайте какой-то чудесный раздел, и хотите послать ссылку на этот раздел другу, обычно приходится отсылать ссылку на сайт с объяснениями как добраться до раздела, на который вы хотели обратить внимание вашего друга.

c) Поскольку броузер ничего не знает о переходах между разделами внутри флэшки, он не может перемещать вас по этим разделам кнопками back/forward.

На хтмл-сайтах таких проблем почти никогда нет: обычно все переходы по страницам сопровождаются изменениями в адресной строке броузера, которая в любой момент отображает ваше расположение на сайте. Кнопки back/forward на хтмл-сайтах работают более-менее одинаково во всех броузерах.
в начало

2. Известные решения

Одно из самых простых решений проблемы — использование так называемых "именованных якорей" (named anchors) во флэше. Именованные якоря во флэше — аналог одноименной концепции в хтмл. Идея метода: мы создаем во флэше специальные метки кадров, и при переходах на эти помеченные кадры к адресу страницы в броузере автоматически добавляется строка "#имя_метки". В результате этого работают кнопки back/forward и адресная строка броузера содержит информацию о текущем состоянии флэшки.

Проблемы с использованием флэшовых named anchors:
  • Использование named anchors навязывает флэш разработчику специфическую и неудобную структуру приложения.
  • Вы заранее должны создать необходимое количество кадров с необходимыми вам метками. Создать их находу, во время работы приложения, нельзя. Во многих случаях создать все метки заранее практически невозможно: либо их требуется очень много, либо вы просто заранее не знаете, сколько их потребуется и какие именно будут нужны (например, если вы делаете динамическую галерею с картинками, и хотите, чтобы якоря указывали на открытую картинку).
  • Большие проблемы с поддержкой броузерами. Собственно, нигде кроме как в IE (win) флэшовые named anchors стабильно не работают.
Итого: при использовании named anchors флэш приложение получается негибким и слишком привязанным к конкретной платформе.


Другим известным способом заставить кнопки back/forward работать является использование фреймов в хтмл. Идея метода: мы создаем на хтмл странице frameset из двух фреймов; в первом фрейме показывается флэшка, второй фрейм невидим и содержит простой хтмл-документ, единственная задача которого — сообщить в главный фрейм о своей загрузке. При необходимости "запомнить" состояние флэшки, мы из флэша вызываем перезагрузку хтмл-документа во втором фрейме, что приводит к добавлению нового адреса в history броузера. А при нажатиях кнопок back/forward в броузере переходы происходят во втором фрейме (поскольку мы его перезагружали), и оттуда мы подаем сигнал основной флэшке об изменении состояния.

Проблемы с использованием frameset:
  • Необходимость использования frameset в хтмл — это уже своего рода проблема. :)
  • В адресной строке броузера не происходит никаких изменений. Соответственно:
    — мы не видим, где находимся,
    — не можем скопировать линк из адресной строки броузера,
    — не можем нормально добавить страницу в избранное
Итого: при использовании техники c frameset'ом задача решается лишь наполовину при общем усложнении структуры документов.
в начало

3. Общая схема решения

Итак, попробуем разобраться в сути проблемы. В качестве основного механизма будем использовать перереходы при помощи javascript по именованным якорям на хтмл странице. При переходах по якорям к адресу страницы автоматически добавляется строка "#имя_якоря", и это состояние добавляется в history броузера. Вдобавок к этому, при переходе к якорю страница не перезагружается, что как нельзя более кстати на флэш сайте.

Чтобы воспользоваться этим механизмом, необходимо решить несколько проблем:

  1. При загрузке страницы передать флэшке текущий якорь из адресной строки.

    Простой и стабильный вариант:
    • Javascript'ом задать параметр FlashVars у тегов <object> и <embed>.
  2. При переходах между разделами внутри флэшки, нужно скомандовать javascript'у выполнить переход на указанный якорь.

    Варианты:
    • воспользоваться функцией fscommand("команда", "аргументы");
    • воспользоваться функцией getURL("javascript:...");
    • использовать класс Flash 8 flash.external.ExternalInterface
  3. При переходах по якорям кнопками броузера back/forward, javascript должен скомандовать флэшке отреагировать соответственно.

    Варианты:
    • в javascript'е использовать метод SetVariable() у объектов object или embed
    • использовать механизм LocalConnection, задавая javascript'ом FlashVars для "прокси" флэшки, которая уже будет сообщать главной об изменениях
    • использовать класс Flash 8 flash.external.ExternalInterface

в начало

3.1 JavaScript -> Flash: при загрузке страницы

При загрузке страницы javascript должен определить, есть ли якорь в адресе, по которому мы пришли на флэш сайт, и передать этот якорь флэшке, чтобы она могла сразу перейти на указанный этим якорем раздел. Флэшка должна проверить значение переменной, которую отдал ей javascript и открыть соответствующий раздел.

В этой части всё просто. Будем использовать параметр FlashVars тегов <object> и <embed>. Для того, чтобы во флэш передать то, что стоит в адресной строке после символа #, сделаем следующее:
// JavaScript:
var FlashVars = "start="+location.hash.substr(1);
Таким образом, переменная FlashVars будет содержать строку вида "start=имя_якоря", или же только "start=", если никакой якорь в адресе не указан.

Теперь сформируем javascript'ом теги <object> и <embed>. Для этого мы в нужном месте хтмл-страницы пишем скрипт, выводящий эти теги функцией document.write().
<script language="JavaScript">
<!--

// код для главной флэшки
var main_html = 
'<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '+
    'codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" '+
    'width="640" height="480" '+
    'id="nav" >'+
    '<param name="FlashVars" value="$$$$" />'+
    '<param name="allowScriptAccess" value="always" />'+
    '<param name="movie" value="nav.swf" />'+
    '<param name="bgcolor" value="#ffffff" />'+
'<embed src="nav.swf" '+
    'FlashVars="$$$$" '+
    'allowScriptAccess="always" '+
    'bgcolor="#ffffff" '+
    'width="640" height="480" '+
    'name="nav" '+
    'swLiveConnect="true" '+
    'type="application/x-shockwave-flash" '+
    'pluginspage="http://www.macromedia.com/go/getflashplayer" />'+
'</object>';

// ...

// там, где нам надо будет вывести флэшку, пишем:
var FlashVars = "start="+location.hash.substr(1);
document.write(main_html.split("$$$$").join(FlashVars));

-->
</script>
 
В результате код для флэшки будет выведен на страницу с заданным нами значением параметра FlashVars.
в начало

3.2 Flash -> JavaScript: при переходе между разделами

При переходе между разделами флэш должен отправить в javascript какое-то обозначение, которое будет отображать в адресной строке броузера состояние флэшки. Это может быть строка (идентификатор раздела), число (номер раздела) или какое-то их сочетание. Главное, чтобы получив потом это обозначение при загрузке страницы, флэшка могла определить какой раздел ("страницу") следует открыть.

Выше было перечислено три варианта передачи сообщений от флэша к javascript'у. Варианты с использованием fscommand() и getURL("javascript:...") по результату одинаковы, но по механизму действия и по кроссплатформенности существенно отличаются друг от друга.


Функция fscommand() — это универсальный механизм для передачи команд от флэш-ролика к оболочке, в которой он проигрывается. То есть, этот механизм сделан не только для взаимодействия флэша с броузерами и javascript'ом, а может использоваться для взаимодействия с оболочкой в виде приложения Macromedia Director или любого другого, в которое можно запихнуть флэшку. Если же речь о сайте, такой оболочкой является броузер, во флэш-плагине которого проигрывается ролик.

Плюсы использования fscommand():
  • Механизм универсален, можно использовать в любой оболочке
  • Работает под Flash Player 5 и выше
Минусы использования fscommand():
  • Нельзя автоматически получить "ответ" от javascript во флэш
  • Проблемы с поддержкой броузерами:
    — в Netscape требуется включение Java
    — в распространенных броузерах для Mac fscommand() не работает вообще
  • требуется не самый простой javascript-код для обработки команд

Функция getURL() может напрямую запускать выполнение инструкций языка оболочки. Например, если оболочкой является приложение Macromedia Director, можно при помощи getURL("lingo:..."); запускать инструкции языка Lingo, используемого в Директоре. Нас же интересует выполнение инструкций javascript, при помощи getURL("javascript:...").

Плюсы использования getURL("javascript:..."):
  • Работает во всех распространенных броузерах, где работает javascript и флэш.
  • Работает под Flash Player 5 и выше
  • Не требует хитрого javascript-кода
Минусы использования getURL("javascript:..."):
  • Нельзя автоматически получить "ответ" от javascript во флэш. (например, возвращаемое значение какой-то функции.)
  • Длина строки с инструкциями ограничена 508 символами. Впрочем, обычно это не является серьёзной проблемой: чаще всего инструкция всего одна — вызов какой-то javascript функции. Теоретически могут возникнуть проблемы, если вы в качестве параметра захотите передать в javascript длинную строку текста.

Сводная таблица поддержки fscommand() и getURL("javascript:...") находится на сайте Макромедии. В ней упоминаются только Netscape и IE, зато всех мыслимых версий. Стоит добавить, что вызов getURL("javascript:...") работает также в следующих броузерах:
  • Opera (win/mac) 5.12 и выше
  • Safari (mac) 1.3 и выше
  • Firefox (win/mac) 1.0 и выше
  • Mozilla (win/mac) 1.0 и выше
В общем, выбор между fscommand() и getURL("javascript:...") в данном случае очевиден.


Во Flash 8 мы можем использовать для связи Flash->JavaScript класс flash.external.ExternalInterface. Перевод на русский статьи хэлпа по ExternalInterface можно найти на сайте Роста. Отметим основные достоинства и недостатки.

Плюсы использования flash.external.ExternalInterface:
  • Можно автоматически получить ответ от вызываемой javascript функции! Это делает необязательным изобретение надежных способов передачи данных от JavaScript во флэш!
  • Поддерживается большинством самых распространенных броузеров
  • Не требует хитрого javascript-кода
Минусы использования flash.external.ExternalInterface:
  • Работает только под Flash Player 8 и выше. А это значит, например, что под Linux этот класс работать не будет вообще ни в одном броузере: компания Adobe заявила, что флэш плеера 8 под Linux не будет. Следующая версия, которую они собираются выпустить для Linux — 8.5.

Официальные данные о поддержке класса ExternalInterface броузерами:
  • Internet Explorer (win) 5.0 и выше
  • Netscape (win/mac) 8.0 и выше
  • Mozilla (win/mac) 1.7.5 и выше
  • Firefox (win/mac) 1.0 и выше
  • Safari (mac) 1.3 и выше
Могу добавить, что в Opera 9 и выше класс ExternalInterface тоже работает. Как видим, поддержка достаточно обширна.
в начало

3.3 JavaScript: отреагировать на команду от флэша

Необходимо, чтобы по команде от флэша javascript на странице выполнил переход по указанному якорю. Изначально такого якоря на странице может и не быть: флэш в качестве имени якоря может передать всё что угодно. Чем это плохо: при переходе на несуществующий якорь ссылка не добавляется в history броузера (по крайней мере в IE), и, соответственно, кнопки броузера back и forward учитывать эти переходы не будут. Значит, javascript должен создать указанный якорь: мы создадим на странице <div>, сделаем его невидимым, и будем дописывать в него теги <a name="..."></a> через свойство innerHTML.
<!-- html: невидимый пустой <div> -->
<style>
#dump { visibility:hidden; };
</style>
...
...
<div id="dump"></div>

/* javascript: эта функция будет создавать
   указанный якорь, а потом выполнять переход на него */

var _title = document.title
var anchor_names = {};
function gotoAnchor (name) {
    // создаем якорь, если его еще нет на странице
    if (!anchor_names[name]) {
        // запоминаем, что такой якорь уже создали
        anchor_names[name] = true;
        
        // дописываем якорь на страницу
        var dump = document.getElementById("dump");
        dump.innerHTML += '<a href="#" name="'+name+'">'+name+'</a>';
    }
    location.hash = name;
    current_loc = location.hash;
    document.title = _title+" - #"+name
}
Код javascript, который потребуется для обработки сигналов от флэшки зависит от того, какой вариант коммуникации Flash->JavaScript вы выбрали.


В случае использования fscommand() требуется самый громоздкий javascript. К счастью, большую часть этого скрипта для нас может сгенерировать среда Flash. В Publish Settings флэша выбираем:
окуда взять javascript код для поддержки fscommand()
Публикуем ролик, смотрим в исходный код хтмл-страницы, сгенерированной флэшем при таких настройках. Нас интересует блок javascript'а, который будет присутствовать на этой странице, а точнее функция: "имя_DoFSCommand". В случае с публикацией "nav.swf" это выглядит следующим образом:
// JavaScript:
// сгенерировано для nav.swf
function nav_DoFSCommand(command, args) {
    var navObj = isInternetExplorer ? document.all.nav : document.nav;
    //
    // Place your code here.
    // (что означает: "поместите ваш код здесь")
}
 
Теперь надо организовать реакции на разные команды. Не стоит расcчитывать, что из флэша не поступит ни одной команды кроме тех, что вы вызовете сами.
К примеру:
// инструкция ActionScript
Stage.showMenu = false; 
// автоматически вызовет fscommand("showmenu", false);
А это может привести к ошибке javascript'а, если внутри DoFSCommand() ваш код на такую команду не рассчитывает. Сделаем следующее:
// JavaScript:
// сгенерировано для nav.swf
function nav_DoFSCommand(command, args) {
  if (window[command] != undefined) {
      var swfObj = isInternetExplorer ? document.all.nav : document.nav;
      args = args.split(",");
      args.unshift(swfObj);
      window[command].apply(this, args);
  }
}
То есть, будем считать, что имя каждой команды совпадает с именем функции javascript, которую надо вызвать. Мы проверяем, определена ли такая функция, и если да — вызываем её. В качестве первого аргумента функциям всегда передается ссылка на объект, представляющий ролик на хтмл странице. Остальные аргументы — те, что присылаются флэшем. Заметим, что параметры передаются от флэша только в строковом виде. Соответственно, если мы хотим передать объект или массив, необходимо позаботиться, чтобы флэш правильно перевел массив/объект в строку (так называемая "сериализация"), и чтобы Javascript, принимающий аргументы, мог эту строку обратно раскодировать.

Теперь, когда мы настроили функцию DoFSCommand как "диспетчера" для команд, мы можем создавать в javascript функции и вызывать их посредством
// ActionScript:
fscommand("имя_функции", "параметр1,параметр2,параметр3,...");
// а в нашем случае:
fscommand("gotoAnchor", "имя_якоря");



В случае использования getURL("javascript:...") всё значительно проще. В этом варианте не требуется никакого специфического кода: из флэша мы просто вызываем нужную нам функцию в javascript. Обратите внимание, поскольку аргументом getURL() является строка, все параметры должны быть переданы в строковом виде (как и в случае с fscommand()).
// ActionScript:
getURL('javascript:gotoAnchor("имя_якоря");');



По удобству ExternalInterface, конечно, на первом месте, потому что в случае его использования, вызванная в javascript функция может просто возвратить какое-то значение, и это значение попадет во флэш. Более того, вызываемая функция javascript в случае использования ExternalInterface может возвращать значение любого типа (числа, строки, массивы, объекты...), и эти значения попадут во флэш в неизменённом виде.

Никакого особенного кода javascript для обработки вызовов через ExternalInterface не требуется. Из флэша мы можем вызвать любую javascript-функцию, передать ей параметры любых типов и получить возвращаемое значение любого типа. Красота.
// ActionScript:
import flash.external.ExternalInterface;
var from_js = ExternalInterface.call("test", 1, "АБВ", {x:1, y:2});

// JavaScript:
function test (num, str, obj) {
    alert(typeof(num) + " : " + num);
    alert(typeof(str) + " : " + str);
    alert(typeof(obj) + " : " + obj.x+","+obj.y);
    return "hey ho!";
}
Правда, в нашем случае всей этой красоты не требуется. Достаточно:
// ActionScript:
import flash.external.ExternalInterface;
var from_js = ExternalInterface.call("gotoAnchor", "имя_якоря");

в начало

3.4 JavaScript -> Flash

Здесь начинается самое интересное. При нажатии кнопок back/forward в броузере javascript должен отреагировать на изменение якоря и сообщить флэшу о том, что нужно перейти на другой раздел. То есть, нужно решить две проблемы:
  • JavaScript должен узнать о том, что нажали кнопку back или forward в браузере
  • JavaScript должен сообщить флэшке новый изменившийся якорь

Рассмотрим варианты передачи чего-либо от javascript к флэшу.

Самым известным вариантом передачи переменных от javascript во флэш является использование метода SetVariable. Схема проста:
// javascript:

// опера может притворяться IE...
var opera = Boolean(window["opera"]);
// IE или не IE, вот в чем вопрос
var MSIE = (navigator.userAgent.indexOf("Microsoft") != -1) && !opera;

var flash = MSIE ? 
               window["id_тега_object"] : document["name_тега_embed"];
flash.SetVariable("имя_переменной", "значение_переменной");
Единственным неудобством является то, что нам приходится по-разному обращаться к ролику на странице для Internet Explorer и для всех остальных броузеров. При этом не достаточно просто проверить строку navigator.userAgent, потому что эта строка предоставляется самим броузером и может содержать, в принципе, что угодно. :) В Opera есть настройка, позволяющая этому броузеру представляться как Opera/Mozilla/IE на выбор. (Даже не знаю, что хуже, сам IE, или Opera, которая притворяется IE... :) )

Теперь о грустном. Нигде, кроме как в Mozilla(pc), Internet Explorer 5/6(pc), Firefox(pc) этот метод не работает. Наши однокнопочные друзья на Mac'ах вообще лишены этого счастья. Тест метода SetVariable находится здесь. Вы можете самостоятельно проверить, работает ли он в интересующем вас броузере (в самом деле, ВСЕ броузеры мне проверить не удалось :) ).


Техника передачи переменных от javascript во флэш при помощи прокси-флэшки и LocalConnection используется в Flash Javascript Integration Kit, написанном небезызвестными программистами Macromedia: Christian Cantrell и Mike Chambers. Плюсы использования этого метода: это работает почти везде. Минусы: система довольно сложная.

Принцип метода: мы создаем на странице <div>, содержащий прокси-флэшку. Задача этой флэшки – принять те переменные, которые переданы ей через FlashVars и передать их при помощи LocalConnection в главную флэшку. Когда нам необходимо передать новые переменные, мы переписываем код тегов <object> и <embed> прокси-флэшки при помощи javascript, чем вызываем её перезагрузку. Таким образом, мы используем для передачи флэшовый механизм LocalConnection, а со стороны javascript используем только передачу переменных через FlashVars (что поддерживается всеми современными броузерами).

Подробности использования такого способа передачи переменных от javascript во флэш см. на сайте Flash Javascript Integration Kit. Там же есть сведения о поддержке этого метода разными броузерами.

В нашем случае использование этого метода может выглядеть, например, так:
// JavaScript:

// функция переписывает код прокси-флэшки, 
// передавая ей через FlashVars переменную
function setFlashVariable (name, value) {
    var refresh = document.getElementById("refresh");
    refresh.innerHTML = refresh_html.split("$$$$").join(name+"="+value);
}

// код для прокси-флэшки, которая будет регулярно перезагружаться
// и передавать сигналы главной флэшке
var refresh_html = 
'<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '+
'codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" '+
    'width="200" height="25" >'+
'<param name="movie" value="refresh.swf?2" />'+
'<param name="FlashVars" value="lcid='+connection_id+'&$$$$" />'+
'<embed src="refresh.swf?2" '+
   'FlashVars="lcid='+connection_id+'&$$$$" '+
   'width="200" height="25" '+
   'type="application/x-shockwave-flash" '+
   'pluginspage="http://www.macromedia.com/go/getflashplayer" />'+
'</object>';
 
Внутри прокси-флэшки не должно быть ничего, кроме следующего кода:
// ActionScript:

/*
от FlashVars приходят переменные
lcid - идентификатор соединения LocalConnection
current - идентификатор текущего раздела

при загрузке шлём через LocalConnection 
сведения о текущем разделе (переменную current)
*/
if (current == "$$$$") {
    // это означает, что передавать сигнал не нужно
    // (позже вы увидите, зачем нужно такое значение переменной)
    return;
}
var lc_name = "lc_"+lcid;
var js_lc = new LocalConnection();
js_lc.send(lc_name, "setCurrent", current);
Теперь для передачи текущего имени якоря необходимо вызвать:
// JavaScript:

setFlashVariable("current", имя_якоря);
Обратите внимание, прокси-флэшке передается идентификатор соединения LocalConnection. Это нужно для того, чтобы прокси-флэшка передавала сообщения главной флэшке на той же странице, а не какой-то другой. Если мы не сделаем этого, при открытии нескольких страниц с нашим сайтом произойдет путаница и сообщения будут передаваться не в то окно.

В главной флэшке должно быть следующее:
// ActionScript:

/*
от FlashVars приходит lcid - идентификатор для создания LocalConnection.
*/
var lc_name = "lc_"+lcid;
var js_lc = new LocalConnection();
js_lc.setCurrent = function(value)
{
    // действия по обработке пришедшего значения
};
js_lc.connect(lc_name);
Теперь при перезагрузке прокси-флэшка будет передавать главной флэшке через LocalConnection значение переменной current.


Использование класса ExternalInterface – cамый удобный и простой способ передачи данных от JavaScript во Flash. Со стороны javascript не требуется совсем никаких дополнительных действий. Выше было описано использование класса ExternalInterface, а также были приведены сведения о поддержке этого класса разными броузерами. По кроссброузерности ExternalInterface уступает только функции getURL("javascript:..."), однако последняя не может передать значение, которое вернула вызванная javascript-функция. В общем, если у вас есть возможность опубликовать ролик для Flash Player 8 — однозначно стоит остановиться на использовании ExternalInterface.

*update 20.02.2008: почему я здесь не упомянул ExternalInterface.addCallback(), и не написал пример использования — до сих по удивляюсь. :) Вот, теперь упомянул. А пример есть в родном хэлпе, и в нем все предельно ясно.
в начало

3.5 JavaScript: реакция на переходы back/forward в броузере

Оказывается, Javascript на странице не может просто так отследить момент перехода кнопками back/forward... Нет события, которое бы сработало в javascript'е на странице при таком переходе. Можно предположить, что необходимо проверять значение переменной location.hash (которая хранит имя текущего якоря) по таймеру, используя setTimeout(). Вобщем-то, почему бы и нет:
// JavaScript
// запоминаем, на каком мы якоре изначально
var current_loc = location.hash;
function checkLocation () {
    var hash = document.location.hash;    
    if (hash != current_loc) {
        current_loc = hash;
        // ага! 
        // якорь изменился, сообщим об этом флэшке
        // ...
    }
    setTimeout("checkLocation();", 500);
}
// запускаем проверку раз в половину секунды
setTimeout("checkLocation();", 500);
Выглядит не так и страшно. То же самое мы могли бы сделать, организовав таймер не в javascript а во флэше (регулярно запрашивать у javascript текущий якорь). Но, как всегда, есть нюанс. :)

Забегая вперед скажу, что в Internet Explorer 5/6 описанный выше метод не сработает. Подробности в следующем разделе.

А чтобы нам было уж точно не скучно, Opera и Safari перезагружают флэш-ролики на странице при переходах по якорям кнопками back/forward (в отличие от всех остальных броузеров). Это не значит, что перезагружается вся страница: перезагружаются только флэш ролики на ней; при перезагрузке флэшка снова получит через FlashVars то значение стартового раздела, которое было передано ей при изначальной загрузке страницы.

На этом теоретическое описание решения заканчивается. Перейдём к практике.
в начало

4. Что же делать?

Решить проблему в полной мере трудно. При должном упорстве можно добиться того, чтобы задача была решена для большинства популярных броузеров. Вы потратите много нервов и времени, добиваясь 90-процентного результата. В принципе, это имеет некоторый смысл, но я бы не стал этим заниматься. :)

4.1 Сужаем задачу

a) Необходимо четко определить, для каких броузеров требуется решение. Если речь о коммерческом проекте – перечень поддерживаемых броузеров должен быть внесен в договор. Что действительно можно/нужно сделать:
  • Решить проблему для Mozilla/Firefox (pc, mac) (самое простое)
  • Решить проблему для Internet Explorer 5/6 (pc) (сложнее)
  • Добиться того, чтобы в остальных броузерах неправильная работа системы не вызывала никаких нежелательных явлений
b) Необходимо определить, так ли вам нужна правильная работа кнопок back/forward в броузере. Добиться того, чтобы адресная строка броузера всегда отображала состояние флэшки довольно легко для почти всех броузеров. А это уже значит, что на флэш-сайте можно будет занести в избранное какой-то определенный раздел или послать ссылку на этот раздел другу. Совсем не плохо.

Кстати, если мы решаем задачу только для Internet Explorer (pc) и Mozilla/Firefox (pc), отпадает еще одна проблема: передача переменных от JavaScript во Flash. Для этих броузеров мы спокойно можем использовать SetVariable, не усложняя системы вариантом с LocalConnection.
в начало

4.2 Упрощаем систему

Если есть возможность публиковать ролик под Flash Player 8, следует использовать ExternalInterface для коммуникации Flash — Javascript: это существенно упростит систему. Если же этой возможности нет, нужно придумать, на чем можно простоту сэкономить.

Во-первых, сделаем, чтобы всем управлял флэш. То есть, периодические проверки текущего значения location.hash будут вызываться из флэша, а не будут зациклены при помощи setTimeout(), как было предложено ранее.

Во-вторых, раз уж флэш всё равно будет обращаться к javascript'у, чтобы тот передал ему текущее значение адреса, нам незачем передавать флэшке изначальный адрес через FlashVars. Флэш сам при загрузке запросит его.
в начало

4.3 Изменения в адресной строке

Для того, чтобы адресная строка отображала состояние флэшки достаточно при переходах по разделам из флэшки вызывать javascript-функцию, которая установит заданное значение location.hash. Самый стабильный и кроссброузерный вариант: использовать во флэше getURL("javascript:...").

Проблема: благодаря негуманоидной логике работы Internet Explorer 6 мы не можем для этой цели использовать во флэше функцию getURL("javascript:..."). Причина в следующем: при использовании этой функции для установки location.hash вся страница, на которой происходят действия, падает. :) При этом если использовать fscommand или ExternalInterface — всё в порядке. Иллюстрацию этого глюка можно посмотреть здесь. Напомню, глюк проявляется только в Internet Explorer 6.

Из вышесказанного следует, что для Internet Explorer нам придется использовать fscommand, либо задействовать ExternalInterface и публиковать ролик для Flash Player 8.

Но печальнее всего то, что Safari ведет себя в данном отношении точно также. А учитывая, что на Mac'ах отсутствует поддержка fscommand(), получается, что единственным вариантом передачи сигналов от флэша в javascript является ExternalInterface под Flash Player 8. И, создавая эту систему для Flash Player 7 мы должны позаботиться, чтобы в Safari она по крайней мере не вызывала фатальных ошибок (то есть, просто не будем переходить по якорям в этом броузере).
в начало

4.4 Кнопки back/forward

Самая магическая часть мероприятия.

Сразу скажем, что Safari 1.3 не добавляет в history создаваемые находу якоря. То есть, для этого броузера данной функциональности мы не добьёмся.

Всё бы ничего, но Internet Explorer 5/6 не понимает, что изменилось значение location.hash при переходе кнопками back/forward с якоря на якорь. То есть, у нас нет нормального способа отловить в javascript такой переход. Пример этого глюка можно посмотреть здесь. Учитывая, что IE 5/6 — это примерно 75% пользователей (в лучшем случае), нужно придумывать, как заставить IE осознавать, что изменился location.hash косвенными методами.

В качестве такого "косвенного" метода можно использовать одну магическую особенность Internet Explorer, на которую я натолкнулся в поисках решения. Дело в том, что при перезагрузке флэшки на странице Internet Explorer добавляет к document.title имя якоря, на котором мы в данный момент находимся. Здесь приведен пример такого поведения Internet Explorer. То есть, если мы будем постоянно перезагружать какую-то флэшку на странице, мы сможем отследить момент перехода кнопками back/forward по тому, как изменяется document.title. Звучит таинственно, но это стабильно работает в Internet Explorer 5/6 (проверено на версиях 5.01, 5.5 и 6.0).

Ага! Cкажете вы, у нас ведь уже есть прокси-флэшка, которая должна перезагружаться время от времени, чтобы сообщать главной флэшке имя текущего якоря (если, конечно, мы не решились использовать ExternalInterface под Flash Player 8). Для того, чтобы всё правильно сработало в Internet Explorer, необходимо проделать следующее:
  • Перезагрузить флэшку на странице (если это прокси-флэшка, то хорошо бы она не передавала в этот момент никакого значения главной флэшке).
  • Проверить, что при этом добавилось в dоcument.title и сверить эту строку с сохраненным ранее именем текущего якоря.
  • Если значение, которое добавилось в dоcument.title не совпадает с сохраненным ранее, нужно перезагрузить прокси-флэшку еще раз, чтобы она передала это значение главной флэшке.
  • Восстановить нормальный вид dоcument.title.
С учетом этих хитростей функция, проверяющая значение текущего якоря может выглядеть так:
// JavaScript:

var current_loc;
/* функция сверяет текущее значение location.hash 
с сохранённым в current_loc, и если оно не совпадает - 
сигналит об этом главной флэшке через прокси-флэшку

function getLocation () {
  if (MSIE) {
      // специально для Internet Explorer
      document.title = _title;
      //
      // магия с перезагрузкой флэшки
      // (см. http://noregret.org/tutor/navigation/test/iemagic2.html)
      setFlashVariable("current", "$$$$");
      //
      var index = document.title.lastIndexOf("#")
      var hash = (index == -1) ? "" : document.title.substr(index);
      if (hash != current_loc) {
          // якорь сменился, сигналим флэшке
          current_loc = hash;
          setFlashVariable("current", hash.substr(1));
      }
      document.title = _title+(hash.length ? " - "+hash : "");
  } else {
      // у всех остальных броузеров с location.hash проблем нет.
      var hash = document.location.hash;
      if (hash != current_loc) {
          // якорь сменился, сигналим флэшке
          current_loc = hash;
          setFlashVariable("current", hash.substr(1));
      }
  }
}
Единственное, что еще к этому можно добавить: это работает, как ни странно.
в начало

5. Результат

[ посмотреть вариант для Flash Player 7 ] [ исходник для FP 7 ]

[ посмотреть вариант для Flash Player 8 ] [ исходник для FP 8 ]

Что получилось:
  • Адресная строка броузера отражает текущее состояние флэш-сайта.
  • Кнопки back/forward в броузере работают как и на любом нормальном сайте.
  • Всё это работает в следующих броузерах:
    Под управлением Flash Player 7: IE 5/6 (pc), Mozilla/Firefox (pc/mac), Opera 9 (pc), Netscape 8 (pc).
    Под управлением Flash Player 8: IE 5/6 (pc), Mozilla/Firefox (pc/mac), Opera 9 (pc), Netscape 8 (pc), Safari (mac, кнопки back/forward не работают).

    (В других броузерах не тестировал, если у вы проверили работоспособность на чем-то еще — сообщите об этом, я дополню этот список.)

в начало
назад к списку уроков и рецептов