W3J.org

Feature Articles

我流のカスタマイズはもう卒業 プロが教えるMovable Typeの構造デザイン[元原稿]


Movable Typeテンプレートの「正しい」カスタマイズ教えます

CSSだけでどうにでも!

MovableType(以下、MT)で表示されるいろいろなページは、どれもこれもテンプレートによってその内容や見栄えが制御されているということは、もはや周知の事実でしょう。XHTMLファイルになっている部分だけでなく、CSSファイルやRDF/RSSによるメタデータファイルも、やはりテンプレートから生成されています。

MTはコンテンツマネジメントシステムですから、テンプレートの内容は気にしなくとも、エントリーを追加していけさえすれば一定の構造と見栄えを備えたページが生成されていきます。ウェブログはやはりコンテンツありきとはいえども、やはりその見栄えも気になるところでしょう。MainIndex.tmplのようなMT独自のテンプレートタグを含むテンプレートを直接いじるのではなく、StyleSheet.tmplのみをいじるだけでも、その見栄えを大きく変更することが可能です。StyleSheet.tmplの内容は単なるCSSです。他のテンプレートのように、ちょっぴりプログラミング言語っぽいというか、MTタグの書き間違いをしてリビルドに失敗するといった心配がありません。CSSは、XHTMLによって意味づけされた文書構造の外観を定義するものです。どのようなStyleSheet.tmplにしても、MainIndex.tmplなどが生成するページの構成要素は変わらないということです。

つまり、たとえばウェブログサイトの顔ともいえるMainIndex.tmplのページの構成要素が標準の状態のままで良いのであれば、あとはStyleSheet.tmplだけの変更で、その見栄えをどうにでもできるということなのです。

【図】MTデフォルトテンプレートでのデザインイメージ)[109KB]

あなたのテンプレートは「正しい」?

皆さんも、MTのデザインを変更しようと思って、「面倒くさい」だとか「難しい」だとか感じたことはありませんか。僕は前述のとおりのことを考えていたので、StyleSheet.tmplだけをいじれば良いと、つまりはCSSでのデザインやレイアウトというものがわかってさえいれば自由自在だと思っていたのです。しかし、実際のところ、StyleSheet.tmplの変更だけでは設計どおりのデザインにはならず、もどかしい思いをすることになりました。

さて、これはどういうことなのでしょう。なんといっても大前提として、CSSはまず文書構造ありきなのです。よろしくない構造になっている文書ではなく、正しいとか妥当とかいう表現の似合う文書構造が求められるわけです。あくまで構造の外観しか定義できないCSSでは、MainIndex.tmplが生成する文書構造を正しく直すことができません。うまくいかない原因として疑うべきはStyleSheet.tmplではなく、むしろMainIndex.tmplのほうにしたほうが良さそうです。

というわけで、標準のMainIndex.tmplが生成する文書構造を、まあこんなものだろうという辺りまでの粗さで図にしてみました。

とりあえず正直な感想レベルでいいますと、「目も当てられない状態」という感じです。これをどうにか良いものにしなければなりませんから、まずは現状把握が大事です。どこがよろしくないのか、ざーっと挙げていきましょう。

【図】標準のMainIndexの文書構造イメージ[Excelワークシート(22KB)]

div要素の階層構造がよろしくない

div id=banner要素を見てみましょう。このdiv要素の中には、ウェブログの名前であるh1要素と、ウェブログの説明がspan class=description要素として入っています。ウェブログの名前はそのページ全体に影響を及ぼしている一番大きな見出しですから、h1要素なのは問題ありません。しかし、となるとdiv id=banner要素が、ウェブログの名前と説明だけを内包したところで終了してしまうのはおかしなことになってきます。h1要素を大見出しだとすると、h2要素は中見出し、h3要素は小見出しです。これは親子関係であると考えられ、たとえば部・章・節の各見出しのような関係だといえます。div id=content要素内にあるh2要素やh3要素は、それぞれ確かに見出しであると考えられますが、h1要素とブロックレベルでの構造を違えてしまっています。親要素のbody要素から見たとき、h1要素を内包するdiv id=banner要素とh2/h3要素を内包するdiv id=content要素とが、並列の関係になってしまっているわけです。これは、あまり良い関係だとはいえません。

div id=content要素の直下にdiv class=blog要素があるのも変ですね。だいたい、div id=content要素の中にはdiv class=blog要素以外のものが何も無いのですから、このどちらかのdiv要素は余計なブロックレベル要素であるといえます。

最後にdiv id=links要素ですが、ここが特にひどい感じです。たとえば検索フォームの入力フィールドには、標準では「Search」という文字列(入力フィールドに対する見出しのようなもの)がその上に表示されますが、その文字列はsidetitleというクラス名を備えたdiv要素で意味づけされているのです。入力フィールドはsideというクラス名を備えたdiv要素として意味づけされています。つまり、sideクラスの内容に対する見出しのようなものだから、sidetitleクラスだというわけなのでしょう。このふたつのdiv要素はどうやらセットのようですが、構造上は並列扱いであってセットだという定義はなされていません。想定上セットのこの組み合わせは、続いて月別アーカイブ一覧である「Archives」のところ、最近のエントリー一覧の「Recent Entries」のところ、そして好きなリンクを追加してね(Add Your Links)というリンク一覧のところです。上からこの順に並ぶように表示することしか頭にないのならともかく、たとえば月別アーカイブ一覧のタイトルと内容をほかのところへ表示したい時などは、ふたつのブロックレベル要素をそれぞれ動かさなければなりません。それに、タイトルと内容が全然違う場所に配置されるということはちょっと無いかと思いますので、そもそもこの対のセットは、対なことが明確な要素として意味づけしてあげれば良いのです。

