前提
代替スタイルシートをJavaScriptで切換可能にしたい。
Firefoxではメニューバー→表示→スタイルシートで代替スタイルへの切換を選択できるが、これをJavaScriptによってページ上部のプルダウン・メニューからも選択可能にし、この機能を実装しないGoogle ChromeやMicrosoft Edge等のFirefox以外のブラウザーでも代替スタイルシートを利用可能にしたい。
ブラウザーの互換性の表によってはOperaも代替スタイルシート対応とするものがあるが、GoogleChrome48がこの機能を削除した2015年11月以降のバージョンでは同じBlink系レンダリング・エンジンであるOperaも非対応となってしまった。Chromeで代替スタイル切替ができる仕組みにすればOperaでも操作可能になるはず。ほかSafariについては、環境がWindowsなので実験できず。
実現したいこと
- フォームのSelectメニューからスタイル名を選択し、submitボタンで切替実行。
- 代替シート名以外に、初期設定(デフォルトの固定スタイルシート)とスタイル解除(プレーンHTMLのみ)も選択肢に出す。
- htmlヘッダーにおけるlink要素で呼び出した外部スタイルシートをHTML仕様書通りに反映させ、指定スタイルの適用順序は、第一に固定スタイルシート(title属性無し。永続スタイルシート)があって、それにtitleありの優先スタイルシートか代替スタイルシートかが上書きされてゆく形にする。
- できれば、Cookieは使用しない方向で。
発生している問題
Firefoxではうまくいったのだが、Google ChromeとMicrosoft EdgeやOperaでは不具合が生じます。
例)link要素に優先スタイルシートが無い場合、リロード直後のページで最初に「代替シート1」を選択→実行してもスタイル表示に変化なく、「スタイル解除」の実行しか有効でない。しかし、その「スタイル解除」の実行後、さらに「初期設定」を選択して実行した後では、「代替シート1」の選択・実行も可能になる。Firefoxならば最初からメニューにある「代替シート1」「スタイル解除」のどれも選択してスタイル変更できた。
ChromeやEdgeやOperaでもFirefox同様の動作をさせるには、プログラムをどう直したらよいのですか。
該当のソースコード
html
1<head> 2<link rel="stylesheet" type="text/css" href="../persistent.css"> 3<!-- <link rel="stylesheet" type="text/css" href="../preferred.css" title="優先シート"> --> 4<link rel="alternate stylesheet" type="text/css" href="../alternate.css" title="代替シート1"> 5<script TYPE="text/javascript" charset="Shift_JIS" src="../SwitchStyle.js"></script> 6</head>
JavaScript
1//** SwitchStyle.js **// 2if (document.styleSheets 3 && !(navigator.userAgent.indexOf("Mac_PowerPC") != -1 4 && navigator.userAgent.indexOf("MSIE 4") != -1)) { 5 main();// 6} 7function main() { 8 sheetTitles=""; 9 if(document.styleSheets){ 10 ChangeStyleAlter(); 11 } 12} 13function ChangeStyleAlter(sheetTitle) { 14 sS=document.getElementsByTagName('link');// document.styleSheetsの代り 15 if(sS) {///* ここでスタイルシートの一覧を取得する */ 16 for( i=0; i<sS.length; i++) 17 { 18 if( sS[i].type!="text/css" )continue; 19 if ( sS[i].title || sS[i].title!="" ) {//titleあり(優先・代替スタイルシートあり) 20 sS[i].disabled = (sS[i].title==sheetTitle) ? false:true;//優先・代替か否か 21 sS[i].disabled = !sS[i].disabled; sS[i].disabled = !sS[i].disabled;//Chrome対策これでよい? 22 } 23 if (sheetTitles.indexOf(sS[i].title)==-1) 24 { 25 sheetTitles+=sS[i].title; 26 sheetTitles+=","; 27 } 28 if (sheetTitles.indexOf(sS[i].title)>-1) var rel = sS[i].rel;//LINK要素のrel属性 29 if (rel == 'stylesheet' && !sS.preferredTitle) sS.preferredTitle = sS[i].title; 30 else if(rel=='alternate stylesheet' && !sS.alternateTitle) sS.alternateTitle = sS[i].title; 31 } 32 if (!sS.preferredTitle) sheetTitles='Default,'+sheetTitles; 33 sheetTitles+="スタイル解除"; 34 sheetTitle=sheetTitles.split(","); 35 } 36} 37function fChangeSS(sheetTitle) {///* スタイルシートの動的切替 */ 38 if(sS) { 39 for(var i=0; i<sS.length; i++) { 40 if( sS[i].type.toLowerCase()!="text/css" )continue; 41 sS[i].disabled = 42 sheetTitle=='NoStyle' ? true : 43 sheetTitle=='Default' ? (sS[i].title!=sS.preferredTitle && sS[i].title) : 44 (sS[i].title!=sheetTitle && sS[i].title); 45 } 46 } 47} 48function DoSelect(sheetTitle) { 49 var sl = document.StyleChangeForm.SEL; 50 sheetTitle = sl.options[sl.selectedIndex].value; 51 fChangeSS(sheetTitle); 52 return false; 53} 54// スタイル変更フォーム(セレクト・メニュー)を出力 55 var select = createSelect(); 56 if (document.styleSheets) 57 { 58 document.writeln('<form action="" name="StyleChangeForm" id="StyleChangeForm" onSubmit="return DoSelect(this);" style="clear:both;">'); 59 document.writeln(select + ' <input type="submit" value="切替">'); 60 document.writeln('</form>'); 61 } 62function createSelect(){ 63 var links = document.all && document.all.tags('link') || document.getElementsByTagName('link'); 64 var titles = new Array(); 65 var select = '<label>スタイル選択:<select name="SEL" id="SEL" size="1">'; 66 select += '<option value="Default"'+ ' style="background:#e5e5ff;">初期設定</option>';//title無し(スタイルシート無し、もしくは固定シート) 67 for(var i=0, sheetTitle; i<links.length; i++) { 68 sheetTitle = links[i].title; 69 if(links[i].type.toLowerCase() == 'text/css' && sheetTitle && !titles[sheetTitle]) { 70 titles[sheetTitle] = true; 71 select += '<option value="' + sheetTitle + '"' 72 + '>' 73 + sheetTitle + '</option>'; 74 } 75 } 76 select += '<option value="NoStyle"' + '>スタイル解除</option>'; 77 select += '</select></label>'; 78 return select; 79}
試したこと
下記を参考にしました。
- スタイルシートの一覧を取得する:いちゆう 「スタイルシート切り替えスクリプト」http://critical.s6.xrea.com/web/cssselect.html
- 固定スタイルシートへ優先or代替スタイルシートを重ね書きする:2ちゃんねる「代替スタイルシートに萌え~」https://mevius.5ch.net/test/read.cgi/hp/991400015/433-439
- Chrome(旧Webkit)対策:炭色地帯「スタイルシートを切り替える」https://www.usamimi.info/~geko/arch_web/02_sample/018/index.html
- 上記の改造:https://mevius.5ch.net/test/read.cgi/hp/991400015/549-550
- スタイル名のリストを取得する際、同じスタイル名が既出である場合(複数シートを一つのスタイル名で括る等)に選択肢への重出を防ぐ:『曉に死す』「CSS切替スクリプト」http://www.akatsukinishisu.net/wiki.cgi?CSS%C0%DA%C2%D8%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8
環境
Windows 10 Home 21H2
Firefix106.0.3
Google Chrome 107.0.5304.88
Microsoft Edge 106.0.1370.52
Opera 92.0.4561.43
Falkon 3.1.0
追記
上掲JavaScriptプログラム中、Chrome対策のif ( sS[i].title || sS[i].title!="" ) {
以下五行を、スタイルシート一覧取得の函数からスタイルシート動的変更の函数へ移すと、ちょっと見には、うまくいったかと見えた。もろもろ修正したその改訂プログラムは下記の通り。
JavaScript
1//** 改訂SwitchStyle.js **// 2if (document.styleSheets 3 && !(navigator.userAgent.indexOf("Mac_PowerPC") != -1 4 && navigator.userAgent.indexOf("MSIE 4") != -1)) { 5 main(); 6} 7function main() { 8 sheetTitles=""; 9 if(document.styleSheets){ 10 setSS(); 11 } 12} 13// ///////////* スタイルシートの一覧を取得する *////// 14function setSS() { 15 sS=document.getElementsByTagName('link');//document.styleSheets;の代り、Chrome対策 16 if(sS) { 17 for(var i=0; i<sS.length; i++) 18 { 19// if(sS[i].type!="text/css")continue;//Chrome対策だがHTML5以降はtype属性省略可 20 if(sS[i].rel.toLowerCase().indexOf("stylesheet")==-1)continue;//Chrome対策改 21 var isSameTitle = false;/// 既に同じスタイル名がある場合は除く 22 for (var j = 0; j < i; j++) { 23 if (sS[j].title == sS[i].title) { 24 isSameTitle = true; 25 break; 26 } 27 } 28 if (!isSameTitle) 29 { 30 sheetTitles+=sS[i].title; 31 sheetTitles+=","; 32 } 33 if (sheetTitles.indexOf(sS[i].title)>-1) 34 var rel = sS[i].getAttribute("rel").toLowerCase();// sS[i].rel.toLowerCase(); 35 if (rel == 'stylesheet' && !sS.preferredTitle) sS.preferredTitle = sS[i].title; 36 else if(/alternate/i.test(rel)/**/ && !sS.alternateTitle) 37 sS.alternateTitle=sS[i].title;//不用? 38 } 39 if (!sS.preferredTitle) sheetTitles='初期設定,'+sheetTitles;// 'Default,' 40 sheetTitles+="スタイル解除";// 'NoStyle' 41 sheetTitle=sheetTitles.split(","); 42 } 43} 44// /////////////* スタイルシートの動的切替 */////////// 45function ChangeStyleAlter(selectedTitle) { 46 if(sS) { 47 for(var i=0; i<sS.length; i++) { 48 if(sS[i].rel.toLowerCase().indexOf("stylesheet")==-1) continue;//Chrome対策改 49 if ( sS[i].title || sS[i].title!="" ) //Chrome対策 50 {// titleがある(優先or代替スタイルシート) 51 sS[i].disabled = (sS[i].title==sheetTitle) ? false:true;//優先か代替か 52 sS[i].disabled = !sS[i].disabled; sS[i].disabled = !sS[i].disabled;//これでよい? 53 } 54 sS[i].disabled = 55 selectedTitle=='スタイル解除' ? true : 56 selectedTitle=='初期設定' ? (sS[i].title!=sS.preferredTitle && sS[i].title) ://優先シート以外無効 57 (sS[i].title!=selectedTitle && sS[i].title);//選択シート以外の代替シート無効 58 } 59 } 60} 61// ///////* ドロップダウン・リストから変更する *////////////// 62function DoSelect() { 63 var sl = document.StyleChangeForm.SEL; 64 selectedTitle = sl.options[sl.selectedIndex].value; 65 ChangeStyleAlter(selectedTitle); 66 return false; 67} 68// スタイル変更フォーム(セレクト・メニュー)を出力 69 var select = createSelect(); 70 if (document.styleSheets) 71 { 72 document.writeln('<form action="" name="StyleChangeForm" id="StyleChangeForm" onSubmit="return DoSelect();" style="clear:both;">'); 73 document.writeln(select + ' <input type="submit" value="切替">'); 74 document.writeln('</form>'); 75 } 76 77function createSelect(){ 78 var select = '<label>スタイル選択:<select name="SEL" id="SEL" size="1">'; 79 for(var i=0; i<sheetTitle.length; i++){ 80 select += '<option value="' + sheetTitle[i] + '"' 81 + (sheetTitle[i] == sS.preferredTitle ? ' selected="selected"' : '')//Firefox不能? 82 + '>' 83 + sheetTitle[i] + '</option>'; 84 } 85 select += '</select></label>'; 86 return select; 87} 88// このファイルが表示された場合、ブラウザーの戻るボタンを押してみて下さい。-->
しかし、このプログラムは下記のごとき優先スタイルシートありの場合に、不具合が生じた。
html
1<head> 2<link rel="stylesheet" type="text/css" href="../persistent.css"><!-- 固定シート --> 3<link rel="stylesheet" type="text/css" href="../preferred.css" title="優先シート(背景色=赤)"> 4<link rel="alternate stylesheet" type="text/css" href="../alternate.css" title="代替シート(文字色=青)"> 5<script TYPE="text/javascript" charset="Shift_JIS" src="../SwitchStyle.js"></script> 6</head>
これで代替シートを選択して実行したら、Firefoxでは固定シートに代替スタイルが重ねられる形でスタイル指定が適用されて問題無かったが、ChromeやEdgeやOperaだと固定シートのみならず優先シートのスタイル指定(上記例では背景色)までもが継承されてしまって、そこに代替シートの指定が上書きされた表示となり、優先/代替の切換(スイッチ)にはなってくれない。但し、そのままプルダウン・メニューで代替シートを選択した状態でsubmitボタンの「切替」をもう一度押し直すと、優先シートのスタイルを切って代替シートのみ適用される。さらにそのまま三度目のsubmitボタンのクリックでは、また優先シートの指定が反映される状態に戻る。
また、リロード直後いったん「スタイル解除」(NoStyle)を選択・実行してから次いで代替シートを選択・実行すると、最初から優先シートのスタイルを引き継がずに代替シートだけが正しく適用される。この場合も、そのままサブミット・ボタンの二度押しをすれば優先シートも有効化するし、三度押し目ではまた優先シートの無効状態になる。
サブミット・ボタンの二度押しをプログラムに仕組めば良いのかとも思ったものの、二重送信を防止するプログラムはサンプルが多々見つかるのに対し、その逆に連続クリックを強制させるプログラムは見当たらない。試しにwindow.onload
イベントでdocument.getElementById('StyleChangeForm').submit();
とかdocument.getElementsByTagName('input').click();
とかやってみたが、作動しないみたいだった。それに、直前に選択されたメニューによって対応を変更せねばならないから場合分け(分岐)がややこしくて手にあまる。
Chrome対策用のsS[i].disabled = !sS[i].disabled;
の二重化が悪さをしたのかもしれないけれど、これを削ってしまったら、改訂前の最初のプログラムと同じく代替シート選択時にまともに動作しなくなる。これ以外にChrome対策の手法を見出せないのだが……もし他にあるのなら御教示戴きたい。
或いは、スタイルシート選択時には一度sS[i].disabled=true;
の値を渡して「スタイル解除」(document.getElementById('SEL').value='NoStyle';
とか?)させてから選択したスタイルシート名の値を実行するといった流れのプログラムを仕込めればいいのかもしれないが、ちゃんと動かすにはどうやればよいのやら……?
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。