达到最大长度后,将焦点转移到下一个输入框

49

当前输入框达到最大长度后,如何使下一个输入框获得焦点?

a: <input type="text" maxlength="5" />
b: <input type="text" maxlength="5" />
c: <input type="text" maxlength="5" />

如果用户粘贴的文本超过了maxlength限制,最好让它溢出到下一个输入框中。

jsFiddle:http://jsfiddle.net/4m5fg/1/

我必须强调,我不想使用插件,因为我更愿意学习背后的逻辑,而不是使用已经存在的东西。谢谢理解。


1
@Musa的目的是“学习其背后的逻辑”。 - Antony
2
可能是当输入文本字段达到最大长度时移动焦点的重复问题。 - Cees Timmerman
11个回答

71

没有使用jQuery,是一种非常干净的实现:

  • 从maxlength属性中读取。
  • 可扩展到容器内的任意数量的输入。
  • 自动找到下一个要聚焦的输入。
  • 无需使用jQuery。

http://jsfiddle.net/4m5fg/5/

<div class="container">
a: <input type="text" maxlength="5" />
b: <input type="text" maxlength="5" />
c: <input type="text" maxlength="5" />
</div>

..

var container = document.getElementsByClassName("container")[0];
container.onkeyup = function(e) {
    var target = e.srcElement || e.target;
    var maxLength = parseInt(target.attributes["maxlength"].value, 10);
    var myLength = target.value.length;
    if (myLength >= maxLength) {
        var next = target;
        while (next = next.nextElementSibling) {
            if (next == null)
                break;
            if (next.tagName.toLowerCase() === "input") {
                next.focus();
                break;
            }
        }
    }
    // Move to previous field if empty (user pressed backspace)
    else if (myLength === 0) {
        var previous = target;
        while (previous = previous.previousElementSibling) {
            if (previous == null)
                break;
            if (previous.tagName.toLowerCase() === "input") {
                previous.focus();
                break;
            }
        }
    }
}

这仅适用于页面中的第一个容器。相对来说,修改它使其适用于所有容器是相对简单的,但值得指出的是,以防新手使用它。 - Mir
var target = e.srcElement || e.target; 帮助我使用了你的解决方案。谢谢! - Cristi Draghici
如果您添加maxlength = 2,则无法编辑输入字段。 - Nuno cruz
4
为了使它生效,我不得不将 maxLength 更改为 target.maxLength,而不是 parseInt(target.attributes["maxlength"].value, 10)。但是,我正在使用正确的监听器而不是重新定义 onkeyup 函数:container.addEventListener('keyup', moveToNext)(其中 moveToNext 是上面回答中的函数)。 - Gleb Sabirzyanov
刚才,https://jsfiddle.net/nsvbtkzq/ - kkasp

25

你可以监视字段中的输入并测试其值:

$("input").bind("input", function() {
    var $this = $(this);
    setTimeout(function() {
        if ( $this.val().length >= parseInt($this.attr("maxlength"),10) )
            $this.next("input").focus();
    },0);
});

演示地址

使用 setTimeout 函数确保代码只有在输入完成且值已更新后才会运行。绑定 input 事件可以使大多数类型的输入都能触发该事件,包括按键、复制/粘贴(甚至是用鼠标进行的复制/粘贴)和拖放(但在这种情况下,由于焦点在可拖动元素上而不是可放置元素上,拖放操作将不起作用)。

注意:在一些老版本的浏览器中,可能还需要绑定 propertychange 事件。


如果用户粘贴的文本超过了限制长度,最好能够自动溢出到下一个输入框中。

为实现此功能,您可能需要使用 JavaScript 移除 maxlength 属性(以便能够捕获完整的输入内容),并自行实现该功能。我创建了一个小例子,以下是相关部分:

$("input").each(function() {
    var $this = $(this);
    $(this).data("maxlength", $this.prop("maxlength"));
    $(this).removeAttr("maxlength");
})

这将移除该属性,但会保存在 data 中,以便稍后可以访问它。

