使用掩码实现输入

Posted

技术标签:

【中文标题】使用掩码实现输入【英文标题】:Implement an input with a mask 【发布时间】:2012-09-16 16:59:54 【问题描述】:

我想为接受日期的文本 input 字段实现掩码。掩码值应直接显示在 input 内部。

类似这样的:

<input type='text' value='____/__/__'>

我在该示例中将掩码写为一个值,但我的目的是让人们无需键入 /- 即可编写日期来分隔月、年和日。用户应该能够在显示的字段中输入数字,而掩码会在用户键入时自动强制执行格式。

我在其他网站上看到过这种行为,但我不知道它是如何工作的,也不知道如何自己实施。

【问题讨论】:

我遇到了同样的问题,但看到了一些有趣的东西并想分享。我定制了我的国家代码+237 ---,---,---。空格只是一系列下划线(_)。如果您的国家/地区号码是+1 777 888 990,只需写+1 ___,___,___(3 个下划线联合)。 nishangsystem.com/nishang_stuff 【参考方案1】:

可以使用keyup 事件和htmlInputElement valueselectionStartselectionEnd 属性的组合来实现输入掩码。这是一个非常简单的实现,它可以完成您想要的一些操作。它当然不完美,但足以证明原理:

Array.prototype.forEach.call(document.body.querySelectorAll("*[data-mask]"), applyDataMask);

function applyDataMask(field) 
    var mask = field.dataset.mask.split('');
    
    // For now, this just strips everything that's not a number
    function stripMask(maskedData) 
        function isDigit(char) 
            return /\d/.test(char);
        
        return maskedData.split('').filter(isDigit);
    
    
    // Replace `_` characters with characters from `data`
    function applyMask(data) 
        return mask.map(function(char) 
            if (char != '_') return char;
            if (data.length == 0) return char;
            return data.shift();
        ).join('')
    
    
    function reapplyMask(data) 
        return applyMask(stripMask(data));
    
    
    function changed()    
        var oldStart = field.selectionStart;
        var oldEnd = field.selectionEnd;
        
        field.value = reapplyMask(field.value);
        
        field.selectionStart = oldStart;
        field.selectionEnd = oldEnd;
    
    
    field.addEventListener('click', changed)
    field.addEventListener('keyup', changed)
ISO Date: <input type="text" value="____-__-__" data-mask="____-__-__"/><br/>
Telephone: <input type="text" value="(___) ___-____" data-mask="(___) ___-____"/><br/>

(View in JSFiddle)

还有许多库可以执行此功能。一些例子包括:

jquery.inputmask MASKED INPUT PLUGIN Politespace(提供输入掩码的替代方法)

【讨论】:

当光标越过掩码中的某个字符(例如连字符或括号)时,光标会向左移动 @Andree 正确,这就是我说“它当然不完美,但足以证明原理”的原因之一。现实世界的实现需要更加复杂,才能考虑到会降低用户体验的边缘情况。 是否有理由使用keyup 而不是keydown 事件? @fbynite 我现在不记得了,但我认为这是因为他们的 keydown 事件在文本框的内容更新之前就被触发了。因此,您可以根据需要使用 keydown,但您必须使用 event.preventDefault() 并使用脚本本身更新框的内容。对于一个简单的例子,比如我的答案中的那个,我认为这有点复杂,所以我改用了 keyup。 对于生产环境中的任何集成 - 我建议使用您引用的 jquery.inputmask。这不是一项简单的任务,为此使用专门的解决方案比自己定制要好得多。【参考方案2】:

响应input 事件而不是关键事件(如keyup)的解决方案将提供流畅的体验(无摆动),并且在没有键盘的情况下进行更改(上下文菜单、鼠标拖动、其他设备...)。

下面的代码将查找同时具有placeholder 属性和data-slots 属性的输入元素。后者应在占位符中定义用作输入槽的字符,例如“_”。可以为可选的data-accept 属性提供一个正则表达式,该表达式定义在这样的槽中允许哪些字符。默认为\d,即数字。