きちんと意味づけされていない

div要素というのは、特に何だという意味づけであるという定義がなされていない、汎用的なブロックレベルの要素です。これの使いどころはというと、XHTMLが備えている各要素では、うまく意味づけしきれないという場合や、要素間の結びつきを明示してやろうという場合などが考えられます。そして汎用的なインライン要素としてspan要素というものもあります。こちらはブロックレベルではないために、要素間の結びつきを明示することにはあまり使えません。存在しうる要素でいえばp要素なんだけど、もっと何か意味づけしておかなきゃと思ったときなんかに、p要素中の該当箇所でspan要素を使ったりします。

ですから、div id=banner要素の中にあるウェブログの説明の部分が、span class=description要素だというのはいただけません。ウェブログの説明はひとつの段落を成すものと考えられますから、ブロックレベルの要素であるp要素とするのが順当です。

div class=blog要素には、h2要素とdiv class=blogbody要素が入っており、div class=blogbody要素にはh3/p/span class=extended/div class=postedという各要素が入っています。div class=blog要素とその内容までは良い感じですが、div class=blogbody要素の中身の、後半ふたつはあまり良くない感じです。span class=extended要素の内容は「Continue reading エントリーの題名」という文章になるようですが、これはp要素であることが明白でしょう(少なくともspan要素ではない)。div class=posted要素も、やはりその内容がdiv要素という意味づけであるとは、胸を張っていえない状態ではないかという感じです。

そして、div id=links要素のところですが、ここはdiv要素の構造がよろしくないだけでなく、内包している殆どのデータをそのままdiv要素でもって意味づけしてしまっています。XHTMLで定義された各要素では按配が悪いというのならともかく、定義済みの要素で適したものがあるのならば、それらで意味づけすることで、要素を適切に明示できます。困ったときのdiv要素、というのもアリではありますが、困ってないならdiv要素だけで意味づけしちゃうのはよろしくないのです。div class=sidetitle要素とdiv class=side要素の対などは、たとえばdl要素が使えるのではないかと考えられます。

どう直したら「正しく」なる?

簡単にいえば、前段にて述べたところを直せば概ね正しくなります。div要素をはじめとするブロックレベル要素の階層構造に、もっと一貫性をもたせるわけですね。それと、div要素やspan要素で意味づけするという横着をせずに、可能な限りXHTMLで定義済みの各要素で意味づけしていけばOKです。

さらに、標準のMainIndex.tmplでは、たとえばXML宣言が無いだとかいう、そもそもXHTMLの文法的にちょっとどうかというところもありますので、その辺も合わせて直していくのも、どうせ直すのだったらという感じでやってしまいたいところです。

というわけで修正後の要素の階層構造のイメージ図を用意したので、ひとまず見てください。

【図】修正後の階層構造イメージ[Excelワークシート(16KB)]

これは、だいたいの完成予想図だと思ってください。

まず、ページ全体に影響力のあるh1要素の大見出しと、それに続く内容をdiv class=heading1要素という大きなブロックレベル要素で括り、h1要素を大見出しとする内容なのだということを明確にしています。中見出しのh2要素から始まるブロック、小見出しのh3要素から始まるブロックは、順にネストしたブロックレベル要素という関係性となります。div class=heading2要素以下は、ようするに各エントリーが相当します。エントリーは複数ありますので、これらをまとめてdiv id=entries要素で囲うことにより、エントリー群であることをより明確にすると良さそうです。div id=utilities要素が内包する各要素の具体例は、イメージ図からはひとまず省略していますが、カレンダーや月別アーカイブ一覧といった、明らかにページの内容を構成する要素だけども、伝えたいコンテンツとしての主体ではなく、むしろ機能提供をしているような要素たちをdiv id=utilities要素でまとめます。div id=entries要素とdiv id=utilities要素は、div class=heading1要素の中で、h1要素とまったく対等の位置づけとなっていますが、これはh1要素の大見出しはページ全体に影響をおよぼすものだから、各div要素の中に入る要素はh1要素より下の階層に位置づけられるであろうという見込みのためです。一方、div id=footer要素はdiv class=heading1要素と同じ階層にあります。div id=footer要素には、XHTMLでいうとaddress要素のようなものが入るようなブロックとして用意してあります。このような階層のブロックは、ページ全体に影響を及ぼす大見出しのような要素の下に紐づくデータではなく、むしろサイト全体に一貫して挿入される、たとえば作者名などといったデータを入れるのに適しているといえます。

id属性とclass属性の使い分け

ページ内にその属性値が一度しか出てこないならid属性を、何度も出てくるのならclass属性を使うというような説明をよく目にするかもしれません。しかしこの説明は間違いでこそないものの、適切なものであるとはいえないでしょう。

id属性は、その要素に固有の名前を与えるために使います。class属性は、その要素がどういった属性というか分類なのかというような区別のために使われるものです。たとえば「森田 雄」という、これは僕の名前ですがまさにid属性的なものですね。そして「森田 雄」というidを持つ僕は、たとえば「サラリーマン」だとかいうclass属性的な値をもっていたりもするのです。この名前をh1要素にする場合があったとしたら、前者は「<h1 id="MoritaYu">森田 雄</h1>」となり、後者は「<h1 class="salaryman">森田 雄</h1>」だとかいう感じになります。

