input输入框按字节统计长度以及按字符长度截取内容

需求说明

有个需求如下,一个input框需要自定义可输入的字符长度范围以及按字节统计已输入的长度和按字符截取最大长度的内容,
例如,
需求1:在一个字符占3个字节的情况下(此处为举例,具体字节字符的关系会在下面详细说明),限制输入框最大只能输入30个长度的字符,那么在输入内容"1"时,输入框提示应该是1/30,在输入内容"12"时,提示应该是1/30,在输入内容"123"时,提示也是1/30,在输入内容"1234"时,提示内容应该是2/30,在输入内容"你1"时,提示内容为2/30,基本情况下可以认为中文字符等价于汉字
需求2:现有字符串"有1个需求如下,现一个input框需要自定义可输入的长度范围以及按字节",以上字符串按照截取长度30个字符来算,需要截取90长度的字节,那么最后一个汉字"节"只能截取到前两个字节,最后一个字节是第91个字节,那么导致的问题就是最后一个汉字会变成乱码

前置知识

首先了解字符和字节之间的关系:
英文字母和中文汉字(包括中文标点)在不同字符集编码下所占的字节数
英文字母:
字节数 : 1;编码:GB2312
字节数 : 1;编码:GBK
字节数 : 1;编码:GB18030
字节数 : 1;编码:ISO-8859-1
字节数 : 1;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE
中文汉字:
字节数 : 2;编码:GB2312
字节数 : 2;编码:GBK
字节数 : 2;编码:GB18030
字节数 : 1;编码:ISO-8859-1
字节数 : 3;编码:UTF-8
字节数 : 4;编码:UTF-16
字节数 : 2;编码:UTF-16BE
字节数 : 2;编码:UTF-16LE,
由以上可知一个字符占多少字节是和字符集相关的,解决这个问题,那么我们可以在computed里面计算一个变量,得到当前环境下一个字符占多少个字节,代码如下:

computed:{
    //一个中文字符占多少字节
    charToByte: function () {
      // let str2 = '中';
      let str2 = ';';
      let byte2 = this.stringToByte(str2)
      return byte2.length;
    },
  },
  methods: {
    //字符串转字节数组
    stringToByte(str) {
      const bytes = new Array()
      let len, c
      len = str.length
      for (let i = 0; i < len; i++) {
        c = str.charCodeAt(i)
        if (c >= 0x010000 && c <= 0x10FFFF) {
          bytes.push(((c >> 18) & 0x07) | 0xF0)
          bytes.push(((c >> 12) & 0x3F) | 0x80)
          bytes.push(((c >> 6) & 0x3F) | 0x80)
          bytes.push((c & 0x3F) | 0x80)
        } else if (c >= 0x000800 && c <= 0x00FFFF) {
          bytes.push(((c >> 12) & 0x0F) | 0xE0)
          bytes.push(((c >> 6) & 0x3F) | 0x80)
          bytes.push((c & 0x3F) | 0x80)
        } else if (c >= 0x000080 && c <= 0x0007FF) {
          bytes.push(((c >> 6) & 0x1F) | 0xC0)
          bytes.push((c & 0x3F) | 0x80)
        } else {
          bytes.push(c & 0xFF)
        }
      }
      return bytes
    }
  }

有了这个变量,下面就可以实现需求

首先是需求一:

<el-input v-model="inputVal" @input="handleInput"/>

先给input绑定@input事件,在用户输入的时候,能实时统计已经输入的字符长度:

 methods: {
    handleInput(val) {
      let byte = this.stringToByte(val);
      this.charNum = Math.ceil(byte.length / this.charToByte);
    }
  }

这里需要向上取整,因为输入1个英文字符时,也要提示长度已经输入了1

需求二:
此需求实现思路如下,首先把需要截取的字符串转换为字节数组,然后根据限制的字符长度截取对应长度的字节数组,再将字节数组转换为字符串
难点一:需要标记哪些字节数组是对应中文字符,哪些是对应英文字符,如"你1"转换为字节数组为[228, 189, 160, 49],前三个字节是汉字"你"所占的字节,最后一位是数字"1"所占字节,解决此问题代码如下:

