はてなブログ: JavaScriptを活用したコードブロックのUX/UI向上術

この記事では、JavaScriptを活用して、コードブロック内のテキストを折り返し可能にし、かつコピー機能を実装する手法に焦点を当てています。フロントエンドデザインにおいて有益なテクニックを紹介し、具体的なコーディング方法も解説していきます。

javaScriptのイラスト



今回ご紹介するのは、preタグを使ったコード表記に折り返しとコピーボタンを生成するスクリプトです。

Markdown

   ```markdown
      ここにコードを書く
   ```

はてな記法(はてなブログのみ)

   >|hatena|
      ここにコードを書く
   ||<

Markdownやはてな記法で上記のように記述すると、以下のようなHTML構造が生成されます。

HTML

<pre class="code lang-markdown" data-lang="markdown" data-unlink="">
   ここにコードを書く
</pre>

このように生成された"code"というクラスを持つpreタグ( <pre class="code"> )をJavaScriptコードで検出し、HTML構造に要素を追加し、コードの折り返し・コピー機能を実装します。

デモページ

デモページはこちらです。

コードと解説

①head内にCSSコードを追加

document.addEventListener( "DOMContentLoaded", function()
{
    var e = document.createElement( "style" );
    e.innerHTML = '.entry-content pre.code{white-space: pre}.entry-content div.code-box a{text-decoration: unset}';
    document.getElementsByTagName( "head" )[ 0 ].appendChild( e );
} );

このコードはhead内に動的にCSSスタイルを追加するスクリプトです。以下、詳細な解説です。


  1. document.addEventListener("DOMContentLoaded", function () { ... });:

    • ページがDOM(Document Object Model)の構築を完了した時点で発生するDOMContentLoadedイベントに対するリスナーを追加しています。
    • これにより、ページが読み込まれた後にコードが実行されます。

      スクリプトの実行タイミングが、DOMが完全に読み込まれて解析された後になるため、挙動が安定します。

  2. var e = document.createElement("style");:

    • 新しいstyle要素を作成しています。この要素は、CSSスタイルを動的に追加するために使用されます。
  3. e.innerHTML = '.entry-content pre{white-space: pre}.entry-content a {text-decoration: unset}';:

    • style要素のinnerHTMLプロパティを設定して、特定のCSSスタイルルールを追加しています。
    • このルールは、pre.code(codeクラスを持つpreタグ)とdiv.code-box a(code-boxクラスを持つdivタグ内の「a」タグ)というセレクタに対して、それぞれwhite-space: pre(コードブロック内の空白を維持する)およびtext-decoration: unset(リンクの下線を取り除く)のスタイルを適用します。
    • white-space: preの指定によりそのpreタグ内は、

      • HTML内の改行や半角スペースはブラウザ表示にそのまま反映され、
      • 改行のない行は自動では折り返されません

      これを初期の値とし、のちのコードでテキストエリアの折り返しON/OFFを切り替えます。

  4. document.getElementsByTagName("head")[0].appendChild(e);:

    • head要素を取得し、appendChildメソッドを使用して新しく作成したstyle要素をhead要素に追加しています。
    • これにより、ページが読み込まれたときに動的にCSSスタイルが追加されます。


総じてこのコードは、ページが読み込まれた時点で指定したCSSスタイルを.entry-content pre.code.entry-content div.code-box aに適用します。

②preタグにHTMLコードを追加

const codeElements = document.querySelectorAll( 'pre.code' );
codeElements.forEach(
    ( codeElement ) =>
    {
        const codeContainer = document.createElement( 'div' );
        codeContainer.className = 'code-box';
        const copyButton = document.createElement( 'button' );
        copyButton.className = 'code-copy-btn';
        copyButton.textContent = 'Copy';
        codeElement.parentNode.insertBefore( codeContainer, codeElement );
        codeContainer.appendChild( codeElement );
        codeContainer.appendChild( copyButton )
    }
);

このコードは、

  1. "code" というクラスの付いたpreタグを取得して
  2. <div class="code-box">で囲み、
  3. </pre>タグの直後には<button class="code-copy-btn">というボタン要素を追加する

