PR

【徹底解説&コピペ可】inputにおける電話番号のバリデーションを本気で考える【HTML,JavaScript】

HTML/CSSを学ぶ

こんにちは。Webのフォームで電話番号を正しく入力してもらうのは意外と難しいですよね。例えば「09012345678」のように正しい形式で入力してほしいのに、「123456」のような不正確なデータが送信されることがあります。

一方バリデーションをしようとしても実は結構難易度が高く、簡易的に済ませてしまうこともありますよね。

そこで本記事では、総務省が示す電話番号の仕様に基づきどのような実装をするのが良いか徹底的に考えていこうと思います。間違いがあればぜひご指摘ください。

前提

本記事では以下を前提として話を進めます。

  • 日本の電話番号であること
  • 一般ユーザーが利用しうる、固定電話と携帯電話の番号であること
  • ハイフン無しの数字のみで入力されること(例:09012345678)

海外番号やハイフン入りの形式を考慮すると実装が複雑になるため、これらは対象外とします(ハイフン入りの場合はそもそもinputを分けてしまう方法も考えられる)。

ちなみに今回のバリデーションの実装パターンとしても紹介しますが、Google製ライブラリの「libphonenumber」を使うことでハイフン入りのフォーマットに簡単かつ安全に変換することができます。人にハイフン入りの番号を入力させるよりはプログラム上のどこかでフォーマットをした方が良いのかなと思ったりもしています。

結論(コピペ可)

解説が非常に長くなったので、最終的なコードを先に載せておきます。

ちなみにCSSに関しては擬似クラスの:invalidが使えます。例えば「バリデーションが通らない時だけ背景を赤くする」には次のコードでOKです。

#tel:invalid {
  background-color: red;
}

HTMLと正規表現のみの場合

index.html
<input
  type="tel"
  id="tel"
  name="tel"
  title="無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。"
  maxlength="11"
  placeholder="例: 09012345678"
  pattern="0[1-9]0[0-9]{8}|0[1-9]{3}[0-9]{6}"
  required
/>

libphonenumber.jsを使った場合

index.html
<input 
  type="tel" 
  id="tel" 
  name="tel" 
  maxlength="11"
  placeholder="例: 09012345678" 
  required
/>

<script src="https://cdnjs.cloudflare.com/ajax/libs/libphonenumber-js/1.11.17/libphonenumber-js.min.js" defer></script>
<script src="./script.js" defer></script>
script.js
const tel = document.getElementById("tel");

tel.addEventListener("input", () => {
  const value = tel.value;
  const isValid = 
    (value.length === 10 || value.length === 11) &&
    libphonenumber.isValidPhoneNumber(value, "JP");

  if (!isValid) {
    tel.setCustomValidity("無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。");
    return;
  }
    
  tel.setCustomValidity("");
});

日本の電話番号仕様を理解する

バリデーションを設計するには、まず日本の電話番号仕様を確認することが重要です。

日本の電話番号の基本構造

総務省によれば、日本の電話番号は大別すると以下の6種類に分けられるようです。

引用:https://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/q_and_a.html

このうち以下の2つが一般ユーザーが利用するものと考えられます。

  1. 0ABCから始まる番号(A、B、Cは0以外)
    • 固定電話で利用される番号。
    • 市外局番 + 市内局番 + 加入者番号という構成(例: 0312345678)。
    • 総桁数は10桁。
  2. 0A0から始まる番号(Aは0以外)
    • 主に携帯電話で利用される番号。
    • 090や080で始まる番号(例: 09012345678)。
    • 総桁数は11桁。

「2〜9から始まる番号」は要するに市外局番抜きの電話番号を指すので今回は無視しても良いでしょう。

上記2つの条件に合致するかどうかでバリデーションルールを考えられそうです。

電話番号に関するルールを整理する

日本の電話番号を正確にバリデーションするためのポイントは以下の通りです。

  • 0から始まる:日本のすべての電話番号は0で始まる
  • 先頭の形式が固定されている
    • 固定電話は「0ABC」(A、B、Cは0以外)
    • 携帯電話は「0A0」(Aは0以外)
  • 携帯電話の範囲:現時点では090、080、070から始まるもののみ存在。しかし今後060が利用される可能性があるため柔軟な条件設定が必要
  • 残りの桁は任意の数字:先頭の形式以外は任意の数字が許容される
  • 桁数:固定電話は10桁、携帯電話は11桁。

ここまで固まれば正規表現を使ってうまく表現できそうですね。

実装を考える

正規表現とHTMLのみのパターン

基本構成

