45
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

手入力禁止 (コピペ専用) のメールアドレス入力欄を作ってみる

Posted at

世の中には、「入力ミス防止のためコピペ禁止」などとぬかしやがるメールアドレス入力欄がある。
マジふざけんな。
手入力なんかしたらtypoの原因になるだろ馬鹿野郎。
「確認のため2回入力してください」という指示もよくあるが、そもそもの記憶が間違っている場合は何十回入力させようがそれで間違いを防げる可能性は低いだろう。

メーラーなどの確実なソースからメールアドレスをコピペすることで、typoも記憶間違いも防ぎ、正しいメールアドレスを入力できる可能性を上げることができる。
そこで、今回は逆に手入力禁止 (コピペ専用) のメールアドレス入力欄を作ってみた。

メーラーからメールアドレスをコピペしても、例えば以下の問題を防ぐことはできず、完璧ではない。

  • 本来入力したい自分のメールアドレスではなく、メールの相手のメールアドレスをコピーしてしまう
  • 複数のメールアドレスを使い分けている人が、本来使いたいのと別のメールアドレスをコピーしてしまう
  • コピーしたメールアドレスが、クリップボードの内容を自動で加工するソフトウェアにより意図と異なる文字列に変化する

メールアドレス入力欄への手入力を禁止する

まずはメールアドレスの入力欄を用意する。

HTML
<input id="sample1" type="email" value="">

beforeinput イベントpreventDefault() を用いることで、ほとんどの入力をキャンセルできる。
ただし、IME を用いた入力 (inputType"insertCompositionText" の場合) はキャンセルできないので、入力内容を記録しておいて input イベントで書き戻すことで対処する。

input 要素の value プロパティは、beforeinput イベントの処理時は変更前の値、input イベントの処理時は変更後の値を表す。

JavaScript: 全ての入力を禁止する
const sample1 = document.getElementById("sample1");
let prevValue = null;
sample1.addEventListener("beforeinput", function(e) {
  e.preventDefault();
  if (e.inputType === "insertCompositionText") prevValue = sample1.value;
});
sample1.addEventListener("input", function(e) {
  if (e.inputType === "insertCompositionText") {
    sample1.value = prevValue;
    prevValue = null;
  }
});

イベントオブジェクトの inputType プロパティにより、どのような入力が行われたかを確認できる。
値と意味のリストは、Input Events Level 2 にある。
値の最初の部分 (接頭辞) により、大まかに以下の種類にわけることができる。

値の最初の部分 入力操作
insert テキストの追加や上書き
delete テキストの削除
history アンドゥ・リドゥ
format スタイルの設定

今回は、以下の仕様にする。

  • テキストが貼り付けられたら、(挿入ではなく) テキスト全体をそのテキストに設定する
  • テキストが削除されたら、(一部ではなく) テキスト全体を削除 (空文字列に設定) する
  • その他の操作は無視 (禁止) する

今回は、以下の種類を「テキストの貼り付け」であるとみなす。
これらの種類において、イベントオブジェクトの data プロパティで貼り付けられた文字列を取得できる。

テキストではなく画像がクリップボードに入っている状態で貼り付け操作を行った場合など、data プロパティが null である場合もある。

種類 意味
insertFromYank キルバッファからの貼り付け
insertFromDrop ドロップによる貼り付け
insertFromPaste クリップボードからの貼り付け
insertFromPasteAsQuotation 引用としてクリップボードから貼り付け

また、delete で始まるすべての種類を「テキストの削除」であるとみなす。

実装例を以下に示す。

JavaScript: 貼り付けのみを受け付ける
const pasteOperations = new Set([
  "insertFromYank", "insertFromDrop",
  "insertFromPaste", "insertFromPasteAsQuotation",
]);
const sample1 = document.getElementById("sample1");
let prevValue = null;
sample1.addEventListener("beforeinput", function(e) {
  e.preventDefault();
  if (e.inputType === "insertCompositionText") prevValue = sample1.value;
  if (pasteOperations.has(e.inputType)) {
    // 貼り付け
    if (typeof e.data === "string") sample1.value = e.data;
  } else if (e.inputType.substring(0, 6) === "delete") {
    // 削除
    sample1.value = "";
  }
});
sample1.addEventListener("input", function(e) {
  if (e.inputType === "insertCompositionText") {
    sample1.value = prevValue;
    prevValue = null;
  }
});

手入力されようとしたら警告を出す

今回の入力欄に手入力がされようとしたら、単に無視するのではなく警告を出したい。
警告を出す方法の一つとして、バリデーション失敗 (入力されたデータが設定した条件を満たさない) 時の警告を利用する方法がある。
この方法は、setCustomValidity() メソッドでエラーメッセージを設定し、reportValidity() メソッドでエラーメッセージを表示することで実現できる。

