フレームオブジェクトとフレーム内ドキュメント

フレーム(frame)やインラインフレーム(iframe)をオブジェクトとして指定する場合や、 そのドキュメントに対してDOM操作などを行う時のオブジェクト指定方法です。

フレームオブジェクトの指定方法には、実際は下位互換も含めていくつか方法があって、 非常に簡単に扱える古い方法から、現在や将来を考えて推奨されている新しい方法まであります。
ネットを見てると無理に分岐させてたり、古い方法と新しい方法をごっちゃにしてるケースもあるので、 方法をそれぞれ書いておきます。

■ フレームオブジェクト指定方法 あれこれ ■ Tips

name 指定による単純な方法

name指定を使いますから現在推奨されていないと思いますが、 今現在のブラウザ(IE7,FireFox2,Netscape7,Safari3,Opera9)では、まだ普通に通用します。

■ フレームの name をそのままグローバル変数?ウィンドウ名として扱う
IE、Netscape系 共通の汎用

例1:
インラインフレーム
<iframe name="frame1" src="cont.html"> </iframe>

フレームセットのフレーム
<frameset -省略- >
<frame name="frame1" src="cont.html"> </frame>
</frameset>
ただの name属性を付けたframe、iframeです。

<script type="text/javascript">
/* frame、iframe内のdocument */
frm_doc = frame1.document;

/* frame、iframe内の BODY内のHTML */
frm_body = frame1.document.body.innerHTML;

/* frame、iframe内のドキュメントのDOM操作。例:IDで指定 */
aaa = frame1.document.getElementById('aaa');
</script>

この様に非常に簡単にフレーム内ドキュメントをオブジェクトとして取り扱えます。
(frame1 内のドキュメントがロード完了している事がDOM操作の前提です)
フレーム(つまりウィンドウ)である frame1 (name属性)をオブジェクトとして直接ウィンドウ名として指定するだけで、 selfドキュメントを扱うのとなんら変わりなく操作できます。 selfの場合は先頭にwindow、つまり自ウィンドウと言うオブジェクトをつけますが selfの場合に限って省略が可能で通常は付けないだけです。 selfであれば window.document.・・・・ 他のウィンドウ(フレーム名)であれば (window name).document. ・・・・ です。

JavaScriptでは window直下に frames というフレームオブジェクトの配列があります。 このフレームオブジェクトはframeタグを指すものではなくフレーム内のwindow自体を意味する為にちょっとだけ特殊ですね。 frame、iframe タグに name 属性がついてる場合は、Netscapge系も含め、いきなり『フレーム名』でオブジェクト指定する事ができます。 従ってフレーム名をそのままウィンドウを意味するグローバル変数(オブジェクト)として取り扱えます。

document.allコレクションを持っている IE、Opera、Safariなどの場合は nameではなく ID でも可能です。 Netscape、FireFoxはIDでは指定できないので nameを使うか、両方付ける と言う事になります。 (でもIEなどの場合も document.all(id)と指定すると意味が変るので注意)


古い?従来からの方法であり name 属性を使う事などから今は推奨されない方法ではありますが、 name属性をつけてあれば、IEとNetscapeのフレーム内ドキュメントの取得方法が同じ記述、しかも短い記述で済むので、 『今でもまだ便利な方法である』と言う事もできます。
いつか使えなくなる可能性のある『下位互換』として残ってる方法だろうと思いますので、 何年も先の将来を見据えての利用には推奨はしませんし、教科書的に『正しい方法』にこだわる人や、 正確なHTMLを提供する人は使わない方が良いです。 が『Netscape系では contentDocument を使わないとフレームのDOM操作ができない』なんて思い込んでる人は知っておく方が良いと思います。

また古くからあるこの方法はformsなどと同じで、frames[0]のようなindexを使った方法でも普通に指定できます。 使える間は活用してしまっても良いかな?と思います。


