【问题标题】:Why is the keyup handler being invoked here, and can I prevent it?为什么在此处调用 keyup 处理程序,我可以阻止它吗?
【发布时间】:2026-01-24 02:10:01
【问题描述】:

考虑以下 HTML5+Javascript:

$(function() {	
    $('#edit').hide();
	
	$('#value')
		.css('cursor', 'pointer')
		.click(function() {
			$('#edit').show();
			$('#edit input').focus();
			$('#value').hide();
		});
	
	$('#edit input')
		.keyup(function(e) {
			if (e.keyCode == 13) {  // <enter>
				$('#value').show();
				$('#edit').hide();
			}
		});
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="value">
	<a href="#">hello</a>
</div>
<div id="edit">
	<input type="text" value="hello" />
</div>

最初,所有这些都是为了捕获文本hello 上的点击事件,将文本替换为输入框。当用户然后点击 Enter 时,新文本将通过 AJAX 保存到服务器,并且输入框再次仅替换为文本。

现在我添加了a 标签以允许通过 Tab+Enter 而不是 only 使用鼠标导航(可访问性;耶!),但我发现,这样做时,对focus() 的调用会神奇地触发keyup() 事件。 (我知道这一点是因为注释掉对 focus() 的调用,否则会导致所需的行为。)

结果是在文本上切换和输入打开但随后立即关闭input 框,就用户而言,根本没有发生任何事情。

为什么inputkeyup 处理程序被一个完全不相关的元素上的click 事件触发?我怎样才能阻止这种情况发生?


工作场景

  1. 点击hello
  2. hello 消失;文本框出现,里面有插入符号
  3. Enter
  4. 文本框消失; hello再次出现

破碎的场景

  1. 关注文档
  2. Tab 直到 hello 成为焦点
  3. Enter
  4. hello 消失;文本框出现,里面有插入符号
  5. 文本框消失; hello 再次出现......在我有机会做任何事情之前。

【问题讨论】:

  • 在您损坏的场景中,在第 3 步中按住 enter。直到您释放它(即,在焦点输入上发生的按键操作),第 4 步和第 5 步才会发生。这就是它发生的原因,至于修复它...
  • @JamesThorpe:是的。 :(
  • 在锚标签上放置一个 tabindex 并且输入可以解决任何问题吗?
  • 内容可编辑的 DIV 不是适合您的解决方案吗? jsfiddle.net/7qjcdqzx
  • @LightningRacisinObrit 这是一个有趣的问题,所以请密切关注它——只是我自己没有时间尝试任何事情!

标签: javascript jquery html


【解决方案1】:

所以问题是当你按下回车键时,当你松开键时会触发keyup事件。

这将创建一个如下所示的流程: 1、点击链接(按回车键) 2、链接隐藏 3、将焦点赋予文本输入(此时仍按回车键) 4、释放key(keyup事件被触发)

如果您将事件更改为keydown,则在链接上按回车不会与输入冲突,因为该输入没有焦点。

此外,在 keydown 事件中使用与 keyup 事件中相同的代码仍将执行完全相同的操作(即按 Enter 键,隐藏输入并显示链接)。

编辑

https://jsfiddle.net/geyut7uv/

我能找到的唯一可靠的解决方案是创建一个“阻止”变量来“禁用”keydown 事件。仅当单击 keydown 时,我才将其附加到链接中。这会通过输入检查 keydown 事件,如果用户按住键,该输入将立即触发。

只有在触发回车键 keyup 事件时,我才允许“blocker”变量通过。

理想情况下,我想删除所有全局范围变量,但我找不到识别事件(keydown)当前正在使用的方法。

<div id="value">
    <a href="#">hello</a>
</div>
<div id="edit">
    <input type="text" value="hello" />
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
    var enter_enabled = true;
    $('#edit').hide();

    $('#value')
    .css('cursor', 'pointer')
    .click(function() {
        $('#edit').show();
        $('#edit input').focus();
        $('#value').hide();
    }).keydown(function(e){
        enter_enabled = false;
    });

    $('#edit input')
    .keydown(function(e){
        if(e.keyCode == 13 && enter_enabled){
            $('#value').show();
            $('#edit').hide();
        }
    })
    .keyup(function(e){
        if(e.keyCode == 13){
            enter_enabled = true;
        }
    });
});
</script>

【讨论】:

  • 如果你按住 return 太久,你会得到重复的按键,所以 keydown 会在输入上触发,将它改回链接。
  • 我认为你的解释是正确的。但是,我不认为修复是正确的。
  • 我只能通过创建一个外部变量来达到预期的效果。我无法识别出某个事件已经在进行中,这很遗憾,因为我觉得该方法应该存在于 JavaScript 中。我已更新代码以反映所需的效果。
  • 您的编辑现在已与其他答案之一复制相同 - 如果您单击链接,您现在需要在输入上按两次返回(或按另一个键)。请注意,这不是全局变量 - 它包含在外部函数中,所以我认为很好 - 它包含在此功能的范围内。
  • 这很奇怪,我只需要按一次回车就可以隐藏输入
【解决方案2】:

这是另一种解决方案。功能上的唯一区别是输入仅在 释放 键时出现并聚焦。它监听keydown 并阻止默认的键盘点击,然后在keyup 事件中执行交换,以便keyup 不会进入输入字段。我已经在 Windows 上的 IE 11、Chrome 和 Firefox 中尝试过。

$(function() {
    $('#edit').hide();

    $('#value')
        .css('cursor', 'pointer')
        .click(function() {
            $('#edit').show();
            $('#edit input').focus();
            $('#value').hide();
         });

    $('#value a')
        .keydown(function(e) { // Prevent default enter click
            if (e.keyCode == 13) {
                e.preventDefault();
                e.stopPropagation();
            }
        }).keyup(function(e) {
            if (e.keyCode == 13) {
                e.preventDefault();
                e.stopPropagation();
                $('#edit').show();
                $('#edit input').focus();
                $('#value').hide();
            }
        });

    $('#edit input')
        .keyup(function(e) {
            if (e.keyCode == 13) {  // <enter>
                $('#value').show();
                $('#edit').hide();
            }
        });
});

【讨论】:

  • 我是代表一位同事询问的,我被告知这是我们将使用的那个,所以接受是你的。谢谢!
【解决方案3】:

我走上了使用标志updated fiddle-again的路线:

html 保持不变,但 js 发生了一些变化。我添加了一个“valueClicked”变量并将其设置为 false;然后,将其用作 keyup 中的条件,但是,在 keyup 条件检查之后,我重置了标志。

     $(function() { 
var valueClickedFromEnter = false;
$('#edit').hide();
function handleLinkClick(event)
{
         $('#edit').show();
        $('#edit input').focus();
        $('#value').hide();
        valueClickedFromEnter = 
            (event.keyCode && event.keyCode == 13);   
}
$('#value')
    .css('cursor', 'pointer')
    .keydown(handleLinkClick).click(handleLinkClick);

$('#edit input')
    .keyup(function(e) {            
        if (e.keyCode == 13 && !valueClickedFromEnter) {  // <enter>
            $('#value').show();
            $('#edit').hide();
        }
        valueClickedFromEnter = false;
    });

});

这个想法是,每当您从制表符到锚点按 Enter 键时,都会设置标志,检查 keyup,然后重置标志,以便在下一次输入时,条件通过。然后,正常使用它,标志永远不会设置,所以条件仍然通过。

已编辑:根据反馈,clientX 和 clientY 不是跨浏览器解决方案。相反,我使用了一个中间函数来处理锚标记的 keydown 和 click 事件,传递事件对象。然后检查此对象的 keycode 属性,如果是 13,则切换标志。

额外的好处是,使用另一个键(如空格键)也会导致链接切换,如果用户开始输入,它将进入输入。

【讨论】:

  • 这正是我最初尝试的。但是,它使用鼠标会中断 - 即现在单击链接。您现在需要在输入中按两次返回(或至少按下一个其他键)
  • 这也是我在对 OP 的评论中得到的 - 如果有一种方法可以检测点击是否是由按键而不是鼠标触发的,那么您可以使用它来设置标志(或不设置),这将解决问题。
  • 好的,我更新了我的答案。从 click 事件中检查客户端 x 和 y 将得出它是 Enter click 还是 mouse click。
  • 在 Mac 上的 FF 中,我分别为 clientX 和 clientY 得到 -141 和 -461,所以你可能想将 ===0 转换为 &lt;=0,但我不确定你完全可以依靠这些信息...
  • -141+-461 仍然不是 0 - 即看起来你真的不能在所有浏览器中依赖 clientXclientY。所以回到区分点击和键盘的问题......
【解决方案4】:

已更新以修复长按问题。问题很简单,你有一个监听 keyup 的事件,你专门把焦点放在那个元素上,所以它在用户释放键时触发。

更新 #2: 这个应该可以完全工作,具有讽刺意味的是,我不得不反转我最初的更改以在元素隐藏时执行所有操作。

试试这个:

var enterPressed = false;
var enteredExclusion = false;
$(document).ready(function() {
$('#edit').hide();

$('#value')
    .css('cursor', 'pointer')
    .click(function() {
        $('#value').hide();
        $('#edit').show();
            $('#editabletext').focusin(function(e){
                enteredExclusion = true;
            }).focusout(function(e){
                enterPressed = false;
                enteredExclusion = false;
            }).focus();
    });

$('#editabletext')
    .keydown(function(e){
        if (e.keyCode == 13 && !enteredExclusion) {  // <enter>
            console.log('setting true');
            enterPressed = true;
        }
     })
    .keyup(function(e) {
        enteredExclusion = false;
        if (e.keyCode == 13 && (enterPressed  )) {
            $('#value').show();
            $('#edit').hide();
            console.log('setting false - keyup');
            enterPressed = false;
        }
    });
 });

html:

<div id="value">
    <a href="#">hello</a>
</div>
<div id="edit">
    <input id="editabletext" type="text" value="hello" />
</div>

【讨论】:

  • 嘿,你知道这还不错 - 迄今为止的结果令人鼓舞。你能解释一下推迟focus() 电话是如何解决这个问题的吗?
  • 这仍然会遇到“按住返回键太久”的问题 - keyup 仍然会在输入时触发
  • 是的,我想这是一个“如果你能把握好时机”的解决方案。目前我确实认为正确的方法是将keyUp 换成其他东西,这样它就不会从原始元素中“窃取”半个按键。 :)
  • 这似乎不再关注上次更新后的输入?
