小説サイトのJavaScript Chips

第5回 折りたたみメニューの表示

 よくお問い合わせを頂くご質問に、「長編小説のメニューをどのように表示しているのか」というものがあります。
 第5回はこれをご紹介したいと思います。

 左は、その表示サンプルです。

 表示された状態での特徴、その一番は「折りたたみ」
 話タイトル(第○夜)をクリックすると、章タイトル(「物語のはじまり」など)が表示されます。
 章タイトルをクリックすると、そのページへ。
 そして話タイトルの下に、次回予告欄、バナー、資料集、他のページへのナビと続きます。

*こちらのスクリプトを記述するにあたり、「from DFJ」さんの「折りたたみメニュー」を参考にいたしました。
 メンテナンス(項目の追加)を容易にすることを主眼に変更を加えていますが、動作の基本となる部分は同じです。
 詳解はリンク先をご覧くださいませ。
 簡単にご説明すると、話タイトルをひとつのdivision(div)とし、その中に章タイトルのdivisionを作成する。章タイトルdivisionの表示をcss(display:none;)で切換えている、という感じ……たぶん (^^ゞ

以下はそのスクリプトソースです。


var num_chapter = 0;
var num_spec = 0;
var num_dl = 0;
var num_link = 0;
var num_lastest = 0;

var Chapters = "";
var Previews = "";
var PreviewsMessage = "";
var Specs = "";
var Dls = "";
var Banner = "";
var Links = "";

//----------------------------- 入力エリア ------------------------------------------
//小説タイトルと副題//
var Title = "風紋";
var SubTitle = "-風は史書に語られぬものを謳う-";

//章番号と章タイトル//
set_chapters("",    "第一夜");
set_chapters("st_00.html",    "物語のはじまり -フィル・シンと祖父-");
set_chapters("st_01.html",    "母后ファ・シィン -ランガの守護-");
set_chapters("st_02.html",    "シェル・カン宮 -ガライダルの宝珠-");
set_chapters("st_03.html",    "サキス・カーラ -その名の重さ-");
set_chapters("",    "第二夜");
set_chapters("st_04.html",    "花傑伝 -もう一人の娘-");
set_chapters("st_05.html",    "凝れる花 -匂いたつ死の息吹-");
set_chapters("st_06.html",    "真実の糸 -時の綾なす-");
set_chapters("st_07.html",    "煉獄 -暁闇を抱きて-");
set_chapters("",    "第三夜");
set_chapters("st_08.html",    "光照らさばまた深く -獅子の影-");
set_chapters("st_09.html",    "東へとたなびく -その槍がすべて-");
set_chapters("st_10.html",    "死を守る翼 -風に消えゆく-");
set_chapters("st_11.html",    "花を愁いては -時は流砂のように-");
set_chapters("",    "第四夜");
set_chapters("st_12.html",    "我が翼は鋼の刃 -ハルギス-");
set_chapters("st_13.html",    "その強き翼を以て -声なき祈り-");
set_chapters("st_14.html",    "天を掴む -蠢く影-");
set_chapters("st_15.html",    "そは辰星のごとくに -花将-");
set_chapters("",    "第五夜");
set_chapters("st_16.html",    "砂に眠る --");
set_chapters("st_17.html",    "時のはざま -物語のように- ");
set_chapters("st_18.html",    "花蔦紋 -- ");

// 予告//
var DoYouUploadPreviews = "yes";
var UpLoadDate = "                     次回6/1";
var PRmessage = "更新予定";
set_previews("",    "次回予告");
set_previews("dummy.html",    "天を掴む -蠢く影-
「悪夢」と語られるその光景。玉石と歌われた城を襲った災禍とは。"); //バナー// set_banner("st.html","bn.gif"); //設定資料など// set_spec("", "資料集"); set_spec("st_biography.html", "人名辞典"); set_spec("st_ancestry.html", "血統図"); set_spec("st_chronology.html", "年表"); set_spec("st_note.html", "フィル=シンの覚書"); //ダウンロードファイル// set_dl("", "Download"); set_dl("../dl/st/st.lzh", "一括ダウンロード"); set_dl("../dl/st/st1.lzh", "第一夜"); set_dl("../dl/st/st2.lzh", "第二夜"); set_dl("../dl/st/st3.lzh", "第三夜"); set_dl("../dl/st/st3.lzh", "第四夜"); //リンク設定 set_link("http://www.hanaakari.jp/bbs2/index.cgi", "掲示板"); set_link("http://www.hanaakari.jp/mail/form.html", "書簡"); set_link("../index.html", "繍花楼"); //-------------------------------- 入力エリア終了 ----------------------------------- function menudisplay(id){ if(document.all)OBJ = document.all(id).style; else if(document.getElementById)OBJ = document.getElementById(id).style; if(OBJ) OBJ.display=='none'?OBJ.display='':OBJ.display='none'; } function set_chapters(str1, str2) { if (str1 == "") { number = new String(num_chapter); var temp = "fo" + number + "0"; if (num_chapter != 0) Chapters += "</p></div>"; Chapters += "<h2 class='mnbr'><a href=javascript:void(0) onclick=menudisplay('"; Chapters += temp; Chapters += "')>"; Chapters += str2; Chapters += "</a></h2>"; Chapters += "<div style='display:none' id='"; Chapters += temp; Chapters += "'>"; Chapters += "<p class='menu'>"; num_chapter++; } else { Chapters += "<a href='"; Chapters += str1; Chapters += "'>"; Chapters += str2; Chapters += "</a><br>"; } num_lastest = num_chapter; } function set_previews(str1, str2) { if (str1 == "") { number = new String(num_chapter); var temp = "fo" + number + "0"; if (num_chapter != num_lastest) Previews += "</p></div>"; Previews += "<hr><p class='menu'><a href=javascript:void(0) onclick=menudisplay('"; Previews += temp; Previews += "')>"; Previews += str2; Previews += "</a></p>"; Previews += "<div style='display:none' id='"; Previews += temp; Previews += "'>"; Previews += "<p class='menu'><span class='pvw'>"; num_chapter++; } else { Previews += str2; Previews += "<br>"; } } PreviewsMessage = UpLoadDate + PRmessage + "</span><br>"; + "</p></div>"; function set_banner(str1, str2) { Banner += "<hr><a href='"; Banner += str1; Banner += "'><img src='"; Banner += str2; Banner += "' /></a><hr>"; } function set_spec(str1, str2) { if (str1 == "") { Specs += "<h2 class='mnbr2'><a href=javascript:void(0) onclick=menudisplay('fospec')>" Specs += str2; Specs += "</a></h2>"; Specs += "<div style='display:none' id='fospec'>"; Specs += "<p class='menu'>"; } else { Specs += "<a href='"; Specs += str1; Specs += "'>"; Specs += str2; Specs += "</a><br>"; } } function set_dl(str1, str2) { if (str1 == "") { Dls += "<h2 class='mnbr2'><a href=javascript:void(0) onclick=menudisplay('fodl')>" Dls += str2; Dls += "</a></h2>"; Dls += "<div style='display:none' id='fodl'>"; Dls += "<p class='menu'>"; } else { Dls += "<a href='"; Dls += str1; Dls += "'>"; Dls += str2; Dls += "</a><br>"; } } function set_link(str1, str2) { if (num_link != 0) Links += "</a> / " Links += "<a href='"; Links += str1; Links += "'>"; Links += str2; Links += "</a>" num_link++; } if (DoYouUploadPreviews == "yes" { var MenuList = "<div class='menu'>" + "<h1>" + Title + "<br>" + "<span style='font-size:10pt'>" + SubTitle + "</span></h1>" + "<div id='fo0'>" + Chapters + " <span>最新章</span><br>" + "</p></div>" + Previews + PreviewsMessage + "</p></div>" + Banner + Specs + "</p></div>" + Dls + "テキスト版のみ。html版および各話ごとのダウンロードは" + "<a href='../dl/download.html#st'>Downloadページ</a>へ" + "</p></div>" + "<hr><p class='menu'>" + Links + "</p>" + "</div>" + "</div>" } else { var MenuList = "<div class='menu'>" + "<h1>" + Title + "<br>" + "<span style='font-size:10pt'>" + SubTitle + "</span></h1>" + "<div id='fo0'>" + Chapters + " <span>最新章</span><br>" + "</p></div>" + Banner + Specs + "</p></div>" + Dls + "テキスト版のみ。html版および各話ごとのダウンロードは" + "<a href='../dl/download.html#st'>Downloadページ</a>へ" + "</p></div>" + "<hr><p class='menu'>" + Links + "</p>" + "</div>" + "</div>" } document.write(MenuList);

 入力エリアを適宜書き換えていただければ、動作します。
 cssは以下のように指定しています。お好みでご変更ください。

div.menu  {background-color:#cccccc; width:220px;
           margin:0 20px 0 0; padding:5px; float:left;
           border:1px solid #666666;}                            //メニュー領域
h2.mnbr   {margin:10px 0px 5px 0px;}                             //話タイトル
h2.mnbr2  {font-size:10pt/1; margin:10px 0 5px 0;}               //ダウンロードや資料集
.menu     {font-size:10pt; line-height:1.2; margin:1px 0 0 5px;} //章タイトル

 欠点は、JavaScript未対応ブラウザやブラウザでJavaScriptを無効に設定している場合、目次が表示されないことです。
 しかし、<noscript></noscript>で前後のページと目次へのリンクを記述することで、この欠点はカバーできると思います。
 また、いらしてくださる方の環境しだいでは、JavaScriptだけでもなんら問題はないでしょう。
 では、小説目次を外部jsで表記する利点をあげてみましょう。

  1. 【更新が簡単】
    ページごとに目次をhtmlで記述していると、新しいページを追加するたびにすべてのファイルにリンク先を記述しなくてはなりません。 さらに、プロットの変更でエピソードの順番を入れ替えたり、追加したりしようものなら、以降すべてのファイルのリンクを書き換えるハメにもなりかねません。
    これを回避しようと思うと、ファイルのナンバリングがメチャクチャに。こんな感じ↓
    00、01、02、03...10、14、11、15、12、16、13、17、18、19、25、20、21、22、23、24、26、27、30、28、29、31...
    こうなると、もう、自分でも何がなんだかわからなくなり、リンク先を間違えるもの必至です。
    これを回避しようと思うと、とあるファイルだけ40KB超とかね……。
    ファイル数が10くらいならまだ大丈夫でしょうが、20を超える頃から目次を更新する作業が本当にイヤになってきます。
    一括変換するのも、面倒くさいくらいにページが増える……長編小説の宿命です。
    外部jsで記述すると、これを回避することができるのです。
  2. 【フレームの指定がいらない】
    目次と本文にフレーム分割をしても、同じこと、だと思われるかもしれません。
    しかし、ついうっかりリンクターゲットを指定し忘れると。
    「目次が表示されなくなっちゃった(target="_top")」
    「目次フレームに本文が表示されちゃった」という事態が発生します。
    外部jsなら、これを回避できます。
  3. 【テキストファイルへの変換も簡単】
    わたしは小説を直接htmlで書いてしまいます。そこから一切のタグを消去することで、ダウンロード用のテキストファイルを作成します。なぜなら、
    テキストで書く → フリーソフトでhtmlに変換する → cssのclass分け等に未対応 → 結局手修正
    明らかに二度手間です。
    それよりも、最初にhtmlで書いて、その後ワイルドカードを利用して<*>を一括削除してしまうと、まあ、素敵。あっという間にテキストファイルの出来上がり。
    htmlで記述する → <*>を一括削除、別名で保存 → ダウンロードファイル完成
    この流れなら、改稿も怖くありません。(それはどうよ、とも思うのですが、本来webというものは、書籍とは異なり配信内容が流動的であることを前提としている媒体ですから、改訂、改稿が難しいようでは問題なのです)

 などです。
 では、次回はこの折りたたみメニューとナビゲーションを一括管理するスクリプトのご紹介を。