frameオブジェクト + contentWindow

■ frame要素オブジェクト 及び contentWindowとして扱う
IE、Netscape系 共通の汎用

例2:
インラインフレーム
<iframe id="frame1" src="cont.html"> </iframe>

フレームセットのフレーム
<frameset -省略- >
<frame id="frame1" src="cont.html"> </frame>
</frameset>
id 属性を付けたiframe、frameです。

<script type="text/javascript">
/* frame、iframe オブジェクト (タグエレメントを指します) */
frm_obj = document.getElementById('frame1')

/* frame、iframe のウィンドウオブジェクト */
frm_winw = document.getElementById('frame1').contentWindow;

/* frame、iframe内のドキュメント */
frm_doc = document.getElementById('frame1').contentWindow.document;

/* frame、iframe内のドキュメントのDOM操作。例:IDで指定。 */
/* 直前述のfrm_docに対して */
aaa = frm_doc.getElementById('aaa');
</script>

このケースで勘違いしやすいのが document.getElementById('frame1') と指定しただけでウィンドウオブジェクトを指定してるつもりになる事です。
document.getElementById('frame1')は、フレームタグをHTMLオブジェクトとして指定しているだけで、 そのフレーム内ウィンドウを意味する物ではありません。
このオブジェクトのcontentWindow プロパティがフレームウィンドウを指します。

勘違いしやすい例として
document.getElementById('frame1').src
これはフレームタグオブジェクトに対してそのsrcですので正解。
document.getElementById('frame1').location
フレームタグにlocationなどと言うプロパティはないので間違い。

document.getElementById('frame1').contentWindow.src
これはwindowオブジェクトには src は無いので間違い。
document.getElementById('frame1').contentWindow.location
これはwindowオブジェクトに対してのlocationなので正解。

『フレーム = ウィンドウ』と言う考え方が通用するのは、従来のwindow.frames配列や、 IE系にある document.frames のケースだけです。 getElementById()やdocument.all()でIDからHTMLオブジェクト指定する場合は、 フレームタグとしてのオブジェクトを指し、ウィンドウを指しません。

と、言う事で
今風にID指定からフレームウィンドウやフレーム内ドキュメントをオブジェクトとして指定するには、 IE、Netscape系共通で
document.getElementById('フレームID').contentWindow
document.getElementById('フレームID').contentWindow.document

となります。


その他の方法

■ フレームオブジェクト.contentDocument
Netscape、FireFox、Safari、Opera では、フレームオブジェクト直下に contentDocumentプロパティがあり、 ウィンドウオブジェクトを意識せずに、フレームウィンドウ内のドキュメントを指定できます。 ま、前述の共通で使える汎用の方法があるので、これをわざわざ使う必要はないですが。
document.getElementById('frame1').contentDocument
の様に使えます。
document.getElementById('フレームID').contentWindow.document
と同じ物を指します。

■ documentオブジェクト下の フレームオブジェクト配列
IE、Opera では window直下にあるフレームオブジェクトの集合配列 frames コレクションが、 document下のオブジェクトとして参照できます。(iframeも含まれています)
多分どっちも同じ物を指してると思います。 これはフレームオブジェクトで、タグを指す物ではなくフレーム内ウィンドウを指す物です。 従って
document.frames['フレームID']
と言う指定の仕方もできます。
これは、
window.frames['フレームID']
と同じであり
frames['フレームID']
と同じであり、
フレームID
と同じであり、
document.getElementById('フレームID').contentWindow
と同じです。
真ん中の3個の方法はページはじめに説明してあるwindow直下のframesを扱う方法(古い方法)と同じです。 document.allを持つIEやOpera、Safariでもいけます。 フレームID が IDでは無くフレームname 指定であれば Netscape系でも通用します。
また、nameやIDを気にせずに
frames[0]
の様にwindow.frames配列をindexで指定すればNetscapeでも普通に使えます。
この時にdocument.frames と書いてしまうと駄目ですよ。