ちなみに、ひとつの要素に対してid属性とclass属性を併用することもできます。一度なのか何度もなのかということだったら、何らかの値がそのページ内に出てくる回数によってどちらの属性にするかという話になってしまいますから、併用するという発想が出てこないかもしれません。しかし固有の名前を持ちつつ何らかの分類に属する要素というのはありえますし、そしてそのように記述できるのです。さっきの例でいえば「<h1 id="MoritaYu" class="salaryman">森田 雄</h1>」になるということですね。

また、CSS的な側面からいうと、id属性やclass属性はセレクタとしての重要な役割を担っています。このとき、id属性のほうがスタイル適用の優先順位が高くなる特徴もあります。最終的な優先順位の決定は、文脈セレクタや!importantキーワードなどがいろいろ絡んでくると、その計算式が煩雑になってきて、id属性のほうが絶対的にclass属性よりも強い結果になるのかというと、いやそうでもないという事例も出てくるかもしれません。しかし現状のブラウザ実装を鑑みれば、というよりもシェアが広いとされているWinIE6などの実装状況からすると、そもそも複雑な文脈セレクタを記述したCSSを用意できないという理屈もあります。したがって、だいたいid属性が絡んだセレクタのほうが優先順位が高いよねと考えておいても、あまり問題なかったりするでしょう。

というわけで直しました

もうウンチクはたくさん、早く「正しく」直したMainIndex.tmplを見せてよという、そろそろそういう頃合でしょうから、先に新生MainIndex.tmplの全貌を明かします。今まで述べてきたことすべてを踏まえた結果、このようになりました。

<?xml version="1.0" encoding="<$MTPublishCharset$>" ?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja" dir="ltr">

<head profile="http://purl.org/net/uriprofile/">

<meta http-equiv="Content-Type" content="text/html;charset=<$MTPublishCharset$>" />

<meta http-equiv="Content-Style-Type" content="text/css" />

<meta http-equiv="Content-Script-Type" content="text/javascript" />

<meta http-equiv="imagetoolbar" content="no" />

<title><$MTBlogName$></title>

<link rel="stylesheet" type="text/css" href="<$MTBlogURL$>styles-site.css" media="screen,tv" />

<link rel="alternate meta" type="application/rss+xml" title="RSS" href="<$MTBlogURL$>index.rdf" />

<link rel="EditURI" type="application/rsd+xml" title="RSD" href="<$MTBlogURL$>rsd.xml" />

<link rel="Contents Start" href="<$MTBlogURL$>" title="<$MTBlogName$> - MainIndex" />

<script type="text/javascript">

function OpenComments (c) {

 window.open(c,'comments','width=480,height=480,scrollbars=yes,status=yes');

}

function OpenTrackback (c) {

 window.open(c,'trackback','width=480,height=480,scrollbars=yes,status=yes');

}

</script>

<MTBlogIfCCLicense>

<$MTCCLicenseRDF$>

</MTBlogIfCCLicense>

</head>

<body>

<div class="mtHeading1">

<h1><$MTBlogName$></h1>

<p><$MTBlogDescription$></p>

<div id="mtEntries">

<MTEntries>

<$MTEntryTrackbackData$>

<MTDateHeader>

<div class="mtHeading2">

<h2><$MTEntryDate format="%x"$></h2>

</MTDateHeader>

<div class="mtHeading3">

<h3><a name="<$MTEntryID pad="1"$>" id="<$MTEntryID pad="1"$>"><$MTEntryTitle$></a></h3>

<div class="mtEntryBody">

<$MTEntryBody$>

<MTEntryIfExtended>

<p class="mtExtended"><a href="<$MTEntryPermalink$>#more">Continue reading "<$MTEntryTitle$>"</a></p>

</MTEntryIfExtended>

<!--/mtEntryBody--></div>

<div class="mtEntryFooter">

<ul>

<li class="mtPosted">Posted by <$MTEntryAuthor$> at <a href="<$MTEntryPermalink$>"><$MTEntryDate format="%X"$></a></li>

<MTEntryIfAllowComments>

<li class="mtComments"><a href="<$MTCGIPath$><$MTCommentScript$>?entry_id=<$MTEntryID$>" onclick="OpenComments(this.href);return false;" onkeypress="OpenComments(this.href);return false;">Comments (<$MTEntryCommentCount$>)</a></li>

</MTEntryIfAllowComments>

<MTEntryIfAllowPings>

<li class="mtTrackback"><a href="<$MTCGIPath$><$MTTrackbackScript$>?__mode=view&amp;entry_id=<$MTEntryID$>" onclick="OpenTrackback(this.href);return false;" onkeypress="OpenTrackback(this.href);return false;">TrackBack (<$MTEntryTrackbackCount$>)</a></li>

</MTEntryIfAllowPings>

</ul>

<!--/mtEntryFooter--></div>

<!--/mtHeading3--></div>

<MTDateFooter>

<!--/mtHeading2--></div>

</MTDateFooter>

</MTEntries>

<!--/mtEntries--></div>

<div id="mtUtilities">

<table summary="Calendar: <$MTDate format="%B %Y"$>" class="mtTableCalendar">

<caption><$MTDate format="%B %Y"$></caption>

<col /><col /><col /><col /><col /><col /><col />

<thead>

<tr>

<th abbr="S" scope="col">Sun</th>

<th abbr="M" scope="col">Mon</th>

