JavaScript中按下Enter键的行为像Tab键

83

我想创建一个表单,按下回车键会将焦点转移到页面上的“下一个”表单元素。我在网上找到的解决方案是...

 <body onkeydown="if(event.keyCode==13){event.keyCode=9; return event.keyCode}">

很遗憾,这种方法似乎只在IE中有效。因此,这个问题的关键是是否有人知道适用于FF和Chrome的解决方案?此外,我不想将 onkeydown 事件添加到表单元素本身,但如果这是唯一的方法,那就只能这样做。

这个问题类似于问题 905222,但我认为它值得拥有自己的问题。

编辑:此外,我看到有人提出这不是很好的风格问题,因为它偏离了用户习惯的表单行为。我同意!这是客户的要求:(

23个回答

99

我使用了Andrew建议的逻辑,非常有效。以下是我的版本:

$('body').on('keydown', 'input, select', function(e) {
    if (e.key === "Enter") {
        var self = $(this), form = self.parents('form:eq(0)'), focusable, next;
        focusable = form.find('input,a,select,button,textarea').filter(':visible');
        next = focusable.eq(focusable.index(this)+1);
        if (next.length) {
            next.focus();
        } else {
            form.submit();
        }
        return false;
    }
});

KeyboardEvent的键码(即e.keycode)已经废弃通知:- https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode


29
建议在第一行的选择器中删除 textarea。在文本域中,您可以使用回车键开始新一行。 - aimfeld
2
我建议忽略具有“readonly”和“disabled”属性的字段:filter(':visible:not([readonly]):enabled') - SnakeDrak
为什么这里使用了 "self" 变量? - Spikolynn
1
我知道这是一个非常老的帖子,但使用这段代码,我们如何允许<input type="button"/>的默认行为?如何允许按回车键点击按钮? - Vishal_Kotecha

30

我想到的最简单的原生JS代码片段:

document.addEventListener('keydown', function (event) {
  if (event.keyCode === 13 && event.target.nodeName === 'INPUT') {
    var form = event.target.form;
    var index = Array.prototype.indexOf.call(form, event.target);
    form.elements[index + 1].focus();
    event.preventDefault();
  }
});

适用于IE 9+和现代浏览器。


4
我认为这应该是正确的答案。原帖并没有要求使用 jquery,而且这种方法更简单、更兼容。 - user2607743
2
不确定为什么这么多答案都假设存在一个表单... 这样是行不通的:Uncaught TypeError: Array.prototype.indexOf called on null or undefined - Justin
如何使用此代码,但只适用于文本框而不是所有输入? - webs
我使用了 charCode 而不是 keyCode。 - Matthias

18

将 [Enter] 键映射为与 [Tab] 键相同的功能

我已经用 jQuery 重写了 Andre Van Zuydam 的答案,因为他的答案对我不起作用。这个代码会捕获到 EnterShift+Enter 两种情况。按下 Enter 后会向前跳格,Shift+Enter 则会向后跳格。

我还重新编写了初始化当前焦点项中的 self 的方式,并以此选择表单。以下是代码:

// Map [Enter] key to work like the [Tab] key
// Daniel P. Clark 2014

// Catch the keydown for the entire document
$(document).keydown(function(e) {

  // Set self as the current item in focus
  var self = $(':focus'),
      // Set the form by the current item in focus
      form = self.parents('form:eq(0)'),
      focusable;

  // Array of Indexable/Tab-able items
  focusable = form.find('input,a,select,button,textarea,div[contenteditable=true]').filter(':visible');

  function enterKey(){
    if (e.which === 13 && !self.is('textarea,div[contenteditable=true]')) { // [Enter] key

      // If not a regular hyperlink/button/textarea
      if ($.inArray(self, focusable) && (!self.is('a,button'))){
        // Then prevent the default [Enter] key behaviour from submitting the form
        e.preventDefault();
      } // Otherwise follow the link/button as by design, or put new line in textarea

      // Focus on the next item (either previous or next depending on shift)
      focusable.eq(focusable.index(self) + (e.shiftKey ? -1 : 1)).focus();

      return false;
    }
  }
  // We need to capture the [Shift] key and check the [Enter] key either way.
  if (e.shiftKey) { enterKey() } else { enterKey() }
});

为什么包含 textarea

是因为我们 "需要" 在其中进行制表。此外,一旦进入,我们不希望停止 Enter 的默认行为放入新行。

为什么 abutton

允许默认操作,"并且" 仍然聚焦于下一个项目,是因为它们不总是加载另一个页面。 它们可以有触发器/效果,例如手风琴或选项卡内容。 因此,一旦触发默认行为,并且页面执行其特殊效果,您仍然希望转到下一个项目,因为您的触发器可能已经引入了它。


4
最后一句代码 if (e.shiftKey) { enterKey() } else { enterKey() } 的目的是什么?看起来应该只需要写成 enterKey() 就行了。 - AVProgrammer
2
@AVProgrammer 如果希望“enter”键在按住“shift”键的情况下起到类似“tab”的作用,需要进行相反的操作。你所询问的那行代码允许在按下“shift”键时检查“enter”键是否被按下。 - 6ft Dan
2
在这两种情况下都检查 e.shiftKey 并调用 enterKey 是没有意义的。 - Jeppe Andreasen
2
无论是否按下Shift键,您都执行相同的操作。因此,if语句是不必要的,正如avprogrammer所指出的那样。 - Jeppe Andreasen
AVProgrammer只是在询问,而不是陈述。我已经在自己的浏览器中测试了这段代码,如果没有if语句,在按住Shift键时它将无法正常工作。 - 6ft Dan
显示剩余3条评论

12

感谢您提供优秀的脚本。

我刚刚在上述函数中添加了shift事件,以便在元素之间返回,我认为有人可能需要这个功能。

$('body').on('keydown', 'input, select, textarea', function(e) {
var self = $(this)
  , form = self.parents('form:eq(0)')
  , focusable
  , next
  , prev
  ;

if (e.shiftKey) {
 if (e.keyCode == 13) {
     focusable =   form.find('input,a,select,button,textarea').filter(':visible');
     prev = focusable.eq(focusable.index(this)-1); 

     if (prev.length) {
        prev.focus();
     } else {
        form.submit();
    }
  }
}
  else
if (e.keyCode == 13) {
    focusable = form.find('input,a,select,button,textarea').filter(':visible');
    next = focusable.eq(focusable.index(this)+1);
    if (next.length) {
        next.focus();
    } else {
        form.submit();
    }
    return false;
}
});

我点了个赞,因为我喜欢这段代码。虽然在测试中它对我没用。所以我写了一个可行的答案:https://dev59.com/iHNA5IYBdhLWcg3wVcFx#27545387 - 6ft Dan
e.keyCodee.which已被弃用。您应该使用e.key === "Enter"。请参见https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode。 - oriadam

10

这个方法对我有用:

$(document).on('keydown', ':tabbable', function(e) {

    if (e.key === "Enter") {
        e.preventDefault();

        var $canfocus = $(':tabbable:visible');
        var index = $canfocus.index(document.activeElement) + 1;

        if (index >= $canfocus.length) index = 0;
        $canfocus.eq(index).focus();
    }

});

1
这个似乎不起作用。Fiddle链接已经失效(并已被删除)。 - isherwood
这将需要使用jquery-ui来选择可切换的选项卡。 - Mat Mannion

4
改变这种行为实际上比本地实现的默认行为创造了更好的用户体验。考虑到从用户的角度来看,回车键的行为已经不一致,因为在单行输入中,回车键倾向于提交表单,而在多行文本框中,它只是添加一个新行到字段内容中。
我最近是这样做的(使用jQuery):
$('input.enterastab, select.enterastab, textarea.enterastab').live('keydown', function(e) {
 if (e.keyCode==13) {
  var focusable = $('input,a,select,button,textarea').filter(':visible');
  focusable.eq(focusable.index(this)+1).focus();
  return false;
 }
});

这并不是非常高效,但足够可靠 - 只需将“enterastab”类添加到任何应以此方式运行的输入元素即可。

1
简短而简洁!一开始也是类似的东西,但现在我已经扩展了它 - 请查看我的答案(在此页面上),其中包括PlusAsTab - Joel Purra
特别寻找的是能够考虑到隐藏字段的物品。大多数其他解决方案都没有。谢谢。 - Simon_Weaver

4
我将OP的解决方案改为了Knockout绑定,并想分享一下。非常感谢:-)