スクリプトです。以下、詳細な解説です。


  1. **const codeElements = document.querySelectorAll('pre.code');**:

    • pre要素でクラスが "code" となっているものをすべて取得します。
  2. codeElements.forEach((codeElement) => { ... });:

    • 取得した各コードブロックに対して、以下の処理を繰り返します。

      余談ですが「For Each」はVBA(マクロ)でも繰り返しの構文です。

  3. const codeContainer = document.createElement('div');:

    • 新しい div 要素を作成し、これをコードブロックを包むコンテナとして使用します。
  4. codeContainer.className = 'code-box';:

    • 作成した div 要素に "code-box" というクラスを追加します。これは後でスタイリングや識別のために使用されます。
  5. const copyButton = document.createElement('button');:

    • 新しい button 要素を作成します。これはコピー機能を提供するボタンとして使用されます。
  6. copyButton.className = 'code-copy-btn';:

    • 作成したボタンに "code-copy-btn" というクラスを追加します。これもスタイリングや識別のためです。
  7. copyButton.textContent = 'Copy';:

    • ボタンの表示テキストを 'Copy' に設定します。
  8. codeElement.parentNode.insertBefore(codeContainer, codeElement);

    • 新しく作成したコードコンテナ(<div class="code-box">)をコードブロックの親要素(<pre>~</pre>)の前に挿入します。
  9. codeContainer.appendChild(codeElement);:

    • <div class="code-box">~</div>内に元のコードブロック(<pre>~</pre>)を移動させます。これにより、コードブロックが新しく作成されたコンテナ内に収められます。
  10. codeContainer.appendChild(copyButton);:

    • コードコンテナ内に作成したコピー用ボタンを追加します。


総じてこのコードは、各コードブロックに対して新しいコンテナとコピー機能を追加し、ページ上でコードの表示とコピーが簡単になるようにします。この時点ではコピー機能は未実装です。

③コードブロックに折り返し機能を追加

var preElements = document.querySelectorAll( ".code-box pre" );
preElements.forEach( function( e )
{
    if ( e.scrollWidth > e.clientWidth )
    {
        var t = document.createElement( "div" );
        t.className = "code-box", t.innerHTML = '<a href="#" onclick="return false;" style="display: flex; align-items: center;"><i class="fa-solid fa-toggle-off"></i>折り返しON', e.parentNode.insertBefore( t, e.parentNode.firstChild );
        var n = t.querySelector( "a" );
        t.addEventListener( "click", function()
        {
            return "auto" === e.style.overflowX ? ( e.style.overflowX = "visible", e.style.whiteSpace = "pre", n.innerHTML = '<i class="fa-solid fa-toggle-off"></i>折り返しON' ) : ( e.style.overflowX = "auto", e.style.whiteSpace = "pre-wrap", n.innerHTML = '<i class="fa-solid fa-toggle-on"></i>折り返しOFF' ), !1
        } )
    }
} );

このコードは、

  1. .code-box preを持つコードブロックが
  2. 水平方向にスクロールバーを持っているかどうかを確認し、
  3. スクロールバーがある場合に
  4. 折り返し機能をトグルするリンクを追加する

スクリプトです。以下、詳細な解説です。


コードブロックの取得:

var preElements = document.querySelectorAll(".code-box pre");
  • .code-box preクラスを持つすべてのpre要素を取得します。

各コードブロックに対する処理:

preElements.forEach(function (e) {...});
  • 各コードブロックに対して以下に続く処理を繰り返します。

水平スクロールバーの有無を確認:

if (e.scrollWidth > e.clientWidth) {...}
  • もしコードブロックの横幅が表示領域の横幅よりも大きい場合(=水平スクロールバーが存在する場合)、以下に続く処理を実行します。

UI要素の作成と追加:

var t = document.createElement("div");
t.className = "code-box", t.innerHTML = '<a href="#" onclick="return false;" style="display: flex; align-items: center;"><i class="fa-solid fa-toggle-off"></i>折り返しON', e.parentNode.insertBefore(t, e.parentNode.firstChild);
  • 新しいdiv要素(t)を作成し、これに対してクラス( code-box )やHTML( <a href="#" onclick="return false;" style="display: flex; align-items: center;"><i class="fa-solid fa-toggle-off"></i>折り返しON )を設定します。
  • その後、作成したdiv要素をコードブロックの親要素の最初の子要素( firstChild )として追加します。

UI要素内の要素の取得:

var n = t.querySelector("a");
  • 作成したdiv要素内のa要素を取得します。