<th abbr="T" scope="col">Tue</th>

<th abbr="W" scope="col">Wed</th>

<th abbr="T" scope="col">Thu</th>

<th abbr="F" scope="col">Fri</th>

<th abbr="S" scope="col">Stu</th>

</tr>

</thead>

<tbody>

<MTCalendar>

<MTCalendarWeekHeader><tr></MTCalendarWeekHeader>

<td><MTCalendarIfEntries>

<MTEntries lastn="1"><a href="<$MTEntryPermalink$>"><$MTCalendarDay$></a></MTEntries>

</MTCalendarIfEntries>

<MTCalendarIfNoEntries><$MTCalendarDay$></MTCalendarIfNoEntries></td>

<MTCalendarWeekFooter></tr></MTCalendarWeekFooter>

</MTCalendar>

</tbody>

</table>

<form action="<$MTCGIPath$><$MTSearchScript$>" method="get">

<fieldset><legend accesskey="S">Search</legend>

<p><input type="hidden" name="IncludeBlogs" value="<$MTBlogID$>" />

<input type="text" name="search" id="search" value="" />

<br /><input type="submit" value="Search" /></p>

</fieldset>

</form>

<dl>

<dt>Archives</dt>

<dd><ul>

<MTArchiveList archive_type="Monthly">

<li><a href="<$MTArchiveLink$>"><$MTArchiveTitle$></a></li>

</MTArchiveList>

</ul></dd>

</dl>

<dl>

<dt>Recent Entries</dt>

<dd><ul>

<MTEntries lastn="10">

<li><a href="<$MTEntryPermalink$>"><$MTEntryTitle$></a></li>

</MTEntries>

</ul></dd>

</dl>

<dl>

<dt>Links</dt>

<dd><ul>

<li>Add Your Links</li>

<li><a href="<$MTBlogURL$>index.rdf">Syndicate this site (XML)</a></li>

</ul></dd>

</dl>

<!--/mtUtilities--></div>

<!--/mtHeading1--></div>

<div id="mtFooter">

<p>Powered by <a href="http://www.movabletype.org/">Movable Type <$MTVersion$></a></p>

<!--/mtFooter--></div>

</body>

</html>

まず注意点ですが、id属性とclass属性の値には、すべて「mt」という接頭辞がつけています。これは他のテンプレートやら既存のセレクタやらと競合してしまう可能性を排除するために、あえてつけました。たとえば、完成イメージ図でdiv class=heading1要素を意図していたものは、この新生MainIndex.tmplでは「<div class="mtHeading1">」という書き出しになっています。これ以降は、この「mt」接頭辞つきの属性値で説明していきます。

実際にエントリーの内容となるdiv class=mtHeading3要素の中には、エントリー本文と投稿者や投稿した時間、エントリーに付いたコメントとTrackBackの情報も記されることになります。ここを、エントリー本文とそれ以外というように大別できると見なし、エントリー本文を担うp要素群はdiv class=mtEntryBody要素で括り、それ以外をdiv class=mtEntryFooter要素で括っています。div class=mtEntryFooter要素の中ですが、ここでは「何時に誰が投稿した」「コメントがいくつついている」「TrackBackが何個あった」という各情報が単に列挙されているものと考えて、ul/li要素で意味づけしています。

先ほど詳細を省いていた、div id=mtUtilities要素の中身がどうなっているのかについても、せっかくなので説明しておきましょう。この要素の中身は、標準のMainIndex.tmplで一番困ったところとされていた、セットを想定しているとしか思えないdiv class=sidetitle要素とdiv class=side要素のまとまりです。これらは対であることをより明確にするために、それぞれdl要素を用いて意味づけし直しています。div class=sidetitle要素はdt要素に変更され、div class=side要素だったものはdd要素となりました。dt要素とdd要素は、dl要素によって対の関係を明示的にされるでしょう。また、div class=side要素の内容は、箇条書き的なデータが入ることが想定されているにも関わらず、標準の状態ではbr要素で一行ごとに改行していくだけのものでした。これはせっかく箇条書きの内容なのだから、きっちり意味づけするの精神に基づいて、ul要素を用いました。dd要素の内容にul要素が入ってくるかたちになります。もちろん、箇条書きですから、ul要素の中にli要素が必要な数だけ生成されるというわけです。

検索フォームのところは、fieldset要素やlegend要素を使うようにしてみました。

そして、カレンダーの部分は、col要素を書き足したり、th要素のabbr属性をちゃんと略称っぽくしてみたりしたほか、CSSで定義するべきレイアウト情報を排除しています。標準のMainIndex.tmplでは、カレンダー周辺はこのように書かれています。

<div align="center" class="calendar">



<table border="0" cellspacing="4" cellpadding="0" summary="Monthly calendar with links to each day's posts">

<caption class="calendarhead"><$MTDate format="%B %Y"$></caption>

<tr>

<th abbr="Sunday" align="center"><span class="calendar">Sun</span></th>

<th abbr="Monday" align="center"><span class="calendar">Mon</span></th>

<th abbr="Tuesday" align="center"><span class="calendar">Tue</span></th>

<th abbr="Wednesday" align="center"><span class="calendar">Wed</span></th>

<th abbr="Thursday" align="center"><span class="calendar">Thu</span></th>

<th abbr="Friday" align="center"><span class="calendar">Fri</span></th>

<th abbr="Saturday" align="center"><span class="calendar">Sat</span></th>

</tr>



<MTCalendar>

<MTCalendarWeekHeader><tr></MTCalendarWeekHeader>



