スタイルシート切り替え

スタイルシートをJavaScript+DOMで動的に切り替えるスクリプト。Safariに無理やり対応。

概要

HTMLの装飾には大概CSSによって記述されたスタイルシートを用いるわけだが、このスタイルシートを読み込むために記述するlink要素にはdisabled属性というものがあり、これをJavaScriptから操作することでスタイルシートの有効/無効を切り替えることができる。スタイルシートの切り替えの方法としてはスタイルシートオブジェクトのdisabledプロパティを弄って切り替えるといった方法もあるが、Safariはこれには対応してないようなので今回link要素のほうを弄ることにした。

大まかな動作を示しておく。

  1. DOMでlink要素を動的生成
  2. cookieに記述しておいた情報から(ないときはデフォルト設定)設定したいスタイルを決める
  3. link要素のdisabled属性を弄る。設定したいスタイルを読み込むlink要素だけdisabled=false、それ以外のlink要素はdisabled=trueに設定。
  4. select要素を埋め込んでスタイルを切り替えるようにする。onchangeイベントでlink要素のdisabled属性を弄る→cookieを書き換える。

ざっとこんな感じ。やってることは案外単純だったりする。・・・まぁそんなもんだよな。

スクリプト

もってけ!

別にGPLライセンスとか主張しませんから。n次配布(n∈N,n≧2)OKです。著作権表記とかいらんです。改変OKです。

styleChange.js

/***************************
| styleChange.js
| last update 2008/6/26
+***************************/
var isOpera = (window.opera);
var isSafari = navigator.userAgent.match("Safari");

var sc = {};
sc.styles = new Array();
sc.current ="";

sc.cssRoot = "/~ide/css/";
//スタイルシートを参照するときの基準パス.
//ここを直接書き換えるかあとから値を上書きするかはご自由に。

sc.init = function(){
  for(var i=0;i<sc.styles.length;i++){
    sc.setLink( sc.styles[i] );
  }

  var current = readCookie("style");
  if(!current||current=="")current = "default";

  if( isOpera ) sc.setStyle( "default" );//Opera対策。
  sc.setStyle( current );
}

sc.onload = function(){
  sc.setSelectBox();
  
  if(isSafari){//Safari対策用。
    var slt = document.getElementById("slt");
    var cur = slt.selectedIndex;
    slt.focus();
    slt.selectedIndex = (cur+1)%slt.options.length;
    slt.blur();//一時的に違うスタイルを設定
    slt.focus();
    slt.selectedIndex = cur;
    slt.blur();//本来設定したかったスタイルを設定
  }
}

sc.setLink = function( style ){
  var l = document.createElement('link');
  l.rel = "stylesheet";
  l.type = "text/css";
  l.href = sc.cssRoot + style + "/main.css";
  //cssのファイル名はmain.cssに固定してある。
  //変えたい人は適宜改変を。
  l.title = style;
  var hd = document.getElementsByTagName('head')[0];
  hd.appendChild(l);
}

sc.add = function(style){
  sc.styles[sc.styles.length] = style;
}

sc.setStyle = function( current ){//スタイル変更
  var ls = document.getElementsByTagName('link');
  for(var i=0;i<ls.length;i++){
    ls[i].disabled = !( ls[i].title == current );
  }
  sc.current = current;
  writeCookie( "style", current );
}

sc.setSelectBox = function(){
  var sp = document.createElement('p');
  sp.className = "style_select";
  sp.appendChild( document.createTextNode('css') );
  var sl = document.createElement('select');
  sl.id = "slt";
  for(var i=0;i<sc.styles.length;i++){
    var op = document.createElement('option');
    op.value = sc.styles[i];
    op.appendChild( document.createTextNode(sc.styles[i]) );
    sl.appendChild( op );
    if( sc.styles[i] == sc.current ){
      sl.selectedIndex = i;
    }
  }
  setEvent( sl, "change", selectStyle );
  sp.appendChild( sl );
  var bd = document.getElementsByTagName('body')[0];
  bd.appendChild( sp );
}

function selectStyle()//selectBoxで別のスタイルを選択すると呼び出される。
{
    var slt = document.getElementById("slt");
    var SelectMenu = slt.options[slt.selectedIndex].value;
    if( SelectMenu )
    {
        sc.setStyle( SelectMenu );
    }
}

function readCookie( property )
{
  var propLen = property.length + 1;
  var rCookie = document.cookie;
  var startPos = rCookie.indexOf( property );
  var endPos;
  
  if( document.cookie.length==0 || startPos==-1 )return null;
  startPos += propLen;
  endPos = rCookie.indexOf( ";", startPos );
  if( endPos==-1 )endPos = document.cookie.length;
  return rCookie.substring( startPos, endPos );
}

function writeCookie( property, value)
{
  var expire = new Date();
  expire.setTime( expire.getTime() + ( 90*24*60*60*1000 ) );
  document.cookie = property + "=" + value + "; expires=" + expire.toGMTString() + "; path=/";//expireとかpathとかは適宜弄ってください。
}

function setEvent( obj,eType,func )//炭色地帯から拝借。
{
	if( obj.addEventListener )obj.addEventListener( eType,func,false );
	else if( obj.attachEvent )obj.attachEvent( 'on'+eType,func );
		else obj[ 'on'+eType ] = func;
}

このスクリプトは以下のようなディレクトリ構成を基準としている。一つのスタイルにつき一つのディレクトリをあてがい、ディレクトリ名=スタイルの名称となっている。自分のサイト(及びPC部のサイト?)で使うことを想定していたのでこういった仕様になっているが、スタイルシートのパスをsc.add()の引数として渡せる方が汎用的だろうな。・・・まぁやりたい人は頑張って改変して。

~ide
  +css
  . +default
  . |   +main.css
  . |
    +example01
    |   +main.css
    |
    +example02
    |   +main.css
    .
    .
    .

使い方

  1. あらかじめstyleChange.jsのsc.cssRootにCSS参照時の基準パスを設定しておく。
  2. デフォルトで参照させたいスタイルシートを読み込ませる。
  3. styleChange.jsを読み込ませる。
  4. sc.add()でスタイルを追加。
  5. sc.init()を呼ぶ。
  6. onloadイベントの処理でsc.onload()を呼ぶ。

以上の通りです。

使用例

example.html(head部)

<head>
・・・前略・・・
  <link rel="stylesheet" href="/~ide/css/default/main.css">
  <script type="text/javascript" src="styleChange.js"></script>
  <script type="text/javasctipt" src="example.js"></script>
・・・後略・・・
</head>

example.js

sc.cssRoot="css/";
sc.add("default");
sc.add("example01");
sc.add("example02");
sc.init();

onload = function(){
  sc.onload();
}

これでdefault、example01、example02の3種類のスタイルを選択できるようになった。(この例ではdefault=exampleに設定している。リンクの先のCSSを参照してくれれば分かる。)

余談

このスクリプトを改変したい人とか向けの事項とか。そんなの知ったこっちゃねーやって人は読んでもつまらんと思います。

一つのスタイルについて複数のCSSファイルを参照する場合

たとえば通常のコンテンツでは通常のページ用のスタイルシートを参照するが、トップページではこれに加えてトップページ用のスタイルシートも参照するといった場合。今現在(2008年6月)のいで庵もこうなっているのだが、その場合の改変法を示しておく。

sc.setLink = function( style ){
  var l = document.createElement('link');
  l.rel = "stylesheet";
  l.type = "text/css";
  l.href = sc.cssRoot + style + "/main.css";
  l.title = style;
  var hd = document.getElementsByTagName('head')[0];
  hd.appendChild(l);
  if(isTopPage){
    var l2 = document.createElement('link');
    l2.rel = "stylesheet";
    l2.type = "text/css";
    l2.href = sc.cssRoot + style + "/top.css";
    l2.title = style;
    hd.appendChild(l2);
  }
}

一応改変するのはsc.setLink()だけでおk。isTopPageはあらかじめlocationオブジェクトでも弄ってトップページかどうかの判定をした真偽値を放り込んでおく。用はlink要素を一個増やしているだけ。title要素に同じ値を使っておくのがポイント。title要素が同じlink要素を複数用意すれば一度に複数のスタイルシートを適用できる。link要素を加える順番がスタイルシートの適用順位に影響するんでその辺は気をつけて。

ブラウザ絡み

このスクリプトではOpera向けの対策処理とSafari向けの対策処理がしてある。

まずOperaについて。ページが読み込まれたときはもともとHTMLで参照するようにしてあるスタイルシートしか参照できない。なのでいったんスタイルの切り替えでデフォルトのスタイルシートに切り替えた後、設定したいスタイルに設定している。

次にSafariについて。どういう症状があったかというと、ページ読み込み時にスタイルが適用されない。これだけならOperaと似たような症状なのだが、問題はOpera対策のような方法も、スタイルの設定をするタイミングを変える方法も通用しなかったことだった。スタイルを変更する機能そのものはちゃんと機能するので問題はスクリプトの実行するタイミングにあるはず、と思ったがどうもそうはいかないようで。Safariのwindow.onloadは実行されるタイミングが他のブラウザと異なり、表示系が完了していない段階で呼び出されてしまう。これと関連があるのかどうだか分からないが、このタイミングでスタイルの設定をしてもスタイルが適用されない。だが、手動で(セレクトボックスから)スタイルを切り替えるときちんと機能する・・・

結果的にたどり着いた対策。手動操作を真似てJavaScriptからセレクトボックスを弄る。遠回りだし、綺麗じゃないし・・・美意識を捨てて実用性を取るといった選択。実用性もどうかと思うが。

var slt = document.getElementById("slt");//セレクトボックスのハンドル取得
var cur = slt.selectedIndex;//現在選択しているオプションの取得
slt.focus();
slt.selectedIndex = (cur+1)%slt.options.length;
slt.blur();//一時的に違うスタイルを設定
slt.focus();
slt.selectedIndex = cur;
slt.blur();//本来設定したかったスタイルを設定

セレクトボックスにフォーカス移して別のスタイルを選択してフォーカスはずしたあと、またフォーカス戻して設定したかったスタイルに切り替えてフォーカスをはずすという手動動作をJSに落とし込んでみたらまぁうまくいったという訳。この処理はonloadイベント発生時に呼び出してるが、Safariは表示系の完了を待たずにonloadイベントを起こすようなので、スタイルが切り替わるまでのタイムラグはそんなにない(はず・・・)。

やたら苦労した挙句、汚ねぇコードになっているがこれ以外方法がなかったんだ(たぶん)。これ以外にもっとクリーンな方法でSafari対策できた人はネットに広く公開したほうがいいです。私が知りたいので(私だけじゃないですよ!結構知りたがっている人はいる・・・はず)。