// This code empowers all input tags having a placeholder and data-slots attribute
document.addEventListener('DOMContentLoaded', () => 
    for (const el of document.querySelectorAll("[placeholder][data-slots]")) 
        const pattern = el.getAttribute("placeholder"),
            slots = new Set(el.dataset.slots || "_"),
            prev = (j => Array.from(pattern, (c,i) => slots.has(c)? j=i+1: j))(0),
            first = [...pattern].findIndex(c => slots.has(c)),
            accept = new RegExp(el.dataset.accept || "\\d", "g"),
            clean = input => 
                input = input.match(accept) || [];
                return Array.from(pattern, c =>
                    input[0] === c || slots.has(c) ? input.shift() || c : c
                );
            ,
            format = () => 
                const [i, j] = [el.selectionStart, el.selectionEnd].map(i => 
                    i = clean(el.value.slice(0, i)).findIndex(c => slots.has(c));
                    return i<0? prev[prev.length-1]: back? prev[i-1] || first: i;
                );
                el.value = clean(el.value).join``;
                el.setSelectionRange(i, j);
                back = false;
            ;
        let back = false;
        el.addEventListener("keydown", (e) => back = e.key === "Backspace");
        el.addEventListener("input", format);
        el.addEventListener("focus", format);
        el.addEventListener("blur", () => el.value === pattern && (el.value=""));
    
);
[data-slots]  font-family: monospace 
<label>Date time: 
    <input placeholder="dd/mm/yyyy hh:mm" data-slots="dmyh">
</label><br>
<label>Telephone:
    <input placeholder="+1 (___) ___-____" data-slots="_">
</label><br>
<label>MAC Address:
    <input placeholder="XX:XX:XX:XX:XX:XX" data-slots="X" data-accept="[\dA-H]">
</label><br>
<label>Alphanumeric:
    <input placeholder="__-__-__-____" data-slots="_" data-accept="\w" size="13">
</label><br>
<label>Credit Card:
    <input placeholder=".... .... .... ...." data-slots="." data-accept="\d" size="19">
</label><br>

【讨论】:

除了非常平滑且不干扰箭头、Home 和 End 的插入符号移动之外,此解决方案的行为与在字符串中间使用插入符号键入时所期望的一样。【参考方案3】:

我使用以下原则进行了自己的实现:

    只允许输入数字。 (按键事件) 获取数组中的所有数字 将掩码的每个“_”字符替换为来自 循环中的数组

欢迎改进。

/**
 * charCode [48,57]     Numbers 0 to 9
 * keyCode 46           "delete"
 * keyCode 9            "tab"
 * keyCode 13           "enter"
 * keyCode 116          "F5"
 * keyCode 8            "backscape"
 * keyCode 37,38,39,40  Arrows
 * keyCode 10           (LF)
 */
function validate_int(myEvento) 
  if ((myEvento.charCode >= 48 && myEvento.charCode <= 57) || myEvento.keyCode == 9 || myEvento.keyCode == 10 || myEvento.keyCode == 13 || myEvento.keyCode == 8 || myEvento.keyCode == 116 || myEvento.keyCode == 46 || (myEvento.keyCode <= 40 && myEvento.keyCode >= 37)) 
    dato = true;
   else 
    dato = false;
  
  return dato;


function phone_number_mask() 
  var myMask = "(___) ___-____";
  var myCaja = document.getElementById("phone");
  var myText = "";
  var myNumbers = [];
  var myOutPut = ""
  var theLastPos = 1;
  myText = myCaja.value;
  //get numbers
  for (var i = 0; i < myText.length; i++) 
    if (!isNaN(myText.charAt(i)) && myText.charAt(i) != " ") 
      myNumbers.push(myText.charAt(i));
    
  
  //write over mask
  for (var j = 0; j < myMask.length; j++) 
    if (myMask.charAt(j) == "_")  //replace "_" by a number 
      if (myNumbers.length == 0)
        myOutPut = myOutPut + myMask.charAt(j);
      else 
        myOutPut = myOutPut + myNumbers.shift();
        theLastPos = j + 1; //set caret position
      
     else 
      myOutPut = myOutPut + myMask.charAt(j);
    
  
  document.getElementById("phone").value = myOutPut;
  document.getElementById("phone").setSelectionRange(theLastPos, theLastPos);


