JavaScriptでボタン連打を防止する方法10選

JavaScriptでボタン連打防止する方法JS
この記事は約22分で読めます。

※本記事のコンテンツは、利用目的を問わずご活用いただけます。実務経験10000時間以上のエンジニアが監修しており、基礎知識があれば初心者にも理解していただけるように、常に解説内容のわかりやすさや記事の品質に注力しております。不具合・分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。(理解できない部分などの個別相談も無償で承っております)
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)


はじめに

ボタンを連打すると、意図しない動作を引き起こすことがあります。

例えば、フォームの送信ボタンを何度もクリックしてしまい、データが重複して登録されてしまうなんてことも。

こういったボタン連打による問題は、ユーザーにとって大変ストレスフルな体験になります。

ただ、JavaScriptを使えば、簡単にボタンの連打を防止できます。

今回は、実務でも使える連打防止のテクニックを10個厳選してお届けします。

コピペですぐ使えるサンプルコードも満載ですよ。

これであなたも、ユーザーにやさしいUIを提供できるはず。

ページの完成度もアップして、きっとチームメイトにも褒められちゃいますよ。

さあ、一緒にボタン連打問題に立ち向かっていきましょう!

○ボタン連打による問題点

まず、ボタンの連打によってどんな問題が起きるのか、具体的に見ていきましょう。

一番よくあるのが、フォームの重複送信です。

ユーザーが送信ボタンを何度もクリックしてしまうと、同じデータが何個も登録されてしまいます。

データの整合性が取れなくなるのは目に見えていますよね。

また、決済処理では致命的な事態を招きかねません。

二重決済なんて事態になったら、ユーザーからのクレームは免れません。

他にも、連打によって意図しないページ遷移が発生したり、アニメーションが何度も再生されたりと、ユーザビリティを大きく損ねる問題が山積みなのです。

○なぜ連打防止が必要?

こうしたボタン連打による問題を放置していると、ユーザーからの信頼を失いかねません。

「また同じ失敗するんじゃないか」と不安に思われては、サービスの評判にも傷がつきます。

また、エンジニアにとっても連打対策は必須のスキルです。

バグが多発したり、無駄な問い合わせ対応に追われたりと、生産性の低下は目に見えています。

かといって連打対策の優先度は後回しにされがち。

でも、ちょっとした工夫でグッとUXは向上するんです。

いつかやろうと思っているあなた、今こそ連打対策に取り組むべきです!

●JavaScriptでのボタン連打防止

さて、JavaScriptを使ったボタン連打防止の具体的な方法に移りましょう。

ここからは本題です。

みなさん、シートベルトの装着はお済みですか?

JavaScriptには、ボタンの連打を防ぐためのいくつかのテクニックがあります。

disabled属性を使ったり、フラグ変数で制御したり、時間差で無効化したり。

どれも、ある程度のロジックが必要になりますが、一度理解してしまえば簡単に実装できるはずです。

○disabledを使う方法

まずは、disabled属性を使ってボタンを無効化する方法から見ていきましょう。

これは、ボタンがクリックされた瞬間にdisabled属性を付与し、ボタンを無効化するというアプローチです。

□サンプルコード1:クリック時にdisabled

具体的なコードを見てみましょう。

こんな感じです。

const btn = document.getElementById('submitBtn');

btn.addEventListener('click', function() {
  // ボタンをdisabledにする
  this.disabled = true;

  // フォームのサブミット処理など
  // ...
});

ボタンがクリックされたら、即座にthis.disabled = trueでボタンを無効化しています。

これで、ボタンが連打されても、2回目以降のクリックは無視されるようになります。

ただ、この方法だと、処理が終わった後もボタンが無効化されたままになってしまいます。

再度ボタンを押せるようにするには、非同期処理の完了後にdisabled属性を外す必要があります。

□サンプルコード2:非同期処理後にdisabled解除

非同期処理の完了後にdisabled属性を外すコードは、こんな感じです。

const btn = document.getElementById('submitBtn');

btn.addEventListener('click', function() {
  this.disabled = true;

  someAsyncProcess()
    .then(() => {
      // 非同期処理の完了後にdisabledを解除
      this.disabled = false;
    });
});

someAsyncProcess()という非同期処理が完了した後、thenのコールバック内でthis.disabled = falseとしてdisabled属性を外しています。

これで、再びボタンが押せるようになります。

