HTML でスペースをいくつ並べても、実際の画面では1つ分にまとめられてしまいます。ログの出力例や図解のように「書いたとおりの位置」で見せたいテキストには、整形済みテキストを表す pre 要素を使います。この記事では、なぜ空白や改行がまとめられるのかという仕組みから、pre の基本的な使い方、code との組み合わせ、スマートフォンではみ出すときの対処までを解説します。
目次
HTML では空白と改行がまとめられる
まず前提として、HTML のソースに書いた半角スペース・タブ・改行は、ブラウザが表示する段階で連続した分がまとめて1つの空白として扱われます。これをホワイトスペースの畳み込み(whitespace collapsing)と呼びます。ソースコードをインデントして読みやすく書いても、そのインデントが画面にそのまま出てこないのはこの仕組みのおかげです。
この動作は CSS の white-space プロパティの初期値 normal によるものです。ふだんの文章では便利ですが、空白の数そのものに意味がある内容には困ります。次のデモで、同じ内容を p と pre で書いた場合の違いを見てみましょう。HTML タブのスペースの数を増やしたり、改行を足したりすると、違いがよりはっきり分かります。
<h3>p 要素で書いた場合</h3>
<p>朝 昼 夜
連続した空白と改行は、まとめられます。</p>
<h3>pre 要素で書いた場合</h3>
<pre>朝 昼 夜
連続した空白と改行が、そのまま残ります。</pre>
body {
font-family: sans-serif;
line-height: 1.7;
padding: 12px;
}
h3 {
font-size: 15px;
color: #0a58ca;
margin: 16px 0 6px;
}
p,
pre {
background: #f5f7fa;
border: 1px solid #d5dbe3;
border-radius: 6px;
padding: 10px 12px;
margin: 0;
}
p のほうは空白が1つにまとめられ、改行も無視されて1行につながっています。一方 pre は、ソースに書いたとおりの空白の数と改行位置が保たれています。
pre 要素の基本的な使い方
pre は preformatted text(整形済みテキスト)の略で、中のテキストをソースに書いたとおりの見た目で表示するブロックレベル要素です。ブラウザの初期スタイルでは white-space: pre と等幅フォント(monospace)が適用されるため、空白と改行が保持され、さらに文字幅がそろって桁が縦に並びます。
<pre>商品名 単価 個数
りんご 150 3
みかん 80 12</pre>
このように、空白で桁をそろえた表形式のテキストや、コマンドの実行ログなどをそのまま貼り付けられます。ただし、本当に表として意味のあるデータであれば table 要素を使うほうが適切です。pre は「もとの文字配置そのものが情報になっている」ときに選びます。
なお pre の中に置けるのはフレージングコンテンツ(テキストや a・strong などのインライン要素)です。div のようなブロックレベル要素や、img・sub・sup といった行の高さを変える要素を入れると、桁ぞろえが崩れる原因になるため避けます。
コードを載せるときは pre と code を組み合わせる
ソースコードを複数行そのまま掲載したいときは、pre だけで囲むのではなく、内側に code を入れた <pre><code>…</code></pre> の形にするのが正しいマークアップです。pre が「書式を保った表示」を担当し、code が「これはソースコードである」という意味を担当する、という役割分担になっています。
<pre><code>function add(a, b) {
return a + b;
}</code></pre>
ここで注意したいのが、<pre> と <code> の間、そして </code> と </pre> の間に改行やスペースを入れないことです。pre の中では空白がそのまま表示されるため、タグを見やすく改行して書くと、その改行がコードの前後の余計な空行として出てしまいます。
また、多くのシンタックスハイライトのライブラリは <pre><code class="language-js"> のような構造を前提にしています。言語を示すクラスの付け方はライブラリによって異なりますが、pre と code の入れ子自体は共通の作法です。
開始タグ直後の改行は無視される
HTML の仕様では、<pre> の開始タグの直後にある改行1つだけは、ブラウザが読み込むときに取り除かれます。これは、タグと内容を別の行に分けて書けるようにするための決まりです。
<!-- 開始タグの直後の改行は消えるので、1行目に空行は出ない -->
<pre>
1行目
2行目</pre>
<!-- 終了タグの前の改行は残るので、末尾に空行が出る -->
<pre>
1行目
2行目
</pre>
取り除かれるのは開始タグの直後の1つだけで、2つ以上改行すれば2つ目からは空行として残ります。そして終了タグの前の改行には同じ扱いがありません。上の2つ目の例のように </pre> を次の行に書くと、その改行がそのまま表示され、最後に不自然な空行が生まれます。「なぜか下に1行分の隙間ができる」というときは、ここを確認してみてください。
中に HTML を載せるときはエスケープする
pre は空白と改行を保持しますが、タグの解釈をやめるわけではありません。中に書いた <p> はそのままタグとして解釈され、文字としては表示されません。HTML のソースを見せたいときは、記号を実体参照に置き換える必要があります。
| 文字 | 書き方(実体参照) |
|---|---|
< | < |
> | > |
& | & |
最低限必要なのは < と & の2つです。< を置き換えないとタグとして解釈されてしまい、& を置き換えないと < のような文字列そのものを表示できなくなります。> は単独ではタグと解釈されませんが、対称性のために合わせて書くことが多いです。
<pre><code><p class="lead">こんにちは</p></code></pre>
このソースは、画面上では <p class="lead">こんにちは</p> という文字列として表示されます。手作業で置き換えるのは大変なので、サーバー側で出力するなら PHP の htmlspecialchars() のようなエスケープ関数を通し、JavaScript で挿入するなら innerHTML ではなく textContent を使うと確実です。
スマートフォンで横にはみ出すときの対処
pre の初期値は white-space: pre なので、行が自動で折り返されません。長いコマンドや URL を載せると、画面幅の狭いスマートフォンではみ出し、ページ全体が横スクロールしてしまうことがあります。対処は大きく2通りで、はみ出した部分を横スクロールで読ませるか、折り返してしまうかです。
<h3>そのまま(はみ出した部分が読めない)</h3>
<div class="frame">
<pre class="plain">npm run build -- --entry ./src/main.js --output ./dist/bundle.min.js --sourcemap</pre>
</div>
<h3>overflow-x: auto(横スクロールできる)</h3>
<div class="frame">
<pre class="scroll">npm run build -- --entry ./src/main.js --output ./dist/bundle.min.js --sourcemap</pre>
</div>
<h3>white-space: pre-wrap(折り返す)</h3>
<div class="frame">
<pre class="wrap">npm run build -- --entry ./src/main.js --output ./dist/bundle.min.js --sourcemap</pre>
</div>
body {
font-family: sans-serif;
padding: 12px;
}
h3 {
font-size: 15px;
color: #0a58ca;
margin: 16px 0 6px;
}
/* 表示できる幅を分かりやすくするための枠 */
.frame {
border: 1px dashed #e0426a;
overflow: hidden;
}
pre {
background: #f5f7fa;
padding: 10px 12px;
margin: 0;
}
/* 何もしないと、長い行が枠からはみ出す */
.plain {
overflow: visible;
}
/* 解決策1: 横スクロールさせる */
.scroll {
overflow-x: auto;
}
/* 解決策2: 折り返す(空白と改行は保持したまま) */
.wrap {
white-space: pre-wrap;
}
1つ目は枠(点線)からはみ出して読めなくなっている状態、2つ目は overflow-x: auto で pre の中だけを横スクロールできるようにしたもの、3つ目は white-space: pre-wrap で折り返したものです。
コードには overflow-x: auto が向いている
ソースコードやコマンドの場合、勝手に折り返されると「どこまでが1行か」が分からなくなり、コピーして貼り付けたときの混乱にもつながります。行の区切りを崩したくないときは、折り返さずに横スクロールさせる方法が向いています。
pre {
/* はみ出した分は pre の中だけで横スクロールさせる */
overflow-x: auto;
/* 幅を親要素に収める(親が狭いときの押し広げを防ぐ) */
max-width: 100%;
}
読み物としてのテキストは pre-wrap で折り返す
ログの抜粋や、改行位置だけを保ちたい文章であれば、折り返してすべて見えるほうが親切です。white-space: pre-wrap にすると、空白と改行は保持したまま、画面幅に応じた自動折り返しが有効になります。長い英数字が1語として続いて折り返せない場合は overflow-wrap: anywhere を併用します。
white-space の値と pre の関係
ここまで出てきたとおり、pre の挙動は CSS の white-space で調整できます。逆に言えば、p や div に white-space: pre を指定すれば、見た目のうえでは pre と同じように空白と改行を保持できます。よく使う4つの値を整理します。
| 値 | 連続する空白 | ソースの改行 | 自動折り返し |
|---|---|---|---|
normal(初期値) | まとめる | まとめる | する |
pre(pre 要素の既定) | そのまま残す | そのまま残す | しない |
pre-wrap | そのまま残す | そのまま残す | する |
pre-line | まとめる | そのまま残す | する |
pre-line は「改行だけ活かしたい」ときに便利です。たとえば入力フォームで受け取った文章をそのまま表示する場合、改行は反映しつつ、余分なスペースは詰めたいことがあります。そうしたケースでは pre 要素ではなく、p に white-space: pre-line を当てるほうが素直です。
ただし CSS はあくまで見た目の指定です。「これは整形済みのテキストのかたまりである」という意味を持たせたいなら、white-space を当てた div ではなく pre 要素を使いましょう。CSS が読み込まれなかった場合でも、pre であればブラウザの初期スタイルによって書式が保たれます。
アスキーアートを載せるときの配慮
pre はアスキーアート(文字で描いた図)にもよく使われます。ただし、記号の羅列をスクリーンリーダーがそのまま読み上げると、聞いている人には意味の分からない音の連続になってしまいます。MDN では、こうした場合に role="img" と aria-label を付け、「その図が何を表しているか」を言葉で示す方法が案内されています。
<pre role="img" aria-label="口を開けて笑っている顔の絵文字">
___
/ o o \
| ^ |
\ \_/ /
-----</pre>
role="img" を付けると、その要素は1つの画像として扱われ、中の文字は個別に読み上げられなくなります。代わりに aria-label に書いた説明が読み上げられるので、画像における alt 属性と同じ感覚で、図の内容を簡潔に書いておきます。
まとめ
HTML では連続する空白や改行がまとめられてしまいますが、pre 要素を使えばソースに書いたとおりの書式を保ったまま表示できます。ブラウザの初期スタイルで white-space: pre と等幅フォントが適用されるため、桁をそろえたテキストもそのまま見せられます。コードを載せるときは <pre><code> の入れ子にし、タグの間に余計な改行を入れないこと、開始タグ直後の改行1つだけが取り除かれる仕様を知っておくこと、< や & は実体参照に置き換えることがポイントです。長い行がはみ出すときは overflow-x: auto か white-space: pre-wrap で、内容に合った見せ方を選びましょう。