当我停止输入文字后,如何触发输入框中的事件?

91

我希望在输入框中停止输入文字后立即触发事件(而不是在输入期间)。

我已尝试使用:

$('input#username').keypress(function() {
    var _this = $(this); // copy of this object for further usage

    setTimeout(function() {
        $.post('/ajax/fetch', {
            type: 'username',
            value: _this.val()
        }, function(data) {
            if(!data.success) {
                // continue working
            } else {
                // throw an error
            }
        }, 'json');
    }, 3000);
});

但是这个例子对于每一个键入的字符都会产生超时,并且如果我键入20个字符,就会得到大约20个AJAX请求。

在这个示例中,我用一个简单的警告框演示了同样的问题,而不是使用AJAX。

是否有解决方法,还是我对此使用了错误的方法?


1
很抱歉,JavaScript没有提供一个事件来通知您用户何时停止在输入字段中输入。您为什么需要它呢? - Darin Dimitrov
3
从这个例子中不是很明显吗?我想要触发一个事件“当最终用户停止在其中输入时”,而不是发送20个请求。 - user1386320
1
除非用户手动提交或更改字段,否则无法确定用户何时实际完成输入。如果用户在句子中间暂停并等待5分钟再继续输入,你怎么知道呢?一种可能的解决方案是使用.blur(),当用户焦点离开该字段时发送。 - Kevin M
15
以上评论很无聊。这是一个常见的用例:当用户完成调整窗口大小、放大地图、拖动、输入等任何连续操作时,需要将其转换为我们的数字世界中的一个事件。即使是单个按键也会遇到这个问题:当你按下一个键时,它实际上会“弹跳”,产生不止一个按键事件,而是许多个。您的计算机的硬件或操作系统会删除这些额外的事件,这就是我们有离散按键事件的错觉的原因。这称为“去抖动”,这就是原帖作者所需要的。 - Ziggy
React用户注意:https://dev59.com/M2Ag5IYBdhLWcg3w1t5a#28046731。 - Maslow
13个回答

174

您需要使用setTimeout(就像您现在所做的那样),但也要存储引用,以便您可以不断重置限制。类似于:


您将不得不使用setTimeout(就像您现在所做的那样),但还要存储该引用以便您可以随时重置时间限制。代码示例如下:

//
// $('#element').donetyping(callback[, timeout=1000])
// Fires callback when a user has finished typing. This is determined by the time elapsed
// since the last keystroke and timeout parameter or the blur event--whichever comes first.
//   @callback: function to be called when even triggers
//   @timeout:  (default=1000) timeout, in ms, to to wait before triggering event if not
//              caused by blur.
// Requires jQuery 1.7+
//
;(function($){
    $.fn.extend({
        donetyping: function(callback,timeout){
            timeout = timeout || 1e3; // 1 second default timeout
            var timeoutReference,
                doneTyping = function(el){
                    if (!timeoutReference) return;
                    timeoutReference = null;
                    callback.call(el);
                };
            return this.each(function(i,el){
                var $el = $(el);
                // Chrome Fix (Use keyup over keypress to detect backspace)
                // thank you @palerdot
                $el.is(':input') && $el.on('keyup keypress paste',function(e){
                    // This catches the backspace button in chrome, but also prevents
                    // the event from triggering too preemptively. Without this line,
                    // using tab/shift+tab will make the focused element fire the callback.
                    if (e.type=='keyup' && e.keyCode!=8) return;
                    
                    // Check if timeout has been set. If it has, "reset" the clock and
                    // start over again.
                    if (timeoutReference) clearTimeout(timeoutReference);
                    timeoutReference = setTimeout(function(){
                        // if we made it here, our timeout has elapsed. Fire the
                        // callback
                        doneTyping(el);
                    }, timeout);
                }).on('blur',function(){
                    // If we can, fire the event since we're leaving the field
                    doneTyping(el);
                });
            });
        }
    });
})(jQuery);