○フラグ変数を使う方法

続いて、フラグ変数を使ってボタンの連打を制御する方法を見ていきましょう。

この方法は、「処理中フラグ」という変数を用意して、処理中はボタンのクリックを受け付けないようにするものです。

□サンプルコード3:処理中フラグでの制御

コードはこんな感じになります。

const btn = document.getElementById('submitBtn');
let isSubmitting = false;

btn.addEventListener('click', function() {
  if (isSubmitting) return;

  isSubmitting = true;

  someAsyncProcess()
    .then(() => {
      isSubmitting = false;
    });
});

isSubmittingという処理中フラグを用意し、初期値はfalseにしておきます。

そして、ボタンがクリックされたら、isSubmittingがtrueの場合は何もせずにreturnします。

これが、処理中のクリックを無視するためのポイントです。

処理が開始されたら、isSubmittingをtrueにセットし、処理中であることを表します。

そして、非同期処理の完了後に再びisSubmittingをfalseに戻すことで、次のクリックを受け付けられる状態にします。

○時間差で無効化する方法

最後は、時間差でボタンを無効化する方法です。

これは、ボタンがクリックされたら一定時間ボタンを無効化し、その間のクリックは無視するという方法です。

□サンプルコード4:一定時間ボタンを無効化

コードはこんな感じになります。

const btn = document.getElementById('submitBtn');

btn.addEventListener('click', function() {
  this.disabled = true;

  setTimeout(() => {
    this.disabled = false;
  }, 2000);

  // フォームのサブミット処理など
  // ...
});

ボタンがクリックされたら、即座にthis.disabled = trueでボタンを無効化します。

そして、setTimeout()を使って2000ミリ秒(2秒)後にthis.disabled = falseとし、ボタンを再び有効化します。

この方法なら、非同期処理の完了を待つ必要がないので、コードがシンプルになります。

ただ、処理時間が2秒より長くかかる場合は、処理中もボタンが有効になってしまうので注意が必要です。

●jQueryでのボタン連打防止

jQueryを使ったプロジェクトなら、ボタンの連打防止もお手の物です。

jQueryには、連打対策に役立つメソッドがいくつも用意されています。

例えば、クリックイベントを一度きりにしたり、連打時の処理をキャンセルしたり。

どちらも、シンプルなコードで実現できるんです。

さあ、jQueryならではの連打防止テクニックを、じっくり見ていきましょう。

きっと、明日からの開発ですぐに使えるはずです。

○one()メソッドを使う

jQueryの「one()」メソッド、聞いたことありますか?

これは、あるイベントを一度だけ発火させるためのメソッドなんです。

ボタンの連打防止に使えば、ボタンがクリックされた瞬間に処理を実行し、それ以降のクリックは無視することができます。

□サンプルコード5:one()での一回限りのイベント登録

コードを見てみましょう。

こんな感じです。

$('#submitBtn').one('click', function() {
  // フォームのサブミット処理など
  // ...
});