<td align="center"><span class="calendar">

<MTCalendarIfEntries><MTEntries lastn="1"><a href="<$MTEntryPermalink$>"><$MTCalendarDay$></a></MTEntries></MTCalendarIfEntries><MTCalendarIfNoEntries><$MTCalendarDay$></MTCalendarIfNoEntries><MTCalendarIfBlank>&nbsp;</MTCalendarIfBlank></span></td><MTCalendarWeekFooter></tr></MTCalendarWeekFooter></MTCalendar>

</table>



</div>

このうちレイアウト情報はどれかといいますと、div要素やth/td要素に書かれているalign属性、table要素に書かれているsummary以外の各属性、td要素の中身が空だったときに挿入されるように書かれている「&nbsp;」という値です。HTMLのalign属性でセンタリングしなくても、CSSのtext-alignプロパティや左右マージンを同じするなどでセンタリングできます。table要素のボーダーサイズやセル間の幅なども、それぞれ対応するプロパティがCSSに用意されています。そして、td要素の中身が空だとそのセルの周囲にボーダーがひかれないことへの対策で「&nbsp;」を値として入れておくという、何だかレガシーな技が使われていますが、これはCSSのborder-collapseプロパティにcollapseという値、empty-cellsプロパティにshowという値をそれぞれ設定してあげることで解消できるのです。だから「&nbsp;」を入れる必要はありません。

また、これだけではレイアウトのためなのか判断できないとはいえ、td要素の中にいちいち入っているspan class=calendar要素にしても、それはtd要素にスタイルをつければ良いだけの話であるからして、ようするに冗長であるし意図がいまひとつ不明瞭です。ですのでspan class=calendar要素のようなものは排除してあります。

最後に、div id=mtFooter要素には、XHTMLのaddress要素を入れることを目論んでいたのですが、今回紹介しているこの新生MainIndex.tmplは僕だけのテンプレートではありませんので、address要素の中身に適切な値を用意できません。別に僕の連絡先を突っ込んでおいても良いのですが、新生MainIndex.tmplの外観をデザインするときは、基本的にStyleSheet.tmplだけをいじりたいと思っているためです。とりあえず、movabletype.orgへのリンク情報を入れる場所として使用することにしておきました。

いよいよデザインできるぞ

さあ、めでたく新生MainIndex.tmplができあがりました。これで思う存分、デザインに取り掛かれるというわけです。もちろん、CSSによるデザインやレイアウトっていうのに慣れていないとちょっと難しいと思うこともあるかもしれませんが、ここではある程度慣れているという前提で進めさせてもらいます。

デザインの手順ですが、ちょっとずつStyleSheet.tmplをいじっては表示して、みたいな感じではなく、新生MainIndex.tmplが生成する文書としての各要素をどのように配置していったらカッコイイよねとか、読みやすいよねとか、そういうノリでやっていくと良いかと思います。コンテンツの構成要素を取り出してそれらを肴に、MTでどうなるとかにはこだわらずにデザインしちゃえということです。

というところで、実はすでにひとつ考えて、作ってしまっています。コレです。

【図】新生MainIndexのデザインイメージ[129KB]

ではこれはどういったCSSを書いたら実現できるのでしょう。コンテンツの構成要素がそれぞれ、ブロックレベル要素をともなってどのように配置されているのかを考えてみましょう。

【図】新生MainIndexのデザイン構造[153KB]

あたかもブロックレベル要素たちにボーダーをつけて表示させているかのような、この状態を見ればもうピンと来たかと思いますが、ようするにこういう配置になるようなスタイル定義をがんばって考えれば良いということなのです。

レイアウト的に注目するのは、まずdiv id=mtEntries要素とdiv id=mtUtilities要素とでもって、段組されているところでしょう。段組にはいくつかの手法がありますが、絶対配置しないほうのブロックの縦サイズが絶対配置しているブロックの縦サイズより長くなる必要があるという前提条件が付くとしても、ブラウザ間での実装上の挙動が比較的一致している、positionプロパティによる絶対配置を採用しました。div id=mtEntries要素の左マージンを大げさにつけることで、左側に空白地帯を設けます。そしてその空白地帯の上に重ねるように、div id=mtUtilities要素を絶対配置してあげるわけです。これで、見た目上は段組されているようになります。

div#mtEntries {

    margin-left:200px;

    margin-right:18px;

    padding-top:1px;

    background-color:#F0F0F0

}

div#mtUtilities {

    position:absolute;

    top:81px;

    right:auto;

    bottom:auto;

    left:20px;

    width:168px;

    padding-top:90px;

    background:transparent url("ptn1utilbg.gif") top left no-repeat;

}

段組されたそれぞれのカラムの中は、概ね上から下へと積み重ねられるレイアウトになっていますから、マージンやパティングなどをきっちり寸法を測って設定していけば、ほとんど問題なく配置できるでしょう。

問題があるとすれば、やはり段組的な箇所となってくるのですが、div class=mtEntryBodyの右にdiv class=mtEntryFooterが来ているところでしょうか。こちらは今度はfloatプロパティを用いて、div class=mtEntryFooter要素を回り込ませています。ちなみに回り込ませっ放しだと、後に続くエントリーが困ってしまうので、h2要素やh3要素でclearしておくのを忘れてはいけませんよ。

div.mtHeading3 div.mtEntryBody {

    padding-right:5px;

    margin-left:3px;

    padding-bottom:20px;

    width:330px;

    float:left;

}