document.getElementById("phone").onkeypress = validate_int;
document.getElementById("phone").onkeyup = phone_number_mask;
&lt;input type="text" name="phone" id="phone" placeholder="(123) 456-7890" required="required" title="e.g (123) 456-7890" pattern="^\([0-9]3\)\s[0-9]3-[0-9]4$"&gt;

【讨论】:

我将使函数名称通用,并使用 Ajedi32 使用的类似逻辑,并从“data-mask”属性中获取掩码。这样,您不仅可以屏蔽电话号码,还可以屏蔽 data-mask 属性中提供的任何类型 我想发布不加掩码的号码。有没有可能 @Sam 您可以将数字存储在另一个输入中(可以隐藏),在提交事件之前解析掩码值或更好地解析服务器端的掩码值【参考方案4】:

您也可以通过使用 javascript 的原生方法来实现这一点。它非常简单,不需要任何额外的库来导入。

<input type="text" name="date" placeholder="yyyy-mm-dd" onkeyup="
  var date = this.value;
  if (date.match(/^\d4$/) !== null) 
     this.value = date + '-';
   else if (date.match(/^\d4\-\d2$/) !== null) 
     this.value = date + '-';
  " maxlength="10">

【讨论】:

很好的解决方案...除非您尝试使用退格键。 :) 所以需要做一个小的修改来处理它。 句柄退格的小修改!!! 【参考方案5】:

你也可以试试我的实现,在输入内容时每次按键都没有延迟,并且完全支持退格和删除。

