どもです。
inputやtextareaでmaxlength属性を設定すれば、最大字数を設定できます。
これを超える入力やペーストは超過分だけカットされるので、非常に使い勝手が良いです。
しかし、バックヤード系だと結構要求されるのが、「字数」でなく「バイト数」の入力制限です。
一度は全部自力で作ってみたのですが、どうにも動作がもっさりしてしまったので、先人の知恵に頼ることにしました。
PisukeCode - Web開発まとめ[jQueryでtextareaの入力をバイト数で制限する方法&コード例]
動作も早く、大変素晴らしいです。
しかしこちらのケースでは、超過入力があるとその全てを無効化してしまいます。
今回欲しいのは、maxlength属性ライクな超過分だけカットする機能です。
というわけで、改造してみましょう。
超過分カットに変更するにあたり、課題となるのはこの辺でしょうか。
①超過分をカットした文字列を作成する
②フォーカスの位置に文字列を挿入する
③選択中の文字列があった場合、削除してから処理する
//全角2バイトチェック(UTF-8的計算が必要な場合は、ここを編集)
$.getByteLengthChar = function(chr){
var length = 0;
if((chr >= 0x00 && chr < 0x81) || (chr === 0xf8f0) || (chr >= 0xff61 && chr < 0xffa0) || (chr >= 0xf8f1 && chr < 0xf8f4)){
//半角文字の場合は1を加算
length = 1;
}else{
//それ以外の文字の場合は2を加算
length = 2;
}
return length ;
};
//字数をカウント
$.getByteLength = function(value){
var result = 0;
var size = value.length;
for(var i=0; size; i++){
var chr = value.charCodeAt(i);
result += $.getByteLengthChar(chr)
}
return result;
};
//指定バイトで文字列をカット
$.getByteCut = function(base, lengthVal){
var le = 0;
var result = '';
var size = base.length;
for(var i=0; i<size; i++){
var chr = base.charCodeAt(i);
le += $.getByteLengthChar(chr)
if(lengthVal>=le){
result += base.charAt(i);
}else{
break;
}
}
return result;
};
//フォーカス位置に挿入
$.insertForcusPosition = function(selector, str){
var sentence = selector.value;
var len = sentence.length;
var pos = selector.selectionStart;
var before = sentence.substr(0, pos);
var after = sentence.substr(pos, len);
selector.value = before + str + after;
var newPos = before.length + str.length;
selector.setSelectionRange(newPos, newPos);
}
//IME入力中の値を取っておく
var maxbytelength_beforeValue = null;
var maxbytelength_beforePos = 0;
$('input[maxbytelength], textarea[maxbytelength]').on('compositionstart', function(e){
deleteSelection(this);
maxbytelength_beforeValue = this.value;
maxbytelength_beforePos = this.selectionStart;
});
//選択状態のとき、その内容を消去してから後続の処理を行う
function deleteSelection(selector){
var pos_start = selector.selectionStart;
var pos_end = selector.selectionEnd;
if(pos_end>0 && pos_end-pos_start>0){
var val = selector.value;
var range = val.slice(pos_start, pos_end);
var beforeNode = val.slice(0, pos_start);
var afterNode = val.slice(pos_end);
selector.value = beforeNode + afterNode;
selector.setSelectionRange(pos_start, pos_start);
}
}
//本体
$('input[maxbytelength], textarea[maxbytelength]').on('keypress compositionend paste drop', function(e){
deleteSelection(this);
var MAX_BLEN = $(this).attr('maxbytelength');
var value = $(this).val();
var valueByteLen = $.getByteLength(value);
if(valueByteLen > MAX_BLEN){
switch(e.type){
case 'compositionend': /// IMEでの入力が確定したとき
var imeData = e.originalEvent.data;
var scope = MAX_BLEN - $.getByteLength(maxbytelength_beforeValue);
var sentence = maxbytelength_beforeValue;
var len = sentence.length;
var before = sentence.substr(0, maxbytelength_beforePos);
var after = sentence.substr(maxbytelength_beforePos, len);
var tmp = $.getByteCut(imeData, scope);
var pos = before.length + tmp.length;
this.value = before + tmp + after;
this.setSelectionRange(pos, pos);
break;
}
}
switch(e.type){
case 'keypress': /// 半角文字が入力されたとき
if($.getByteLength(e.key)==1 && valueByteLen+1 > MAX_BLEN)
e.preventDefault(); e.stopPropagation();
break;
case 'paste': /// 貼り付けが反映される前
var pasted = (window.clipboardData && window.clipboardData.getData)
? window.clipboardData.getData('Text')
: e.originalEvent.clipboardData.getData('text/plain');
var futureByteLen = valueByteLen + $.getByteLength(pasted);
if(futureByteLen > MAX_BLEN){
var scope = MAX_BLEN - valueByteLen;
$.insertForcusPosition(this, $.getByteCut(pasted, scope));
e.preventDefault();
e.stopPropagation();
}
break;
case 'drop': /// ドロップが反映される前
var dropped = e.originalEvent.dataTransfer.getData("text/plain");
var futureByteLen = valueByteLen + $.getByteLength(dropped);
if(futureByteLen > MAX_BLEN){
var scope = MAX_BLEN - valueByteLen;
$.insertForcusPosition(this, $.getByteCut(dropped, scope));
e.preventDefault();
e.stopPropagation();
}
break;
}
});
Chrome、FF、IE11で動作確認済み。
使い方は「maxbytelength」属性をinputまたはtextareaに設置し、切り取りたいバイト数を設定するだけ。
ペースト時のフォーカスの位置など、細かな調整をすると追記が山のよう_(:3」∠)_
これでも完璧とは言い難いですが、おおよそmaxlength属性に近づけられたかな、と。
追記
「最大値内なら加算」から「最大値超過なら減算」に変更で、バイト数カットはだいぶスリムにできますね…
※他処理との整合性は整えてないので、このまま入れ替えても動きません。
//全角2バイトチェック(UTF-8的計算が必要な場合は、ここを編集)
String.prototype.getByteLengthChar = function(){
let length = 0;
for (let i = 0; i < this.length; i++) {
let chr = this.charCodeAt(i);
if((chr >= 0x00 && chr < 0x81) || (chr === 0xf8f0) || (chr >= 0xff61 && chr < 0xffa0) || (chr >= 0xf8f1 && chr < 0xf8f4)){
length += 1;
} else {
length += 2;
}
}
return length;
};
//指定バイトで文字列をカット
$.getByteCut = function(base, lengthVal){
while(base.bytes() > lengthVal){
base = base.slice(0, -1);
}
return base;
};