//获取字节数组每一位代表的是否为中文
getHanZiByteArr(str){
	 let byte = this.stringToByte(str);
	 const resultArr = new Array;
	 for (let i = 0; i < byte.length; i++) {
	   const obj = {
	     ishanzi: false,//是否是汉字字节
	     groupFlag: null//同组标记,一个汉字转换来的3个字节的此属性值相同
	   }
	   resultArr.push(obj);
	 }
	 let strArr = str.split("");
	 //匹配中文标点字符的正则
	 const bdReg = /[\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]/
	 //匹配中文汉字字符的正则
	 const hzReg =/[\u4E00-\u9FA5]/;
	 let realIndex = 0;
	 for (let i = 0; i < strArr.length; i++) {
	   let singleStr = strArr[i];
	   //生成唯一的标记UUID
	   let groupId = this.generateUUID();
	   if(bdReg.test(singleStr) || hzReg.test(singleStr)){
	     //是中文字符
	     for (let k = 0;k<this.charToByte;k++){
	       resultArr[realIndex].ishanzi = true;
	       resultArr[realIndex].groupFlag = groupId;
	       realIndex++;
	     }
	   }else{
	     resultArr[realIndex].ishanzi = false;
	     resultArr[realIndex].groupFlag = groupId;
	     realIndex++;
	   }
	 }
	 return resultArr;
}

难点二,需要根据截取的最后一位字节的ishanzi属性值判断是汉字字节还是非汉字字节,并做相应处理
情况一:如果最后一位字节为false,表示截取到限制长度的最后一位刚好是非中文字节,因为非汉字字节本身只占一个字节,那么不存在被截断的情况,即如下情况"你好123哈哈"截取3个字符,截取刚好是"你好123",此情况直接将截取好的字节数组转换为字符串即可
情况二:如果最后一位字节为true,表示截取到限制长度的最后一位是中文字符,那么判断和此字节的groupFlag相同的字节个数是否等于前面计算属性中一个中文字符对应的字节个数,如果相等,即如下情况"你好123哈哈"截取4个字符刚好是"你好123哈",此种情况同情况一将截取好的字节数组转换为字符串即可
情况三:情况二中如果和此字节的groupFlag相同的字节个数不等于(一定是小于)前面计算属性中一个中文字符对应的字节个数,表示一个汉字所占的三个字节被截断了,即如下情况,"你1好123哈哈"截取4个字符长度,那么对应的字节数组[228, 189, 160, 49, 229, 165, 189, 49, 50, 51, 229, 147, 136, 229, 147, 136]被截取成[228, 189, 160, 49, 229, 165, 189, 49, 50, 51, 229, 147],第一个汉字"哈"被截掉了最后一个字节136,那么转换为字符串后就会乱码,此种情况应该舍弃不完整的字节数组,再转换为字符串。
完整版代码如下:

<template>
  <div>
    请输入内容:<el-input v-model="inputVal" @input="handleInput"/>
    所占字符长度:<span>{{ charNum }}</span>
    <el-divider></el-divider>
    目标内容:<el-input v-model="targetStr" />
    截取长度:<el-input v-model="limit" />
    <el-button type="primary" @click="handleSubstr">截取</el-button>
    <el-divider></el-divider>
    截取结果:<span>{{afterSubstr}}</span>
  </div>
</template>

