Vue 3 でクリップボードにコピーできるボタンをつくる

こんにちは❗フリーランス・エンジニアの 九保すこひ です。

さてさて、このところLaravelAWSがメインの記事ばかりになってしまい、Vueをほったらかしにしていることに気がつきました😂

実際には開発でバリバリで使ってはいるのですが、「ちょっとちょっと!たまには出演させてよ」と言われそうな気がしたので、今回はVue 3の話題をお届けしたいと思います。

そして、今回の話題は「ユーザビリティ」や「業務効率化」にも関わってくるものです。

それは・・・・・・

ワンクリックで、クリップボードにテキストをコピーできるボタン

です。

例えば、以下のようにメールアドレスが表示されていて「コピー」ボタンをクリックするだけで、自動的にそのメールアドレスがクリップボードにコピーできれば便利ですよね。

つまり、「Ctrl(Command) + c」をワンクリックだけで実行できるようにするというわけです👍

さらに言うと、この機能は1ヶ所で使うというよりはいろんな場所でも使えた方が便利です。

そこで❗

今回は、Vue 3を使って「クリップボード・コピー」できるコンポーネントを作ってみたいと思います。

何かの参考になりましたら嬉しいです😊✨

「おじさん構文は、
感情が伝わりやすいので
続けていきます😊✨」

開発環境: Vue 3、Bootstrap 5(初)

JavaScript でクリップボード・コピーを実装する手順

JavaScriptにはテキストをクリップボードへコピーするためのdocument.execCommand('copy');という命令文があります。

しかし、この命令文はちょっとクセがあって「現在選択されている文字列をクリップボードへコピーする」ことになります。

(もう第2引数にテキストを入れられるようにしてほしいですよね…😫)

分かりやすく言うと、以下のように選択されているテキストがコピーされます。

つまり、その他の部分は自前のJavaScriptで実装しないといけません。

では、JavaScriptの流れをみてみましょう。

  1. まず <textarea></textarea> 要素をつくる
  2. コピーしたい文字列をそこにセットする
  3. textarea をページ本体にセット
  4. textarea のテキストを選択する
  5. document.execCommand(‘copy’); を実行する

では次の項目では実際のコードを見ていきましょう!

コンポーネントをつくる

では、いきなりコードのご紹介です。

<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="p-4">
    <div class="row">
        <div class="col-3">
            <div class="input-group">
                <input type="text" class="form-control" v-model="copyingText">
                <v-copy-to-clipboard v-model="copyingText"></v-copy-to-clipboard>
            </div>
        </div>
    </div>
    <hr>
    <input type="text" placeholder="ペーストテスト用">
</div>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue@3.1.1/dist/vue.global.prod.js"></script>
<script>

    const copyToClipboardComponent = {  // 👈 ここがコンポーネント本体です
        props: {
            modelValue: {
                type: String,
                default: ''
            }
        },
        data() {
            return {
                copying: false
            }
        },
        methods: {
            onClick() {

                let textarea = document.createElement('textarea');
                textarea.value = this.modelValue;
                textarea.style.top = '-100px';          // 👈 ここの設定で
                textarea.style.maxHeight = '100px';     // 👈 画面では見えないよう
                textarea.style.position = 'absolute';   // 👈 外へ追いやる
                document.body.appendChild(textarea);

                textarea.select();
                document.execCommand('copy');
                document.body.removeChild(textarea); // 👈 もう不要なので削除する

                this.changeLabel();

            },
            changeLabel() { // コピー完了が分かりやすいようにボタン表示を一時的に変更します

                this.copying = true;
                const originalLabel = this.$refs['copy-button'].innerHTML;
                this.$refs['copy-button'].innerHTML = '完了&#x1F44D;';

                setTimeout(() => {

                    this.$refs['copy-button'].innerHTML = originalLabel;
                    this.copying = false;

                }, 1000); // 👈 1秒後に元へ戻す

            }
        },
        computed: {
            getClasses() {

                let classes = ['btn'];

                if(this.copying === true) { // 👈 コピーしているときのクラス

                    classes.push('btn-success');

                } else {

                    classes.push('btn-primary');

                }

                return classes;

            }
        },
        setup() {

            return {
                button: Vue.ref(null)
            };

        },
        template: `
          <button ref="copy-button" type="button" :class="getClasses" @click="onClick">コピー</button>
        `
    };

    Vue.createApp({
        data() {
            return {
                copyingText: 'ここの文字列がコピーされます'
            }
        },
    })
    .component('v-copy-to-clipboard', copyToClipboardComponent) // 👈 コンポーネントを有効にする
    .mount('#app');

</script>
</body>
</html>

まず、コンポーネントにmodelValueというプロパティをつけていますが、これはv-modelと対応しているので、以下のように使用することができるようになります。

<v-copy-to-clipboard v-model="copyingText"></v-copy-to-clipboard>

copyingTextは、data(){ ... } などで用意しておいてください。

クリップボードへのコピーをする部分

また、ボタンがクリックされたときに実行されるのがonClickで、この中で先ほど紹介した「JavaScript でクリップボード・コピーを実装する手順」が入っています。

ちなみに、テキストエリアのスタイルシートは「表示位置を画面外に追い出す」、つまり画面には一切変更がないようにするためにセットしています。

イメージは以下になります。

見た目を変更する部分

