关于javascript:如何延迟.keyup()处理程序直到用户停止输入?

How to delay the .keyup() handler until the user stops typing?

我有一个搜索字段。现在它搜索每个键。因此,如果有人键入"windows",它将使用ajax搜索每个键:"w"、"wi"、"win"、"wind"、"windo"、"window"、"windows"。

我想有一个延迟,所以它只在用户停止输入200毫秒时搜索。

keyup函数中没有这个选项,我已经尝试过setTimeout了,但是没有用。

我该怎么做?


我出于同样的目的使用这个小函数,在用户停止键入指定的时间后或在高速率触发的事件中执行一个函数,如resize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function delay(callback, ms) {
  var timer = 0;
  return function() {
    var context = this, args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function () {
      callback.apply(context, args);
    }, ms || 0);
  };
}


// Example usage:

$('#input').keyup(delay(function (e) {
  console.log('Time elapsed!', this.value);
}, 500));
1
2
3
4
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
<label for="input">Try it:
<input id="input" type="text" placeholder="Type something here..."/>
</label>

它是如何工作的:

delay函数将返回一个在内部处理单个计时器的包装函数,在每次执行中,计时器将以提供的时间延迟重新启动,如果在此时间之前发生多个执行,计时器将重新设置并重新启动。

当计时器最终结束时,将执行回调函数,传递原始上下文和参数(在本例中,jquery的事件对象和dom元素作为this)。

更新2019-05-16

我已经使用ES5和ES6功能为现代环境重新实现了该功能:

1
2
3
4
5
6
7
function delay(fn, ms) {
  let timer = 0
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(fn.bind(this, ...args), ms || 0)
  }
}

实现由一组测试覆盖。

要了解更复杂的内容,请查看jquery typewatch插件。


如果要在类型完成后进行搜索,请使用全局变量保存从setTimout调用返回的超时,如果尚未发生,则使用clearTimeout取消该超时,以便它不会触发超时,除非在上一个keyup事件上。

