実践JavaScript スタイルシート

「プログラミング経験はそこそこだけど、JavaScriptはあんまり」な人向け社内勉強会資料vol.6

Shogo Ohta, 2009-09-29

JavaScriptとCSS

CSSの基本

CSSとは、
Cascading Style Sheets
HTML や XML の要素をどのように修飾(表示)するかを指示する仕様
W3Cによる勧告の一つ
文書の構造と体裁を分離させるという理念を実現する為に提唱されたスタイルシートの、具体的な仕様の一つ

Cascading Style Sheets - WikiPediaより

HTMLのID名、クラス名、タグ名、属性を使って特定の要素に対して、指定のスタイルを適用します。

ID,クラスの違いとは?

id

個体識別、利用者識別、およびそのための符号(Identification)、身分証明書 (Identity Document)、識別子(identifier)

<address id="太田昌吾">#太田昌吾</address>
class

学校でいう1組,2組,3組のクラス。

<address class="A" id="太田昌吾">#太田昌吾.A</address>
<section id="ALBERT">
   
<ul id="ALBERT_A" class="A">
     
<li id="A_manager" class="manager member">A A</li>
     
<li class="member">B B</li>
     
<li class="member">太田 昌吾</li>
     
<li class="staff">C C</li>
   
</ul>
   
<ul id="ALBERT_B" class="B ALBERT">
     
<li id="B_manager" class="manager member">D D</li>
   
</ul>
</section>
ul.A li{
background
-color:#333366;
color
:#ffffff;
}
ul
.A li.manager{
background
-color:#990033;
}
#ALBERT ul li{
border
:2px solid #9999ff;
}

CSSではセレクタを組み合わせることでスタイルの範囲を限定することができる。思わぬ場所にスタイルが影響してしまうことを避けるために、なるべく#IDでセレクタを始めるようにすると良いです。

Reset CSS

ブラウザごとに、デフォルトの状態のスタイルが異なるため、そのままでは細かい見た目の調整に手間がかかります。そこでブラウザごとの違いがなくなるようにリセットするCSSが普及しています。

YUIのReset CSSが有名です。ただし、inputやtextareaのパディングを0にするのはあまりオススメできません。

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;
}
table
{
   border
-collapse:collapse;
   border
-spacing:0;
}
fieldset
,img {
   border
:0;
}
address
,caption,cite,code,dfn,em,strong,th,var {
   font
-style:normal;
   font
-weight:normal;
}
ol
,ul {
   list
-style:none;
}
caption
,th {
   text
-align:left;
}
h1
,h2,h3,h4,h5,h6 {
   font
-size:100%;
   font
-weight:normal;
}
q
:before,q:after {
   content
:'';
}
abbr
,acronym { border:0;
}

CSS Sprites

ウェブサイトの高速化テクニックのひとつで、サイト内で使用する画像をなるべくひとつの画像にまとめてしまい、背景画像としてその表示位置を制御して一枚の画像で多数の画像を表示する手法です。

のようにGoogleで使用されていることでも有名です。

ただし、多用しすぎると表示が重くなることがあるので注意が必要です。

例えば、10KBの画像を100枚配置するとき、100回リクエストが発生するのを避けたいとして、CSS Spritesで100枚の画像を1枚にまとめ、100箇所に配置すると、仮にまとめた画像が1MBあるとしたら、合計で100MB相当の背景画像を配置したことになってしまいます。これによりスクロールが重くなるなどの現象が起こります(これはあくまで単純計算をした場合で、実際にはそこまで顕著に重くなるわけではありません)

IE6-7との戦い

IE8はCSSの対応が大幅に改善されましたが、IE6,7は取り残されています。特にIE6はバグだらけです。ie+css+バグで検索すれば、たくさんヒットします。今回はそれらから、少しだけ紹介します。

float時のmargin2倍

IE6の有名なバグで、ブロック要素をfloatで並べるときにmarginが倍になるため、ずれます。

div.float{
background
:#0066cc;
width
:200px;
height
:100px;
float:left;
margin
:0 100px;
}
div
.clear{
clear
:left;
}

float:left;に加えて、display:inline;を指定すれば解決します(floatする要素は必ずblock要素として扱われるので、IE6以外ではinlineの指定は無視される)。

ごく小さい要素を作れない

<div style="height:1px;border:1px solid #000000;"></div>

font-sizeに依存しているので、font-size:0;を指定すると解決します。

<div style="height:1px;border:1px solid #000000;font-size:0;"></div>

IEの独自仕様

バグではないが、IEだけ独自仕様になっているケースも多々あります。よく使うものでは半透明化などがあります。

半透明

div.alpha img.alpha{
   opacity
:0.5;
   filter
:alpha(opacity=50);
   position
:absolute;
}

Firefoxのバージョン1.5以下は-moz-opacityも書く必要がありましたが、現在は不要(Firefox3.5では-moz-opacityは廃止済み)です。

CSS ハック

特殊な書き方をすることで、その書き方に対応したブラウザだけにスタイルを適用するバッドノウハウです。こちらもCSS+ハックで検索すれば多くの情報が見つかります。

なるべく使用しないことが望ましい(特にブラウザのバグを利用したハックは安定しないので避けたほうが良い)が、どうしても使わざるを得ないこともあります。

JavaScriptとCSSの連携

CSSの操作

JavaScriptから動的にCSSを操作する方法は大きく分けて2通りあります。1つはCSSの定義自体を操作して、ルールを追加したり削除したり、CSSファイル自体を追加したりする方法です。もう1つはHTML要素に対してclass名やstyleプロパティを操作して個々に操作する方法です。

前者のCSSの定義を操作する方法は予めCSSを静的に読み込んだほうが効率的なので、特に理由がない限りあまり使用されません。

後者はJavaScriptで動的に表示を制御する方法としてよく利用されます。

this.value++;
this.style.backgroundColor='rgb('+this.value+','+this.value+','+this.value+')';

CSSではbackground-color:#ffffff;だが、JavaScriptではbackgroundColor='#ffffff'のように、-を除き、その代わりに続く文字を大文字にする(camelize)点に注意が必要です。

スライドするタブメニューの実装

シンプルなスライド方式のタブメニューを実装してみます。

1
2
3
#menus2 li,
#menus li{
display
:inline;
float:left;
width
:100px;
background
:#eeeeee;
cursor
:pointer;
}
#menus2 li a,
#menus li a{
display
:block;
}
#menus2 li.active,
#menus li.active{
background
:#999999;
}
#main2,
#main{
clear
:left;
width
:400px;
height
:400px;
background
:#bbbbbb;
overflow
:hidden;
position
:relative;
}
#inner2,
#inner{
width
:1200px;
height
:400px;
position
:absolute;
}
#inner2 div.item,
#inner div.item{
float:left;
display
:inline;
width
:400px;
height
:400px;
}
#inner2 .d1,
#inner .d1{
background
:#0099ff;
}
#inner2 .d2,
#inner .d2{
background
:#ff6666;
}
#inner2 .d3,
#inner .d3{
background
:#ffcc33;
}
<ul id="menus">
   
<li id="doc1_" class="active"><a href="#doc1">#1</a></li>
   
<li id="doc2_"><a href="#doc2">#2</a></li>
   
<li id="doc3_"><a href="#doc3">#3</a></li>
</ul>
<div id="main">
   
<div id="inner">
     
<div id="doc1" class="d1 item">
         1
     
</div>
     
<div id="doc2" class="d2 item">
         2
     
</div>
     
<div id="doc3" class="d3 item">
         3
     
</div>
   
</div>
</div>
(function(){
var menu = document.getElementById('menus');
var menus = menu.getElementsByTagName('a');
var inner = document.getElementById('inner');
for (var i = 0; i < menus.length;i++) {
   
(function(i){
      menus
[i].onclick = function(){
         
for (var j = 0; j < menus.length;j++) {
            menus
[j].parentNode.className='';
         
}
         
this.parentNode.className = 'active';
         inner
.style.left = -400 * i + 'px';
         location
.hash = this.hash + '_';
         
return false;
     
};
   
})(i);
   
if (menus[i].hash === location.hash ||
       menus
[i].hash + '_' === location.hash) {
      inner
.style.left = -400 * i + 'px';
   
}
}
})();

仕組みは、外側のdiv(#main)に幅400pxでoverflow:hidden;(つまり、400pxからはみ出た部分は表示されない)を指定して、その内側のdiv(#inner)は幅を1200pxにして、位置を絶対値指定にしておきます。この時点で表示されるのは#innerの中の1つ目のdiv(#d1)です。

タブ切り替え時は押されたボタンの番号に応じて#innerの位置を400pxずらしてやることで、表示されるコンテンツを切り替えています。

1
2
3
(function(){
var menu = document.getElementById('menus2');
var menus = menu.getElementsByTagName('a');
var inner = document.getElementById('inner2');
inner
.style.left = '0px';
for (var i = 0; i < menus.length;i++) {
   
(function(i){
      menus
[i].onclick = function(){
         
for (var j = 0; j < menus.length;j++) {
            menus
[j].parentNode.className='';
         
}
         
this.parentNode.className = 'active';
         
//inner.style.left = -400 * i + 'px';
         
var hash = this.hash;
         
new Tween(inner.style,{
            left
:{to:i * -400,tmpl:'$#px'},
            time
:0.2,
            onComplete
:function(){
               location
.hash = hash + '_';
           
}
         
});
         
return false;
     
};
   
})(i);
   
if (menus[i].hash === location.hash ||
       menus
[i].hash + '_' === location.hash) {
      inner
.style.left = -400 * i + 'px';
   
}
}
})();

おまけで、#innerの位置をアニメーションさせることで、スライドするタブメニューが実現できます。Tween2.jsを使用しています。