コードブロックに折り返しボタンを実装する

ブログやサイトなどにソースコードを載せる際、行を要素の端で折り返すか、それとも折り返さずに横スクロールありで表示するかで迷ったことはありませんか?

筆者はどちらかというと折り返さない派なんですが、折り返したほうが見やすいという方も当然いるわけです。折り返しのオン・オフを切り替えるボタンがあったらユーザーフレンドリーなのでは?と思うようになりました。

そこで、pre 要素と code 要素を用いたコードブロックに対して、折り返しボタンを実装する方法を考えてみました。

アイキャッチ
この記事の目次

CSS でソースコードを折り返す方法

CSS を用いてソースコードを折り返すには、prewhite-space: pre-wrap を指定します。

pre {
  white-space: pre-wrap;
}

white-space は、要素内で空白や改行の扱いを指定するプロパティです。

pre 要素にはデフォルトで white-space: pre が指定されているため、空白や改行がそのまま適用され、折り返しはされません。コードが長くなると横スクロール、もしくは親要素からはみ出でて表示されます。

そこで pre 要素に white-space: pre-wrap を指定すると、改行や空白をそのまま表示しつつ、要素の端に達すると自動的に行が折り返されます。これによりソースコードの折り返しが実現できます。

折り返しボタンの実装

ソースコードの折り返しボタンを JavaScript と CSS で実装していきます。

JavaScript

まず、JavaScript でコードブロックに折り返しボタンを付与します。以下のコードを </body> タグの直前に貼り付けてください。

<script>//<![CDATA[
(function(){
  // ページ内のすべての pre を取得
  const pres = document.querySelectorAll('pre');

  // pre がない場合なにもせず終了
  if(pres.length == 0) return;

  pres.forEach(pre => {
    // pre 内の code を取得
    const code = pre.querySelector('code');

    // 折り返しボタンを作成
    const wrapBtn = document.createElement('button');
    wrapBtn.className = 'wrap-button';
    wrapBtn.textContent = '折り返し';
    wrapBtn.title = wrapBtn.ariaLabel = 'コードを折り返す';

    // ボタンのクリックイベントを設定
    wrapBtn.addEventListener('click', wrap_code, false);

    // ボタンを pre の先頭に追加
    pre.prepend(wrapBtn);
  })
})();

function wrap_code(){
  // クリックされたボタンの親要素 pre を取得
  const pre = this.closest('pre');

  // .wrap のオン・オフを切り替え
  pre.classList.toggle('wrap');
}
//]]></script>

pre 要素に対して prepend() で折り返しボタンを追加します。prepend() は指定した要素内の先頭に要素を追加するメソッドのため、pre 要素の先頭(code 要素の直前)にボタンが表示されるようになります。

反対に pre 要素の末尾(code 要素の直後)にボタンを追加したいときは、append()appendChild() を使います。

// append() を使う場合
pre.append(wrapBtn);

// appendChild() を使う場合
pre.appendChild(wrapBtn);

(ちなみに prependChild() は JavaScript には存在しないようです。謎……)

ボタンがクリックされると、wrap_code() 関数が呼び出されます。この関数では、まず closest() を使って、クリックされたボタンの親である pre 要素を取得します。closest() は指定されたセレクタに一致する最も近い祖先要素もしくは自分自身を探すメソッドです。

次に、classList.toggle() を用いて、.wrappre 要素に追加または削除します。toggle() メソッドは要素にクラスが存在する場合は削除し、存在しない場合は追加するため、ボタンをクリックするたびに .wrap の付け外しが行われます。

この .wrap の有無により、コードブロックの表示が変わります。.wrappre 要素に追加されると、後述する CSS の設定で white-space: pre-wrap が適用され、コードが要素の幅に合わせて折り返されます。反対に .wrap がない場合は、横スクロールで表示されるようになります。

CSS

次に、ソースコードの折り返しとボタンのデザインに関する CSS を指定します。以下のコードを head 内に貼り付けてください。

<style>/*<![CDATA[*/
pre{
  position: relative;
  margin: 24px 0;
  padding: 0;
  line-height: 1;
  font-size: 14px;
  background: #eee;
  color: #333;
  word-break: break-all;
}
pre.wrap{
  white-space: pre-wrap;
}
pre code{
  display: block;
  margin: 0;
  padding: 32px 16px 16px;
  line-height: 1.4;
  overflow: auto;
}
.wrap-button{
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  appearance: none;
  -moz-appearance: none;
  -webkit-appearance: none;
  margin: 0 auto;
  padding: 0 16px;
  height: 24px;
  border: none;
  border-radius: 0;
  line-height: 1;
  font-weight: bold;
  background: #666;
  color: #fff;
  cursor: pointer;
}
.wrap-button:hover{
  background: #999;
}
pre:not(.wrap) .wrap-button::after{
  content: 'オフ';
}
pre.wrap .wrap-button::after{
  content: 'オン';
}
/*]]>*/</style>

まず、ソースコードをデフォルトで折り返さず表示させるために、code 要素に overflow: auto を指定します。pre 要素に同様の指定をすると、右上に固定したはずのボタンが横スクロールの際に一緒に流れていってしまうので注意が必要です。

次に、.wrap が追加された pre 要素に対して white-space: pre-wrap を適用します。前述の通り、このクラスの付け外しによってコードの折り返す・折り返さないが決まります。

最後に、.wrap-button でボタンのデザインを指定し、コードブロックの右上に固定表示させています。ボタンの内部テキストの一部に ::after 擬似要素を用い、ボタンに最初から表示されている「折り返し」という文字列の隣に .wrap がついていない場合は「オフ」、ついている場合は「オン」と表示させます。

ここまでの作業を行い、以下の画像のようにボタンをクリックするたびにコードの折り返しが変化したら完成です!

コードブロックの折り返しがオフのとき。右上のボタンに「折り返しオフ」と表示されている
コードブロックの折り返しがオフのとき
コードブロックの折り返しがオンのとき、右上のボタンに「折り返しオン」と表示されている
コードブロックの折り返しがオンのとき

あとがき

ソースコードを折り返す・折り返さない問題に終止符を打つべく、コードブロックに折り返しボタンを実装する方法を紹介しました。

Web 制作関連の調べ物をするときによくお世話になっている Zenn のコードブロックに折り返しボタンが実装されているのに気が付いて、自分のブログにもこれ欲しいなーと思ったので方法を考えてみました。Zenn がどういうコードで折り返しボタンを実装しているのかはわからないんですが、クラスの付け外しで折り返しを制御している点は同じみたいなので、自分が書いたコードとそう遠くはないはずです。

この記事がコードを折り返すか否かで迷っている方の手助けになれば幸いです。ここまでお読みいただきありがとうございました!

編集
ホーム