您可以在线尝试: https://jsfiddle.net/qmyo6a1h/1/

    <html>
    <style>
    input
      font-family:'monospace';
    
    </style>
    <body>
      <input type="text" id="phone" placeholder="123-5678-1234" title="123-5678-1234" input-mask="___-____-____">
      <input type="button" onClick="showValue_phone()" value="Show Value" />
      <input type="text" id="console_phone" />
      <script>
        function InputMask(element) 
          var self = this;

          self.element = element;

          self.mask = element.attributes["input-mask"].nodeValue;

          self.inputBuffer = "";

          self.cursorPosition = 0;

          self.bufferCursorPosition = 0;

          self.dataLength = getDataLength();

          function getDataLength() 
            var ret = 0;

            for (var i = 0; i < self.mask.length; i++) 
              if (self.mask.charAt(i) == "_") 
                ret++;
              
            

            return ret;
          

          self.keyEventHandler = function (obj) 
            obj.preventDefault();

            self.updateBuffer(obj);
            self.manageCursor(obj);
            self.render();
            self.moveCursor();
          

          self.updateBufferPosition = function () 
            var selectionStart = self.element.selectionStart;
            self.bufferCursorPosition = self.displayPosToBufferPos(selectionStart);
            console.log("self.bufferCursorPosition==" + self.bufferCursorPosition);
          

          self.onClick = function () 
            self.updateBufferPosition();
          

          self.updateBuffer = function (obj) 
            if (obj.keyCode == 8) 
              self.inputBuffer = self.inputBuffer.substring(0, self.bufferCursorPosition - 1) + self.inputBuffer.substring(self.bufferCursorPosition);
            
            else if (obj.keyCode == 46) 
              self.inputBuffer = self.inputBuffer.substring(0, self.bufferCursorPosition) + self.inputBuffer.substring(self.bufferCursorPosition + 1);
            
            else if (obj.keyCode >= 37 && obj.keyCode <= 40) 
              //do nothing on cursor keys.
            
            else 
              var selectionStart = self.element.selectionStart;
              var bufferCursorPosition = self.displayPosToBufferPos(selectionStart);
              self.inputBuffer = self.inputBuffer.substring(0, bufferCursorPosition) + String.fromCharCode(obj.which) + self.inputBuffer.substring(bufferCursorPosition);
              if (self.inputBuffer.length > self.dataLength) 
                self.inputBuffer = self.inputBuffer.substring(0, self.dataLength);
              
            
          

          self.manageCursor = function (obj) 
            console.log(obj.keyCode);
            if (obj.keyCode == 8) 
              self.bufferCursorPosition--;
            
            else if (obj.keyCode == 46) 
              //do nothing on delete key.
            
            else if (obj.keyCode >= 37 && obj.keyCode <= 40) 
              if (obj.keyCode == 37) 
                self.bufferCursorPosition--;
              
              else if (obj.keyCode == 39) 
                self.bufferCursorPosition++;
              
            
            else 
              var bufferCursorPosition = self.displayPosToBufferPos(self.element.selectionStart);
              self.bufferCursorPosition = bufferCursorPosition + 1;
            
          

          self.setCursorByBuffer = function (bufferCursorPosition) 
            var displayCursorPos = self.bufferPosToDisplayPos(bufferCursorPosition);
            self.element.setSelectionRange(displayCursorPos, displayCursorPos);
          

          self.moveCursor = function () 
            self.setCursorByBuffer(self.bufferCursorPosition);
          

          self.render = function () 
            var bufferCopy = self.inputBuffer;
            var ret = 
              muskifiedValue: ""
            ;

            var lastChar = 0;

            for (var i = 0; i < self.mask.length; i++) 
              if (self.mask.charAt(i) == "_" &&
                bufferCopy) 
                ret.muskifiedValue += bufferCopy.charAt(0);
                bufferCopy = bufferCopy.substr(1);
                lastChar = i;
              
              else 
                ret.muskifiedValue += self.mask.charAt(i);
              
            

            self.element.value = ret.muskifiedValue;

          

          self.preceedingMaskCharCount = function (displayCursorPos) 
            var lastCharIndex = 0;
            var ret = 0;

            for (var i = 0; i < self.element.value.length; i++) 
              if (self.element.value.charAt(i) == "_"
                || i > displayCursorPos - 1) 
                lastCharIndex = i;
                break;
              
            

            if (self.mask.charAt(lastCharIndex - 1) != "_") 
              var i = lastCharIndex - 1;
              while (self.mask.charAt(i) != "_") 
                i--;
                if (i < 0) break;
                ret++;
              
            

            return ret;
          

          self.leadingMaskCharCount = function (displayIndex) 
            var ret = 0;

            for (var i = displayIndex; i >= 0; i--) 
              if (i >= self.mask.length) 
                continue;
              
              if (self.mask.charAt(i) != "_") 
                ret++;
              
            

            return ret;
          

          self.bufferPosToDisplayPos = function (bufferIndex) 
            var offset = 0;
            var indexInBuffer = 0;

            for (var i = 0; i < self.mask.length; i++) 
              if (indexInBuffer > bufferIndex) 
                break;
              

              if (self.mask.charAt(i) != "_") 
                offset++;
                continue;
              

              indexInBuffer++;
            
            var ret = bufferIndex + offset;

            return ret;
          

          self.displayPosToBufferPos = function (displayIndex) 
            var offset = 0;
            var indexInBuffer = 0;

            for (var i = 0; i < self.mask.length && i <= displayIndex; i++) 
              if (indexInBuffer >= self.inputBuffer.length) 
                break;
              

              if (self.mask.charAt(i) != "_") 
                offset++;
                continue;
              

              indexInBuffer++;
            

            return displayIndex - offset;
          

          self.getValue = function () 
            return this.inputBuffer;
          
          self.element.onkeypress = self.keyEventHandler;
          self.element.onclick = self.onClick;
        

        function InputMaskManager() 
          var self = this;

          self.instances = ;

          self.add = function (id) 
            var elem = document.getElementById(id);
            var maskInstance = new InputMask(elem);
            self.instances[id] = maskInstance;
          

          self.getValue = function (id) 
            return self.instances[id].getValue();
          

          document.onkeydown = function (obj) 
            if (obj.target.attributes["input-mask"]) 
              if (obj.keyCode == 8 ||
                obj.keyCode == 46 ||
                (obj.keyCode >= 37 && obj.keyCode <= 40)) 

                if (obj.keyCode == 8 || obj.keyCode == 46) 
                  obj.preventDefault();
                

                //needs to broadcast to all instances here:
                var keys = Object.keys(self.instances);
                for (var i = 0; i < keys.length; i++) 
                  if (self.instances[keys[i]].element.id == obj.target.id) 
                    self.instances[keys[i]].keyEventHandler(obj);
                  
                
              
            
          
        

        //Initialize an instance of InputMaskManager and
        //add masker instances by passing in the DOM ids
        //of each HTML counterpart.
        var maskMgr = new InputMaskManager();
        maskMgr.add("phone");

        function showValue_phone() 
          //-------------------------------------------------------__Value_Here_____
          document.getElementById("console_phone").value = maskMgr.getValue("phone");
        
      </script>
    </body>

    </html>