ま、そんなこんなでIEだけが除外される方法、Netscape、FireFoxが除外される可能性のある方法などがありますが、 わざわざそれを使う必要がある時だけに使う方法ですね。

出来立てほやほやのiframe

フレームの取り扱いはウィンドウが絡むのでちょっと変な動きがあります。

document.createElement('iframe')で iframeを作った場合(一連のスクリプトの流れ中)や、 同じく流れの中で (場所指定).innerHTML = "<iframe></iframe>";の様に新たに iframeを作成した場合には、最初からiframe記述が存在してた場合と違って、ちょっと操作にワンクッション必要です。 IEはdocument.documentElementを調べると null になります。 そのせいで動的生成のiframeに関してはすぐに書込みができないなどの問題が起きます。 元々指定ファイル(src)が無いのですから onload待ちのせいで扱えないのとはちょっと違いますが、 『新たにドキュメントを作る』作業をしてしまえば余計な事を気にしなくて済みます。
/* createElementで*/
var ifr = document.createElement('iframe');
document.body.appendChild(ifr);

var ifrdoc = ifr.contentWindow.document;
ifrdoc.open();
ifrdoc.close();
if(document.charset) ifrdoc.charset = document.charset;

/* div id="div1"にiframeをinnerHTMLで*/
document.getElementById('div1').innerHTML = '<iframe id="ifr"></iframe>
var ifrdoc = document.getElementById('ifr').contentWindow.document;
ifrdoc.open();
ifrdoc.close();
if(document.charset) ifrdoc.charset = document.charset;

フレームのdocumentオブジェクトを開いて・閉じるだけでOKです。
これで普通にiframe記述のSRC無しのblank状態にしてるのと同じになります。
window.open()の時もそうなのですが、ブランク(無指定)の場合や、 document.open()で書き込む場合は文字コードセットがありませんので、 document.charset を理解出来る IE、Opera、Safariでは 親ドキュメントと同じか、 必要に応じてcharset を指定します。
Netscape、FireFox の document.characterSetはリードオンリーなので、 他の方法があればその方法で、なければUTF-8のままで扱う事になりそうです。

ロード(読込)済 確認の重要性

フレーム内のドキュメントのDOM操作(HTMLエレメントに関する操作)を行う場合は、 必ずフレーム内のドキュメントが読み込まれているかどうか?を確認してから操作を実行する必要があります。 これを行わないと『一瞬表示されすぐに消える・・・』なんて現象で頭を悩ましたりしますし、 ローカルやキャッシュを持ってる管理者や常連さんはエラーが出ないけど、 初回訪問の人はエラーが出まくり・・・ なんて事もあります。

で、この場合問題なのは IEであれば readystate(ドキュメントの読込状態) や onreadystatechange (イベントハンドラ)を使ってしっかり確認ができる。 Netscape系はAjax(XMLHttpRequest)のドキュメントには用意してるけど普通のドキュメントはない?(多分)って事。 なので onload イベントで確認をとるベタな方法を使います。

■ ベタなインライン (でも確実?)

/* TOPにグローバル変数を作る (iframeの場合はベースのHTML) */
<script type="text/javascript">
var frm1 = false; /* フレーム1のロード確認用変数 */
</script>

/* フレームの場合 */
<frame id="frame1" src="1.html" onload = "frm1 = true;" >

/* iフレームの場合 */
<iframe id="frame1" src="1.html" onload = "frm1 = true;" ></iframe>
この様に対象とする frameタグ や iframeタグ に onloadイベントハンドラを付けてしまいます。 このイベントハンドラによってロードが完了するとグローバル変数 frm1true に書き換わります。
この例の場合なら、別フレーム内のドキュメントからでも自ドキュメント内からでも top.frm1 と言う名前で、フレームのロード状態を参照できます。
ドキュメントを差し替える時は、差し替える直前に top.frm1 = false; を実行させるようにします。 アンカーで普通に差し替える場合もonclickなどをつけて top.frm1 = false; を行います。 他にも方法がありますがとにかく差し替える時に フラグを一度 top.frm1 = false; にします。 あとは、ドキュメントがロードされれば onloadイベントハンドラが勝手にtrue にしてくれますので

