javascriptとflashの連携、ExternalInterfaceを研究してみよう | Ameblo Hacks ~アメブロを10倍楽しむために努力するブログ~

javascriptとflashの連携、ExternalInterfaceを研究してみよう

$meets AS3.0

FlashにはExternalInterfaceというjavascriptと連携するためのクラスが用意されています。
Flashはjavascriptと連携させることで、やれることの幅がかなり広がるので必須項目なのですが、細かく書かれている本も少なく、その割に躓きやすかったりするんですよね。。。
そこで、備忘録も兼ねて、ExternalInterfaceに関する部分を纏めてみます。

addCallbackについてはこちら


madtag

●ExternalInterfaceを使うには

ExternalInterfaceを使うにあたって、まず基本的な部分で躓きやすいポイントを抑えてみましょう。
以下、一般的なFlashを埋め込む際のHTMLタグです。

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="SWFファイルの幅" height="SWFファイルの高さ" id="好きなID" align="middle">
<param name="wmode" value="opaque" />
<param name="allowScriptAccess" value="always" />
<param name="movie" value="SWFファイルのURL" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="SWFファイルのURL" wmode="opaque" quality="high" bgcolor="#ffffff" width="150" height="60" name="objectタグのID" align="middle" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>

このタグの書き方によって、ExternalInterfaceがうまく動かなかったりしちゃいます。

まず、<param name="allowScriptAccess" value="always" />この部分と、embedタグ内の、allowScriptAccess="always" この部分がきちんと記述できているか、確認しなければいけません。
alwaysの部分がsameDomainだと、SWFファイルとHTMLが同一ドメインに置かれていなければいけなくなり、neverだとSWFファイルとHTMLの間での通信はできません。

次にIE限定の問題なのですが、objectタグのidを「"external"を含む"external"ではない文字列」にしなくてはいけません。
例えば"aexternal"だとか"externala"だとかであればOKです。
なぜか"external"だと動きません。。。
ここが一番わけのわからない規則なんですよね・・・。

で、例えばアメブロにフラッシュを設置する場合、アメーバピグをプロフィール画像にしていると、プロフィール画像を表示するFlashのidが"aexternal"ですので"aexternal"を使用するとどちらかが表示されなかったりしてしまいます。要注意。

次に、wmodeのパラメータにも注意が必要です。
<param name="wmode" value="opaque" />
この部分ですが、オペラなどではopaqueの部分をtransparentにしていると上手く通信ができなくなることがあるようです。

最後に、Flashを設置している部分を「display:none」などで不可視にしていると、これもうまく通信できないようなので注意が必要です。


●ExternalInterface.callについて

ExternalInterface.callの基本は、ExternalInterface.call("HTML上にjavascriptで記述した関数名",その関数の引数1,その関数の引数2,その関数の引数3...)というような感じです。
が、javascriptの関数を直接記述することも可能です。例えば、

ExternalInterface.call("function(){ var i = 1; alert(i); }");
こう記述すると、ちゃんと実行されちゃいます。
と、いうことで例えば、

var jsCode:String = "var i = 12;";
jsCode += "var n = 5.5;";
jsCode += "alert(i*n);";
ExternalInterface.call("function(){" + jsCode + "}");

このようにString型の変数にjavascriptのコードを丸々代入して記述してもちゃんと実行されるんですね~。
外部テキストなどにjavascriptコードを記述し、URLLoaderで読み込んだコードも実行できます。ただし、

ExternalInterface.call("var i = 1; alert(i);");
これは動きません。

ExternalInterface.callは、javascript関数をその場で実行するというメソッドだからです。
var i = 1; alert(i); ←これはステートメントであり関数ではないので、実行されないのです。

あらかじめ ExternalInterface.call("function(){ var i = 1;}") というように定義した変数iを後から呼び出して使用することもできません。



●戻り値も受け取れる

ExternalInterface.callメソッドは、実行した関数の戻り値を受け取ります。すなわち、

var message:String = ExternalInterface.call("function(){return document.getElementById("main").innerText;});

こうすれば、idがmainである要素内のテキストがmessageに代入されます。
変数の型指定に要注意!



●navigateToURLで外部ライブラリを使う

前述の通り、あらかじめ ExternalInterface.call("function(){ var i = 1;}") というように定義した変数iを後から呼び出して使用することはできません。
しかし、navigateToURL(new URLRequest("javascript: var i = 9;"), "_self"); のように、navigateToURLを使ってjavascriptを実行した場合、この変数iは後から呼び出して使用することができます。

と、いうことは、これを使えばjqueryなどの外部ライブラリをflashで読み込んで使うことが可能なんです。
と、いうわけで、早速実験。

html側
<div id="jqtst">test</div>
idがjqtstのdiv要素を作り、中身をtestという文字列に設定します。
Flash側からjQueryライブラリを使ってこれを読み込んでアラート表示してみます。

as3.0ソースコード
//jQueryライブラリをURLLoaderで読み込む
var req:URLRequest = new URLRequest("jquery-1.3.2.js");
var jsloader:URLLoader = new URLLoader();
jsloader.addEventListener(Event.COMPLETE,jscomp);
jsloader.load(req);

//読み込み完了イベント
function jscomp(e:Event):void{
//変数jsCodeにjQueryライブラリを文字列として丸ごと代入
var jsCode:String = e.target.data;
//navigateToURLでjQueryをjavascriptコードとして読み込む
navigateToURL(new URLRequest("javascript:" + jsCode), "_self");
//navigateToURLよりなぜかExternalInterfaceの方が優先して実行されるため、200ミリ秒待ってからExternalInterface.callメソッドを実行
setTimeout(function():void{ExternalInterface.call("function(){alert($('#jqtst').html());}");},200);
}

実行結果はこちら

上手くいきました!

※追記:IEのみ動作しないケースが多いです。現在研究中。

この時の注意点は、navigateToURLよりなぜかExternalInterfaceの方が優先して実行される、ということ。つまり、

navigateToURL(new URLRequest("javascript: var i = 9;"), "_self");
ExternalInterface.call("function(){alert(i)}");

と記述すると、ExternalInterface.callが先に実行されてしまうので、変数iが未定義の状態でalertが実行され、その後でiがnavigateToURLによって定義される、という結果になってしまうのです。
今回はsetTimeoutでお茶を濁していますが、他にも様々な方法があるかもしれません。


さて、長々とつまらない専門的な話になってしまいましたが、誰かの役に立てれば幸いです。
ちなみに冒頭の写真は全く本件と関係ありません・・・・・。