<script>
export default {
  name: 'subs',
  data() {
    return {
      charNum: 0,
      inputVal: '',
      limit:0,
      afterSubstr:'',
      targetStr:''
    }
  },
  computed:{
    //一个中文字符占多少字节
    charToByte: function () {
      // let str = '中';
      let str2 = ';';
      // let byte = this.stringToByte(str)
      let byte2 = this.stringToByte(str2)
      return byte2.length;
    },
  },
  methods: {
    handleSubstr(){
      this.afterSubstr = this.subAndTransToStr(this.targetStr,this.limit);
    },
    //截取前n个字符
    subAndTransToStr(str,n){
      let resourceArr = this.stringToByte(str);
      let finalByteArr = []
      let hanZiByteArr = this.getHanZiByteArr(str);
      const sumNum = n * this.charToByte
      if(sumNum >= hanZiByteArr.length ){
        finalByteArr = resourceArr;
      }else{
        finalByteArr = hanZiByteArr.slice(0,sumNum);
        if(finalByteArr[finalByteArr.length - 1].ishanzi){
          let count = 0;
          //最后一位为true,需要判断同groupFlag的charToByte个元素的ishanzi是否全为true,是代表最后一位刚好是汉字,否则就是截断了,直接舍弃
          var lastGroupFlag = finalByteArr[finalByteArr.length - 1].groupFlag;
          finalByteArr.forEach((item,index)=>{
            if(item.groupFlag == lastGroupFlag){
              count++;
            }
          });
          if(count == this.charToByte){
            finalByteArr = resourceArr.slice(0,finalByteArr.length);
          }else{
            finalByteArr = resourceArr.slice(0,finalByteArr.length - count);
          }
        }else{
          finalByteArr = resourceArr.slice(0,finalByteArr.length);
        }
      }
      let result = this.utf8ByteToUnicodeStr(finalByteArr);
      return result;
    },
    //获取字节数组每一位代表的是否为中文
    getHanZiByteArr(str){
      let byte = this.stringToByte(str);

      const resultArr = new Array;
      for (let i = 0; i < byte.length; i++) {
        const obj = {
          ishanzi: false,
          groupFlag: null
        }
        resultArr.push(obj);
      }

      let strArr = str.split("");
      const bdReg = /[\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]/
      const hzReg =/[\u4E00-\u9FA5]/;
      let realIndex = 0;
      for (let i = 0; i < strArr.length; i++) {
        let singleStr = strArr[i];
        let groupId = this.generateUUID();
        if(bdReg.test(singleStr) || hzReg.test(singleStr)){
          //是中文字符
          for (let k = 0;k<this.charToByte;k++){
            resultArr[realIndex].ishanzi = true;
            resultArr[realIndex].groupFlag = groupId;
            realIndex++;
          }
        }else{
          resultArr[realIndex].ishanzi = false;
          resultArr[realIndex].groupFlag = groupId;
          realIndex++;
        }
      }
      return resultArr;
    },
    handleInput(val) {
      let byte = this.stringToByte(val);
      this.charNum = Math.ceil(byte.length / this.charToByte);
    },
    //字符串转字节数组
    stringToByte(str) {
      const bytes = new Array()
      let len, c
      len = str.length
      for (let i = 0; i < len; i++) {
        c = str.charCodeAt(i)
        if (c >= 0x010000 && c <= 0x10FFFF) {
          bytes.push(((c >> 18) & 0x07) | 0xF0)
          bytes.push(((c >> 12) & 0x3F) | 0x80)
          bytes.push(((c >> 6) & 0x3F) | 0x80)
          bytes.push((c & 0x3F) | 0x80)
        } else if (c >= 0x000800 && c <= 0x00FFFF) {
          bytes.push(((c >> 12) & 0x0F) | 0xE0)
          bytes.push(((c >> 6) & 0x3F) | 0x80)
          bytes.push((c & 0x3F) | 0x80)
        } else if (c >= 0x000080 && c <= 0x0007FF) {
          bytes.push(((c >> 6) & 0x1F) | 0xC0)
          bytes.push((c & 0x3F) | 0x80)
        } else {
          bytes.push(c & 0xFF)
        }
      }
      return bytes

    },
    //字节数组转字符串(处理了乱码)
    utf8ByteToUnicodeStr(utf8Bytes) {
      let unicodeStr = ''
      for (let pos = 0; pos < utf8Bytes.length;) {
        const flag = utf8Bytes[pos]
        let unicode = 0
        if ((flag >>> 7) === 0) {
          unicodeStr += String.fromCharCode(utf8Bytes[pos])
          pos += 1

        } else if ((flag & 0xFC) === 0xFC) {
          unicode = (utf8Bytes[pos] & 0x3) << 30
          unicode |= (utf8Bytes[pos + 1] & 0x3F) << 24
          unicode |= (utf8Bytes[pos + 2] & 0x3F) << 18
          unicode |= (utf8Bytes[pos + 3] & 0x3F) << 12
          unicode |= (utf8Bytes[pos + 4] & 0x3F) << 6
          unicode |= (utf8Bytes[pos + 5] & 0x3F)
          unicodeStr += String.fromCharCode(unicode)
          pos += 6

        } else if ((flag & 0xF8) === 0xF8) {
          unicode = (utf8Bytes[pos] & 0x7) << 24
          unicode |= (utf8Bytes[pos + 1] & 0x3F) << 18
          unicode |= (utf8Bytes[pos + 2] & 0x3F) << 12
          unicode |= (utf8Bytes[pos + 3] & 0x3F) << 6
          unicode |= (utf8Bytes[pos + 4] & 0x3F)
          unicodeStr += String.fromCharCode(unicode)
          pos += 5

        } else if ((flag & 0xF0) === 0xF0) {
          unicode = (utf8Bytes[pos] & 0xF) << 18
          unicode |= (utf8Bytes[pos + 1] & 0x3F) << 12
          unicode |= (utf8Bytes[pos + 2] & 0x3F) << 6
          unicode |= (utf8Bytes[pos + 3] & 0x3F)
          unicodeStr += String.fromCharCode(unicode)
          pos += 4

        } else if ((flag & 0xE0) === 0xE0) {
          unicode = (utf8Bytes[pos] & 0x1F) << 12

          unicode |= (utf8Bytes[pos + 1] & 0x3F) << 6
          unicode |= (utf8Bytes[pos + 2] & 0x3F)
          unicodeStr += String.fromCharCode(unicode)
          pos += 3

        } else if ((flag & 0xC0) === 0xC0) { //110
          unicode = (utf8Bytes[pos] & 0x3F) << 6
          unicode |= (utf8Bytes[pos + 1] & 0x3F)
          unicodeStr += String.fromCharCode(unicode)
          pos += 2

        } else {
          unicodeStr += String.fromCharCode(utf8Bytes[pos])
          pos += 1
        }
      }
      return unicodeStr
    },
    generateUUID() {
      var d = new Date().getTime();
      if (window.performance && typeof window.performance.now === "function") {
        d += performance.now(); //use high-precision timer if available
      }
      var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
      });
      return uuid;
    }
  }
}
</script>
<style scoped>