【讨论】:

爱它!干得好【参考方案6】:

我前段时间写了一个类似的解决方案。 当然这只是一个 PoC,可以进一步改进。

此解决方案涵盖以下功能:

无缝字符输入 模式自定义 在您输入时进行实时验证 完整的日期验证(包括每个月的正确日期和闰年考虑) 描述性错误,以便用户在无法输入字符时了解发生了什么 修复光标位置并防止选择 如果值为空,则显示占位符

const patternFreeChar = "_";
const dayValidator = [/^[0-3]$/, /^0[1-9]|[12]\d|3[01]$/];
const monthValidator = [/^[01]$/, /^0[1-9]|1[012]$/];
const yearValidator = [/^[12]$/, /^19|20$/, /^(19|20)\d$/, /^(19|20)\d\d$/];

/**
 * Validate a date as your type.
 * @param string date The date in the provided format as a string representation.
 * @param string format The format to use.
 * @throws Error When the date is invalid.
 */
function validateStartTypingDate(date, format='DDMMYYYY') 
  if ( !date ) return "";

  date = date.substr(0, 8);

  if ( !/^\d+$/.test(date) )
    throw new Error("Please type numbers only");

  const formatAsArray = format.split('');
  const dayIndex = formatAsArray.findIndex(c => c == 'D');
  const monthIndex = formatAsArray.findIndex(c => c == 'M');
  const yearIndex = formatAsArray.findIndex(c => c == 'Y');

  const dayStr = date.substr(dayIndex,2);
  const monthStr = date.substr(monthIndex,2);
  const yearStr = date.substr(yearIndex,4);

  if ( dayStr && !dayValidator[dayStr.length-1].test(dayStr) ) 
    switch (dayStr.length) 
      case 1:
        throw new Error("Day in month can start only with 0, 1, 2 or 3");
      case 2:
        throw new Error("Day in month must be in a range between 01 and 31");
    
  

  if ( monthStr && !monthValidator[monthStr.length-1].test(monthStr) ) 
    switch (monthStr.length) 
      case 1:
        throw new Error("Month can start only with 0 or 1");
      case 2:
        throw new Error("Month number must be in a range between 01 and 12");
    
  

  if ( yearStr && !yearValidator[yearStr.length-1].test(yearStr) ) 
    switch (yearStr.length) 
      case 1:
        throw new Error("We support only years between 1900 and 2099, so the full year can start only with 1 or 2");
      default:
        throw new Error("We support only years between 1900 and 2099, so the full year can start only with 19 or 20");
    
  

  const day = parseInt(dayStr);
  const month = parseInt(monthStr);
  const year = parseInt(yearStr);
  const monthName = new Date(0,month-1).toLocaleString('en-us',month:'long');

  if ( day > 30 && [4,6,9,11].includes(month) )
    throw new Error(`$monthName have maximum 30 days`);

  if ( day > 29 && month === 2 )
    throw new Error(`$monthName have maximum 29 days`);

  if ( date.length === 8 ) 
    if ( !isLeap(year) && month === 2 && day === 29 )
      throw new Error(`The year you are trying to enter ($year) is not a leap year. Thus, in this year, $monthName can have maximum 28 days`);
  

  return date;


/**
 * Check whether the given year is a leap year.
 */
function isLeap(year) 
  return new Date(year, 1, 29).getDate() === 29;


/**
 * Move cursor to the end of the provided input element.
 */
function moveCursorToEnd(el) 
  if (typeof el.selectionStart == "number") 
    el.selectionStart = el.selectionEnd = el.value.length;
   else if (typeof el.createTextRange != "undefined") 
    el.focus();
    var range = el.createTextRange();
    range.collapse(false);
    range.select();
  


/**
 * Move cursor to the end of the self input element.
 */