pattern属性と正規表現を使って簡単にバリデーションを実装できます。例えば「10桁もしくは11桁の数字」を指定したのが以下の例です。

<input type="tel" pattern="[0-9]{10,11}" />

入力が合致しない状態でformを送信しようとすると「指定されている形式で入力してください」というメッセージが表示されます。以下のフォームで試してみてください。

この方式は、pattern属性に適切な正規表現を与えさえすればバリデーションチェックや送信時エラーなどを標準の機能で全て賄ってくれます。簡単でありがたいですね。

ちなみにMDNによれば、

パターンの正規表現は、部分文字列に一致させるのではなく、入力の value 全体に一致させる必要があります。パターンの始めに ^(?: が、終わりに )$ が含まれているかのように扱われます。

とのことです。

参考:https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/pattern

正規表現

固定電話と携帯電話の番号形式に対応するため、それぞれの正規表現を作成しOR条件で結合しましょう。以下に詳しく解説します。

固定電話

固定電話は「0ABCから始まる(A、B、Cは0以外)10桁の番号」です。正規表現で表すと以下のようになります。

0[1-9]{3}[0-9]{6}

分解すると、

  • 0:0が1桁
  • [1-9]{3}:1〜9の数字が3桁
  • [0-9]{6}:0〜9の数字が6桁

となり、これで合計10桁を表現できます。

0312345678    // true
09012345678   // false
1234567890    // false
03-1234-5678  // false
hoge          // false
携帯電話

携帯電話は「0A0から始まる(Aは0以外)11桁の数字」です。正規表現で表すと以下のようになります。

0[1-9]0[0-9]{8}

分解すると、

  • 0:0が1桁
  • [1-9]:1〜9の数字が1桁
  • 0:0が1桁(ここまの3つで0A0を表現)
  • [0-9]{8}:0〜9の数字が8桁

となり、これで合計11桁を表現できます。

0312345678    // false
09012345678   // true
12345678900   // false
03-1234-5678  // false
hoge          // false
2つをOR条件にする

両方に対応するには2つの正規表現を | で結合します。したがってinputは次のようになります。

<input
  type="tel"
  id="tel"
  name="tel"
  pattern="0[1-9]{3}[0-9]{6}|0[1-9]0[0-9]{8}"
/>

これで固定電話、携帯電話の両方がtrueになります。

0312345678    // true
09012345678   // true
1234567890    // false
03-1234-5678  // false
hoge          // false

必須条件の追加や、エラーメッセージ調整などをする

必須の場合はrequired属性をつければOKです。

エラーメッセージですが、デフォルトだと「指定されている形式で入力してください」としか表示されません。title属性にテキストを入れると補足情報を載せることができます。とはいえあまり詳細に説明しても分かりにくいのでうまい説明を考えてみてください。

<input
  type="tel"
  id="tel"
  name="tel"
  title="無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。"
  pattern="0[1-9]{3}[0-9]{6}|0[1-9]0[0-9]{8}"
  required
/>

下のinputで試してみてください。

また、maxlength属性で最大桁数の指定もできます。maxlength=”11″を指定しておいてもいいでしょう。こうすると11桁以上の入力ができなくなります。

最終的なinputの書き方

<input
  type="tel"
  id="tel"
  name="tel"
  title="無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。"
  maxlength="11"
  placeholder="例: 09012345678"
  pattern="0[1-9]0[0-9]{8}|0[1-9]{3}[0-9]{6}"
  required
/>

あえて緩めにする考え方もある

ここまでの正規表現ですが、ルール的には正しいはずです。一方で「実在の電話番号を弾いてしまうリスク」「今後のメンテナンス性」を考えてあえて緩めにする運用方針も十分考えられます。

その場合は、

  • 0から始まる
  • 残りは0〜9の数字が9桁もしくは10桁

くらいのルールに留めておいて0[0-9]{9,10}としても良いです。

<input
  type="tel"
  id="tel"
  name="tel"
  title="無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。"
  maxlength="11"
  placeholder="例: 09012345678"
  pattern="0[0-9]{9,10}"
  required
/>

libphonenumberを使うパターン

libphonenumberとは、GoogleがAndroid携帯向けに開発した究極の電話番号フォーマット・解析ライブラリとGoogle自身が自称するほどの素晴らしいライブラリです。実際僕もそう思います。

これを使うことで簡単かつ安全にバリデーションを行うことができます。JavaScript向けのパッケージがあるのでそちらを使います。

libphonenumber-js
A simpler (and smaller) rewrite of Google Android's libphonenumber library in javascript. Latest version: 1.11.17, last ...

バリデーション用の関数、メソッドはたくさんあるのですが今回はisValidPhoneNumber()を使いましょう。基本的な書き方は以下の通り。CDNで読み込んで利用します。

<input type="tel" id="tel" name="tel" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/libphonenumber-js/1.11.17/libphonenumber-js.min.js" defer></script>
<script src="./script.js" defer></script>
const tel = document.getElementById("tel");
const isValid = libphonenumber.isValidPhoneNumber(tel.value, "JP");

このように、引数には検証したい値と国コードを渡します。libphonenumberは世界の電話番号に対応しているため国コードを渡してどのルールで検証すべきかを指定します。

標準の挙動に合うように方針を立てる

実装コストや挙動の安定性を考えると、Web標準に準拠したinputやformの挙動をそのまま活かしながらlibphonenumberを利用したバリデーションを実装したいです。

inputやformには、以下のようなデフォルトのバリデーション機能があります。

  • 入力時、条件に合致しない場合はinput要素がinvalid状態になる。
  • invalid状態のinputがある場合、フォーム送信時に送信が停止しエラーメッセージが表示される。

次の例で標準の挙動を確認してみてください。

上記のフォームでは以下のようなサイクルが発生します。

  • ページ読み込み時:required属性により、空の状態ではinvalidになる。
  • 入力時:pattern属性の正規表現に一致しない限り、invalid状態が続く。
  • 送信時:invalid状態の場合、フォームの送信が停止し、エラーメッセージが表示される。

となります。

この標準的な挙動をlibphonenumberで再現するため、次のように方針を立てます。

  1. 入力時にlibphonenumberを使って電話番号を検証する。
  2. 検証結果に応じてsetCustomValidity()を使い、inputのvalid/invalid状態を切り替える。
  3. 標準のフォーム送信時エラーメッセージ表示機能をそのまま活用する。

実装

input側はpatternやtitle属性が不要になります。

<input 
  type="tel" 
  id="tel" 
  name="tel" 
  maxlength="11"
  placeholder="例: 09012345678" 
  required
/>

入力のたびにチェックが必要なのでinputイベントにイベントリスナーを追加してその中で検証を行います。

const tel = document.getElementById("tel");

tel.addEventListener("input", () => {
	const value = tel.value;
	const isValid = libphonenumber.isValidPhoneNumber(value, "JP");
});

問題はどうやってinputをinvalid状態にするかですが、input要素のメソッドsetCustomValidity()を使います。引数にはカスタムメッセージを渡します。

これを呼び出すことでその要素がinvalid状態になります。空文字を渡すと逆にvalid状態になります。

const tel = document.getElementById("tel");

tel.addEventListener("input", () => {
  const value = tel.value;
  const isValid = libphonenumber.isValidPhoneNumber(value, "JP");

  if (!isValid) { // バリデーションが通らないときはinputをinvalid状態にする
    tel.setCustomValidity("無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。");
    return;
  }
    
  tel.setCustomValidity(""); // バリデーションが通ったらinputをvalid状態にする
});

これで入力のたびに正しい電話番号かどうかlibphonenumberが検証をし、inputの状態を正しく変化させます。以下のinputで試してみてください。inputがinvalidである限り送信ボタンを押してもエラーメッセージが出ます。

isValidPhoneNumber()の注意点

isValidPhoneNumber()ですが、「00000000(0が8桁)」など一部の値を通してしまいます。より正確にバリデーションするなら10桁か11桁に制限した方がいいでしょう。

const tel = document.getElementById("tel");

tel.addEventListener("input", () => {
  const value = tel.value;

  // 桁数チェックを追加
  const isValid = 
    (value.length === 10 || value.length === 11) &&
    libphonenumber.isValidPhoneNumber(value, "JP");

  if (!isValid) {
    tel.setCustomValidity("無効な電話番号です。10桁、もしくは11桁の数字を入力する必要があります。");
    return;
  }
    
  tel.setCustomValidity("");
});

これでOKです。

まとめ

本記事では、日本の電話番号バリデーションをHTML標準機能とlibphonenumber.jsを用いた2つの方法で解説しました。最終的なコードはシンプルですが、それに至るのに必要な情報や、実装における考慮事項はかなり多くて大変ですね。

とはいえフォームにおけるバリデーションの目的はあくまで誤入力の防止であって正しく電話番号を判定することではありません。まずはpattern属性の簡易版を利用し、状況に合わせて段階的に厳密なルールを導入してくのが良いのではないでしょうか。

コメント

タイトルとURLをコピーしました