このデザインを実装する場合のポイントは以上です。CSSでデザインすることは別に難しいことではないので、ポイントになってくるところなんて基本的にこれくらいなんです。特に新生MainIndex.tmplが生成するXHTMLなら、さらに簡単でしょう。XHTML側は完全に見栄えにこだわらず構造のみを定義しており、CSS側は構造に見栄えをただひたすらつけていくだけという、かなり双方を分離した作業になります。さらには、MTにこだわらない既存の他のWebページ(MTが生成したわけではないページ)で使用しているCSSを、ほぼそのままに流用することもできると思われます。

ブラウザのバグ対策はどうしたら良い?

ブラウザのCSS実装がしょぼくて特定のプロパティを解釈できないためにCSSでの表現が限られてくる、ということが往々にしてあります。解釈できないプロパティを無視してくれるのであればまだ良いのですが、がんばって解釈しようとして滅茶苦茶な表示結果を出してしまうということが、さらに往々にしてあります。それはようするに、ブラウザがバグっているのですが。

最新版でない古めのブラウザでCSSに対応してしまっているものは、だいたいCSS実装がかなりヤバイ感じです。それらに対しては、可能ならCSSそのものを読み込ませないような細工をしてあげると良いというか楽です。

たとえばNetscape 4.xであれば、link要素のmedia属性にscreen以外の値を設定しておけば、そこで指定しているCSSファイルを読み込まなくなります。

<link rel="stylesheet" type="text/css" src="styles-site.css" media="screen,tv" /><!--Netscape4.xはこのファイルを読めない-->

たとえばWinIE3であれば、link要素で複数のCSSを指定しているときに、一番最後に書いたlink要素しか認識しません。ですから、内容の無いCSSファイル(本当に0バイトのファイルではなく、コメントだけ書かれているようなもの)などを最後にダミーで指定するという手が使えます。

<link rel="stylesheet" type="text/css" src="styles-site.css" />

<link rel="stylesheet" type="text/css" src="dummy.css" /><!--IE3はこのファイルしか読まない-->

さてこういった、全体的に実装がボロボロだからCSSを無効にさせたいという場合の対策はまだ楽なのですが、特定のブラウザに特定のプロパティだけどうしても読み込ませたくないとかいうことが発生することがあります。こういうときは、そのブラウザが未実装なCSSの文法を織り交ぜて読ませなくするだとか、やはりバグを利用してその部分を無視させるだとかいったやり方で対応します。

個人的な経験則ですが、MacIE5だけがコメントだと勘違いするエスケープ技は、かなりよく使いますので紹介しておきましょう。たとえばletter-spacingプロパティに正の数値を設定していると、MacIE5はその分を幅の計算からなぜか除外するために、widthを固定しているボックスから中身の文章がはみ出してしまうことがあります。ですから、letter-spacingプロパティを使うときは、もうそこは絶対にMacIE5に読み込ませなくしたりしています。

/* MacIE5はここから \*/

letter-spacing:5px;

/* ここまでをコメントだと勘違いする */

さらに突き詰めれば、@importルールを利用するなどして複数のCSSファイルをたくみに操ることで、かなり多くのブラウザ間で、こいつにはコレを読ませるがコレは読ませないというような複雑な振り分け規則を作り出すことも可能ではあります。しかしMTにおいての管理のしやすさなどを踏まえると、1ファイル程度で済むくらいの対策にとどめておいて、あまりにもCSSのテンプレートだらけになってしまうような状況にはしないほうが良いでしょう。何事も引き際が肝心です。

もうひとつの「正しい」テンプレート

実はもうひとつ、違うデザインのものも作ってしまいました。こちらはこのようなデザインになります。

【図】もうひとつの新生MainIndexのデザインイメージ[313KB]

前のデザインと比べて、とても大きな違いがあります。そう、カレンダーが表ではなく縦列になっているのです。もうこの時点で、さっきの新生MainIndex.tmplそのままでは、こちらのデザインを実現することができません。このようなカレンダーをtable要素で意味づけしないようなパターンも十分にありえますので、こちらのバージョンの新生MainIndex.tmplも別途用意しておきました。基本的な構造の階層などは変わらないのですが、いくつか多少異なる構造が含まれます。どちらの構造が妥当かという比較は困難で、いうなればどちらも正しいと考えられるものです。まずはこちらの新生MainIndex.tmplの内容を見てください。

<?xml version="1.0" encoding="<$MTPublishCharset$>" ?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja" dir="ltr">

<head profile="http://purl.org/net/uriprofile/">

<meta http-equiv="Content-Type" content="text/html;charset=<$MTPublishCharset$>" />

<meta http-equiv="Content-Style-Type" content="text/css" />

<meta http-equiv="Content-Script-Type" content="text/javascript" />

<meta http-equiv="imagetoolbar" content="no" />

<title><$MTBlogName$></title>

<link rel="stylesheet" type="text/css" href="<$MTBlogURL$>styles-site.css" media="screen,tv" />

<link rel="alternate meta" type="application/rss+xml" title="RSS" href="<$MTBlogURL$>index.rdf" />

<link rel="EditURI" type="application/rsd+xml" title="RSD" href="<$MTBlogURL$>rsd.xml" />

<link rel="Contents Start" href="<$MTBlogURL$>" title="<$MTBlogName$> - MainIndex" />

<script type="text/javascript">

function OpenComments (c) {

 window.open(c,'comments','width=480,height=480,scrollbars=yes,status=yes');

}