这里有一个Fiddle

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js" type="text/javascript"></script>


</head>
<body>

    <div data-bind="nextFieldOnEnter:true">
        <input type="text" />
        <input type="text" />
        <select>
          <option value="volvo">Volvo</option>
          <option value="saab">Saab</option>
          <option value="mercedes">Mercedes</option>
          <option value="audi">Audi</option>
        </select>
        <input type="text" />
        <input type="text" />
    </div>


    <script type="text/javascript">
    ko.bindingHandlers.nextFieldOnEnter = {
        init: function(element, valueAccessor, allBindingsAccessor) {
            $(element).on('keydown', 'input, select', function (e) {
                var self = $(this)
                , form = $(element)
                  , focusable
                  , next
                ;
                if (e.keyCode == 13) {
                    focusable = form.find('input,a,select,button,textarea').filter(':visible');
                    var nextIndex = focusable.index(this) == focusable.length -1 ? 0 : focusable.index(this) + 1;
                    next = focusable.eq(nextIndex);
                    next.focus();
                    return false;
                }
            });
        }
    };

    ko.applyBindings({});
    </script>
</body>
</html>

你的解决方案正是我所需要的,只有一个例外:我不想在按钮输入类型上出现这种行为。我该如何排除它们?我设法将按钮定义为<button>元素,但我希望能够将其定义为<input type="button">。谢谢。 - bzamfir
1
你好。也许尝试将选择器更改为以下内容? .... $(element).on('keydown', 'input:not(input[type=button]), select', function (e) {..... - Damien Sawyer
+1;这太棒了,我经常使用knockout,但我需要这个功能的项目是一个AngularJS应用程序。如果您不介意,我会借用您的impl代码,并发布一个自定义指令的答案供Angular使用。 - joshperry
一切都很好。当你理解了自定义绑定时,它们就非常棒。 :-) - Damien Sawyer

2

这里是一个angular.js指令,它可以使enter键转到下一个字段,其他答案的启发。这里有一些看起来可能有点奇怪的代码,因为我只使用了angular中打包的jQlite。我相信这里大部分功能在所有浏览器> IE8都能正常工作。

angular.module('myapp', [])
.directive('pdkNextInputOnEnter', function() {
    var includeTags = ['INPUT', 'SELECT'];

    function link(scope, element, attrs) {
        element.on('keydown', function (e) {
            // Go to next form element on enter and only for included tags
            if (e.keyCode == 13 && includeTags.indexOf(e.target.tagName) != -1) {
                // Find all form elements that can receive focus
                var focusable = element[0].querySelectorAll('input,select,button,textarea');

                // Get the index of the currently focused element
                var currentIndex = Array.prototype.indexOf.call(focusable, e.target)

                // Find the next items in the list
                var nextIndex = currentIndex == focusable.length - 1 ? 0 : currentIndex + 1;

                // Focus the next element
                if(nextIndex >= 0 && nextIndex < focusable.length)
                    focusable[nextIndex].focus();

                return false;
            }
        });
    }

    return {
        restrict: 'A',
        link: link
    };
});

这是我在正在开发的应用程序中使用它的方法,只需在元素上添加pdk-next-input-on-enter指令即可。我正在使用条形码扫描器将数据输入到字段中,扫描器的默认功能是模拟键盘,在键入扫描条形码的数据后注入回车键。
这段代码有一个副作用(对我的用例来说是积极的),如果它将焦点移动到按钮上,回车键弹起事件将导致按钮的操作被激活。这对我的流程非常有效,因为我的标记中最后一个表单元素是一个按钮,我希望在通过扫描条形码“切换”所有字段后激活它。
<!DOCTYPE html>
<html ng-app=myapp>
  <head>
      <script src="angular.min.js"></script>
      <script src="controller.js"></script>
  </head>
  <body ng-controller="LabelPrintingController">
      <div class='.container' pdk-next-input-on-enter>
          <select ng-options="p for p in partNumbers" ng-model="selectedPart" ng-change="selectedPartChanged()"></select>
          <h2>{{labelDocument.SerialNumber}}</h2>
          <div ng-show="labelDocument.ComponentSerials">
              <b>Component Serials</b>
              <ul>
                  <li ng-repeat="serial in labelDocument.ComponentSerials">
                      {{serial.name}}<br/>
                      <input type="text" ng-model="serial.value" />
                  </li>
              </ul>
          </div>
          <button ng-click="printLabel()">Print</button>
      </div>
  </body>
</html>

2
function return2tab (div)
{
    document.addEventListener('keydown', function (ev) {
        if (ev.key === "Enter" && ev.target.nodeName === 'INPUT') {

            var focusableElementsString = 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]';

            let ol= div.querySelectorAll(focusableElementsString);

            for (let i=0; i<ol.length; i++) {
                if (ol[i] === ev.target) {
                    let o= i<ol.length-1? ol[i+1]: o[0];
                    o.focus(); break;
                }
            }
            ev.preventDefault();
        }
    });
}

