IEのDOMで特徴的だと思ったこと3点

最近、職場で勉強がてらJavaScriptでプログラムを書いています。会社の案件では少しjQueryを触ったりしたのですが、こちらではライブラリは極力使わない方針で。
そんな中、いくつか気づいたことを書き留めておきます。今回はIEの挙動で、特徴的だと思った点を3点。

responseXMLされたテキストノードをappendChildしようとするとエラーになる

Ajax通信したものをresponseXMLで受け取り、そこからテキストノードを抜き出して、createElementしたコンテナ要素にappendChildしようとすると、IEでは何故かエラーになりました。

回避方法:

// hogeが該当のテキストノード
var fuga = document.createTextNode(hoge.nodeValue)
container.appendChild(fuga)

周りくどいですが、テキストノードのnodeValue(テキストそのもの)を取り出して改めてcreateTextNodeします。これだけで解決。

table要素をcreateElementする際、子要素にtbodyを作らないとダメ

FirefoxとかOperaは、こっちがわざわざ作らなくても仮想的にtbodyを作ってDOMツリーを構成してくれます。だけどIEの場合はtbodyもこっちが作らないとダメ。作らなかった場合は不完全な要素ノードとなるため、appendChildとかしても何も表示されません。

そんな情報を検索している時に発見した、珍しいメソッドがtBodies[]

tBodies[]
table要素ノードのメソッド。tbody要素をノードリストで返す。
tableElement.tBodies[0]みたいにして使います。まぁ、tableElement.getElementsByTagName("tbody")[0]と書いても同じなわけですけど・・・。

参考:tBodies Collection (TABLE) – MSDN

要素ノードリストのアイテムにインデックス番号を振る際、 変数に格納した数値を入れるとエラーになることがある

解決方法:
とりあえずitem()メソッドで書けばOK

他のコードでも書いてみましたが、エラーが再現できず・・・どういう条件が重なると引き起こるのかよく分かりません。

置換インライン要素の縦方向の位置をborderで調整してみる

同じ行内にラジオボタンもしくはちっちゃな画像とか(つまり置換インライン要素)とテキストが混じっている場合、テキストの表示される縦方向の位置がブラウザによって結構違っていたりして、困りませんか?これを調整するには普通、ラジオボタンにvertical-alignプロパティを指定しますよね。baseline(規定値)とか、middleとか、bottomとか。

しかしvertical-alignプロパティで同じ値を指定しても、やっぱりブラウザによって微妙に位置がズレてしまうし、ピクセル単位での細かい指定ができません。これをborderで強引にやってみました。

置換インライン要素に縦方向のborderを設定してやることによって、borderの太さ分だけ縦位置をズラしてあげることができる……はずです。もちろんvertical-alignも併用するといいでしょう。
border-colorは背景色と同じにできるのがベストですが、背景画像があったりするとアウトなのでtransparentにします。

以下、実際にやってみたサンプルへのリンクです。

サンプルを見た方は既にお気づきかもしれませんが、残念ながらすごい微妙です。私がサンプルを表示確認をしたブラウザはIE6、IE7、Firefox2、Opera9、Safari3です。

Continue reading

コーダーのための(X)HTMLテンプレート

あ!ああいうコードを書いたらどうなるんだろう?!とアイデアを思いついた時に、ポンと開いてすぐコードを書き始められるテンプレートがあったら便利だなと思ったので、作りました。
以下のリンクからお好きにお持ち帰りください。

コーダーのための(X)HTMLテンプレート(zip形式)

(X)HTMLのテンプレートなんて、いくらでも配布されていると思いますのでありがたみはないと思いますが・・・。

で、内容は以下のようなものになります。

  • 文字コードShift_JISとUTF-8の2種類のファイルを用意。
  • HTML 4.01 Strict/Transitionalのそれぞれシステム識別子有りと無しを用意。
  • XHTML 1.0 Strict/TransitionalのそれぞれXML宣言有りと無しを用意
    (ただし文字コードShift_JISではXML宣言の省略はできないのでXML宣言有り版のみ)
  • すぐにCSSやJavaScriptを書けるようにstyle要素とscript要素を予め記述

Continue reading