$('#example').donetyping(function(){
  $('#example-output').text('Event last fired @ ' + (new Date().toUTCString()));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<input type="text" id="example" />
<p id="example-output">Nothing yet</p>

当以下情况之一发生时,将执行该操作:

  1. 超时已过期,或者
  2. 用户切换了字段(blur事件)

(以先到者为准)


2
@Brad:这个 JQuery 解决方案很好,但是在最新的 Chrome(38.0.2125.111)中无法检测到输入框中键入的退格键。只有将其更改为keyup才有效。您可能需要检查并相应地修改代码。 - palerdot
3
谢谢这个插件 - 运行得很好。我不得不做一个调整,因为当有人将内容粘贴到输入框中时它无法工作。我编辑了以下代码行以包括 paste -- $el.is(':input') && $el.on('keyup keypress paste',function(e){ - Matt
1
@Sangar82:现在应该可以了(虽然我有点困惑,ctrl+v应该被keypress捕获——除非你是右键->粘贴?) - Brad Christie
1
@BradChristie 如果使用DEL键删除了文本,无论是在Android Chrome上还是桌面Chrome上,都无法工作。 - rwx
1
@KenIngram 分号的作用是为了防止在与其他模块压缩时出现无效语句(来自不完整的前置脚本)。至于上下文,它扩展了jQuery库以添加一个donetyping扩展方法(类似于jquery之前版本中的.click,在.on(click', ...)成为标准之前)。希望这有所帮助。 - Brad Christie
显示剩余10条评论

81

解决方案:

以下是解决方案。在用户停止输入一段特定时间后执行函数:

var delay = (function(){
  var timer = 0;
  return function(callback, ms){
  clearTimeout (timer);
  timer = setTimeout(callback, ms);
 };
})();

使用方法

$('input').keyup(function() {
  delay(function(){
    alert('Hi, func called');
  }, 1000 );
});

有人能解释一下这是如何工作的吗?似乎立即调用函数表达式是主要的部分,但我不认为我理解正在发生的事情。 - NulisDefo
等一下...这是设置为IIFE只是为了以一种封闭的方式设置计时器变量(而不是在delay变量之外单独设置)吗?接着,其余部分只是普通的函数表达式? - NulisDefo

17

你可以使用underscore.js的"debounce"函数

$('input#username').keypress( _.debounce( function(){<your ajax call here>}, 500 ) );
这意味着在按下键后的500毫秒内执行函数调用。但如果在500毫秒内按下另一个键(触发另一个按键事件),则先前的函数执行将被忽略(抖动),新的函数将在新的500毫秒时间器后执行。
附加信息:使用_.debounce(func,timer, true )意味着第一个函数将执行,随后500毫秒计时器内的所有其他按键事件都将被忽略。

10

你需要防抖!

这里有一个jQuery 插件,以及所有关于防抖的知识。如果你从 Google 来到这里,并且 Underscore 已经融入了你的应用程序中,它已经在内部集成了防抖功能


10
你应该将setTimeout分配给一个变量,并在按键时使用clearTimeout来清除它。
var timer = '';

$('input#username').keypress(function() {
  clearTimeout(timer);
  timer = setTimeout(function() {
    //Your code here
  }, 3000); //Waits for 3 seconds after last keypress to execute the above lines of code
});

Fiddle

希望这能有所帮助。


7

清洁的解决方案:

$.fn.donetyping = function(callback, delay){
  delay || (delay = 1000);
  var timeoutReference;
  var doneTyping = function(elt){
    if (!timeoutReference) return;
    timeoutReference = null;
    callback(elt);
  };

  this.each(function(){
    var self = $(this);
    self.on('keyup',function(){
      if(timeoutReference) clearTimeout(timeoutReference);
      timeoutReference = setTimeout(function(){
        doneTyping(self);
      }, delay);
    }).on('blur',function(){
      doneTyping(self);
    });
  });

  return this;
};

3
我创建了一个非常简单的插件(plugin),可以实现这一点。与建议的解决方案相比,它需要的代码要少得多,而且非常轻量化(约0.6kb)。
首先,您需要创建一个 Bid 对象,随时可以进行 bumped。每次 bump 都会延迟触发下一个指定时间的 Bid 回调函数。
var searchBid = new Bid(function(inputValue){
    //your action when user will stop writing for 200ms. 
    yourSpecialAction(inputValue);
}, 200); //we set delay time of every bump to 200ms

Bid 对象准备好后,我们需要以某种方式进行 bump。让我们将其附加到 keyup 事件上。
$("input").keyup(function(){
    searchBid.bump( $(this).val() ); //parameters passed to bump will be accessable in Bid callback
});

这里发生的事情是:

每当用户按下键时,bid(出价)会被“延迟”(推迟)200毫秒。如果在200毫秒内没有再次“推迟”,则会触发回调函数。

此外,还有两个额外的函数用于停止bid(例如,如果用户按下esc或在输入框外单击),以及立即完成并触发回调函数(例如,当用户按下enter键):

searchBid.stop();
searchBid.finish(valueToPass);

1

我们可以在React中使用useDebouncedCallback来执行这个任务。

导入{ useDebouncedCallback } from 'use-debounce'; - 如果未安装,请安装相应的npm包

const [searchText, setSearchText] = useState('');

const onSearchTextChange = value => {
    setSearchText(value);
  };

//call search api
  const [debouncedOnSearch] = useDebouncedCallback(searchIssues, 500);
  useEffect(() => {
    debouncedOnSearch(searchText);
  }, [searchText, debouncedOnSearch]);

1

我一直在寻找一个简单的HTML/JS代码,但是没有找到。然后,我使用onkeyup="DelayedSubmission()"编写了下面的代码。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pt-br" lang="pt-br">
<head><title>Submit after typing finished</title>
<script language="javascript" type="text/javascript">
function DelayedSubmission() {
    var date = new Date();
    initial_time = date.getTime();
    if (typeof setInverval_Variable == 'undefined') {
            setInverval_Variable = setInterval(DelayedSubmission_Check, 50);
    } 
}
function DelayedSubmission_Check() {
    var date = new Date();
    check_time = date.getTime();
    var limit_ms=check_time-initial_time;
    if (limit_ms > 800) { //Change value in milliseconds
        alert("insert your function"); //Insert your function
        clearInterval(setInverval_Variable);
        delete setInverval_Variable;
    }
}

</script>
</head>
<body>

<input type="search" onkeyup="DelayedSubmission()" id="field_id" style="WIDTH: 100px; HEIGHT: 25px;" />

</body>
</html>

0

当你只想重置一个时钟的时候,为什么要做那么多的事情呢?

var clockResetIndex = 0 ;
// this is the input we are tracking
var tarGetInput = $('input#username');

tarGetInput.on( 'keyup keypress paste' , ()=>{
    // reset any privious clock:
    if (clockResetIndex !== 0) clearTimeout(clockResetIndex);

    // set a new clock ( timeout )
    clockResetIndex = setTimeout(() => {
        // your code goes here :
        console.log( new Date() , tarGetInput.val())
    }, 1000);
});

如果你正在使用WordPress,那么你需要将所有这些代码放在一个jQuery块中:
jQuery(document).ready(($) => {
    /**
     * @name 'navSearch' 
     * @version 1.0
     * Created on: 2018-08-28 17:59:31
     * GMT+0530 (India Standard Time)
     * @author : ...
     * @description ....
     */
        var clockResetIndex = 0 ;
        // this is the input we are tracking
        var tarGetInput = $('input#username');

        tarGetInput.on( 'keyup keypress paste' , ()=>{
            // reset any privious clock:
            if (clockResetIndex !== 0) clearTimeout(clockResetIndex);

            // set a new clock ( timeout )
            clockResetIndex = setTimeout(() => {
                // your code goes here :
                console.log( new Date() , tarGetInput.val())
            }, 1000);
        });
});

如果您想扩展jQuery并希望在多个输入元素中使用此方法,则批准的答案就是您要寻找的答案。 - insCode

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