1
如果可以的话,我建议您重新考虑这个问题:在表单中按下 <Enter> 键的默认操作是提交表单,而任何更改此默认操作/预期行为的操作都可能导致网站的可用性问题。

2
通常我会同意,但我们的应用程序允许用户单独设置此类内容,而且客户正在提供报酬,因此我认为我们无法坚持立场。 - Ross
除非默认情况下的回车键行为已经不一致。只有在文本输入类型内,它才会提交表单;而其他大多数输入类型都有自己定义的回车键行为。只要他没有将 tab 键映射到其他功能,这似乎比默认情况下奇怪的行为更好。 - Beep beep
3
我发现按回车键提交表单并不是用户所期望的,实际上这非常让人恼火。事实上,在我建立的每一个网站上,客户都要求我更改此行为,因为它是不受欢迎的。我认为这种持续的行为是某个(咳咳,微软)无法承认他们所做出的错误选择的结果,尽管有压倒性的证据表明这是不被需要的。 - user2611793
我正在为仓库物品开发一个条形码扫描器,使用的是Windows CE和Internet Explorer 4。屏幕上有几个需要扫描的字段,用于填写一个表单。扫描器每次扫描一个条形码时都会按下回车键。因此,每次我为一个字段扫描一个条形码时,只有该字段的数据被提交。这是一个糟糕的默认行为。 - undefined

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