クリックイベントリスナーの追加:

t.addEventListener("click", function() {...});
  • div要素(UI要素)にクリックイベントリスナーを追加します。クリックイベントリスナーとは、ボタンやリンクなどがクリックされたときに実行される関数です。

折り返し機能のトグル:

return "auto" === e.style.overflowX ? ( e.style.overflowX = "visible", e.style.whiteSpace = "pre", n.innerHTML = '<i class="fa-solid fa-toggle-off"></i>折り返しON' ) : ( e.style.overflowX = "auto", e.style.whiteSpace = "pre-wrap", n.innerHTML = '<i class="fa-solid fa-toggle-on"></i>折り返しOFF' ), !1


総じてこのコードは、コードブロックに水平スクロールバーが存在する場合に、ユーザーがクリックすることで折り返し機能をON/OFFできるUIを追加しています。

④コードブロックにコピー機能を追加

var copyButtons = document.querySelectorAll( ".code-copy-btn" );
copyButtons.forEach( function( e )
{
    e.addEventListener( "click", function()
    {
        var t = e.previousElementSibling.textContent
            , n = document.createElement( "textarea" );
        n.value = t, document.body.appendChild( n ), n.select(), document.execCommand( "copy" ), document.body.removeChild( n ), e.textContent = "Copied!", setTimeout( function()
        {
            e.textContent = "Copy"
        }, 2e3 )
    } )
} );

このコードは、ボタンがクリックされたときにテキストをクリップボードにコピーするスクリプトです。以下、詳細な解説です。


  1. querySelectorAll(".code-copy-btn"):
    • クラスが "code-copy-btn" であるすべての要素(ボタン)を取得します。
  2. copyButtons.forEach( function( e ):
    • 各ボタンに対し繰り返しメソッドでクリックイベントリスナー( addEventListener( "click", function() )を追加します。
  3. previousElementSibling:
    • クリック時に、クリックされたボタン( button )の前にある要素( previousElementSibling )を取得します。
  4. createElement( "textarea" ):
    • 取得したテキストをコピーするために、一時的なテキストエリアを作成し、その中にテキストを設定( n.value = t )します。
  5. document.execCommand("copy"):
    • テキストエリア内のテキストをクリップボードにコピーします。
  6. document.body.removeChild( n ):
    • テキストエリア( n )をページ( document.body )から削除( .removeChild(n) )します。
  7. e.textContent = "Copied!":
    • ボタンの表示を "Copied!" に変更して、ユーザーにコピーが完了したことを示します。
  8. setTimeout( function() { e.textContent = "Copy" }, 2e3
    • 一定時間(ここでは2秒)が経過した後に、ボタンの表示を "Copy" に戻します。2e3 は指数表記で、2 * 10^3(10の3乗)と解釈されます。つまり、2e3 は 2 * 1000 と同じで、2e3 は2秒(1秒=1000ミリ秒)を表します。


総じてこのコードは、ボタンがクリックされるとそのボタンの前にあるテキストがクリップボードにコピーされ、ボタンの表示も一時的に変更されます。

minify化したまとめコード

解説した4つのJSコードをまとめました。ご自身のブログで利用する場合、<script>~</script>タグでコードを囲み、フッタや記事下などに貼り付けてください。

クリックでコピー

Font Awesome

コードでは、切り替えボタンにFont Awesomeを利用していますが、これははてなアイコンでも同様のアイコンがあり、代用可能です。

Icon HTML Code Icon Name
<i class="fa-solid fa-toggle-on"></i> Font Awesome
<i class="blogicon-toggle-on"></i> Hatena
<i class="fa-solid fa-toggle-off"></i> Font Awesome
<i class="blogicon-toggle-off"></i> Hatena

Font Awesomeをまだ設定していない場合、以下のコードを<head>要素もしくは記事内に追加することで、Font Awesomeが利用可能になります。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"/>

fontawesome.com

Font Awesomeを利用したくない、という場合は以下のコードを利用してください。

クリックでコピー(はてなアイコン仕様)

おわりに

コードブロックの使いやすさを向上させるためのテクニックを学びました。折り返し機能やコピー機能の実装は、読者の利便性を高めるだけでなく、ブログ全体のユーザーエクスペリエンスを向上させる重要なステップです。

これらの手法を活用して、より魅力的で使いやすいブログを構築しましょう~