$(‘#submitBtn’)でボタン要素を取得し、one(‘click’, …)でclickイベントを一度だけ登録しています。

これで、2回目以降のクリックは無視されるようになります。

ただ、この方法の欠点は、処理が完了した後もボタンがクリック不可の状態が続くこと。

必要に応じて、非同期処理後にイベントを再登録する工夫が必要ですね。

○連打時の処理をキャンセルする

「ボタンの連打を検知したら、2回目以降の処理をキャンセルする」というアプローチもあります。

jQueryなら、こんなふうに書けます。

□サンプルコード6:イベント発生のキャンセル

let isSubmitting = false;

$('#submitBtn').on('click', function(event) {
  if (isSubmitting) {
    event.preventDefault();
    return;
  }

  isSubmitting = true;

  someAsyncProcess()
    .then(() => {
      isSubmitting = false;
    });
});

isSubmittingという処理中フラグを用意し、処理中の場合はevent.preventDefault()でイベントの発生をキャンセルしています。

そして、非同期処理の開始時にisSubmittingをtrueにセットし、完了時にfalseに戻すことで、連打をコントロールしているんです。

処理中フラグの考え方は、JavaScriptの例でも登場しましたね。

馴染みのあるロジックだと思います。

jQueryを使うと、こんなふうにシンプルなコードでボタンの連打を防げるんです。

でも、実際のプロジェクトでは、もう少し工夫が必要なケースも多いはず。

例えば、Ajaxを使った通信処理と連打防止を組み合わせたい場合は、どんなコードになるでしょうか。

次は、そんな実践的なシチュエーションに対応したテクニックを見ていきましょう。

●Ajaxでのボタン連打防止のコツ

Ajaxを使った非同期通信は、Webアプリケーションではもはや必須の機能ですよね。

でも、その便利なAjaxでも、ボタンの連打にはちょっと注意が必要なんです。

例えば、Ajaxのリクエストを送信した直後にボタンを連打されたら、どうなるでしょうか?

最悪の場合、サーバーに同じリクエストが何度も送られてしまうかもしれません。

こんなトラブルを防ぐには、Ajaxの処理中は適切にボタンを無効化することが大切。

ここからは、そのためのコツを2つ紹介しましょう。

○サンプルコード7:Ajax開始時と完了時の処理

まずは、Ajaxのリクエストを送信する前にボタンを無効化し、レスポンスを受け取ったら再び有効化する方法です。

こんなコードになります。

$('#submitBtn').on('click', function() {
  const $this = $(this);
  $this.prop('disabled', true);

  $.ajax({
    url: '/api/some-process',
    method: 'POST',
    data: { ... }
  })
  .done(function(response) {
    // Ajaxの処理が成功した場合の処理
    // ...
  })
  .fail(function(jqXHR, textStatus, errorThrown) {
    // Ajaxの処理が失敗した場合の処理
    // ...
  })
  .always(function() {
    $this.prop('disabled', false);
  });
});

ポイントは、.always()です。

これは、Ajaxの処理が成功しても失敗しても必ず実行されるメソッド。

ここでボタンのdisabledを解除することで、リクエストの結果に関わらずボタンを再び押せるようにしているんです。

ただ、この方法だと、Ajaxのレスポンスが返ってくるまでの間、ボタンが押せなくなってしまいます。

UXの観点からは、あまりスマートではないかもしれませんね。

○サンプルコード8:Promise使ったAjax制御

そこで、Promiseを使ってAjaxの処理をラップし、ボタンの制御と分離する方法をお見せしましょう。

// Promiseを返すAjax処理をラップした関数
function someAsyncProcess() {
  return new Promise(function(resolve, reject) {
    $.ajax({
      url: '/api/some-process',
      method: 'POST',
      data: { ... }
    })
    .done(function(response) {
      resolve(response);
    })
    .fail(function(jqXHR, textStatus, errorThrown) {
      reject(new Error(errorThrown));
    });
  });
}

let isSubmitting = false;

$('#submitBtn').on('click', function() {
  if (isSubmitting) {
    return;
  }

  isSubmitting = true;
  $(this).prop('disabled', true);

  someAsyncProcess()
    .then(function(response) {
      // Ajaxの処理が成功した場合の処理
      // ...
    })
    .catch(function(error) {
      // Ajaxの処理が失敗した場合の処理
      // ...
    })
    .finally(function() {
      isSubmitting = false;
      $('#submitBtn').prop('disabled', false);
    });
});

someAsyncProcess()という関数を用意し、その中でPromiseを使ってAjaxの処理をラップしています。

これにより、Ajaxの処理とボタンの制御を分離できるんです。

また、isSubmittingという処理中フラグを使って、Ajaxのレスポンスを待っている間の連打を防止しています。

これなら、ボタンを連打されても、余計なリクエストが送信されることはありません。

.finally()は、Promiseの処理が成功しても失敗しても必ず実行されるメソッド。

ここで、isSubmittingをfalseに戻し、ボタンのdisabledを解除しています。

●フレームワークでのボタン連打防止

最近は、Vue.jsやReactを使った開発が主流になってきましたよね。

これらのフレームワークでも、もちろんボタンの連打対策は必要です。

でも、フレームワークならではの簡潔な記述で、スマートに連打を防止できるといったらどうでしょうか。

ここからは、そんなフレームワークでのアプローチを2つ紹介していきましょう。

○サンプルコード9:Vueでのボタン連打防止

まずは、Vue.jsでのボタン連打防止の方法です。

Vueなら、ボタンの状態を「disable」という変数で管理するのがポイントですよ。

<template>
  <button :disabled="isSubmitting" @click="handleSubmit">送信</button>
</template>

<script>
export default {
  data() {
    return {
      isSubmitting: false
    }
  },
  methods: {
    handleSubmit() {
      this.isSubmitting = true;

      someAsyncProcess()
        .then(response => {
          // 処理成功時の処理
          // ...
        })
        .catch(error => {
          // エラー処理
          // ...
        })
        .finally(() => {
          this.isSubmitting = false;
        });
    }
  }
}
</script>

:disabled="isSubmitting"で、isSubmittingがtrueの時にボタンを無効化しています。

そして、@click="handleSubmit"でボタンがクリックされた時の処理を定義。

handleSubmit()メソッド内では、isSubmittingをtrueにしてボタンを無効化し、非同期処理を実行します。

処理が完了したら.finally()でisSubmittingをfalseに戻し、ボタンを再び有効化するという流れですね。

こうすることで、シンプルかつ確実にボタンの連打を防止できるんです。

Vueの双方向データバインディングのおかげで、ボタンの状態とロジックを明確に分離できるのが嬉しいところ。

○サンプルコード10:Reactでのボタン連打防止

続いては、Reactでのボタン連打防止です。

Reactでも、Vueと同じような考え方で連打を防げます。

import React, { useState } from 'react';

function SubmitButton() {
  const [isSubmitting, setIsSubmitting] = useState(false);

  function handleSubmit() {
    setIsSubmitting(true);

    someAsyncProcess()
      .then(response => {
        // 処理成功時の処理
        // ...
      })
      .catch(error => {
        // エラー処理 
        // ...
      })
      .finally(() => {
        setIsSubmitting(false);
      });
  }

  return (
    <button disabled={isSubmitting} onClick={handleSubmit}>
      送信
    </button>
  );
}

export default SubmitButton;

Reactの場合は、useState()フックを使ってisSubmittingの状態を管理します。

setIsSubmitting()でisSubmittingの値を更新することで、ボタンの disabled 属性をコントロールしているんですね。

あとは、onClick={handleSubmit}でクリック時の処理を定義するだけ。

handleSubmit()の中身は、ほぼVueの例と同じです。

Reactの宣言的UIの考え方に沿った、スッキリとした記述が実現できましたね。

ボタンの状態管理が、フレームワークの機能に沿ってシンプルになるのは、やはり嬉しいポイントです。

●よくある連打関連のエラーと対処法

さて、ここまでボタン連打を防止するためのさまざまな方法を見てきましたが、実際のプロジェクトでは、思わぬエラーに遭遇することもあるでしょう。

連打対策を施したはずなのに、ボタンが反応しなかったり、処理が2回実行されてしまったり。

そんなトラブルに頭を悩ませている方も多いのではないでしょうか。

でも、大丈夫です。ここからは、よくある連打関連のエラーとその対処法を3つ紹介します。

これらを押さえておけば、もう連打トラブルに振り回されることはありませんよ。

○「ボタンが反応しない」への対処

まずは、「連打防止の処理を入れたら、ボタンが全く反応しなくなった」というケース。

よくあるのは、ボタンの無効化を解除し忘れているパターンです。

例えば、こんなコードだと、一度ボタンを押すと二度と有効化されません。

$('#submitBtn').on('click', function() {
  $(this).prop('disabled', true);

  // 何らかの処理
  someProcess();
});

こういう時は、処理の完了後にボタンのdisabledを解除することを忘れずに。

非同期処理なら、コールバックや.then()の中で解除するのがポイントですよ。

$('#submitBtn').on('click', function() {
  $(this).prop('disabled', true);

  someAsyncProcess()
    .then(function() {
      // 非同期処理完了後にdisabledを解除
      $('#submitBtn').prop('disabled', false);
    });
});

ボタンが反応しない時は、まず「無効化の解除漏れ」を疑ってみてください。

○「処理が2回実行される」への対処

次は、「連打防止の処理を入れたのに、何故か処理が2回実行されてしまう」という問題。

これは、イベントハンドラの重複登録が原因のことが多いです。

例えば、こんなふうにイベントハンドラを2回登録してしまっていると…

$('#submitBtn').on('click', function() {
  // 処理A
});

$('#submitBtn').on('click', function() {
  // 処理B
});

ボタンをクリックした時、処理Aと処理Bの両方が実行されてしまいます。

こうした問題を防ぐには、イベントハンドラの登録は一か所にまとめるのが賢明。

あるいは、.off()で既存のイベントハンドラを削除してから新しいハンドラを登録するのも手です。

// 既存のclickイベントハンドラを削除
$('#submitBtn').off('click');

// 新しいclickイベントハンドラを登録
$('#submitBtn').on('click', function() {
  // 処理A
});

イベントハンドラの管理を適切に行うことが、この手のトラブル防止の鍵となります。

○「通信エラーが多発」への対処

最後は、「連打対策を入れたら、Ajaxの通信エラーが多発するようになった」という事例。

これは、連打によって複数の通信が同時に行われ、サーバー側で処理に失敗しているのかもしれません。

こういう時は、一度通信が完了するまで次の通信を受け付けないようにするのが有効です。

具体的には、「処理中フラグ」を使ったロジックを実装しましょう。

let isSubmitting = false;

$('#submitBtn').on('click', function() {
  if (isSubmitting) {
    return;
  }

  isSubmitting = true;
  $(this).prop('disabled', true);

  someAsyncProcess()
    .then(function() {
      // 通信成功時の処理
    })
    .catch(function() {
      // 通信失敗時の処理
    })
    .finally(function() {
      isSubmitting = false;
      $('#submitBtn').prop('disabled', false);
    });
});

isSubmittingフラグを使って、「通信中は次のリクエストを受け付けない」という制御を行えば、エラー多発の悩みともサヨナラできるはずです。

●連打防止の応用例

これまで、JavaScriptやjQuery、Ajax、フレームワークなど、さまざまな場面でのボタン連打防止の方法を見てきました。

でも、これらのテクニックは、ボタンだけに使えるわけではありません。

実は、連打対策のノウハウは、UI/UX改善のいろいろな場面で応用できるんです。

例えば、アコーディオンメニューの連打防止や、フォームの2重送信防止など。

どちらも、ユーザビリティを大きく左右する重要な課題ですよね。

ここからは、そんな連打防止テクニックの応用例を2つ紹介しましょう。

これを機に、皆さんも「連打対策」の可能性を広げてみてはいかがでしょうか。

○サンプルコード11:アコーディオンの連打防止

まずは、アコーディオンメニューの連打を防止する方法です。

アコーディオンを開閉するたびに、アニメーションが何度も発生して煩わしい…そんな経験、ありませんか?

これを防ぐには、アニメーションの実行中はクリックイベントを無効化するのが有効です。

こんなふうに書けば、スマートに連打を防げますよ。

$('.accordion').on('click', function() {
  // アコーディオンが開閉中なら終了
  if ($(this).hasClass('animating')) {
    return;
  }

  $(this).addClass('animating');

  const $content = $(this).find('.content');
  $content.slideToggle(300, function() {
    $(this).parent().removeClass('animating');
  });
});

ポイントは、「animating」というクラスの付け外しです。

アニメーション中は「animating」クラスを付与し、クリックイベントの最初でこのクラスの有無をチェック。

これにより、アニメーション中の連打を無視できるわけです。

アコーディオンのような、アニメーションを伴うUIコンポーネントでは、この手法が役立つこと間違いなしですよ。

○サンプルコード12:フォーム2重送信の防止

もう1つは、フォームの2重送信を防ぐテクニックです。

ユーザーが送信ボタンを連打して、知らない間にデータが重複して登録されてしまった…なんて失敗談、聞いたことがあるのではないでしょうか。

こういった事故を防ぐには、次のようなロジックを実装します。

let isSubmitted = false;

$('form').on('submit', function() {
  if (isSubmitted) {
    return false;
  }

  isSubmitted = true;

  // フォームのサブミット処理
  // ...
});

isSubmittedというフラグ変数を使って、「1度フォームがサブミットされたら、2回目以降のサブミットは受け付けない」という制御を行っています。

これなら、ユーザーが送信ボタンを連打しても、データの重複登録は防げるはずです。

このテクニックは、ECサイトの注文フォームや、アンケートフォームなど、データの整合性が大切な場面で特に重宝するでしょう。

まとめ

JavaScriptでボタンの連打を防ぐ方法、いかがでしたか?

今回紹介した10個のテクニックを押さえれば、もうボタンの連打に振り回されることはありません。

サンプルコードを参考に、ぜひ皆さんの開発に取り入れてみてください。

ユーザビリティの高いUIを実現できるはずです。

常にユーザー目線に立ち、ストレスのないUXを追求する。

それがフロントエンドエンジニアの腕の見せ所ですからね。

さあ、明日からの開発が楽しみですよ!