【解决方案5】:

我决定在不可见时禁用input 元素,而不是搞乱标志之类的东西。禁用的元素无法获得焦点,因此理论上您不会遇到在元素处于活动状态时 Enter 键仍在出现的问题。

当在input 元素中按下 Enter 键时,我还明确地将焦点设置到锚点。这可确保在禁用该元素之前焦点不再位于该元素上,这可能会导致问题。

请注意,我只在 Windows 8.1 上的 Chrome 42(哦,还有 FF 36 和 IE 11)中对此进行了测试,所以是 YMMV。

$(function() {
  $('#edit').hide();

  var keyupHandler = function(e) {
    if (e.keyCode === 13) { // <enter>
      $('#value').show().focus();
      $('#edit').hide().find('input').prop('disabled', true);
    }
  };

  var clickHandler = function(e) {
    $('#value').hide();
    $('#edit').show(500, function() {
      $(this).find('input').prop('disabled', false).focus();
    });
    e.preventDefault();
  };

  $('#value').on('click', clickHandler);
  $('#edit').on('keydown', 'input', keyupHandler);

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="value">
  <a href="#">hello</a>
</div>
<div id="edit">
  <input type="text" value="hello" />
</div>

【讨论】:

  • 这不起作用。按住“enter”键超过几分之一秒,原来的问题就重现了!
  • 这似乎有点含蓄:P
  • 我想我当然不会按住 Enter 键。我点击我的keysssssssssssssssssssssssssssssssssssssssssssssssssssssss:p span>
  • 我总是不去想我做什么,而是想我的用户会做什么。 :)
  • 那么,他们可以按住 Enter 键多长时间?我的意思是,半秒(正如我在上面添加的)似乎是一个非常长的时间来按住一个键。
【解决方案6】:

发生的事情似乎如下:

1) 用户在焦点元素上按下 enter -> 这将触发 click 事件以及 keyup 事件。先触发点击事件。

2) 点击#value 的处理程序将被调用。此处理程序显示#edit 字段并将焦点设置在该字段上。

3) keyup 事件被触发。但由于现在焦点位于#edit,因此将针对该字段触发(!)

4) 将调用#edit 上的keyup 处理程序并再次隐藏#edit 字段。

【讨论】:

  • 我认为这只回答了一半的问题,“为什么”,OP 还想要“如何修复”部分。
  • @JasonWilczak 我认为人们不知道向上和向下投票按钮的用途。如果答案有用,您就投赞成票,如果答案没有用,您就投反对票。你是说半个答案没有用吗?有总比没有好不是吗?我的意思是,我明白了,它并不能回答所有问题。这也不是说我不能接受反对票。但是还是……为什么?否决正确答案并解决原始问题(或至少一半)的意义何在?
  • 我的意思是,我不确定。我没有反对你的回答,因为它是关于主题的,但我也没有反对它,因为它没有回答完整的问题。我同意,如果它是关于主题并回答了手头问题的一部分,那么否决票是不合适的,但是,显然,有些人感觉不同::shrug::