環境構築からWEBアプリ開発・スマホアプリ開発まで。ときには動画制作やゲームも。

supilog
すぴろぐ

【highlight.js】第3回 コピーボタンを実装する

【highlight.js】第3回 コピーボタンを実装する

第1回からのサンプルコードを引き継いでいます。こちらからご覧あれ。

最近少しずつhighlight.jsを学んでおり、せっかく学んだからには、本サイトのコードブロックもhighlight.jsにて独自実装するように切り替えようかなと思い始めた今日この頃です。おそらくプラグインで動作させているよりも、高速化には一役買ってくれると思うので、そういうメリットもある。

(意外とファイル名表示に助けられてる部分があるので、そこを妥協するかどうか・・)

さて。今回は、コピーボタンを実装する予定だが、調べてみるとライブラリを組み合わせるような手もあるようだ。せっかくなので、JavaScriptを書いて実装してみることにしよう。

まずはHTMLの修正

<pre><code>…</code></pre>タグの内側にボタンを入れたいと思ったので、単純に書いても良かったが、いずれwordpressに導入することを想定すると、単純な追加は微妙なのか。

ビジュアルエディターを使ってコードブロックを使うブロックの仕様自体はwordpress様の仕様だし、根幹機能を強引に修正してもバージョンアップを気にしないといけなくなるので、今回はJSを使ってボタンタグを追加する。

highlight.jsの処理後に記述することにする。

jquery3.6の導入

今後もお世話になるだろうとふんで、jquery3.6を導入。CDNを使う。(jquery公式から以下のようなCDNの記述をコピーして貼り付けるだけ。)

<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>

ボタンの追加

<script>
    $("pre code").append("<button class="copybtn">COPY</button>");
</script>

こうしておけば、コードブロックの最後にbuttonタグが追加されるはずだ。しかし、よく考えたら、前回の対応で、行数の表示をするために、highlight.jsのaddPlugin機能を利用しているので、それを使う手もありあそう。

↑の実装でも可能だが、今回は下記のように対応することにした。追加したのは、5行目の1行のみ。(※ 追加箇所意外は、第2回の内容を踏襲している)

<script>
hljs.addPlugin({
    'after:highlightBlock': ({ block, result }) => {
        result.value = result.value.replace(/^/gm,'<span class="line-number"></span>');
        result.value = `${result.value}<button class="copybtn">COPY</button>`;
    }
});
</script>

見た目の微修正

pre code {
    position: relative;
}
pre code button {
    display: inline-block;
    position: absolute;
    right: 6px;
    top: 6px;
}

荒削りではあるが、最低限の見た目として、少しだけCSSを適用しました。重要なところは、親要素にposition: relativeを指定し、ボタンタグにはposition: absoluteを指定して、位置を調整しているところ。

確認

ここまで書くと、↓の画像のように、コードブロックにコピーボタンが表示されるようになった。

コピー機能の実装

ボタンは作成できたので、ボタンを押した際に、ソースコードをコピーするという機能の実装に入る。

ポイントは

  1. コピーボタンがクリックされたら、親要素のcodeタグを探す。
  2. 中身のテキストをコピーする。

そして、忘れてはいけないのが、ボタンを内側に付けたので、コピー内容からは除外してあげること。(外側につければよかったー)ということで、こんな風なJSを組んでみました。

<script>
$(function(){
    const copybtns = $(".copybtn");
    copybtns.click(function(){
        const selection = window.getSelection();
        const code = this.parentNode;
        selection.selectAllChildren(code);
        selection.extend(code, code.childNodes.length-1);
        document.execCommand('copy');
    });
});
</script>

window.getSelection

ユーザーが選択した文字列を取得するというもので、Selectionオブジェクトが取得できるようだ。ただ、ここでは実際に選択した文字列を取得しているわけではなく、ただSelectionオブジェクトが取得したかっただけ。

というのも、document.execCommand(‘copy’)というクリップボードへのコピーを行う処理の際には、このSelectionオブジェクトがコピー対象になるからだ。

selection.selectAllChildren(code), selection.extend(code, code.childNodes.length-1)

本来document.execCommand(‘copy’)を実行した場合には、ユーザーが選択しているものがコピーされてしかるべき。だが、Selectionオブジェクトを意図的に操作してやることによって、コピー内容を操ることが出来るというわけだ。

Selectionオブジェクトが取得できたので、ここからは無理やりオブジェクトを書き換えにいく。クリックされたボタンの親ノードをcodeという定数に取得済みなので、selectAllChildrenを呼び出すことにより、ノードの内側全てを選択対象としたということである。

selection.extendは選択範囲の拡張を行うもので、終点となるポイントをセットできるようだ。これを使って、実は、自分でノードの最後に追加してある、ボタンタグのノードをコピー対象から外している。

document.execCommand(‘copy’)

無事に、コピー対象範囲を設定できたので、コピーコマンドを実行してあげればOKという具合だ。

ちょっとわかりやすく説明できていないかもしれないので、分からなかった方は各々調べていただくということで、お願いしますm

さいごに

jqueryも使って、出来る限り最小限の労力で実現してみたつもりです。第3回でいよいよ仕上がってきた感じです。第4回は、highlight.js自慢の97スタイルについて、見ていこう。

それでは。