</style>
闲来无事记录一下,如有谬误欢迎指正!
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用 JavaScript 的事件监听器来实现输入框限制字节长度的功能。具体做法如下: 1. 在 HTML 中,给输入框添加一个事件监听器,监听输入事件(oninput): ``` <input type="text" oninput="checkLength(this)" /> ``` 2. 在 JavaScript 中,编写 checkLength 函数,该函数会在用户输入时被调用: ``` function checkLength(input) { // 获取输入框的值 var value = input.value; // 计算字节数 var byteLength = 0; for (var i = 0; i < value.length; i++) { var charCode = value.charCodeAt(i); if (charCode <= 127) { byteLength += 1; // 英文字符 } else { byteLength += 2; // 中文字符 } } // 如果字节数超过了限制,截断输入框的值 if (byteLength > 50) { input.value = truncate(value, 50); } } function truncate(str, len) { var byteLen = 0; for (var i = 0; i < str.length; i++) { var charCode = str.charCodeAt(i); if (charCode <= 127) { byteLen += 1; } else { byteLen += 2; } if (byteLen > len) { return str.substring(0, i); } } return str; } ``` 3. checkLength 函数中,先获取输入框的值,然后计算字节数。为了区分中文字符和英文字符,我们使用 charCodeAt 函数获取字符的 Unicode 编码,如果编码小于等于 127,就认为是英文字符,否则就认为是中文字符。英文字符算一个字节,中文字符算两个字节。 4. 如果字节数超过了限制,我们就使用 truncate 函数截断输入框的值。truncate 函数也是通过计算字节数来实现的。如果字节数超过了 len,就返回字符串的前 len 个字符。 这样,就可以实现输入框限制字节长度的功能了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值