差し替えてからDOM操作をする場合は常にこの top.frm1 の様な固定HTMLにあるグローバル変数を参照し、 駄目ならsetTimeout()などで繰り返し確認して true になったら実行するようにします。

■ JavaScriptで イベントを埋め込む
/* TOPにグローバル変数を作る (iframeの場合はベースのHTML) */
<script type="text/javascript">
var frm1 = false; /* フレーム1のロード確認用グローバル変数 */

window.onload = function(){
var f = document.getElementById('frame1');
var loaded = function(){ frm1 = true; }
if(f.addEventListener){ f.addEventListener('load',loaded, false) }
else{ if(f.attachEvent) f.attachEvent('onload', loaded) };

}
</script>

/* フレームの場合 */
<frame id="frame1" src="1.html" >

/* iフレームの場合 */
<iframe id="frame1" src="1.html" ></iframe>
インラインでイベントハンドラを書く方法はだんだん推奨されなくなっているので、 JavaScriptでイベントを付けています。 iframeを createElement()で作成する時もこの方法でつけるしかありません。
で、わざわざattachEvent()を使っているのは、
Netscape系は(フレームエレメント).onload = function(){ }で、 後からつけた onload も普通に動いてくれますが、IEはこの少し古い方法ではonloadに関しては有効にならないようです。 従ってメソッドを使ってイベントを追加しています。