function spill($this, val) {
    var maxlength = $this.data("maxlength");
    if ( val.length >= maxlength ) {
        $this.val(val.substring(0, maxlength));
        var next = $this.next("input").focus();
        spill(next, val.substring(maxlength));
    }
    else
        $this.val(val);
}

在JavaScript中重新引入了最大长度逻辑,以及获取“被丢弃”的部分并在递归调用spill中使用。如果没有下一个元素,对data的调用将返回undefined,循环将停止,因此输入将在最后一个字段中被截断。


你的fiddle完美运行,点个赞。但是,绑定propertyChange是强制性的吗?似乎即使没有那个绑定也能正常工作。 - exexzian
2
根据 这个答案input 适用于 Firefox,而 propertychange 适用于其他浏览器,但是事情可能已经发生了变化(例如,现在每个浏览器都支持 input)。确保准确性的唯一方法是在不同的环境中进行测试。(附注:应该是 propertychange 而不是 propertyChange - 我已经更正了我的回答) - mgibsonbr
1
这是一个不错的替代方案,但存在问题,例如如果您将每个输入放在不同的 div 中,则无法正常工作。 - oriaj
1
这是正确的,但代码需要一种指定“下一个输入”的方法 - 不能只搜索页面中的任何输入并使用它,因为这种行为可能会导致问题... 总体代码很好,只是选择部分(在这种情况下,.next 调用)需要根据每个特定情况进行调整(例如,如果它们都在同一个 div 中,则使用 .nextAll,如果它们各自在其自己的 div 中,则使用 .parent().next().find 等)。 - mgibsonbr
1
绑定已被弃用,请使用 on。 - Srdjan Dejanovic
显示剩余2条评论

10

您可以使用纯JavaScript:

请参见演示

使用el.value.length检查字符长度。如果等于最大值,则使用focus()移动到下一个字段。将此函数绑定到onkeyup事件,以便在用户键入字符后每次触发该函数。

var a = document.getElementById("a"),
    b = document.getElementById("b"),
    c = document.getElementById("c");

a.onkeyup = function() {
    if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
        b.focus();
    }
}

b.onkeyup = function() {
    if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
        c.focus();
    }
}

2
使用keyup函数可能会创建问题,例如,如果您必须在文本框中删除一些数字,则该事件将不允许删除,因为它是一个keypress。相反,请使用oninput事件。这将很有帮助。 - Surya R Praveen

7

如果你需要很多个字段,可以使用以下方法。

基本上,在keyup事件中获取输入的长度,然后将其与maxlength进行比较,如果匹配,则将焦点集中到下一个输入字段。

http://jsfiddle.net/btevfik/DVxDA/

$(document).ready(function(){
    $('input').keyup(function(){
        if(this.value.length==$(this).attr("maxlength")){
            $(this).next().focus();
        }
    });
});

1
已更新您的代码以获得更好的解决方案- http://jsfiddle.net/ssuryar/DVxDA/224/ - DEMO - Surya R Praveen

4

let otp = document.querySelector('#otp-screen');

for(let pin of otp.children) {
    pin.onkeyup = function() {
        if(pin.nextElementSibling) {
            pin.nextElementSibling.focus();
        }
    }
}
<div class="otp-screen" id="otp-screen">
    <input type="text" placeholder="0" maxlength="1"/> 
    <input type="text" placeholder="0" maxlength="1"/> 
    <input type="text" placeholder="0" maxlength="1"/> 
    <input type="text" placeholder="0" maxlength="1"/> 
</div>


1
这仅适用于maxlength为1的情况。问题显然没有假定这种最大长度。 - Lajos Arpad

3

更新了 btevfik 代码,使用 Onkeyup 或 onkeydown 会出现问题,因为在标签导航时你将无法删除先前的输入。在输入框中编辑或更改文本将很困难,因为它将受到 maxlength 的限制。所以我们可以使用 oninput 事件来实现这个任务。

演示

HTML

