はじめに
先日、対戦型ポケモンWordleをリリースしました。
リリース当初、ただテキストボックスにポケモンの名前を入力する形にしており、入力履歴の管理ができておりませんでした。
入力履歴の機能を追加しようと、大元のWordleを参考に入力用のボタンを用意しようと思いました。
ただ、日本語は50音+濁点・半濁点で80ボタン以上必要で、このボタンからポチポチするのは入力が大変だろうなと思いました。
そこで、ブラウザ上でフリック入力できる仮想キーボードを作ってみました。
完成品
See the Pen フリック入力 by yotty (@yotty) on CodePen.
パソコンからでは直接フリック機能が使えません。
パソコンからフリック機能を確認したい方は、デベロッパーツールでデバイスを変更することで試すことができます。
※Chromeの場合
F12でデベロッパーツールを開き、以下のアイコンからデバイスをモバイル端末に変更
実装
以下、実装内容の解説です。
事前準備
また、フリック入力の際、長押しで文字選択されたり、入力のタップでズームされたりしないように以下を追加しておきます。
body{
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
touch-action: none;
}
さらに、フリックした際、濁点・半濁点の切り替えを行った際に入力すべき文字を特定するために以下のような配列を用意しておきます。
const l_word = [
['ア', 'イ', 'ウ', 'エ', 'オ' ]
, ['カ', 'キ', 'ク', 'ケ', 'コ' ]
, ['サ', 'シ', 'ス', 'セ', 'ソ' ]
, ['タ', 'チ', 'ツ', 'テ', 'ト' ]
, ['ナ', 'ニ', 'ヌ', 'ネ', 'ノ' ]
, ['ハ', 'ヒ', 'フ', 'ヘ', 'ホ' ]
, ['マ', 'ミ', 'ム', 'メ', 'モ' ]
, ['ヤ', '', 'ユ', '', 'ヨ' ]
, ['ラ', 'リ', 'ル', 'レ', 'ロ' ]
, ['ワ', 'ヲ', 'ン', 'ー', '' ]
];
const l_word_switch = [
['ア', 'ァ']
, ['イ', 'ィ']
, ['ウ', 'ゥ']
, ['エ', 'ェ']
, ['オ', 'ォ']
, ['カ', 'ガ']
, ['キ', 'ギ']
, ['ク', 'グ']
, ['ケ', 'ゲ']
, ['コ', 'ゴ']
, ['サ', 'ザ']
, ['シ', 'ジ']
, ['ス', 'ズ']
, ['セ', 'ゼ']
, ['ソ', 'ゾ']
, ['タ', 'ダ']
, ['チ', 'ヂ']
, ['ツ', 'ッ', 'ヅ']
, ['テ', 'デ']
, ['ト', 'ド']
, ['ハ', 'バ', 'パ']
, ['ヒ', 'ビ', 'ピ']
, ['フ', 'ブ', 'プ']
, ['ヘ', 'ベ', 'ペ']
, ['ホ', 'ボ', 'ポ']
, ['ヤ', 'ャ']
, ['ユ', 'ュ']
, ['ヨ', 'ョ']
];
フリック入力
フリックを検知するために、「touchstart」「touchmove」「touchend」の3つのイベントを利用します。
「touchstart」で、タッチした場所を記憶しておきます。
その他、フリックしたときのオブジェクトの位置や文字などの設定もしています。
function onTouchStart(event) {
$(event.currentTarget).addClass('gray_for_touch');
position = event.originalEvent.touches[0];
direction = 'c';
//フリックのオブジェクトの位置を移動させておく
offset = $(event.currentTarget).offset();
$('.kb_key_u').offset({ top: offset.top - 48, left: offset.left });
$('.kb_key_d').offset({ top: offset.top + 48, left: offset.left });
$('.kb_key_l').offset({ top: offset.top, left: offset.left - 48 });
$('.kb_key_r').offset({ top: offset.top, left: offset.left + 48 });
//テキストセット
var l = l_word.find(x => x.includes($(event.currentTarget).text()));
$('.kb_key_u').text(l[2]);
$('.kb_key_d').text(l[4]);
$('.kb_key_l').text(l[1]);
$('.kb_key_r').text(l[3]);
};
「touchmove」では、移動した現在の位置を取得して上下左右にどれだけ移動したかを計算します。
上下左右それぞれの移動距離のmaxを取って移動方向を検知し、閾値20を超えたらその移動方向にフリックしたと判定させています。
function onTouchMove(event) {
direction = 'c';
$('.kb_key_u').addClass('transparent');
$('.kb_key_d').addClass('transparent');
$('.kb_key_l').addClass('transparent');
$('.kb_key_r').addClass('transparent');
$(event.currentTarget).addClass('gray_for_touch');
$(event.currentTarget).removeClass('transparent');
var new_position = event.originalEvent.touches[0];
var u = position.screenY - new_position.screenY;
var d = new_position.screenY - position.screenY;
var l = position.screenX - new_position.screenX;
var r = new_position.screenX - position.screenX;
var max = Math.max(u, d, r, l);
if (max < 20) {
return;
}
var input_key = $(event.currentTarget).text();
$(event.currentTarget).removeClass('gray_for_touch');
$(event.currentTarget).addClass('transparent');
if (max == u) {
direction = 'u';
$('.kb_key_u').removeClass('transparent');
}
else if (max == d) {
direction = 'd';
$('.kb_key_d').removeClass('transparent');
}
else if (max == l) {
direction = 'l';
$('.kb_key_l').removeClass('transparent');
}
else if (max == r) {
direction = 'r';
$('.kb_key_r').removeClass('transparent');
}
};
「touchend」では、「touchmove」で検知したフリックの方向から入力すべき文字を判断します。
フリックの方向によってindexを変えて、事前に用意した「l_word 」から入力する文字を決定します。
あとは、テキストボックスの値の最後にその文字を付与します。
function onTouchEnd(event) {
$('.kb_key_u').addClass('transparent');
$('.kb_key_d').addClass('transparent');
$('.kb_key_l').addClass('transparent');
$('.kb_key_r').addClass('transparent');
$(event.currentTarget).removeClass('gray_for_touch');
$(event.currentTarget).removeClass('transparent');
var input_key = '';
var key = $(event.currentTarget).text();
var l = l_word.find(x => x.includes($(event.currentTarget).text()));
var index = 0;
if (direction == 'c') {
index = 0;
}
else if (direction == 'u') {
index = 2;
}
else if (direction == 'd') {
index = 4;
}
else if (direction == 'l') {
index = 1;
}
else if (direction == 'r') {
index = 3;
}
var txt = $('#txt').val();
if (l[index] == '') {
return;
}
$('#txt').val(txt + l[index]);
};
濁点・半濁点切り替え
テキストボックスに入力されている最後の文字を「l_word_switch」から探し、indexを1ずらして濁点・半濁点を切り替えて文字を取得しています。
function onTouchStart_switch(event) {
$(event.currentTarget).addClass('gray_for_touch');
temp_word = $('#txt').val();
targer_word = temp_word.slice(-1);
var l_switch = l_word_switch.find(x => x.includes(targer_word));
if (l_switch === undefined) {
return;
}
var index = l_switch.indexOf(targer_word);
if (index < 0) {
return;
}
else if (index == l_switch.length - 1) {
index = 0;
}
else {
index += 1;
}
input_key = l_switch[index];
$('#txt').val(temp_word.slice(0, -1) + input_key);
};
バックスペース
テキストボックスの最後の文字を削除するだけで実装できます。
$(document).on('touchstart', '#kb_key_bs', function (event) {
$(event.currentTarget).addClass('gray_for_touch');
var temp_txt = $('#txt').val();
$('#txt').val(temp_txt.slice(0, -1));
});
おわりに
実際のアプリでは、今回紹介したフリック入力を組み込み、入力欄を細かく分けることによって入力履歴の管理を実現しています。
アプリが気になった方はこちらから遊ぶことができるので、ぜひ一度触ってみてください!
参考