他にも読み込まれる方のドキュメント側で共通JSライブラリなどを使って、 自ドキュメントのonloadでtrueになる変数を用意しておきそれを外から参照する方法もありです。
なんかこれを書いてて怖いのが、オンロード確認の方法なんて『・・・』これだけで済むよって言う方法がありそうで怖い。 とりあえず『無知の恥』(造語)と言う事で・・・勘弁してください(^^ゞ

■ 行けそうで駄目な方法
<script type="text/javascript">
何らかの起動 → function(){
var f = document.getElementById('frame1');
if(f.contentWindow.document.getElementById('bbb')){
f.contentWindow.document.getElementById('bbb').innerHTML ='';
}
}
</script>

/* iフレームの場合 */
<iframe id="frame1" src="1.html" ></iframe>
この例では操作する前に、フレーム内のドキュメントにターゲットとなるエレメントがあるかどうか確認し、 『あるなら操作』をしています。
ターゲットがあるなら操作可能と言う確認方法は悪く無いのですが・・・

正確な事は知りませんがドキュメントはロードが完了した時点で、 HTMLとCSSやJavaScriptなどを含めて最終整理のような動作段階があるようです。 自ドキュメント内で ロード中にdocument.writeで実行された物や、 エレメントが読み込まれてからすぐ(まだ全ロード済前)にエレメント操作された物は残ってくれますが、 他のウィンドウからJavaScriptで書き込まれたり、操作された物は、ロード完了の最終段階で『存在しない物』として弾かれてしまうケースがあるようです。
これがロード中に一瞬書き込めて表示されて、ロード完了と共に白紙に戻るパターンだと思います。
なので『他のウィンドウ内に対象エレメントがあるかどうか?』と言う確認方法では確認が足りないと言う事になり、 やはりまず『ロード済』の確認をきちんとしてから操作しないと駄目だと思います。

フレームコンテンツや子ウィンドウドキュメントの操作は、 ほんのちょっとした事でエラーがでやすいですが、 製作者は環境的にエラーがでないケースもあって確認をする工程を忘れがちですので、気をつけてください。

HTMLオブジェクトのcreateElement()

他のウィンドウ(フレームや子ウィンドウ)に新しいHTMLオブジェクトを生成して入れ込む場合、 IEでの規制というかIEの特徴に注意しなければなりません。

新しいHTMLオブジェクトを生成する方法として、
HTMLObject = document.createElement(tagName)
で作る方法があります。
これをドキュメントに入れ込むのは一般的に
(挿入先object).appendChild(HTMLObject)
です。

このdocument.createElement()は、documentのメソッドです。 IEの場合はこの document に対してHTMLオブジェクトが生成されるようなので、 他のドキュメント(フレームや子ウィンドウ)にこれをHTMLエレメントとして挿入する事ができません。 一方Netscapeでは生成されると『独立したグローバルオブジェクト』のような物として扱えるので、 どのdocumentで生成されたかは関係なくなります。
ですのでIE対策として、createElement()を使う時は常にどのwindow.documentの中に挿入するか?を意識して作らねばなりません。
/* iframeの名前が frame1 とする*/
var frmdoc = document.getElementById('frame1').contentWindow.document;
var div1 = frmdoc.createElement('div');
/* これで frame1 のドキュメントに挿入可能な DIVが作られました */

/* frame1のBODYに追加挿入 */
frmdoc.body.appendChild(div1);
この様に挿入先ドキュメントに対してcreateElement()してやらなければ、 IEでは『サポートされてない』と言うエラーが出てしまいます。

createElement()は、昔なら new Image()や new Option() の様なHTMLエレメントのコンストラクタを呼び出す行為と同じ事(HTMLオブジェクトを作る)をするメソッドです。
new演算子では他のウィンドウのコンストラクタを呼び出して インスタンスを作るなどと言う方法はありません。
従って new Image() は (自document).createElement('img') と同じになります。
ま、src参照するだけなら別に問題ないですが、他のウィンドウに挿入するならnew Image()はIEを考えると駄目です。 Netscapeならこの場合も可能です。
/* 自document内に画像を挿入 */
var img = new Image();
img.src = 'a.jpg';
document.body.appendChild(img);
Netscape系なら この例で生成した img を別のウィンドウドキュメントに挿入(appendChild)する事も可能です。

何らかの問題点があってどうしても上手くいかない・・・なんて事はあまり無いでしょうが、 最悪悩んじゃった場合はオブジェクト挿入ではなくHTML文字列挿入の方法でごまかします(^^;

appendChildと同じく最後尾に挿入なら

/* iframeの名前が frame1 とする*/
var frmdoc = document.getElementById('frame1').contentWindow.document;
frmdoc.body.innerHTML += '<div id="div8"></div>';

先頭なら(HTML文字列挿入方法なら)
/* iframeの名前が frame1 とする*/
var frmdoc = document.getElementById('frame1').contentWindow.document;
frmdoc.body.innerHTML = '<div id="div8"></div>' + frmdoc.body.innerHTML

先頭なら(オブジェクト挿入方法なら)
/* iframeの名前が frame1 とする*/
var frmdoc = document.getElementById('frame1').contentWindow.document;
var div1 = frmdoc.createElement('div');
/* frame1のBODYに先頭挿入 */
frmdoc.body.insertBefore(div1, frmdoc.body.childNodes[0]);

私は未だにベタな書き方が多い(染み付いてる^^;)ので、
例えば
div.innerHTML = '<img id="img1" src="./a.jpg" onclick="func();">'
なんていう書き方を今でも普通にやります。
img1 = document.createElement('img');
div.appendChild(img1);
img1.src = 'a.jpg';
img1.id = 'img1';
img1.onclick = function(){ }
と書くべきなのでしょうが、気分次第です(^^ゞ
横に長くなりすぎると見辛いのでcreateElementを使うでしょうし、 軽く1行で済むなら、HTML文字列挿入にしてしまうかも。
私の場合は 『何が正しい』かではなく使えるものは全部OKです。
廃止予定や下位互換打ち切りなどがMSやMozillaで正式にアナウンスされれば辞めますが、 当面は各社のブラウザは今後も古い表記を解釈できるように工夫していくと思いますので。

IE2とかNN1.xなどの時代からの進化を見てきた経験からすると
1つ前の方法(A)でできる事で今でも通用する方法は無理に今の標準(B)に合わせる必要は無いかな?って思ってます。
今の標準(B)も次の標準(C)がができれば1つ前の方法になります。 その時になって今までの標準(B)を使って(C)にも通用する方法を作ればよいです。 最新の標準(C)が出ても、1つ前の標準(B)くらいは必ず理解できるようにしてるはずです。 (C)で通用する便利な機能が新設されたら、分岐処理を行って便利な物は当然使った方が良いです。 でも1つ前の(B)に対する配慮は必ず必要です。 2つ前の標準(A)は、さすがに古いのでそれにしか対応できないブラウザを使ってる人は切り捨てるか、 暫定処理などを残しておけば良いでしょう。
大きな転換期などで、ブラウザを出してる会社が『○○を廃止して別の方法に』(例えばNetscapeが装備したLayer) と言うのがあれば、さすがにそれは使わない方向で考えていくべきです。

最新の方法を使わないといけない人は限られてくると思います。
しかし、、、ブラウザの違いや、バージョンによる違いなど、ほんと悩まされますね (^^;

これを書いてる時点でIE7が出てからかなり時間が経過していますが、 ごく最近やっと大手企業オンラインサービスのIE7対応がほぼ完了したって事らしいです。 それを受けてMicrosoftがIE7の自動インストールを開始しました。 でも、某自動車メーカーの業者専用のオンラインサービスなんて・・・未だに『IE7にしないでください』なんて状態ですし・・・。

クロスドメイン

他のフレーム内のドキュメントの操作に関して書いてきましたが、 フレーム内のドキュメントが他のドメインの場合は操作対象外です。 これは子ウィンドウも同じです。
テストしてませんが、同じドメインを持つ同一プロバイダーのHPスペースなどなら 他人のコンテンツでも可能かもしれません。

ま、悪い事をしようと思えばもっと他の方法でいくらでも偽装サイトなんて作れるのですが、 とりあえずJavaScriptでは規制されています。 ブラウザのセキュリティレベルをいじれば可能になるケースもあるかもしれないですが、 興味が無いので全然調べていません。自分だけ可能になってもHP上では何の意味も無いですし・・・。



HPページを1枚のドキュメント(文書)として考えるのが主流になってからは、 フレーム構成が嫌われiframeを使う事も増えています。 ま、iframeも嫌われがちですけど。 私はフレーム構成でメニューとコンテンツが分離してるページが好きですが、 iframeはあまり好きじゃない方です。^^; また、どんどん深層に入っていくような構成や、ジャンルやカテゴリなどが一見してわかり辛いブログ形式もあまり好きではありません。 しかしこれだけブログが流行るとブログ形式の方に目が慣れて行って『見やすいページ』の定義もどんどん変わっていくんだろうなぁと思います。
HPを自作してる人は作る楽しみもあるでしょうから、 あまりしがらみにとらわれ過ぎずに自分の好きなように自由に作りましょうね。


専用ページから申し込むと
So-netより高い3万円CB

案ずるより産むが易し
使ってみれば疑問も解決

XREA+ (plus) 206円/月
( お試し7日間 )

CORE SERVER 428円/月
( お試し15日間 )

ロリポップ 270円/月
( お試し期間10日間 )

ヘテムル 1620円/月
( お試し期間15日間 )

さくら 129円/月
( お試し期間2週間 )

無駄な高額ドメイン管理料金払ってませんか?

バリュードメイン
ムームードメイン

2010年で第二世代携帯がサービス終了。ソフトバンクに乗り換えて安くなった人が7割以上います!