Twitter風入力文字数チェック
今日のjQueryTipsは、Twitter風文字数カウントです。
Twitterをやってる人ならご存知かもしれませんが、
文字を入力していくと、右下にドーナツ型のアイコンが出てきて、「あとどれくらい入力できるか」が視覚的にわかるようになってます。
この機能が実装された当初は「わかりにくい」と否定的な意見もあったみたいですが、個人的にはお洒落で気に入ってます。
TwitterはReactで実装されてますがjQueryでもこの仕組みは作ることができます。ただ、完璧にコピーしようと思うと、もうちょっと頑張らないといけません。
気になる人は試してみてくださいな。
実装する機能を整理する
まず、どんな処理を書かないといけないのか、整理してみましょう。
・入力された文字数をリアルタイムで取得(半角は1全角は2で計算)
・取得した文字数に応じて、バーに色をつける
・未入力だったら送信ボタンは押せない
・既定の文字数を超えた場合、バーを赤色+送信ボタンを押せない
入力文字数をリアルタイムにチェックする
CSSでは特に特別なことはしておらず、TwitterのUIに近づけるのを頑張ったくらいです。
基本、jQueryをゴリゴリ書けばオッケー。
サクッとコピーしたい人はcodepenからどうぞ。
順番にみていきましょう。
入力された文字数をリアルタイムで取得(半角は1全角は2で計算)
ここでのポイントは、半角全角のチェックと入力テキストのリアルタイムチェックです。
まず、半角全角の判定用の関数を定義します。
//カウント用の関数を定義
function getLen(str){
let result = 0;
for(var i=0;i<str.length;i++){
let chr = str.charCodeAt(i);
if((chr >= 0x00 && chr < 0x81) ||
(chr === 0xf8f0) ||
(chr >= 0xff61 && chr < 0xffa0) ||
(chr >= 0xf8f1 && chr < 0xf8f4)){
//半角文字の場合は1を加算
result += 1;
}else{
//それ以外の文字の場合は2を加算
result += 2;
}
}
//結果を返す
return result;
};
このコードはこちらからいただきました(コピペかよ!)
何をやっているかというと、charCodeAt() メソッドでUTF-16コード取得し、半角文字の判定を行います。
半角文字だった場合は1を返し、それ以外の場合は2を返してくれます。
これで半角全角のチェックはOK。
次に、入力文字のリアルタイムカウントです。
入力された文字をカウントするには、onメソッドのイベントタイプ「input」を使います。
$('.text').on('input', function(){
// .textに入力された時に実行
});
この中でconsole.logを実行するとわかるのですが、指定された要素にテキストが入力された時に実行されます。
つまり、要素のvalueが変化するたびに発生するイベントです。
ざっくり変数宣言をしますが、ここで、さっき定義した関数を使って、半角全角のチェックをして文字数を取得しておきます。
let cnt = getLen($(this).val()) //文字数を取得
リアルタイムに文字数を表示させる方法はこれでOK。
$('.now_cnt').text(cnt);//現在の文字数を表示
入力するたびに、.now_cntの文字数が変化するはずです。
取得した文字数に応じて、バーに色をつける
さて、文字数を取得できたところで、肝心のTwitterのようなバーを表示させましょう。
この部分は、SVGを使って実装しています。
<div class="circle_wrap">
<svg height="100%" viewBox="0 0 20 20" width="100%" style="overflow: visible;">
<circle cx="50%" cy="50%" fill="none" stroke-width="2" r="9" stroke="#38444D"></circle>
<circle id="circle" cx="50%" cy="50%" fill="none" stroke-width="2" r="9" stroke="#1DA1F2" stroke-linecap="round" style="stroke-dashoffset: 56.3; stroke-dasharray: 56.5;"></circle>
</svg>
</div>
SVGについては、こちらの記事が参考になります。
LIGさんは流石という感じ。
こっちは英語ですが、動きがよくわかるのでイメージが掴みやすい。
svgのcircleで円を書いて、以下のスタイルを文字数に合わせて増減させることで、Twitterのようなバーガ表現できます。
style="stroke-dasharray: 56.5;"
SVGじゃなくても多分できますが、Twitterがsvgで作られてたので、本家に近づけてみました。
あとは、変数として、マックスの文字数がバーの最大値になるよう計算し、文字数単位の数字を割当てます。
let cnt = getLen($(this).val()) //文字数を取得
let cnt_bar = cnt * 0.1999; //文字数を元に計算
let dash_array = 56.5 + cnt_bar; // グラフに反映する計算式
let circle = $('#circle');
let submit_btn = $('.submit_btn');
/* 280文字でいい感じに100%になる計算 */
そしてこう書いてあげれば、valueが変更されるごとにinputイベントが実行され、その度にstroke-dasharrayの値が書き換わるという寸法です。
// stroke-dasharrayにdash_arrayの数値を反映
circle.css('stroke-dasharray',dash_array);
ここまでで、文字数をリアルタイムにカウントしつつ、バーに反映する動きが完成しました。
これだけじゃちょっと不十分なので、バリデーションチェックの処理も追加しておきましょう。
未入力、既定の文字数を超えた場合、送信ボタンを押せない
ちょっと長くなりますが、jQueryのif文の基礎を抑えておけば簡単です。一気に書いてしまいましょう。
慣れないうちは、日本語で整理してから書いていくと作りやすいです。
0文字
・ボタン無効
・circleは隠す
1文字以上280文字以内
・circle表示
・ボタン有効化
280文字以上
・circleを赤色
・文字色を赤色
・ボタン無効
これをif文にしたのがこちら。
if(cnt == 0){ //0文字
submit_btn.prop('disabled', true); // ボタン無効
$('.circle_wrap').hide(); // circleを隠す
} else if(cnt > 0 && 280 > cnt){ // 1文字以上、280文字以内
$('.circle_wrap').fadeIn(); // circleを表示
submit_btn.prop('disabled', false); // ボタン有効化
$('.cnt_area').removeClass('cnt_danger'); // 文字色を戻す
circle.attr('stroke','#1DA1F2'); // errorから復帰した時、circleの色を青に戻す
} else { // 280文字を超える場合
circle.attr('stroke','red'); // circleを赤色
$('.cnt_area').addClass('cnt_danger'); // 文字色を赤色
submit_btn.prop('disabled', true); // ボタン無効
}
});
以上、これで完成かと思いきや、
入力が始まってからcircleを表示させたいのに、初めから表示された状態になってしまっています。
これは、inputイベントの中で「0文字の時はcircleを非表示にする」という処理が書かれているからです。
ただ画面を表示しただけでは、処理は実行されません。
ここで、triggerメソッドを使い、inputイベントを手動で実行させてあげます。
});
// triggerメソッドで、とりあえずinputイベントを手動で発生させる。
// リロード時に初期文字列が入っていた時の対策 + circleを非表示にしたい
$('.text').trigger('input');
});
こうすることで、画面が表示された時に、一旦inputイベントの中身が走ります。すると、.textのvalueは当然0なわけですから、circleは非表示になってくれる。
はい、以上です。
完成したコードをみてみましょう。
あとやってみたいのが、「制限文字数を超過したテキストに背景色をつける機能」ですが、これはtextareaでは難しそうな感じがしてます。
超過した文字数をチェックして、spanで囲うってのはできそうなのですが、textareaだと<span>がそのまま入ってしまいます。。
気になる方はぜひ挑戦してみてください!(そして僕に教えてくださいw)
一応、全てのコードを載せておきますね。
HTML
<div class="container">
<textarea class="text" placeholder='いまどうしてる?'></textarea>
<div class="submit_wrap">
<div class="circle_wrap">
<svg height="100%" viewBox="0 0 20 20" width="100%" style="overflow: visible;">
<circle cx="50%" cy="50%" fill="none" stroke-width="2" r="9" stroke="#38444D"></circle>
<circle id="circle" cx="50%" cy="50%" fill="none" stroke-width="2" r="9" stroke="#1DA1F2" stroke-linecap="round" style="stroke-dashoffset: 56.3; stroke-dasharray: 56.5;"></circle>
</svg>
</div>
<div class="cnt_area"><span class="now_cnt">0</span> / 280</div>
<button type="button" class="submit_btn">ツイートする</button>
</div>
</div>
SCSS
// リセット
button {
display: inline-block;
cursor:pointer;
border:none;
box-sizing:border-box;
}
textarea {
width: 100%;
box-sizing:border-box;
border:none;
resize: none;
}
//fontとbackgroundをtwitterっぽく
body {
background-color: #16202C;
font-family: Arial, "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Osaka, メイリオ, Meiryo, "MS Pゴシック", "MS PGothic", sans-serif;
}
// コンテナ
.container {
margin: 30px auto;
width: 400px;
padding: 10px;
border:1px solid rgb(56, 68, 77);
border-radius:16px;
}
//テキストエリア
.text {
color:#fff;
font-size: 20px;
padding: 10px;
background-color: #16202C;
&::placeholder {
color:#777;
}
&:focus {
border:none;
outline:none;
&::placeholder {
color:#eee;
}
}
}
//下部のwrap
.submit_wrap {
display: flex;
justify-content:flex-end;
align-items:center;
margin-top: 15px;
}
//文字数カウント
.cnt_area{
text-align: center;
width: 100px;
color:#fff;
&.cnt_danger{
color: red;
}
}
// グラフのwrap
.circle_wrap {
position:relative;
top: 0;
right: 0;
height: 30px;
width: 30px;
transform: rotate(-90deg);
}
//ツイートするボタン
.submit_btn {
background-color: #1CA1F2;
padding: 10px 20px;
border-radius:50px;
color:#fff;
font-weight: bold;
font-size: 16px;
&:disabled {
opacity:0.6;
}
}
JavaScript(jQuery)
$(function(){
//カウント用の関数を定義
function getLen(str){
let result = 0;
for(var i=0;i<str.length;i++){
let chr = str.charCodeAt(i);
if((chr >= 0x00 && chr < 0x81) ||
(chr === 0xf8f0) ||
(chr >= 0xff61 && chr < 0xffa0) ||
(chr >= 0xf8f1 && chr < 0xf8f4)){
//半角文字の場合は1を加算
result += 1;
}else{
//それ以外の文字の場合は2を加算
result += 2;
}
}
//結果を返す
return result;
};
//入力時のイベント
$('.text').on('input', function(){
let cnt = getLen($(this).val()) //文字数を取得
let cnt_bar = cnt * 0.1999; //文字数を元に計算
let dash_array = 56.5 + cnt_bar; // グラフに反映する計算式
let circle = $('#circle');
let submit_btn = $('.submit_btn');
// stroke-dasharrayにdash_arrayの数値を反映
circle.css('stroke-dasharray',dash_array);
$('.now_cnt').text(cnt);//現在の文字数を表示
if(cnt == 0){ //0文字
submit_btn.prop('disabled', true); // ボタン無効
$('.circle_wrap').hide(); // circleを隠す
} else if(cnt > 0 && 280 > cnt){ // 1文字以上、280文字以内
$('.circle_wrap').fadeIn(); // circleを表示
submit_btn.prop('disabled', false); // ボタン有効化
$('.cnt_area').removeClass('cnt_danger'); // 文字色を戻す
circle.attr('stroke','#1DA1F2'); // errorから復帰した時、circleの色を青に戻す
} else { // 280文字を超える場合
circle.attr('stroke','red'); // circleを赤色
$('.cnt_area').addClass('cnt_danger'); // 文字色を赤色
submit_btn.prop('disabled', true); // ボタン無効
}
});
// triggerメソッドで、とりあえずinputイベントを手動で発生させる。
// リロード時に初期文字列が入っていた時の対策 + circleを非表示にしたい
$('.text').trigger('input');
});
少しでも参考になれば幸いです。
「ここ間違ってるよー!」というご意見ご感想大歓迎です。
サポートいただければ嬉しいです。