<ul>
<li>a: <input type="text" maxlength="5" /></li>
<li>b: <input type="text" maxlength="3" /></li>
<li>c: <input type="text" maxlength="5" /></li>
<li>d: <input type="text" maxlength="3" /></li>
<li>e: <input type="text" maxlength="6" /></li>
<li>f: <input type="text" maxlength="10" /></li>
<li>g: <input type="text" maxlength="7" /></li>
</ul>

Javascript

$(document).ready(function(){
    $('input').on("input", function(){
        if($(this).val().length==$(this).attr("maxlength")){
            $(this).next().focus();
        }
    });
});

CSS

ul {list-style-type:none;}
li {padding:5px 5px;}

2
这对我非常有效,我还修改了它,使其在您从聚焦字段中删除所有内容时返回到上一个字段。 - icortesi
@icortesi,你可以分享这个修改,这对其他人来说会非常有用。 - Vitor Hugo

1

其他答案确实提供了一些实现方法,但我发现他们没有考虑到一些细节问题,其中包括:

  1. 事实上,您不希望自动聚焦整个页面上的任何元素,而是在特定表单内部进行聚焦。
  2. 输入元素可以包含在其他元素中(例如,我将它们包装在
    中以通过CSS实现浮动标签,并且我见过使用进行结构的表单)。
  3. 当溢出或自动移动到下一个字段时,字段的有效性。
  4. 溢出时的输入事件。
  5. 返回到先前字段时的光标位置(看起来浏览器可以保存它,因此回退可以将焦点放在字段的末尾而不是中间)。
  6. codepen123456789123456789012345678903454353434534youtube
    //List of input types, that are "textual" by default, thus can be tracked through keypress and paste events. In essence,
    // these are types, that support maxlength attribute
    const textInputTypes = ['email', 'password', 'search', 'tel', 'text', 'url', ];
    
    formInit();
    
    //Add listeners
    function formInit()
    {
        document.querySelectorAll('form input').forEach((item)=>{
            if (textInputTypes.includes(item.type)) {
                //Somehow backspace can be tracked only on keydown, not keypress
                item.addEventListener('keydown', inputBackSpace);
                if (item.getAttribute('maxlength')) {
                    item.addEventListener('input', autoNext);
                    item.addEventListener('change', autoNext);
                    item.addEventListener('paste', pasteSplit);
                }
            }
        });
    }
    
    
    //Track backspace and focus previous input field, if input is empty, when it's pressed
    function inputBackSpace(event)
    {
        let current = event.target;
        if ((event.keyCode || event.charCode || 0) === 8 && !current.value) {
            let moveTo = nextInput(current, true);
            if (moveTo) {
                moveTo.focus();
                //Ensure, that cursor ends up at the end of the previous field
                moveTo.selectionStart = moveTo.selectionEnd = moveTo.value.length;
            }
        }
    }
    
    //Focus next field, if current is filled to the brim and valid
    function autoNext(event)
    {
        let current = event.target;
        //Get length attribute
        let maxLength = parseInt(current.getAttribute('maxlength'));
        //Check it against value length
        if (maxLength && current.value.length === maxLength && current.validity.valid) {
            let moveTo = nextInput(current, false);
            if (moveTo) {
                moveTo.focus();
            }
        }
    }
    
    async function pasteSplit(event)
    {
        let permission = await navigator.permissions.query({ name: 'clipboard-read',});
        //Check permission is granted or not
        if (permission.state === 'denied') {
            //It's explicitly denied, thus cancelling action
            return false;
        }
        //Get buffer
        navigator.clipboard.readText().then(result => {
            let buffer = result.toString();
            //Get initial element
            let current = event.target;
            //Get initial length attribute
            let maxLength = parseInt(current.getAttribute('maxlength'));
            //Loop while the buffer is too large
            while (current && maxLength && buffer.length > maxLength) {
                //Ensure input value is updated
                current.value = buffer.substring(0, maxLength);
                //Trigger input event to bubble any bound events
                current.dispatchEvent(new Event('input', {
                    bubbles: true,
                    cancelable: true,
                }));
                //Do not spill over if a field is invalid
                if (!current.validity.valid) {
                    return false;
                }
                //Update buffer value (not the buffer itself)
                buffer = buffer.substring(maxLength);
                //Get next node
                current = nextInput(current);
                if (current) {
                    //Focus to provide visual identification of a switch
                    current.focus();
                    //Update maxLength
                    maxLength = parseInt(current.getAttribute('maxlength'));
                }
            }
            //Check if we still have a valid node
            if (current) {
                //Dump everything we can from leftovers
                current.value = buffer;
                //Trigger input event to bubble any bound events
                current.dispatchEvent(new Event('input', {
                    bubbles: true,
                    cancelable: true,
                }));
            }
        }).catch(err => {
            //Most likely user denied request. Check status
            navigator.permissions.query({ name: 'clipboard-read',}).then(newPerm => {
                if (newPerm.state === 'granted') {
                    console.log('Failed to read clipboard', err);
                } else {
                    console.log('Request denied by user. Show him some notification to explain why enabling permission may be useful');
                }
            }).catch(errPerm => {
                console.log('Failed to read clipboard', errPerm);
            });
        });
    }
    
    //Find next/previous input
    function nextInput(initial, reverse = false)
    {
        //Get form
        let form = initial.form;
        //Iterate inputs inside the form. Not using previousElementSibling, because next/previous input may not be a sibling on the same level
        if (form) {
            let previous;
            for (let moveTo of form.querySelectorAll('input')) {
                if (reverse) {
                    //Check if current element in loop is the initial one, meaning
                    if (moveTo === initial) {
                        //If previous is not empty - share it. Otherwise - false, since initial input is first in the form
                        if (previous) {
                            return previous;
                        } else {
                            return false;
                        }
                    }
                } else {
                    //If we are moving forward and initial node is the previous one
                    if (previous === initial) {
                        return moveTo;
                    }
                }
                //Update previous input
                previous = moveTo;
            }
        }
        return false;
    }
    
    以下代码至少试图解决所有这些问题。其中大部分可以在上进行测试:粘贴溢出在那里无法工作,看起来是因为剪贴板API(其他带有此功能的codepen对我也不起作用)。 如果代码中有任何不清楚的地方,请告诉我,我会更新我的答案和代码。如果您发现一些未涵盖的边缘情况,请告诉我。 对于使用codepen表单进行粘贴溢出测试,您可以使用类似于此的内容: 在上查看其在更"实时"的环境中的视频示例。

