prototype.jsを読み解く

第6回 Prototypeライブラリ(1609~2051行目)

この記事を読むのに必要な時間:およそ 18 分

昔話

Google MapsやPrototypeライブラリが認知されるにつれ,JavaScriptに対する評価はずいぶんと変わってきました。

それ以前は,我々が制作するような企業向けのサイトでは,JavaScriptへの依存は最低限にとどめて,古いブラウザ(Netscape Navigator 4やInternet Explorer 4)でも同じように動作することが求められてきました。

JavaScriptを使ったとしても,実験的なページ(今はもうありませんがNavigation Voodoo(1999年)など)での公開か,ブラウザごとにJavaScriptをまったく別個に記述する(今も残っているものとしてはearth from above(富士写真フィルム, 2000年)がIEで動くようです)ようなことをしてきました。

今ではブラウザの UserAgent 文字列やバージョンを見て分岐する方法よりは,object detectionを使ってより長生きするページを制作する手法が好まれますが,この当時では(少なくとも弊社では)このあたりが限界でした。

最近は「古いブラウザでもまったく同じに見えるように」というリクエストは少なくなり,最新のブラウザではその機能の恩恵を受けた実装を行い,古いブラウザではそれなりに見える,くらいのところが落としどころとなっています。

では,今回は前回から引き続いてElementへの拡張です。

1609:   getDimensions: function(element) {
1610:     element = $(element);
1611:     var display = $(element).getStyle('display');
1612:     if (display != 'none' && display != null) // Safari bug
1613:       return {width: element.offsetWidth, height: element.offsetHeight};
1614: 
1615:     // All *Width and *Height properties give 0 on elements with display none,
1616:     // so enable the element temporarily
1617:     var els = element.style;
1618:     var originalVisibility = els.visibility;
1619:     var originalPosition = els.position;
1620:     var originalDisplay = els.display;
1621:     els.visibility = 'hidden';
1622:     els.position = 'absolute';
1623:     els.display = 'block';
1624:     var originalWidth = element.clientWidth;
1625:     var originalHeight = element.clientHeight;
1626:     els.display = originalDisplay;
1627:     els.position = originalPosition;
1628:     els.visibility = originalVisibility;
1629:     return {width: originalWidth, height: originalHeight};
1630:   },
1631: 

1609行目からはgetDimensions()です。

style.displayが表示状態の場合,offsetWidth, offsetHeightを使います。Safariではstyle.displayが'none'の場合,値を取得するとnullが返ってきますので,それにも対応できるようにしてあります。

次に,style.displayが非表示の場合には,visibility: hidden,position:absolute,display:blockとした上でclientWidth,clientHeightを取得します。positionがabsoluteになっているので,この変更によってレンダリングが崩れることが無いようになっています。もちろんvisibilityもhiddenなので要素自体もレンダリングはされません。

displayの値によって,offset{Width,Height},client{Width,Height}が使い分けられています。ボーダーの幅を含むか含まないかの違いがあるので注意してください。

1632:   makePositioned: function(element) {
1633:     element = $(element);
1634:     var pos = Element.getStyle(element, 'position');
1635:     if (pos == 'static' || !pos) {
1636:       element._madePositioned = true;
1637:       element.style.position = 'relative';
1638:       // Opera returns the offset relative to the positioning context, when an
1639:       // element is position relative but top and left have not been defined
1640:       if (window.opera) {
1641:         element.style.top = 0;
1642:         element.style.left = 0;
1643:       }
1644:     }
1645:     return element;
1646:   },
1647: 
1648:   undoPositioned: function(element) {
1649:     element = $(element);
1650:     if (element._madePositioned) {
1651:       element._madePositioned = undefined;
1652:       element.style.position =
1653:         element.style.top =
1654:         element.style.left =
1655:         element.style.bottom =
1656:         element.style.right = '';
1657:     }
1658:     return element;
1659:   },
1660: 

1632行目からはmakePositioned()です。

style.positionを見て,'static'か何も指定されていなければ,'relative'に変更します。その際,後でundoPositioned()で利用するために,_madePositionedフラグを設定しておきます。

'relative'に設定することで,内部に含まれるposition:absoluteな要素がこの要素からの相対位置になるようにしたり,要素自身を本来のposition:staticな位置からtop, leftを使って移動させたい時に利用します。

コメントで言及されているOperaでの問題ですが,positionをstaticからrelativeに変えたとき,top, leftはFirefoxでは0pxに,IEではnullになります。ところがOperaでは親のうちposition:static以外のものからの相対位置が返ってきてしまいます。W3Cの勧告では,positionプロパティが変更された際にtop, leftがどうなっているべき,という記述は無いようなので,どのブラウザの挙動も間違っているわけではないのですが(といっても取得した値をそのまま再度設定すると移動してしまう,というのもどうかと思いますが),Operaだけが他と大きく異なるので,Firefoxに合わせるように代入しています。

1648行目からはundoPositioned()です。

先ほどmakePositioned()で設定した_madePositionedフラグが設定されていれば元に戻す処理を行い,そうでなければ何もしません。

戻す際には,_madePositionedを使っていない状態に戻し,styleのposition,top,left,bottom,rightを空文字に設定することでデフォルトの挙動に戻します。

1661:   makeClipping: function(element) {
1662:     element = $(element);
1663:     if (element._overflow) return element;
1664:     element._overflow = element.style.overflow || 'auto';
1665:     if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
1666:       element.style.overflow = 'hidden';
1667:     return element;
1668:   },
1669: 
1670:   undoClipping: function(element) {
1671:     element = $(element);
1672:     if (!element._overflow) return element;
1673:     element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
1674:     element._overflow = null;
1675:     return element;
1676:   }
1677: };
1678: 

1661行目からはmakeClipping()です。

この関数とundoClipping()では,_overflowプロパティを保存領域としてつかっています。もしそこに値が設定されていれば,すでにmakeClipping()が呼ばれている状態と判断してそのまま要素を返します。

そして,1664行目でstyle.overflowを_overflowに保存します。style.overflowが空の場合は'auto'扱いですが,空のままだと1663行目の判別式で偽となってしまうので,'auto'という文字列を入れておきます。

次にElement.getStyle()を使ってできるだけその要素自体の現状のoverflowプロパティを取得しようとします。何も返ってこなければ,デフォルトの'visible'の扱いとします。そしてその取得した値が'hidden'でなければ,style.overflowを'hidden'に設定して,クリップされる状態にします。

そして1670行目からはundoClipping()です。

makeClipping()関数により_overflowプロパティが設定されていなければ,呼び出しは無視されます。

設定されていれば,その値が'auto'なら空文字列を要素のstyle.overflowに代入し,そうでなければ_overflowに入っている文字列を代入します。

最後に,判別用の_overflowプロパティをnullクリアして終了です。

1679: Object.extend(Element.Methods, {
1680:   childOf: Element.Methods.descendantOf,
1681:   childElements: Element.Methods.immediateDescendants
1682: });
1683: 

1679行目からはElement.MethodsのdescendantOf,immediateDescendantsに別名をそれぞれchildOf,childElementsとして設定しています。

著者プロフィール

栗山淳(くりやまじゅん)

S2ファクトリー株式会社株式会社イメージソース所属。
本業はWeb制作会社の裏方。得意分野はFreeBSDやPerlのはずだが,必要に迫られるとHTML/CSSやJavaScriptも書く。

コメント

コメントの記入