Javascript 自动在每N个字符后面加-

本文介绍了一种使用JavaScript实现的信用卡号格式化方法。通过监听输入框的键盘事件,自动将用户输入的数字按标准信用卡格式进行排列,并插入分隔符。此功能利用jQuery简化DOM操作,提高用户体验。
function format(input, format, sep) {
    var output = "";
    var idx = 0;
    for (var i = 0; i < format.length && idx < input.length; i++) {
        output += input.substr(idx, format[i]);
        if (idx + format[i] < input.length) output += sep;
        idx += format[i];
    }

    output += input.substr(idx);

    return output;
}

$('.creditCardText').keyup(function() {
    var foo = $(this).val().replace(/-/g, ""); // remove hyphens
    // You may want to remove all non-digits here
    // var foo = $(this).val().replace(/\D/g, "");

    if (foo.length > 0) {
        foo = format(foo, [3, 2, 4, 3, 3], "-");
    }
  
    
    $(this).val(foo);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input class="creditCardText" />
<think>我们有一个需求:在用户输入时,自动每隔两个字符插入一个空格。这通常用于格式化输入,比如信用卡号、身份证号等,使输入更易读。 思路: 1. 监听输入框的变化(如onChange事件) 2. 获取当前输入的值,并移除所有非字母数字字符(根据需求,这里我们只考虑字符,不考虑其他符号,但实际可能需要根据需求调整) 3. 然后每隔两个字符插入一个空格 4. 将格式化后的字符串设置回输入框 注意:我们需要处理光标位置,因为直接替换整个输入框的值会导致光标跳到末尾。为了更好的用户体验,应该在插入空格后恢复光标到原来的位置(或者根据插入的空格数调整光标位置)。 但是,由于我们是在输入时自动格式化,所以需要记录光标位置,并在格式化后重新设置光标位置。 步骤: - 获取当前光标的位置(selectionStart和selectionEnd) - 获取输入的值,移除所有空格(因为我们要重新格式化,所以先清除已有的空格) - 然后每两个字符插入一个空格 - 将新的字符串设置回输入框 - 计算新字符串中光标应该处于的位置:在格式化之前,光标前有多少个非空格字符,那么在格式化之后,这些字符(包括新增的空格)之后的位置就是光标应该放的位置。 但是,有一个更简单的方法:在输入时,我们只关心用户输入的字符,而不关心格式。我们可以记录在格式化之前,光标之前有多少个非空格字符(即实际有效字符的个数),然后格式化后,计算这些字符前面有多少个空格(因为每两个字符插入一个空格,所以可以计算出来),然后调整光标位置。 然而,由于我们移除了所有空格,然后重新插入,所以我们可以根据原始字符串(无空格)中光标的位置,来推算新字符串中光标的位置。 例如,假设原始字符串(无空格)长度为n,光标在位置pos(在原始字符串中,这个位置之前有pos个字符)。那么在新字符串中,每两个字符插入一个空格,那么在第k组(每组两个字符后面会有一个空格。所以,在pos个字符之前,有 Math.floor(pos / 2) 个空格(因为每两个字符插入一个空格,所以每两个字符就会多一个空格)。因此,新字符串中光标的位置应该是:pos + Math.floor(pos / 2) (注意:如果pos=0,则没有空格;pos=2,则前面有一个空格;pos=4,则前面有两个空格,以此类推) 但是,这里有一个边界:当pos=0时,新位置为0;当pos=1时,新位置为1(因为还没有到插入空格的时候);当pos=2时,新位置为3(因为两个字符后面了一个空格,所以位置是2+1=3)。 但是,如果用户是在中间插入字符,那么我们需要调整光标位置。 然而,我们也可以使用另一种方法:在格式化之前,记录当前光标之前有多少个非空格字符(记为originalCursorIndex),然后格式化后,新字符串中,在每两个字符后插入空格,那么新字符串中,在originalCursorIndex个字符前面有多少个空格?就是 Math.floor(originalCursorIndex / 2)(注意:如果originalCursorIndex是偶数,那么前面有originalCursorIndex/2个空格;如果是奇数,那么前面有(originalCursorIndex-1)/2个空格?)。所以新位置 = originalCursorIndex + Math.floor(originalCursorIndex / 2) 但是,这个公式在originalCursorIndex=0时,结果为0;originalCursorIndex=1时,结果为1;originalCursorIndex=2时,结果为3(因为两个字符后有一个空格);originalCursorIndex=3时,结果为3+1=4(因为第三个字符前面有两个字符和一个空格,然后第三个字符是第二个组中的第一个字符,所以前面还有一个空格?不对,应该是:原始字符串为3个字符,格式化后为:前两个字符+空格+第三个字符,所以第三个字符在新字符串中的位置是3(空格位置)+1=4。所以公式正确:3 + floor(3/2)=3+1=4。 因此,我们可以使用这个公式。 但是,注意:如果用户删除字符,我们也要处理。所以,我们统一处理:在格式化之前,先记录当前光标在原始字符串(无空格)中的位置(即当前光标之前的非空格字符数)。然后格式化,然后根据公式计算新位置。 实现步骤(以React为例,但原理适用于其他框架): 1. 在onChange事件中,我们可以通过event.target获取输入框的值和当前光标位置。 2. 移除所有空格,得到原始字符串(rawValue)。 3. 计算原始字符串中光标的位置:由于我们移除了空格,那么当前光标在原始字符串中的位置(originalCursorIndex)等于当前光标位置(oldCursorPos)减去从开始到oldCursorPos位置中空格的数量。但是,我们也可以这样:在移除空格之前,记录光标位置之前的非空格字符数。因为原始字符串中,从0到oldCursorPos之间的字符,去掉空格后就是originalCursorIndex。所以,我们可以取输入值的前oldCursorPos个字符,然后移除这些字符中的空格,得到这些字符的数量,即为originalCursorIndex。 但是,更简单的方法:我们不需要这样,因为我们在格式化之前移除了所有空格,所以整个字符串就是没有空格的。那么,在格式化之前,光标在原始字符串(无空格)中的位置就是:当前光标位置(oldCursorPos)减去从0到oldCursorPos之间出现的空格数。例如,输入字符串为"12 34",光标在位置3(在第一个空格之后,即'12 '之后,光标在空格后面,也就是在3的位置),那么从0到3之间的字符串是"12 ",其中有两个非空格字符(因为空格被移除,所以非空格字符为2),所以originalCursorIndex=2。 因此,我们可以这样计算originalCursorIndex: let value = event.target.value; let oldCursorPos = event.target.selectionStart; // 计算从0到oldCursorPos之间的非空格字符数 let textBeforeCursor = value.substring(0, oldCursorPos); let nonSpaceCountBeforeCursor = textBeforeCursor.replace(/\s/g, '').length; 4. 然后,我们得到原始字符串(整个字符串去掉空格):rawValue = value.replace(/\s/g, ''); 5. 格式化rawValue:每两个字符插入一个空格 let formatted = rawValue.replace(/(\w{2})/g, '$1 ').trim(); 注意:这里使用trim是因为最后可能会多一个空格,所以去掉首尾空格。 但是,这样会在每两个字符后面一个空格,包括最后两个字符后面也会,所以我们可能要去掉最后一个空格。或者我们可以用另一种方式:将字符串拆分成每两个字符一组,然后用空格连接,最后去掉末尾可能多余的空格。 6. 计算新光标位置:newCursorPos = nonSpaceCountBeforeCursor + Math.floor(nonSpaceCountBeforeCursor / 2); 但是,注意:如果nonSpaceCountBeforeCursor为0,那么newCursorPos=0;如果为1,则为1;如果为2,则为3;如果为3,则为4;如果为4,则为6,等等。 7. 将formatted设置到输入框,并设置光标位置。 但是,还有一个问题:如果用户输入了非字母数字字符,我们可能要去除(根据需求)。上面的例子中,我们只去除了空格,但可能还需要去除其他非数字字符(例如,如果是格式化信用卡号,则只允许数字)。所以,在第一步,我们可以先过滤掉非数字字符(如果是数字的话),然后再格式化。 因此,我们可以修改为: rawValue = value.replace(/\D/g, ''); // 只保留数字 或者,如果允许字母,则用:rawValue = value.replace(/\s/g, ''); // 只去掉空格,保留其他 根据需求:我们要求每隔两个字符插入空格,并没有限定是数字还是字母,所以我们可以保留所有非空格字符(或者根据需求,可能要去除非字母数字的字符?)。这里我们假设只允许字母和数字,那么我们可以这样: rawValue = value.replace(/[^\da-zA-Z]/g, ''); // 去除非数字和字母的字符 或者,如果需求是任意两个字符(包括符号),那么就不需要过滤,只去掉空格即可。 由于需求是“自动每隔两个字符插入空格”,没有指定字符类型,我们可以只处理空格,但为了不让其他特殊字符影响格式,我们也可以选择只允许字母数字。这里我们按照只允许字母数字来处理。 实现代码(React示例): 我们将这个逻辑封装在一个函数中,然后在输入框的onChange事件中调用。 注意:由于我们每次onChange都会重新设置输入框的值,所以可能会触发多次渲染。在React中,我们需要使用受控组件。 示例代码: ```jsx import React, { useState } from 'react'; const MaskedInput = () => { const [value, setValue] = useState(''); const handleChange = (event) => { const input = event.target.value; const cursorPos = event.target.selectionStart; // 计算光标之前的非空格字符数(这里我们实际要去除所有非字母数字字符,所以先计算旧值中光标前的非字母数字字符数?) // 但是,我们这里先不过滤,因为我们要先计算光标位置。我们只考虑字母数字和空格,所以先去掉空格,然后计算光标前有多少个字母数字(包括其他字符?不,我们下一步会过滤掉非字母数字) // 因此,我们分两步: // 1. 计算当前光标位置之前(0到cursorPos)有多少个字母数字(因为非字母数字会被过滤掉,所以这些字符将不会出现在rawValue中) let textBeforeCursor = input.substring(0, cursorPos); // 计算textBeforeCursor中字母数字的数量(即不会被过滤的字符) let nonSpaceCountBeforeCursor = textBeforeCursor.replace(/[^\da-zA-Z]/g, '').length; // 2. 过滤整个输入:只保留字母数字 let rawValue = input.replace(/[^\da-zA-Z]/g, ''); // 3. 格式化:每两个字符插入一个空格 let formatted = ''; for (let i = 0; i < rawValue.length; i++) { if (i > 0 && i % 2 === 0) { formatted += ' '; } formatted += rawValue[i]; } // 或者用正则表达式:但是注意,这样会在每两个字符后面空格,包括最后也会,所以我们需要去掉末尾空格 // let formatted = rawValue.replace(/(\w{2})/g, '$1 ').trim(); // 但是,用循环可以避免最后多一个空格,所以我们用循环。 // 4. 计算新光标位置 let newCursorPos = nonSpaceCountBeforeCursor + Math.floor(nonSpaceCountBeforeCursor / 2); // 但是,注意:如果nonSpaceCountBeforeCursor为0,那么newCursorPos=0;如果nonSpaceCountBeforeCursor等于rawValue.length(即光标在末尾),那么newCursorPos可能大于formatted的长度,所以我们要限制在formatted.length之内 if (newCursorPos > formatted.length) { newCursorPos = formatted.length; } // 5. 更新状态 setValue(formatted); // 6. 在下一个事件循环中设置光标位置,因为设置状态后重新渲染,输入框会重置,我们需要在渲染后设置光标 // 使用setTimeout setTimeout(() => { const inputElem = event.target; inputElem.setSelectionRange(newCursorPos, newCursorPos); }, 0); }; return ( <input type="text" value={value} onChange={handleChange} /> ); }; export default MaskedInput; ``` 注意:上面的代码中,我们使用setTimeout来设置光标位置,因为React的状态更新是异步的,我们需要在DOM更新后设置光标。 但是,使用setTimeout可能不是最佳实践,我们可以使用useEffect,但是这里我们在事件处理函数中,无法直接使用useEffect。所以,我们可以使用一个ref来引用输入框,然后在useLayoutEffect中设置光标?这样会复杂一些。所以,这里使用setTimeout是常见的做法。 另外,上面的循环格式化可以改为: let formatted = rawValue.replace(/(\w{1,2})/g, (match, p1) => { if (match.length === 2) { return match + ' '; } return match; }); 或者,更简单的: let formatted = rawValue.replace(/\w{2}(?=\w)/g, '$& '); // 匹配两个字母数字,并且后面还有字符(使用正向前瞻),然后替换为这两个字符一个空格。这样就不会在最后空格。 但是,这样只能处理中间的部分,最后两个字符后面不会空格。所以,我们可以用: let formatted = rawValue.replace(/(\w{2})/g, '$1 ').trim(); // 然后去掉末尾空格 这样比较简单。 修改: let formatted = rawValue.replace(/(\w{2})/g, '$1 ').trim(); 注意:trim会去掉字符串首尾的空格,所以中间的空格保留,而最后如果多了一个空格就会被去掉。 但是,如果原始字符串长度是奇数,那么最后会剩下一个字符,那么格式化后,最后就不会有空格。所以这样是可行的。 测试: rawValue = '123' -> 替换后:'12 ' + '3' -> '12 3',然后trim()不会去掉中间空格,所以结果是'12 3' rawValue = '1234' -> 替换后:'12 34 ' -> trim后变成'12 34' 所以,可以这样用。 因此,我们可以将格式化步骤改为: let formatted = rawValue.replace(/(\w{2})/g, '$1 ').trim(); 但是,注意:如果输入是奇数个,那么最后会有一个空格在倒数第二个字符后面,然后最后一个字符单独,然后整个字符串末尾没有空格(因为trim去掉了末尾空格)。所以,这样是符合要求的。 但是,如果用户输入一个字符,那么rawValue='a',替换后:'a'(因为正则匹配两个字符,所以没有匹配到,所以不会空格),trim后还是'a'。 所以,这个方法可行。 因此,最终代码: ```jsx import React, { useState } from 'react'; const MaskedInput = () => { const [value, setValue] = useState(''); const handleChange = (event) => { const input = event.target.value; const cursorPos = event.target.selectionStart; // 计算当前光标前(0到cursorPos)的非过滤字符数(字母数字) let textBeforeCursor = input.substring(0, cursorPos); let nonSpaceCountBeforeCursor = textBeforeCursor.replace(/[^\da-zA-Z]/g, '').length; // 过滤整个输入,只保留字母数字 let rawValue = input.replace(/[^\da-zA-Z]/g, ''); // 格式化:每两个字符一个空格,并去掉首尾空格 let formatted = rawValue.replace(/(\w{2})/g, '$1 ').trim(); // 计算新光标位置 let newCursorPos = nonSpaceCountBeforeCursor + Math.floor(nonSpaceCountBeforeCursor / 2); // 确保不超过字符串长度 if (newCursorPos > formatted.length) { newCursorPos = formatted.length; } setValue(formatted); // 设置光标位置 setTimeout(() => { const inputElem = event.target; if (inputElem) { inputElem.setSelectionRange(newCursorPos, newCursorPos); } }, 0); }; return ( <input type="text" value={value} onChange={handleChange} placeholder="输入..." /> ); }; export default MaskedInput; ``` 注意:这个组件在输入时会自动格式化,并且光标位置会正确调整。 但是,如果用户使用退格键删除,或者剪切操作,这个逻辑也能处理,因为我们只关心字母数字字符的数量。 另外,如果用户从中间插入,我们也能正确调整光标位置。 但是,如果用户输入了非字母数字的字符,它们会被过滤掉,所以不会出现在输入框中。 如果需求是允许任意字符(除了空格),那么我们可以修改过滤规则:不过滤非字母数字,只过滤空格(因为我们用空格来格式化)。但是,这样会导致非字母数字字符也被计入分组。所以,我们可以不过滤非字母数字,只过滤掉空格(因为我们要重新格式化,所以先去掉所有空格,然后每两个字符插入空格)。那么,修改如下: // 只去掉空格,保留其他字符 let rawValue = input.replace(/\s/g, ''); // 格式化:每两个字符插入空格 let formatted = rawValue.replace(/(.{2})/g, '$1 ').trim(); 这样,任意两个字符(包括特殊字符)都会被分组。 因此,根据需求,我们可以灵活调整过滤规则。 以上代码是一个基本实现,可以根据具体需求调整。</think>### 实现输入时自动每隔两个字符插入空格 要实现输入时自动每隔两个字符插入空格的功能,可以通过以下两种方式实现: --- #### 方法一:纯 JavaScript 实现 ```html <input type="text" id="formattedInput" placeholder="输入内容..."> <script> const input = document.getElementById('formattedInput'); input.addEventListener('input', function(e) { // 1. 移除所有空格并限制为字母数字 let rawValue = e.target.value.replace(/\s/g, '').replace(/[^\da-zA-Z]/g, ''); // 2. 每两个字符插入空格 let formatted = ''; for (let i = 0; i < rawValue.length; i++) { if (i > 0 && i % 2 === 0) formatted += ' '; formatted += rawValue[i]; } // 3. 更新输入框值 e.target.value = formatted; }); </script> ``` **实现原理**: 1. 监听 `input` 事件,在用户输入时触发 2. 清除已有空格和非字母数字字符(如 `AB12-CD` → `AB12CD`) 3. 每两个字符插入一个空格(如 `AB12CD` → `AB 12 CD`) 4. 更新输入框显示格式化后的值 --- #### 方法二:使用 Input Mask 库(推荐) 安装轻量级库 [inputmask](https://github.com/RobinHerbots/Inputmask): ```bash npm install inputmask ``` ```javascript import Inputmask from 'inputmask'; // 设置输入掩码:每两个任意字符后空格 const mask = new Inputmask({ mask: '** ** ** ** **', // 根据需求调整 * 的数量 placeholder: '_', // 未输入时的占位符 greedy: false // 非贪婪模式(不满时不显示多余空格) }); mask.mask(document.getElementById('formattedInput')); ``` **优势**: - 支持退格键、粘贴、光标定位等边缘情况 - 内置验证和占位符处理 - 可自定义格式(如 `99 99 99` 只允许数字) --- ### 关键注意事项 1. **光标定位**: 纯JS方案需额外处理光标位置(通过 `setSelectionRange()`),否则连续输入时光标会跳至末尾[^1]。 2. **字符过滤**: 正则表达式 `/[^\da-zA-Z]/g` 移除非字母数字字符,根据需要调整: ```javascript // 只允许数字:/[\D]/g // 允许数字和横杠:/[^\d-]/g ``` 3. **性能优化**: 对于长文本(如信用卡号),使用 `substring` 分段处理避免卡顿: ```javascript rawValue.match(/.{1,2}/g)?.join(' '); ``` --- ### 应用场景示例 - 银行卡号格式化:`6225 8801 2345 6789` - 身份证号分段:`1101 0519 9801 0234 56` - 产品序列号:`AB12-CD34-EF56` - 日期快捷输入:`22 03 15`(年月日) [^1]: 当直接修改 `input.value` 时,浏览器默认将光标置于末尾。需记录原始光标位置,计算新位置后通过 `setSelectionRange()` 恢复。 [^2]: 输入掩码库通过缓存格式化规则和事件委托优化性能,避免高频输入时的卡顿。 --- ### 相关问题 1. 如何实现输入时自动将字母转换为大写? 2. 输入手机号时如何自动国际区号(如+86)? 3. 如何处理格式化输入框的复制/粘贴行为? 4. 在 React/Vue 中如何优雅地实现输入格式化?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值