0

已验证的答案存在一个问题,即如果前一个字段具有有效长度,则会聚焦在前一个字段上。

我修改了上面的答案,以修复前一个标签的完整长度。

var container = document.getElementsByClassName("container")[0];
    container.onkeyup = function(e) {
    var target = e.srcElement || e.target;
    var maxLength = parseInt(target.attributes["maxlength"].value, 10);
    var myLength = target.value.length;
    if (myLength >= maxLength) {
        var next = target;
        while (next = next.nextElementSibling) {
            if (next == null)
                break;
            if (next.tagName.toLowerCase() === "input") {
                next.focus();
                break;
            }
        }
    }
    // Move to previous field if empty (user pressed backspace)
    else if (myLength === 0) {
         var previous = target;
       // Move to previous field if backspace is pressed
        if (code == 8) {
            previous = previous.previousElementSibling;
            if (previous != null) {
                if (previous.tagName.toLowerCase() === "input") {
                    previous.focus();
                }
            }
        } else {
            while (previous = previous.previousElementSibling) {
                if (previous == null)
                    break;
                if (previous.tagName.toLowerCase() === "input") {
                    var mLength = parseInt(previous.attributes["maxlength"].value, 10);
                    var pMyLength = previous.value.length;
                    // Move to previous field if it does not have required length
                    if (mLength == pMyLength) {
                        break;
                    } else {
                        previous.focus();
                        break;
                    }
                }
            }
        }
    }
}

0
如果您正在动态添加输入文本字段,则可以尝试以下方法。
这将重新将脚本注入到DOM中,并且完美地工作。

$('body').on('keyup', '#num_1',function(){
  if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
    $('#num_2').focus();
  }
})
$('body').on('keyup','#num_2', function(){
   if (this.value.length === parseInt(this.attributes["maxlength"].value)) {
    $('#num_3').focus();
  }
})
<input type="text" class="form-control" name="number" maxlength="3" id="num_1">
<input type="text" class="form-control" name="number" maxlength="3" id="num_2">
<input type="text" class="form-control" name="number" maxlength="4" id="num_3">


0
如果你的关注点是创建卡片(借记/信用)号码输入类型,那么使用以下清晰易管理的jQuery版本:

/*..............................................................................................
* jQuery function for Credit card number input group
......................................................................................................*/
            // make container label of input groups, responsible
            $('.card-box').on('focus', function(e){
                $(this).parent().addClass('focus-form-control');
            });
            $('.card-box').on('blur', function(e){
                $(this).parent().removeClass('focus-form-control');
            });
            $('.card-box-1').on('keyup', function(e){
                e.preventDefault();
                var max_length = parseInt($(this).attr('maxLength'));
                var _length = parseInt($(this).val().length);
                if(_length >= max_length) {
                    $('.card-box-2').focus().removeAttr('readonly');
                    $(this).attr('readonly', 'readonly');
                }
                if(_length <= 0){
                    return;
                }
            });
            $('.card-box-2').on('keyup', function(e){
                e.preventDefault();
                var max_length = parseInt($(this).attr('maxLength'));
                var _length = parseInt($(this).val().length);
                if(_length >= max_length) {
                    $('.card-box-3').focus().removeAttr('readonly');
                    $(this).attr('readonly', 'readonly');
                }
                if(_length <= 0){
                    $('.card-box-1').focus().removeAttr('readonly');
                    $(this).attr('readonly', 'readonly');
                }
            });
             $('.card-box-3').on('keyup', function(e){
                e.preventDefault();
                var max_length = parseInt($(this).attr('maxLength'));
                var _length = parseInt($(this).val().length);
                if(_length >= max_length) {
                    $('.card-box-4').focus().removeAttr('readonly');
                    $(this).attr('readonly', 'readonly');
                }
                if(_length <= 0){
                    $('.card-box-2').focus().removeAttr('readonly');
                    $(this).attr('readonly', 'readonly');
                }
            });
            $('.card-box-4').on('keyup', function(e){
                e.preventDefault();
                var max_length = parseInt($(this).attr('maxLength'));
                var _length = parseInt($(this).val().length);
                if(_length >= max_length) {
                    return;
                }
                if(_length <= 0){
                    $('.card-box-3').focus().removeAttr('readonly');
                    $(this).attr('readonly', 'readonly');
                }
            });
/*..............................................................................................
* End jQuery function for Credit card number input group
......................................................................................................*/
/* Hide HTML5 Up and Down arrows. */
                                input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button {
                                    -webkit-appearance: none; margin: 0;
                                }
                                input[type="number"] { -moz-appearance: textfield; }
                                .card-box {
                                    width: 20%; display: inline-block; height: 100%; border: none;
                                }
                                
                                .focus-form-control {
                                    border-color: #66afe9; outline: 0;-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
                                        box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6);
                                }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="form-control" style="padding: 0; max-width: 300px; ">
                                        <input class="card-box card-box-1" type="number" id="CreditCard_CardNumber1" required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
                                            onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;"/> 
                                        <input class="card-box card-box-2" type="number" id="CreditCard_CardNumber2" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
                                            onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
                                        <input class="card-box card-box-3" type="number" id="CreditCard_CardNumber3" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
                                            onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
                                        <input class="card-box card-box-4" type="number" id="CreditCard_CardNumber4" readonly required step="1" minlength="4" maxlength="4" pattern="[0-9]{4}" value="" placeholder="0000"
                                            onClick="this.setSelectionRange(0, this.value.length)" oninput="this.value=this.value.slice(0,this.maxLength||'');this.value=(this.value < 1) ? ('') : this.value;" />
                                    </div>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接