ただし、setCustomValidity() でエラーメッセージを空でない文字列に設定すると、入力が常に無効であるとみなされる。
今回の実装では、手入力は無視するため、手入力を試みても入力されているデータは無効にならない。
そのため、実際には無効ではないにもかかわらず無効であるとみなされ、問題になる可能性がある。
無効判定が行われる「直前」に発火し、無効状態の解除に使えるようなイベントは見つからなかった。

そこで、今回は入力されているデータが空の場合のみエラーメッセージを設定し、警告を出す仕様にした。
データが空の場合は、貼り付けによる入力が行われる前であるため、警告によりユーザーに手入力ではなく貼り付けをするよう指示を出す意味が大きいと考えられる。
さらに、メールアドレスの入力が必須である場合は、データが空のときはどうせ無効なので、エラーメッセージを設定により無効状態になっていても大きな問題にならない。
有効な入力 (貼り付け) および削除が行われたとき、無効状態を解除するため、エラーメッセージを空文字列に設定する。

以下に実装例を示す。

JavaScript: 手入力されようとしたら警告を出す
const pasteOperations = new Set([
  "insertFromYank", "insertFromDrop",
  "insertFromPaste", "insertFromPasteAsQuotation",
]);
const sample1 = document.getElementById("sample1");
let prevValue = null;
sample1.addEventListener("beforeinput", function(e) {
  e.preventDefault();
  if (e.inputType === "insertCompositionText") prevValue = sample1.value;
  if (pasteOperations.has(e.inputType)) {
    // 貼り付け
    if (typeof e.data === "string") sample1.value = e.data;
    sample1.setCustomValidity("");
  } else if (e.inputType.substring(0, 6) === "delete") {
    // 削除
    sample1.value = "";
    sample1.setCustomValidity("");
  } else if (e.inputType.substring(0, 6) === "insert") {
    // 手入力
    if (sample1.value === "") {
      // データが空の場合、警告を出す
      sample1.setCustomValidity("入力ミス防止のため、コピペで入力してください。");
      sample1.reportValidity();
    }
  }
});
sample1.addEventListener("input", function(e) {
  if (e.inputType === "insertCompositionText") {
    sample1.value = prevValue;
    prevValue = null;
  }
});

デモ

See the Pen paste-only email area test by MikeCAT (@mike_cat) on CodePen.

ソースコード
HTML
<form method="POST" action="https://example.com/" id="theform">
  <p>
    メールアドレスを入力してください。<br>
    <strong>入力ミス防止のため、手入力はせず、コピペしてください。</strong><br>
    <input name="email" type="email" value="" size="50" required>
  </p>
  <p>
    <input type="submit" value="送信">
  </p>
</form>
JavaScript
const theform = document.getElementById("theform");
theform.addEventListener("submit", function(e) {
  e.preventDefault();
  alert(theform.email.value);
});

const pasteOperations = new Set([
  "insertFromYank", "insertFromDrop",
  "insertFromPaste", "insertFromPasteAsQuotation",
]);
let prevValue = null;
theform.email.addEventListener("beforeinput", function(e) {
  e.preventDefault();
  if (e.inputType === "insertCompositionText") prevValue = theform.email.value;
  if (pasteOperations.has(e.inputType)) {
    // 貼り付け
    if (typeof e.data === "string") theform.email.value = e.data;
    theform.email.setCustomValidity("");
  } else if (e.inputType.substring(0, 6) === "delete") {
    // 削除
    theform.email.value = "";
    theform.email.setCustomValidity("");
  } else if (e.inputType.substring(0, 6) === "insert") {
    // 手入力
    if (theform.email.value === "") {
      // データが空の場合、警告を出す
      theform.email.setCustomValidity("入力ミス防止のため、コピペで入力してください。");
      theform.email.reportValidity();
    }
  }
});
theform.email.addEventListener("input", function(e) {
  if (e.inputType === "insertCompositionText") {
    theform.email.value = prevValue;
    prevValue = null;
  }
});

試してみるのに便利なよう、コピー元となる適当なメールアドレスを用意した。

webmaster@example.com

おわりに

今回は、入力ミス防止のため手入力を禁止し、コピペによる入力のみを受け付けるメールアドレスの入力欄を作ってみた。
これは、世の中に出回っているコピペ禁止のメールアドレス入力欄に対するアンチテーゼとして作成した、技術的な実験である。
実際に運用するウェブサイトに、このような入力欄を設置することを推奨するものではない。
とはいえ、設置することを禁止はしないので、設置するかの判断は自己責任で行ってほしい。

今回の実装では、「手入力」と「パスワード管理ソフトやキーボードマクロツールなどによる自動入力」を区別することができない。
このような受け付ける操作を限定する入力欄を設置し、ユーザーに使用させることは、ユーザーの体験を損ねる可能性がある。
このような入力欄を設置するかを検討する際は、これを含むデメリットも考慮した上で、慎重に判断してほしい。

45
17
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?