ど素人から毛を生やす。<延>

maxlength属性みたいにinputでバイト数制限する

Web > javascript 2019年10月15日(最終更新:1年前)

2019年10月15日に作成されたページです。
情報が古かったり、僕が今以上のど素人だった頃の記事だったりする可能性があります。

どもです。

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;
};
この記事は役に立ちましたか?
  • _(:3」∠)_ 面白かった (1)
  • (`・ω・´) 役に立った (1)
  • (・∀・) 参考になった (2)