function OpenTrackback (c) {

 window.open(c,'trackback','width=480,height=480,scrollbars=yes,status=yes');

}

</script>

<MTBlogIfCCLicense>

<$MTCCLicenseRDF$>

</MTBlogIfCCLicense>

</head>

<body>

<div class="mtHeading1">

<h1><img src="ptn2h1.gif" width="746" height="270" alt="My First Weblog" /></h1>

<p><$MTBlogDescription$></p>

<div id="mtEntries">

<MTEntries>

<$MTEntryTrackbackData$>

<MTDateHeader>

<div class="mtHeading2">

<h2><$MTEntryDate format="%x"$></h2>

</MTDateHeader>

<div class="mtHeading3">

<h3><a name="<$MTEntryID pad="1"$>" id="<$MTEntryID pad="1"$>"><$MTEntryTitle$></a></h3>

<div class="mtEntryBody">

<$MTEntryBody$>

<MTEntryIfExtended>

<p class="mtExtended"><a href="<$MTEntryPermalink$>#more">Continue reading "<$MTEntryTitle$>"</a></p>

</MTEntryIfExtended>

<!--/mtEntryBody--></div>

<div class="mtEntryFooter">

<dl>

<dt>Posted By</dt>

<dd><$MTEntryAuthor$></dd>

</dl>

<dl>

<dt>Posted At</dt>

<dd><a href="<$MTEntryPermalink$>"><$MTEntryDate format="%X"$></a></dd>

</dl>

<MTEntryIfAllowComments>

<dl>

<dt>Comments</dt>

<dd><a href="<$MTCGIPath$><$MTCommentScript$>?entry_id=<$MTEntryID$>" onclick="OpenComments(this.href);return false;" onkeypress="OpenComments(this.href);return false;"><$MTEntryCommentCount$> Posted</a></dd>

</dl>

</MTEntryIfAllowComments>

<MTEntryIfAllowPings>

<dl>

<dt>TrackBack</dt>

<dd><a href="<$MTCGIPath$><$MTTrackbackScript$>?__mode=view&amp;entry_id=<$MTEntryID$>" onclick="OpenTrackback(this.href);return false;" onkeypress="OpenTrackback(this.href);return false;"><$MTEntryTrackbackCount$> Refered</a></dd>

</dl>

</MTEntryIfAllowPings>

<!--/mtEntryFooter--></div>

<!--/mtHeading3--></div>

<MTDateFooter>

<!--/mtHeading2--></div>

</MTDateFooter>

</MTEntries>

<!--/mtEntries--></div>

<div id="mtCalendar">

<h2><$MTDate format="%b %Y"></h2>

<ul class="mtListCalendar">

<MTCalendar>

<MTCalendarIfEntries>

<MTEntries lastn="1"><li><a href="<$MTEntryPermalink$>"><$MTCalendarDay$></a></li></MTEntries>

</MTCalendarIfEntries>

<MTCalendarIfNoEntries><li><$MTCalendarDay$></li></MTCalendarIfNoEntries>

</MTCalendar>

</ul>

<dl>

<dt>Archives</dt>

<dd><ul>

<MTArchiveList archive_type="Monthly">

<li><a href="<$MTArchiveLink$>"><$MTArchiveTitle$></a></li>

</MTArchiveList>

</ul></dd>

</dl>

<!--/mtCalendar--></div>

<div id="mtUtilities">

<dl>

<dt>Recent Entries</dt>

<dd><ul>

<MTEntries lastn="10">

<li><a href="<$MTEntryPermalink$>"><$MTEntryTitle$></a></li>

</MTEntries>

</ul></dd>

</dl>

<dl>

<dt>Links</dt>

<dd><ul>

<li>Add Your Links</li>

<li><a href="<$MTBlogURL$>index.rdf">Syndicate this site (XML)</a></li>

</ul></dd>

</dl>

<form action="<$MTCGIPath$><$MTSearchScript$>" method="get">

<fieldset><legend accesskey="S">Search</legend>

<p><input type="hidden" name="IncludeBlogs" value="<$MTBlogID$>" />

<input type="text" name="search" id="search" size="20" value="" />

<br /><input type="submit" value="Search" /></p>

</fieldset>

</form>

<!--/mtUtilities--></div>

<!--/mtHeading1--></div>

<div id="mtFooter">

<p>Powered by <a href="http://www.movabletype.org/">Movable Type <$MTVersion$></a></p>

<!--/mtFooter--></div>

</body>

</html>

ひとつめの新生MainIndex.tmplと大きく違うのは、まず前述のとおりカレンダー自体の意味づけがul/li要素になっているということですが、このカレンダーを内包するブロックレベル要素が、div id=mtUtilities要素ではなくなっているという点も挙げられます。div id=mtEntries要素とdiv id=mtUtilities要素、このふたつと同等の階層にdiv id=mtCalendar要素を用意してあります。このdiv id=mtCalendar要素には、カレンダーのul/li要素だけでなく、月別アーカイブ一覧のdl要素も含むように、構造を変更しています。このデザインでは、時系列的な意味合いをもつ構成要素をひとつの段組カラムに配置するようになっていますが、これはカレンダーと月別アーカイブ一覧のまとまりを強く関連づける設計をしているためです。それを意味づけに反映して、このような階層構造に作り変えたというわけです。

また、div class=mtEntryFooter要素の中も、単なる箇条書きの内容とはせずに、「投稿したのは:誰、投稿した時間は:何時」という表現で構成要素を組み替えたこのデザイン意図を反映して、dl要素で意味づけされるように書き換えてあります。