見た目がそのままだと「本当にコピーができたかどうかが分からない」ので、changeLabel()内でボタンの見た目(テキスト&CSS)が変更されるようしています。

なお、なぜcopyingの値が切り替わると見た目まで変わるかというと、そのすぐ下にあるgetClasses()に影響を与えるからです。CSSクラスの切り替えはここで行っています。

テストしてみる

では、今回はシンプルですがこの状態でテストしてみましょう❗

ブラウザで開いて「コピー」ボタンをクリックし、さらに「Ctrl + V」で貼り付けしてみます。

これを実行すると・・・・・・

ボタンの装飾が変化しました。
そして、

はい❗

ボックスが小さいのですべて表示されていませんが、うまく貼り付けすることができました。

もちろん、1秒後にはもとのボタンに戻ります。

成功です😊

では、次に入力ボックスの中身を変更してから同じようにテストしてみましょう。

どうなるでしょうか・・・・・・??

はい❗コピー内容が変更されました👍

ちなみに: Bootstrap のモーダル内ではうまくいきません(対処法あり)

実はBootstrap(4, 5 で確認済み)のモーダルが表示されているときは、ページ本体にフォーカスができないようになっているようで、そのため、document.body.appendChild()したテキストエリアへのアクセスができなくなってしまいます。

これに対応させるためには、モーダル内に textarea をセットしてあげるとうまくいきます。

<div class="modal" tabindex="-1">
    <div class="modal-dialog">
        
        <!-- 省略 -->

    </div>
    <!-- 👈 ココに textarea を入れればいい -->
</div>

では、これが実現できるように、コンポーネントにcopy-targetというプロパティを追加し、挿入する場所を指定できるようにしてみましょう。

const copyToClipboardComponent = {
    props: {
        modelValue: {
            type: String,
            default: ''
        },
        copyTarget: { // 👈 ここを追加しました
            type: String,
            default: ''
        }
    },

    // 省略

    methods: {
        onClick() {

            let textarea = document.createElement('textarea');
            textarea.value = this.modelValue;
            textarea.style.top = '-100px';          // ここの設定で
            textarea.style.maxHeight = '100px';     // 画面では見えないよう
            textarea.style.position = 'absolute';   // 外へ追いやる
            let appendingTarget = (this.copyTarget === '') // 👈 挿入場所が指定されている場合はそこにする
                ? document.body
                : document.querySelector(this.copyTarget)

            appendingTarget.appendChild(textarea);

            textarea.select();
            document.execCommand('copy');
            appendingTarget.removeChild(textarea); // もう不要なので削除する

            this.changeLabel();

        },

// 以下省略

では、実際の使い方です。

<div id="app">

    <!-- モーダル -->
    <div id="modal" class="modal" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">クリップボード・コピーのテスト</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <p v-text="copyingText"></p>
                    <hr>
                    <input type="text" placeholder="ペーストテスト用">
                </div>
                <div class="modal-footer">
                    <v-copy-to-clipboard copy-target="#modal" v-model="copyingText"></v-copy-to-clipboard>
                </div>
            </div>
        </div>
    </div>

</div>

まず、モーダル本体にidをつけて、コンポーネントにその場所(セレクタ)を指定するだけです。

では、こちらもテストしてみましょう❗
同じくボタンをクリックして、そのまま「Ctrl + V」で貼り付けをしてみます。

どうなったでしょうか・・・・・・??

はい❗
こちらも(全て表示されていませんが)うまく貼り付けすることができました。

全て成功です😊✨

企業様へのご提案

ワンクリックでのコピーは、一回だけではそれほどプラスになることはないかもしれません。

ただ、何度何度もコピー&ペーストする作業においては、その回数分の時間節約することができ、結果として業務の効率化が実現できるかと思います。

もし今回の機能だけでなく、作業効率アップのためのユーザビリティに興味があるようでしたら、ぜひお問い合わせからご連絡ください。

どうぞよろしくお願いいたします。m(_ _)m

デモページを用意しました

今回開発したコードのデモページを用意しました。
ぜひお試しください。

📝 デモページ:

1.基本のクリップボード・コピー

2.Bootstrap モーダルに対応したもの

おわりに

ということで、今回はVue 3を使って「クリップボードにコピーができる」コンポーネントを作ってみました。

ちなみに、今回きちんとBootstrap 5を使ってみましたがやはりjQueryを使わなくなった影響で、コード量が増えている部分が出てきました。

例えば(記事には入っていませんが)実際に書いたコードでいうと、モーダルを自動で開く部分です。

mounted() {

    const modal = new bootstrap.Modal(document.querySelector('#modal'));
    modal.show();

}

以前でしたら、$('#modal').modal('show');というようにすることができましたが、Bootstrap 5からは、クラスを使うようになったため若干コードが増えています。また、$のショートカットはやはり便利だと感じました。

なお、Vue + Bootstrap 5を使う場合はdata変数にmodalを入れておき、this.modalを使ってアクセスすればよさそうです。

ぜひBootstrap 5も一度体験してみてくださいね。

ではでは〜❗


「髪の毛の乾かし方を変えたら、
クセッ毛が治ってきたかも❗❓」

開発のご依頼お待ちしております 😊✨ お問い合わせ
また、こちらもお待ちしております。
  • 実案件の開発サポート: 詳細
  • ツイッターのフォロー: 詳細
  • 投げ銭のご支援: 詳細
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly