【HTML】preタグの余分なインデントを削除する方法まとめ

# はじめに

:::note info
急いでいる方は[解決方法を考える](#解決方法を考える)まで飛ばしてください。
:::

この記事は、`pre`タグの余分なインデントを削除する方法について、数種類の方法を紹介しています。
また、最初の方は問題発生までの手順や、余分なインデントの正体なども書いています。

作者自身が初心者のため、間違いがある可能性があります。
もしあったらすみません。


## 環境

* OS: Mac
* エディタ: VSCode
    * インデント: スペース×2（`  `）
* HTMLの表示: Chrome

ついさっき知ったんですが、インデントにもタブとスペースの2種類あるんですね...
ずっとTabキーを押したらインデントできるくらいにしか思ってなかったので、まさかTabキーを押して入力されていたのがスペースだとは思いませんでした...

VSCodeのインデントの設定については、以下の記事をご覧ください。

https://qiita.com/e6nlaq/items/cbd90086f4de18cc68b6


# 問題

:::note info
茶番です。
急いでいる方は[余白の正体](#余白の正体)まで飛ばしてください。
:::


さて、今日も今日とてHTMLを書いていきます。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HTMLファイル</title>
</head>
<body>
  <main>
    <article>
      <h1>Hello World!</h1>
    </article>
  </main>
</body>
</html>
```

よく見る普通のHTMLですね。
中身のコンテンツは~~演出のため~~`main`と`article`で囲まれています。

ということで、ここに文章を追記していきたいと思います。

```html
<body>
  <main>
    <article>
      <h1>Hello World!</h1>
      <p>
        これはpタグ<br>
        普通の文章<br>
      </p>
      <a href="">これはaタグ</a>で<code>これはcodeタグ</code>
      <blockquote>
        これはblockquoteタグ<br>
        引用に使われる
      </blockquote>
      <pre>
        これはpreタグ
        用途は正直よくわからん
          スペースや改行がそのまま表示されるから、
          こんな風にインデントすることができる
      </pre>
    </article>
  </main>
</body>
```

見ての通り`p`タグや`a`タグ、`pre`タグなどが追加されました。
至って普通のHTMLですね。

CSSで装飾するのもいいかもしれません。
`pre`タグの背景色を`skyblue`にしたらいい感じになりそうです。

```html
<style>
  pre {
    background-color: skyblue;
  }
</style>
```

...`pre`タグに変な余白がついてしまいました。

![余白.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/155d4d3b-f994-9faa-2dd4-ecfb19fa97d7.png)

見ての通り、`pre`タグの内側の左と下に謎の余白があります。
`padding`は設定していませんし、この余白は一体なんでしょうか。


## 余白の正体

もしかしたら`pre`タグの中身に問題があるのかもしれません。
コピペして調べてみましょう。

```
        これはpreタグ
        用途は正直よくわからん
          スペースや改行がそのまま表示されるから、
          こんな風にインデントすることができる
      
```

コピペしてみたところ、なぜか余白が再現されました。
これは`pre`タグのスタイルではなく、`pre`タグの中身に原因があると見て間違いなさそうです。

これだと分かりづらいので、余白部分を特殊文字に置き換えてみます。
[HTML特殊文字変換](https://mugineko.com/tool/html)というサイトを見つけたので、これを使って変換したものがこちらです。

```
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;これはpreタグ
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;用途は正直よくわからん
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;スペースや改行がそのまま表示されるから、
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;こんな風にインデントすることができる
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
```

`&nbsp;`というのは半角スペース（` `）を表しています。
ということはつまり、余白の正体は大量の半角スペースだったみたいです。

ではこの半角スペースは一体なんなのでしょうか。
...待てよ、そういえばこの形の余白、どこかで見たような...

```html:htmlファイル
      </blockquote>
      <pre>
        これはpreタグ
        用途は正直よくわからん
          スペースや改行がそのまま表示されるから、
          こんな風にインデントすることができる
      </pre>
    </article>
```

```:preタグの中身
        これはpreタグ
        用途は正直よくわからん
          スペースや改行がそのまま表示されるから、
          こんな風にインデントすることができる
      
```

左余白の大きさが一致していますね。
大量の半角スペースの正体は、どうやらインデントだったみたいです。


# 解決方法を考える

さて、原因が分かったのはいいですが...
この余白は一体どうやって消したらいいのでしょう。

困った時はGoogle先生に聞いてみましょう。
~~Bing AIでもいいんだけど参考ソース書くのがめんどいから...~~

ということで、「preタグ インデント 削除」でググった結果、以下の方法が出てきました。


## CSSで対処する

こちらの記事を参考にしています。

https://qiita.com/yuikoito/items/fd863cd5c492f7fdf72e

どうやら、`white-space: pre-line;`というCSSを適用させると、余分な余白が消えるそうです。

`white-space`プロパティは、要素内の[ホワイトスペース](https://developer.mozilla.org/ja/docs/Glossary/Whitespace)（改行文字や半角スペースなど）をどのように扱うか決めるCSSプロパティです。
このプロパティに`pre-line`を設定すると、ソース内の改行はそのまま残されますが、ホワイトスペースは詰めて表示されます。

以下はMDNにある`white-space`の説明です。

> このプロパティは 2 つのことを指定します。
> * ホワイトスペースを折り畳むかどうか、およびその方法。
> * 行を自動折り返しの場面で折り返すことができるかどうか。

> `pre-line`
連続するホワイトスペースは詰められて 1 つになります。行の折り返しは、改行文字や`<br>`要素のあるときか、行ボックスを埋めるのに必要なときに行われます。

`white-space`プロパティについて詳しく知りたい方は、MDNをご覧ください。

https://developer.mozilla.org/ja/docs/Web/CSS/white-space

ということで試してみます。

```diff_html
<style>
  pre {
    background-color: skyblue;
+   white-space: pre-line;
  }
</style>
```

こちらのCSSを追加すると、HTMLの表示が以下のように変わります。

![white-space.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/79139480-ba8e-da15-d43e-7027f474eb27.png)

比較用：`white-space`を指定する前

![余白.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/155d4d3b-f994-9faa-2dd4-ecfb19fa97d7.png)

見ての通り、行の最初にあったインデントが**全て**削除されています。


### 問題点

上の画像だと、余白はしっかり消えているのですが、故意にインデントした部分も一緒に消えてしまいました。

```html:こんな風に表示してほしい
これはpreタグ
用途は正直よくわからん
  スペースや改行がそのまま表示されるから、
  こんな風にインデントすることができる
```

故意にインデントすることなんてないという場合は、`white-space: pre-line;`で大丈夫だと思います。


## CSSと特殊文字を組み合わせる

さて、上で言った通り、`white-space: pre-line;`を指定すると行頭のインデントが消え去ります。
では、消える対象として認識されない=ホワイトスペースではない空白文字はないのでしょうか。

あります。空白の特殊文字`&nbsp;`です。

https://www.asoblock.net/contents/nbsp

ということはつまり、インデントしたい箇所に`&nbsp;`を入れればうまく表示されるのではないでしょうか。

```diff_html
<style>
  pre {
    background-color: skyblue;
+   white-space: pre-line;
  }
</style>
```

```diff_html
      </blockquote>
      <pre>
        これはpreタグ
        用途は正直よくわからん
-         スペースや改行がそのまま表示されるから、
-         こんな風にインデントすることができる
+       &nbsp;スペースや改行がそのまま表示されるから、
+       &nbsp;こんな風にインデントすることができる
      </pre>
    </article>
```

これを表示してみます。

![完成形.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/5c72e4b9-63d3-5eea-5323-914cf56af86d.png)

うまく表示されましたね。これがやりたかったのです。


### 問題点

...これでもいいのですが、少しHTMLの視認性が悪くなってしまいました。
気にならないと言われれば気にならないのですが、気になると言われれば気になります。

```diff_html
      </blockquote>
      <pre>
        これはpreタグ
        用途は正直よくわからん
        &nbsp;スペースや改行がそのまま表示されるから、
        &nbsp;こんな風にインデントすることができる
      </pre>
    </article>
```

また、他からコピペしてくる場合、いちいちインデントを`&nbsp;`に置き換えなければいけません。
置き換える作業自体は自動化できると思いますが、その場合その自動化ツールを通すという手間が生まれます。

こういった問題点はありますが、これでも気にならないという方はこれで大丈夫だと思います。


### `&nbsp;`以外のスペース

先ほども載せた[こちら](https://www.asoblock.net/contents/nbsp)のサイトには、`&nbsp;`について以下のような記述があります。

> 実は`&nbsp;`の本来の役割は「半角スペースの代わり」ではありません。本来は「改行の禁止（＝当該空白部分での自動的な改行を防ぐ）」という機能を果たすものだそう。英文などの場合、単語と単語の間は半角スペースで区切られますが、例えば会社名など複数単語でひとつながりのものを改行させずに表示したいときなどに使うのが、`&nbsp;`なのです。

どうやら`&nbsp;`は半角スペースの代わりではないそうです。
ということを考えると、上で挙げた使い方は少し邪道にも思えてきます。

では`&nbsp;`以外に使える文字はあるのか？
上のサイトには以下のような特殊文字が紹介されていました。

> `＆ensp;` 　半角スペースよりやや広いスペース
`＆emsp;` 　全角スペース
`＆thinsp;` 　半角よりさらに小さいスペース

時と場合によりますが、もしかしたらこちらを使った方がいいのかもしれません。

* * *

ちなみに、スペースの代わりにタブ文字（`&#009;`）を使うこともできます。
こちらはCSSの`tab-size`プロパティでインデントのサイズを調整することができたりもします。

こちらも時と場合によりますが、ぜひ使ってみてください。


## preタグのインデントをなくす

よく考えてみれば、余白があるから余白が表示されるのです。
つまり**最初から余白がなければ**うまくいくのではないでしょうか。

ということで、`pre`タグを以下のように編集してみます。

```html
      </blockquote>
<pre>これはpreタグ
用途は正直よくわからん
  スペースや改行がそのまま表示されるから、
  こんな風にインデントすることができる
</pre>
    </article>
  </main>
```

ポイントはこんな感じです。

* `pre`タグは左端につめる
* 最初の行を`<pre>`の直後に持ってくる
* `pre`タグ内でのインデントは、故意に字下げしたい箇所以外では入れない

ということで、これを表示してみます。

![完成形.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/5c72e4b9-63d3-5eea-5323-914cf56af86d.png)

※画像はイメージです

うまく表示されました。これは嬉しいですね。


### 問題点

...うまくいったのはいいですが、正直このHTML読みづらくないですか？
`pre`タグだけ左端にあるHTML、美しくなくないですか？

```html
      </blockquote>
<pre>これはpreタグ
用途は正直よくわからん
  スペースや改行がそのまま表示されるから、
  こんな風にインデントすることができる
</pre>
    </article>
  </main>
```

このHTMLでも気にならない / 問題ないという場合は、この方法で大丈夫だと思います。


## JavaScriptで削除する

今まで紹介した方法には、どれも問題点やデメリットがありました。
ですが、このJavaScriptを使った方法ならば、今まで挙げたようなデメリットがありません。

* 特殊文字を使わなくてもインデントして表示できる
* ソース内の`pre`タグをインデントできる
* 一度コードを書いてしまえば、あとは**何も考えなくていい**

それに加え、JavaScriptなので柔軟性が高いです。
挙動の仕様を自分で決めることができます。

ということで私が考えたのがこちらです。

```js
document.querySelectorAll('pre').forEach(pre => {
  const space = pre.textContent.match(/^[ |\t]*/gm)[0].length;
  const result = pre.textContent.split('\n').map(str => {
    return str.slice(space);
  }).join('\n');
  pre.textContent = result;
});
```

このコードを書いた`script`タグを`</body>`の直前に配置すると、なんと余分な余白を消し去ってくれます。

```html:ソースコード全文
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HTMLファイル</title>
  <style>
    pre {
      background-color: skyblue;
    }
  </style>
</head>
<body>
  <main>
    <article>
      <h1>Hello World!</h1>
      <p>
        これはpタグ<br>
        普通の文章<br>
      </p>
      <a href="">これはaタグ</a>で<code>これはcodeタグ</code>
      <blockquote>
        これはblockquoteタグ<br>
        引用に使われる
      </blockquote>
      <pre>
        これはpreタグ
        用途は正直よくわからん
          スペースや改行がそのまま表示されるから、
          こんな風にインデントすることができる
      </pre>
    </article>
  </main>

  <!-- preタグのインデントを削除 -->
  <script>
    document.querySelectorAll('pre').forEach(pre => {
      const space = pre.textContent.match(/^[ |\t]*/gm)[0].length;
      const result = pre.textContent.split('\n').map(str => {
        return str.slice(space);
      }).join('\n');
      pre.textContent = result;
    });
  </script>
</body>
</html>
```

プレビュー:

![完成形.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3588083/5c72e4b9-63d3-5eea-5323-914cf56af86d.png)

※画像はイメージです

ちなみにこのコードの仕様はこんな感じです。

* **最初の行よりインデントが少ない行はバグる**（仕様）
* タブ文字（`&#009;` `\t`）にも対応しているはず
* 仕組み上タブ文字とスペースが混ざったインデントは未対応


### 上のコードの問題点（仕様）

「最初の行よりインデントが少ない行はバグる」についてですが、例えばこんな感じのHTMLを書くとバグります。

```html
<body>
    <pre>
        ここは1行目 <!-- この行が基準 -->
    ここは2行目
        ここは３行目
ここは４行目
        ここは５行目
            ここは6行目
    </pre>
</body>
```

すると、こんなふうに表示されます。
（画像は面倒だったため、テキストでのお届けになります）

```
ここは1行目 
行目
ここは３行目

ここは５行目
    ここは6行目
```

見ての通り2行目が途中からしか表示されていません。
また、４行目に至っては何もありません。

これは仕様上の問題です。
1行目を基準としてそれ以降の行にインデントがあるかを判断しているため、1行目の最初の文字の位置より左側にあるものは全て消されます。

これに関しては割とどうしようもないです。すみません。
~~まあでもそんな場面滅多にないでしょ...（お祈り）~~


### この方法の問題点

JavaScriptで余白を消す方法にどんなデメリットがあるかですが、正直に言うと私にはわかりません。
一応懸念している点を挙げておきます。

* **エラーが出る可能性**がある、予想外の挙動をする可能性がある
* JavaScriptなので表示が遅くなるかも？（未検証）

上のコードを使用する際や自分でコードを書く場合はご注意ください。


# まとめ

この記事では以下の方法を紹介しました。

|リンク|インデントの表示|HTMLの綺麗さ
|:--|:--|:--|
[CSSで対処する](#cssで対処する) | 不可 | 綺麗 |
[特殊文字と組み合わせる](#cssと特殊文字を組み合わせる) | 特殊文字を使う | 微妙 |
[preタグを左にする](#preタグのインデントをなくす) | HTMLのインデントを反映 | うーん |
[JavaScriptで削除する](#javascriptで削除する) | HTMLのインデントを反映 | 綺麗

時と場合に応じて使い分けてみてください。

* * *

余談ですが、Prism.jsのプラグインにNormalize Whitespaceというものがあります。
Prism.jsというのはHTML上のコードをハイライトするためのライブラリで、Normalize Whitespaceは余分な余白を削除するプラグインです。

もし`pre`タグを使う理由がコードを載せるためな場合<sub>（過去の自分）</sub>、Prism.jsの使用を検討してみてはいかがでしょうか。
余分な余白を消してくれる上、副産物としてコードのハイライトもついてきます。

Prism.jsの導入については過去に記事にしているので、よければ見て見てください。

https://qiita.com/nanasi-1/items/ab3cd94f5627b4e31dea