さて、こちらのデザインについても、CSSをどう書いたら良いかのポイントをいくつかピックアップしていきます。まずは先ほどと同様に、ブロックレベル要素がどの配置に対応しているのかを表した図を見てください。

【図】もうひとつの新生MainIndexのデザイン構造[244KB]

一番困りそうなのはやはり段組でしょうか。こちらも芸がないようですがposition:absoluteを使って絶対配置で解決してみます。div id=mtUtilities要素とdiv id=mtCalendar要素をそれぞれ絶対配置して、div id=mtEntries要素は左マージンを大きく空けるという感じです。ただ、この三段カラムにはどれもグレーの透け透け背景が敷かれているのですが、一番縦に長いブロックの長さまで追従した背景になっています。通常、絶対配置とかフロートさせた要素の背景を、させていない要素の縦の長さまで追従させることはできません。ですので、見かけ上は追従しているかに見えるようにしてみました。div id=mtUtilities要素の背景が、もともと三段カラムの段組の背景になっているわけです。この場合、div id=mtUtilities要素の左側を空けるためにマージンを設定してしまうと、背景の左上開始地点がそのマージンをとった後からになってしまうので、マージンではなくパディングで左側を空ける必要が出てきます。

その他の要素配置や表示のスタイルに関しては、基本的なところはひとつ前のCSSと概ね変わりないので、ひたすらに書いていけば完成するかと思われます。

div#mtEntries {

    padding-left:294px;

    background:transparent url("ptn2h2bg.gif") top left repeat-y;

}

div#mtCalendar {

    position:absolute;

    top:270px;

    left:198px;

    width:80px;

}

div#mtUtilities {

    position:absolute;

    top:270px;

    left:20px;

    width:168px;

}
最後にして最大の注意ポイント

今回作り直したふたつの新生MainIndex.tmplは、どちらもXHTML 1.0 Transitionalとして妥当なXHTMLを生成します。さらにいえば、XHTML 1.0 Strictとしても妥当な内容になっていますので、少し手を入れればXHTML 1.1としても妥当な内容に書き換えることができます。話が最初に戻ってしまいますが、MTはコンテンツマネジメントシステムですから、XHTMLの文法などを気にせずに、おもしろおかしいエントリーなどを登録していくことで、魅力的なウェブログサイトが作られることでしょう。しかし、エントリーの内容に、採用しているXHTMLのバージョンに見合わない文法で書いたXHTMLを含めてしまうと、そのページは妥当なXHTML文書ではなくなってしまいます。そういう意味では、やはりある程度、XHTMLの仕様にも通じてないとよろしくないといえます。

よく見受けるのが、引用文をblockquote要素で意味づけしているときの誤りです。引用のために使うブックマークレットが、誤った文法を助長させるようなblockquote要素の塊を吐き出したりしているのでしょう。標準のCMS.pmファイルではそのようなことは発生しませんが、引用文を挿入しやすくするために、CMS.pmファイルの該当箇所の出力内容をblockquote要素をともなうものに変更する方法が、インターネットで公開されていますね。しかしそれが、誤った文法のblockquote要素をともなっているのです。blockquote要素の直下に、テキストやインライン要素を書くことはできません。必ずp要素やdiv要素などのブロックレベル要素を書いてから、はじめてテキストやインライン要素が書けるようになるのです。この誤った文法のblockquote要素をそもそも生成させないように、その変更済みのCMS.pmファイルをさらに変更しておくと良いでしょう。標準の状態で「qq(<a title="%s" href="%s">%s</a>\n\n%s)」となっている部分を、たとえば以下のように書き直します。

qq(<blockquote><p><cite><a title="%s" href="%s">%s</a></cite></p><div>%s</div></blockquote>)

この例では最後の「%s」をdiv要素で括るようにしています。引用したのがふつうの文章ならば、このdiv要素はp要素に書き直して意味づけを明確にしたほうが良いでしょう。しかしこれはテンプレートとして汎用的な挙動をすることが求められますので、あえてdiv要素にしているわけです。最初からp要素が生成されてしまうと、p要素にはブロックレベル要素を含めることができないため、たとえば箇条書き(ul要素)引用したりすることができなくなってしまうからです。

さて、これまでに解説してきましたように、テンプレートが生成するXHTML自体を「正しい」ものにしていけば、あとは自由自在なCSSデザインができるようになります。ここではMainIndex.tmplしか取り上げませんでしたので、実際にはまだまだやることが盛りだくさんなわけですが、すべてのテンプレートを一貫した構造で意味づけしてあげることで、StyleSheet.tmplの定義内容をテンプレートごとにどんどん増やしていくのではなく、MTが備える各ページ全体を通して同じスタイルを適用するようなやり方が可能になるわけです。つまり、まずはとにかく、StyleSheet.tmpl以外のテンプレートを書き直すということが肝だといえるでしょう。

オリジナルテンプレートのダウンロード

この記事で扱ったオリジナルテンプレートは、「Movable Typeオリジナルテンプレートのダウンロード」からダウンロードできます。


森田 雄もりた ゆう 〔yuu Morita〕 ライターもしているウェブレイバー(ウェブ業界の労働者)。記事執筆のご依頼を含む各種お問い合わせは securecat@gmail.com まで。ただし、ウェブサイト制作とかのお仕事は、個人では承っておりませんので悪しからず。

最終更新日:2004.2.4