function selfMoveCursorToEnd() 
  return moveCursorToEnd(this);


const inputs = document.querySelectorAll("input");

inputs.forEach(input => 
  const  format, pattern  = input.dataset;
  input.addEventListener("keydown", function(event)
    event.preventDefault();
    document.getElementById("date-error-msg").innerText = "";

    // On digit pressed
    let inputMemory = this.dataset.inputMemory || "";

    if ( event.key.length === 1 ) 
      try 
        inputMemory = validateStartTypingDate(inputMemory + event.key, format);
       catch (err) 
        document.getElementById("date-error-msg").innerText = err.message;
      
    

    // On backspace pressed
    if ( event.code === "Backspace" ) 
      inputMemory = inputMemory.slice(0, -1);
    

    // Build an output using a pattern
    if ( this.dataset.inputMemory !== inputMemory ) 
      let output = pattern;
      for ( let i=0, digit; i<inputMemory.length, digit=inputMemory[i]; i++ ) 
        output = output.replace(patternFreeChar, digit);
      
      this.dataset.inputMemory = inputMemory;
      this.value = output;
    

    // Clean the value if the memory is empty
    if ( inputMemory === "" ) 
      this.value = "";
    
  , false);

  input.addEventListener('select', selfMoveCursorToEnd, false);
  input.addEventListener('mousedown', selfMoveCursorToEnd, false);
  input.addEventListener('mouseup', selfMoveCursorToEnd, false);
  input.addEventListener('click', selfMoveCursorToEnd, false);
);
input 
  width: 250px;
<div><input type="text" placeholder="DD/MM/YYYY" data-format="DDMMYYYY" data-pattern="__/__/____" /></div>
<div><input type="text" placeholder="MM/DD/YYYY" data-format="MMDDYYYY" data-pattern="__/__/____" /></div>
<div><input type="text" placeholder="YYYY-MM-DD" data-format="YYYYMMDD" data-pattern="____-__-__" /></div>
<div><input type="text" placeholder="Day: DD, Year: YYYY, Month: MM" data-format="DDYYYYMM" data-pattern="Day: __, Year: ____, Month: __" /></div>
<div id="date-error-msg"></div>

jsfiddle 的链接: https://jsfiddle.net/sm3xw61n/2/

祝你好运!

【讨论】:

将光标移动到末尾不允许用户使用左/右箭头键,或者用鼠标选择一些文本,例如复制它。我个人认为这不是用户友好的。 这会使我的浏览器在我聚焦该字段后立即进入 CPU 循环。 @LawrenceDol 你用的是什么浏览器? 火狐 90.0.2. @SlavikMeltser 非常感谢您编写了支持更多格式的代码。你让我开心!上帝保佑你,兄弟!【参考方案7】:

用它来实现掩码:

https://cdnjs.cloudflare.com/ajax/libs/jquery.inputmask/5.0.6/jquery.inputmask.min.js

<input id="phone_number" class="ant-input" type="text" placeholder="(XXX) XXX-XXXX" data-inputmask-mask="(999) 999-9999">



jQuery("#phone_number").inputmask("mask": "(999) 999-9999");

【讨论】:

【参考方案8】:
Array.prototype.forEach.call(document.body.querySelectorAll("*[data-mask]"), applyDataMask);

function applyDataMask(field) 
    var mask = field.dataset.mask.split('');

    // For now, this just strips everything that's not a number
    function stripMask(maskedData) 
        function isDigit(char) 
            return /\d/.test(char);
        
        return maskedData.split('').filter(isDigit);
    

    // Replace `_` characters with characters from `data`
    function applyMask(data) 
        return mask.map(function(char) 
            if (char != '_') return char;
            if (data.length == 0) return char;
            return data.shift();
        ).join('')
    

    function reapplyMask(data) 
        return applyMask(stripMask(data));
    

    function changed()    
        var oldStart = field.selectionStart;
        var oldEnd = field.selectionEnd;

        field.value = reapplyMask(field.value);

        field.selectionStart = oldStart;
        field.selectionEnd = oldEnd;
    

    field.addEventListener('click', changed)
    field.addEventListener('keyup', changed)

Date: <input type="text" value="__-__-____" data-mask="__-__-____"/><br/>
Telephone: <input type="text" value="(___) ___-____" data-mask="(___) ___-____"/><br/>

【讨论】:

如果您提供一段介绍性文字来解释您的代码以及它如何回答问题,将会很有帮助。【参考方案9】:

下面我描述我的方法。我在input 的输入上设置了事件,以调用 Masking() 方法,该方法将返回我们在input 中插入的格式化字符串。

HTML:

<input name="phone" pattern="+373 __ ___ ___" class="masked" required>

JQ:这里我们在输入上设置事件:

$('.masked').on('input', function () 
    var input = $(this);
    input.val(Masking(input.val(), input.attr('pattern')));
);

JS:函数,按模式格式化字符串;

function Masking (value, pattern) 
var out = '';
var space = ' ';
var any = '_';

for (var i = 0, j = 0; j < value.length; i++, j++) 
    if (value[j] === pattern[i]) 
        out += value[j];
    
    else if(pattern[i] === any && value[j] !== space) 
        out += value[j];
    
    else if(pattern[i] === space && value[j] !== space) 
        out += space;
        j--;
    
    else if(pattern[i] !== any && pattern[i] !== space) 
        out += pattern[i];
        j--;
    


return out;

【讨论】:

【参考方案10】:

我从这个线程决策 Implement an input with a mask 中提取并针对 IE10 进行了调整,并添加了 setter- 和 getter- 函数。

但我只测试了电话面罩

$(document).ready(function()
    var el_arr = document.querySelectorAll("[placeholder][data-slots]");
    for (var el_ind=0; el_ind < el_arr.length; el_ind++ )
        var el = el_arr[el_ind];
        var pattern = el.getAttribute("placeholder"),
            slots = new Set(el.getAttribute("data-slots") || "_"),
            prev = function(j)return Array.from(pattern, function(c,i) return slots.has(c)? j=i+1: j;);(0),
            first = pattern.split('').findIndex(function(c)return slots.has(c); ),
            accept = new RegExp(el.getAttribute("data-accept") || "\\d", "g"),
            clean = function(input)input = input.match(accept) || [];return Array.from(pattern, function(c)return input[0] === c || slots.has(c) ? input.shift() || c : c;);,
            format = function()
                var elem = this;
                var i_j_arr = [el.selectionStart, el.selectionEnd].map(function(i)
                    i = clean(el.value.slice(0, i)).findIndex(function(c) return slots.has(c););
                    return i<0? prev[prev.length-1]: elem.back? prev[i-1] || first: i;
                );
                el.value = clean(el.value).join('');
                el.setSelectionRange(i_j_arr[0], i_j_arr[1]);
                this.back = false;
            ,
            // sdo added
            get_masked_value = function()
                var input = this.value;
                var ret=[];
                for(var k in pattern)
                    if ( !input[k] )break;
                    if( slots.has(pattern[k]) && input[k]!=pattern[k])
                        ret.push(input[k]);
                     
                 
                return ret.join('');
            ,
            set_masked_value = function(input)
                var ret=[];
                var index_in_value = 0;
                for(var k in pattern)
                    if( slots.has(pattern[k]) && input[index_in_value])
                        ret.push(input[index_in_value]);
                        index_in_value++;
                    
                    else
                        ret.push(pattern[k]);
                    
                 
                this.value = ret.join('');
                                
        ;
        el.get_masked_value = get_masked_value;
        el.set_masked_value = set_masked_value;
        el.back = false;
        el.addEventListener("keydown", function(event) this.back = event.key === "Backspace";);
        el.addEventListener("input", format);
        el.addEventListener("focus", format);
        el.addEventListener("blur", function()  return el.value === pattern && (el.value=""); );
    

);   

【讨论】:

以上是关于使用掩码实现输入的主要内容,如果未能解决你的问题,请参考以下文章

如何使用十六进制字符限制输入掩码范围

Python 输入IP地址及掩码告诉你该网段包含的全部地址(IPy模块练习)

使用美国日期的日期时间输入的 jQuery 输入掩码

利用输入掩码使用 Knockout 自定义绑定禁用 Knockout 验证

Blazor 输入掩码

如何使用下拉菜单更改数据输入掩码前缀