1
2
3
4
5
6
7
8
9
var globalTimeout = null;  
$('#id').keyup(function(){
  if(globalTimeout != null) clearTimeout(globalTimeout);  
  globalTimeout =setTimeout(SearchFunc,200);  
}  
function SearchFunc(){  
  globalTimeout = null;  
  //ajax code
}

或使用匿名函数:

1
2
3
4
5
6
7
8
9
10
11
12
var globalTimeout = null;  
$('#id').keyup(function() {
  if (globalTimeout != null) {
    clearTimeout(globalTimeout);
  }
  globalTimeout = setTimeout(function() {
    globalTimeout = null;  

    //ajax code

  }, 200);  
}

对CMS答案的另一个细微改进。为了方便地允许单独的延迟,您可以使用以下方法:

1
2
3
4
5
6
7
function makeDelay(ms) {
    var timer = 0;
    return function(callback){
        clearTimeout (timer);
        timer = setTimeout(callback, ms);
    };
};

如果你想重复使用同样的延迟,就这么做吧。

1
2
3
var delay = makeDelay(250);
$(selector1).on('keyup', function() {delay(someCallback);});
$(selector2).on('keyup', function() {delay(someCallback);});

如果你想分开晚点,你可以

1
2
$(selector1).on('keyup', function() {makeDelay(250)(someCallback);});
$(selector2).on('keyup', function() {makeDelay(250)(someCallback);});


您还可以查看underline.js,它提供了像debounce这样的实用方法:

1
2
var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);


根据CMS的回答,我做了如下:

在include jquery之后放入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * delayKeyup
 * http://code.azerti.net/javascript/jquery/delaykeyup.htm
 * Inspired by CMS in this post : http://stackoverflow.com/questions/1909441/jquery-keyup-delay
 * Written by Gaten
 * Exemple : $("#input").delayKeyup(function(){ alert("5 secondes passed from the last event keyup."); }, 5000);
 */

(function ($) {
    $.fn.delayKeyup = function(callback, ms){
        var timer = 0;
        $(this).keyup(function(){                  
            clearTimeout (timer);
            timer = setTimeout(callback, ms);
        });
        return $(this);
    };
})(jQuery);

简单使用如下:

1
$('#input').delayKeyup(function(){ alert("5 secondes passed from the last event keyup."); }, 5000);

小心:作为参数传递的函数中的$(this)变量与输入不匹配


使用标签延迟多功能调用

这就是我的解决方案。它将延迟您想要的任何函数的执行。它可以是键下搜索查询,也可以是快速单击上一个或下一个按钮(否则,如果连续快速单击,将发送多个请求,并且根本不使用)。它使用一个全局对象来存储每个执行时间,并将其与最新的请求进行比较。

因此,结果是,只有最后一次单击/操作才会被实际调用,因为这些请求存储在一个队列中,如果队列中不存在具有相同标签的其他请求,那么在X毫秒之后才会被调用!

1
2
3
4
5
6
function delay_method(label,callback,time){
    if(typeof window.delayed_methods=="undefined"){window.delayed_methods={};}  
    delayed_methods[label]=Date.now();
    var t=delayed_methods[label];
    setTimeout(function(){ if(delayed_methods[label]!=t){return;}else{  delayed_methods[label]=""; callback();}}, time||500);
  }

您可以设置自己的延迟时间(可选,默认为500ms)。并以"闭包方式"发送函数参数。

例如,如果要调用以下函数:

1
function send_ajax(id){console.log(id);}

为了防止多个发送Ajax请求,可以使用以下方法延迟它们:

delay_method("check date", function(){ send_ajax(2); } ,600);

只有在600毫秒时间段内没有其他请求时,才会触发使用标签"检查日期"的每个请求。此参数是可选的

标记独立性(调用相同的目标函数),但同时运行:

1
2
delay_method("check date parallel", function(){send_ajax(2);});
delay_method("check date", function(){send_ajax(2);});

结果调用相同的函数,但由于它们的标签不同而独立地延迟它们


此函数对gaten的答案进行了一点扩展,以使元素返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$.fn.delayKeyup = function(callback, ms){
    var timer = 0;
    var el = $(this);
    $(this).keyup(function(){                  
    clearTimeout (timer);
    timer = setTimeout(function(){
        callback(el)
        }, ms);
    });
    return $(this);
};

$('#input').delayKeyup(function(el){
    //alert(el.val());
    // Here I need the input element (value for ajax call) for further process
},1000);

http://jsfiddle.net/us9bu/2/


这对我很有效,我延迟了搜索逻辑操作,并检查值是否与文本字段中输入的值相同。如果值相同,则继续执行与搜索值相关的数据的操作。

1
2
3
4
5
6
7
8
9
10
11
$('#searchText').on('keyup',function () {
    var searchValue = $(this).val();
    setTimeout(function(){
        if(searchValue == $('#searchText').val() && searchValue != null && searchValue !="") {
           // logic to fetch data based on searchValue
        }
        else if(searchValue == ''){
           // logic to load all the data
        }
    },300);
});


我很惊讶没人提到CMS中多个输入的问题。

基本上,您必须为每个输入单独定义延迟变量。否则,如果某人将文本放在第一个输入并快速跳转到其他输入并开始键入,则不会调用第一个输入的回调!

请参阅下面的代码,我是根据其他答案提供的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(function($) {
    /**
     * KeyUp with delay event setup
     *
     * @link http://stackoverflow.com/questions/1909441/jquery-keyup-delay#answer-12581187
     * @param function callback
     * @param int ms
     */

    $.fn.delayKeyup = function(callback, ms){
            $(this).keyup(function( event ){
                var srcEl = event.currentTarget;
                if( srcEl.delayTimer )
                    clearTimeout (srcEl.delayTimer );
                srcEl.delayTimer = setTimeout(function(){ callback( $(srcEl) ); }, ms);
            });

        return $(this);
    };
})(jQuery);

此解决方案将setTimeout引用保留在输入的delaytimer变量中。它还按照fazzyx的建议将元素的引用传递给回调。

在IE6、8(comp-7)、8和Opera 12.11中测试。


超简单的方法,设计用于在用户在文本字段中完成键入后运行函数…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script type="text/javascript">
$(document).ready(function(e) {
    var timeout;
    var delay = 2000;   // 2 seconds

    $('.text-input').keyup(function(e) {
        console.log("User started typing!");
        if(timeout) {
            clearTimeout(timeout);
        }
        timeout = setTimeout(function() {
            myFunction();
        }, delay);
    });

    function myFunction() {
        console.log("Executing function for user!");
    }
});


<textarea name="text-input" class="text-input"></textarea>

如果有人想延迟相同的函数,并且没有外部变量,他可以使用下一个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
function MyFunction() {

    //Delaying the function execute
    if (this.timer) {
        window.clearTimeout(this.timer);
    }
    this.timer = window.setTimeout(function() {

        //Execute the function code here...

    }, 500);
}

延迟功能调用每个键。需要jquery 1.7.1或更高版本

1
2
3
4
5
6
7
8
9
10
jQuery.fn.keyupDelay = function( cb, delay ){
  if(delay == null){
    delay = 400;
  }
  var timer = 0;
  return $(this).on('keyup',function(){
    clearTimeout(timer);
    timer = setTimeout( cb , delay );
  });
}

用法:$('#searchBox').keyupDelay( cb );


基于CMS的回答,这里有一种新的延迟方法,它在使用中保留了"this"的含义:

1
2
3
4
5
6
7
var delay = (function(){
  var timer = 0;
  return function(callback, ms, that){
    clearTimeout (timer);
    timer = setTimeout(callback.bind(that), ms);
  };
})();

用途:

1
2
3
4
5
$('input').keyup(function() {
    delay(function(){
      alert('Time elapsed!');
    }, 1000, this);
});

这是CMS的解决方案,但为我解决了几个关键问题:

  • 支持多个输入,可以同时运行延迟。
  • 忽略没有更改值的关键事件(如ctrl、alt+tab)。
  • 解决争用条件(执行回调且值已更改时)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var delay = (function() {
    var timer = {}
      , values = {}
    return function(el) {
        var id = el.form.id + '.' + el.name
        return {
            enqueue: function(ms, cb) {
                if (values[id] == el.value) return
                if (!el.value) return
                var original = values[id] = el.value
                clearTimeout(timer[id])
                timer[id] = setTimeout(function() {
                    if (original != el.value) return // solves race condition
                    cb.apply(el)
                }, ms)
            }
        }
    }
}())

用途:

1
2
3
4
5
signup.key.addEventListener('keyup', function() {
    delay(this).enqueue(300, function() {
        console.log(this.value)
    })
})

代码是以我喜欢的方式编写的,您可能需要添加一堆分号。

要记住的事情:

  • 唯一的ID是基于表单ID和输入名称生成的,因此必须定义它们并且唯一,或者您可以根据自己的情况对其进行调整。
  • Delay返回一个易于扩展的对象,以满足您自己的需要。
  • 用于延迟的原始元素绑定到回调,因此this按预期工作(如示例中所示)。
  • 在第二次验证中忽略空值。
  • 注意排队,它首先需要毫秒,我希望如此,但您可能希望切换参数以匹配setTimeout

我使用的解决方案增加了另一层复杂性,例如,允许您取消执行,但这是一个很好的基础。


基于CMS的答案,它只忽略不改变值的关键事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var delay = (function(){
    var timer = 0;
    return function(callback, ms){
      clearTimeout (timer);
      timer = setTimeout(callback, ms);
    };
})();

var duplicateFilter=(function(){
  var lastContent;
  return function(content,callback){
    content=$.trim(content);
    if(content!=lastContent){
      callback(content);
    }
    lastContent=content;
  };
})();

$("#some-input").on("keyup",function(ev){

  var self=this;
  delay(function(){
    duplicateFilter($(self).val(),function(c){
        //do sth...
        console.log(c);
    });
  }, 1000 );


})

使用

1
mytimeout = setTimeout( expression, timeout );

其中expression是要运行的脚本,timeout是运行前等待的时间(以毫秒为单位)-这不会拖拽脚本,只是将该部分的执行延迟到超时完成为止。

1
clearTimeout(mytimeout);

将重置/清除超时,这样只要脚本尚未执行,它就不会在表达式中运行脚本(如取消)。


将CMS答案与Miguel的答案结合起来,可以产生一个允许并发延迟的健壮解决方案。

1
2
3
4
5
6
7
8
var delay = (function(){
    var timers = {};
    return function (callback, ms, label) {
        label = label || 'defaultTimer';
        clearTimeout(timers[label] || 0);
        timers[label] = setTimeout(callback, ms);
    };
})();

当您需要独立地延迟不同的操作时,请使用第三个参数。

1
2
3
4
5
6
7
8
9
10
11
$('input.group1').keyup(function() {
    delay(function(){
        alert('Time elapsed!');
    }, 1000, 'firstAction');
});

$('input.group2').keyup(function() {
    delay(function(){
        alert('Time elapsed!');
    }, 1000, '2ndAction');
});

下面是我写的一个建议,它可以处理您表单中的多个输入。

此函数获取输入字段的对象,放入代码

1
2
3
4
function fieldKeyup(obj){
    //  what you want this to do

} // fieldKeyup

这是实际的DelayCall函数,负责多个输入字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function delayCall(obj,ms,fn){
    return $(obj).each(function(){
    if ( typeof this.timer == 'undefined' ) {
       // Define an array to keep track of all fields needed delays
       // This is in order to make this a multiple delay handling    
          function
        this.timer = new Array();
    }
    var obj = this;
    if (this.timer[obj.id]){
        clearTimeout(this.timer[obj.id]);
        delete(this.timer[obj.id]);
    }

    this.timer[obj.id] = setTimeout(function(){
        fn(obj);}, ms);
    });
}; // delayCall

用途:

1
2
3
$("#username").on("keyup",function(){
    delayCall($(this),500,fieldKeyup);
});

1
2
3
4
5
6
7
8
9
10
var globalTimeout = null;  
$('#search').keyup(function(){
  if(globalTimeout != null) clearTimeout(globalTimeout);  
  globalTimeout =setTimeout(SearchFunc,200);  
});
function SearchFunc(){  
  globalTimeout = null;  
  console.log('Search: '+$('#search').val());
  //ajax code
};


使用BindWithDelay jQuery插件:

1
element.bindWithDelay(eventType, [ eventData ], handler(eventObject), timeout, throttle)

用户lodash javascript库和使用debounce函数

1
2
3
changeName: _.debounce(function (val) {
  console.log(val)                
}, 1000)

看看自动完成插件。我知道它允许您指定一个延迟或最小字符数。即使你最终没有使用这个插件,浏览代码也会给你一些关于如何自己实现它的想法。


好吧,我还编写了一段代码来限制由keyup/keydown引起的高频Ajax请求。看看这个:

网址:https://github.com/raincious/jqueue

您的查询如下所示:

1
2
3
var q = new jQueue(function(type, name, callback) {
    return $.post("/api/account/user_existed/", {Method: type, Value: name}).done(callback);
}, 'Flush', 1500); // Make sure use Flush mode.

像这样绑定事件:

1
2
3
$('#field-username').keyup(function() {
    q.run('Username', this.val(), function() { /* calling back */ });
});

今天看到这个有点晚了,但只是想把这个放在这里,以防别人需要。只需分离函数使其可重用。输入stop后,下面的代码将等待1/2秒。

1
2
3
4
5
6
    var timeOutVar
$(selector).on('keyup', function() {

                    clearTimeout(timeOutVar);
                    timeOutVar= setTimeout(function(){ console.log("Hello"); }, 500);
                });


从ES6中,也可以使用箭头函数语法。

在本例中,当用户在调用searchFunc发出查询请求之前完成类型化之后,代码将keyup事件延迟400毫秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const searchbar = document.getElementById('searchBar');
const searchFunc = // any function

// wait ms (milliseconds) after user stops typing to execute func
const delayKeyUp = (() => {
    let timer = null;
    const delay = (func, ms) => {
        timer ? clearTimeout(timer): null
        timer = setTimeout(func, ms)
    }
    return delay
})();

searchbar.addEventListener('keyup', (e) => {
    const query = e.target.value;
    delayKeyUp(() => {searchFunc(query)}, 400);
})