リダイレクト手法まとめ

サイト内でページを移設するとか、拡張子を変更するとか(html→php など)、ドメインを変えるとかいったことになると、旧URLから新URLへのリダイレクトをするケースがあります。リダイレクトが必要とされるのは、旧URLを参照するユーザーに不便をかけないためだけではなく、検索エンジンのロボットにURL変更を通知するためという側面もあります。

ここでは3つのリダイレクトの方法を取り上げてまとめてみました。

  • (1)301リダイレクトと呼ばれる方法で、.httaccessを使うなどして行うもの。
  • (2)HTML<meta http-equiv="Refresh" ...>を使う方法
  • (3)JavaScriptのlocation.href、またはlocation.replace()を使う方法

(1)はサーバー側に.htaccessを設置する方法です。具体的なコードは以下のようになります。

Redirect permanent /index.html https://likealunatic.jp/index.php
Redirect permanent /about.html https://likealunatic.jp/about.php

これで/index.htmlにアクセスしようとした人は/index.php にリダイレクトされるようになります。/about.htmlについても同様。この例は実際に当サイトで使っているもので、ファイルの拡張子をhtmlからphpに変更した際のものです。このようにリダイレクトしたい分だけずらずらと行を増やしていけばOK。
Googleでも Yahoo!でもはこの方法が推奨されています。

(2)HTMLファイルのヘッダーに直接書き込む方法です。head要素内に下記のようなコードを書けば良いだけです。簡単。

<meta http-equiv="Refresh" content="0; URL=/index.php">

この例は /index.php に0秒後にリダイレクトする、ということになります。
ただし当然ながらこの手法は古いURLにリダイレクト用のHTMLを残しておかねばならないことになります。

また、この方法はGoogleでは推奨されていないようです。しかしYahoo!においては正当な手法としてきちんと言及されています。
この方法でリダイレクトした場合、ブラウザによってはリダイレクト用のページが履歴に残ってしまい、「戻る」ボタンを押した時の挙動がおかしくなる場合があります(「戻る」を押しても再度リダイレクトされてしまい、戻れない)。検証不足により「どのブラウザがこうなる」とは具体的に言えませんが、少し試した限りではIE7やOpera9はこの挙動になってしまうようです。
これについては似たようなことを実験しているページを発見したのでリンクを張っておきます……リファラ実験

(3)はJavaScriptを使ってリダイレクトです。ただしlocation.hrefを使うと"「戻る」ボタンで戻れない問題"が発生しますので、履歴の残らないlocation.replace()の方がいいです(こちらでは戻れる)。こちらの手法も古いURLにリダイレクト用のHTMLファイルを置いておかねばならないという点で、(1)に劣ります。
また、GoogleではJavaScriptによるリンクをクロールできないことを明示しています。

Googlebot は JavaScript を含んでいるページもインデックスに登録しますが、JavaScript そのものに含まれているリンクをたどったりインデックスに登録することはできません。

よってこのJavaScriptによるリダイレクトを使用する場合には、(2)のmeta要素による手法と併用したり、noscript要素で代替コンテンツを記述しておく必要があります。

以上3つの手法においてはダントツで(1)の301リダイレクトが優秀であるのは言うまでもありません。しかし、何らかの理由によりサーバーに手を入れることができない場合、(2)(3)を併用するのがベストな手法でしょう。

具体的には、下記のようなコードが良いのではないかと思います。(XHTML1.0 Strictの場合)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title>Like@Lunatic トップページへ</title>
<script type="text/javascript">
location.replace('https://likealunatic.jp/');
</script>
<meta http-equiv="Refresh" content="0; URL=https://likealunatic.jp/" />
</head>
<body>
<noscript>
<p><a href="https://likealunatic.jp/">Like@Lunatic トップページへ</a></p>
</noscript>
</body>
</html>

ちなみにa要素によるリンクはnoscript要素で囲わなくても良いですが、リダイレクトが実行される前に一瞬だけリンクが見えてしまいますのでご注意を……。

hasLayout問題を解決するzoom:1;の落とし穴

IEのヘンテコなCSS解釈の原因と言われるhasLayoutプロパティ。
どうやらこのhasLayoutプロパティの値がfalse(デフォルト値)の場合に、いろいろとまずいことが起こるようです。IEだけfloatした要素周辺のmarginやpaddingがおかしかったり、相対配置/絶対配置した要素がどこかに消えてしまったりする・・・という経験はcssレイアウトの際に誰もがぶつかる問題です。

また、hasLayoutがTrueとFalseの要素が混在している場合には、IE7のズーム機能を利用した際に、隣あった要素が重なってしまったりします
この辺はコリスさんのIEでのCSSのバグを回避するhasLayoutに分かりやすい説明があります。

このhasLayoutの問題を解決するためにzoomプロパティが利用されることがあります。(zoomプロパティはIEの独自拡張であるため、これを使うこと自体どうか、という意見もありますがここでは触れません)
具体的には次のように、

* {
margin: 0;
padding: 0;
zoom: 1;
}

ユニバーサルセレクタに指定したり、

body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td {
margin:0;
padding: 0;
zoom:1;
}

などとして、ブラウザスタイルをリセットする際に同時に指定するのが一般的なようです。しかし、zoom:1;をli要素に指定してしまうとマーカーの表示がおかしくなってしまう問題があります
これに対しては

li {zoom: normal;}

としてzoomプロパティの値をデフォルトに戻すことで対処をできるようです。

ここまでは前提で、本題はここからです。

そんなわけで実際に業務で、スタイルリセットにzoom:1;を導入してみたのですが、さらなる落とし穴があることに気づいてしまいました。
STOPN’ LISTENさんのclearfixに関するの記事をご覧いただくとお分かりになると思うのですが・・・そうです。zoom:1;はclearfixにも使われるのです。

つまり、何でもかんでもzoom:1;にしてしまうと、floatが意図しないところでclearされてしまう恐れがあります。具体的には次の図のような例で、問題が起こります。

図: *{zoom:1}で問題の起こるレイアウトの例

XHTMLのコード

<p><img src="/img/2007/09/16_example_img.gif" alt="画像" width="120" height="90" class="photo" /> 段落1テキスト段落1テキスト段落1テキスト段落1テキスト段落1テキスト段落1テキスト段落1テキスト段落1テキスト段落1テキスト</p>
<p>段落2テキスト段落2テキスト段落2テキスト段落2テキスト段落2テキスト段落2テキスト段落2テキスト段落2テキスト段落2テキスト</p>

最初の段落に含まれている画像に対してfloat:leftを指定し、本文を右側に回りこませているという、何の変哲もないレイアウトです。回り込んでいる本文が途中で段落分けされていることがポイントです
ここで* {zoom: 1;}を指定すると、以下の図のようになります。

図: *{zoom:1}でレイアウトに問題が起こった状態の例

図と同じサンプルページも用意しました(IE6/7で見てくださいね)。

段落分けされた途端、clearされてしまっているのがお分かりになるかと思います。これはp要素に対してzoom:1;が効いているからです。これを回避するにはp {zoom:normal;}のようにしてデフォルトの値を上書きするしかありません。個人的にこれは全くナンセンスだと思うのですが・・・。

スタイルリセットの段階でzoom:1;を指定してしまうと、そのサイトのコンテンツは全てがこの挙動になってしまいます。

サイトをリニューアルする際にzoom:1;を新たに導入する、という場合には注意が必要です。今まで正常に表示されていたコンテンツが、先述の例のようにレイアウトが変わってしまうことがあるからです。

サイトを一人で制作し運営していく分には、この挙動を理解した上で制作をすることになるので、全く問題はないと思います。しかし、複数人でチームを組んで一つのサイトを制作・運営する場合には状況が違います。
制作チーム全員がこの挙動を理解することが必須となるのです。人数の多いチームともなると、これはなかなかに簡単なことではありません。

よって、複数人でチームを組んで制作・運営をしている大規模サイトの制作においては、スタイルリセットにzoom:1;を組み込むべきではない、と言えます。

ただ、やはりzoom:1;の便利さは捨てがたいものがあります。idセレクタや子孫セレクタなどを使用して、特定の領域にだけ限定的に利用する分には問題はないでしょう。

zoom:1;は便利ですがご利用は計画的にということです。