Как отложить обработчик .keyup (), пока пользователь не перестанет печатать?

643

У меня есть поле поиска. Прямо сейчас он ищет все ключи. Поэтому, если кто-то наберет «Windows», он выполнит поиск с помощью AJAX для каждого ключа: «W», «Wi», «Win», «Wind», «Windo», «Window», «Windows».

Я хочу иметь задержку, поэтому она выполняет поиск только тогда, когда пользователь перестает печатать в течение 200 мс.

Для этой keyupфункции нет опции , и я попробовал setTimeout, но она не сработала.

Как я могу это сделать?

ajsie
источник
4
Дубликат stackoverflow.com/questions/1620602/…
Роатин Март
2
Если бы я мог, я бы закрыл это как дубликат.
a432511 15.12.09
7
Я не вижу вреда в наличии дубликатов, если даются и принимаются правильные ответы. Добавление в базу данных вопросов должно быть чем-то хорошим и к чему стремиться.
Маттис
14
Вред состоит в том, что люди в будущем не смогут извлечь выгоду из блестящих ответов, которыми все делятся, если есть 100 одинаковых вопросов, поэтому закрытие дубликатов и перенаправление всех на исходный вопрос лучше для поиска лучших практик и исправлений. См. Stackoverflow.com/help/duplicates для получения дополнительной информации о том, почему дубликаты закрыты.
rncrtr
14
Это намного популярнее, чем тот, который якобы дублировал. Это лучше сформулировано, у него есть лучшие ответы, более высокие оценки в Google и т. Д. Так много выиграли от этого ответа. Оглядываясь назад, было бы стыдно, если бы это было закрыто. Есть некоторые тривиальные, которые плохо дублируют друг друга, но это не относится к этой категории.
пользователь

Ответы:

1124

Я использую эту небольшую функцию для той же цели, выполняя функцию после того, как пользователь прекратил печатать в течение определенного периода времени или в событиях, которые запускаются с высокой скоростью, например resize:

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));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label for="input">Try it:
<input id="input" type="text" placeholder="Type something here..."/>
</label>

Как это работает:

delayФункция возвращает обернутую функцию , которая обрабатывает внутри отдельный таймер, в каждом исполнении таймер перезапускается с задержкой по времени при условии, если несколько казней произойти до проходит этот раз, таймер будет просто сбросить и начать заново.

Когда таймер наконец заканчивается, выполняется функция обратного вызова, передавая исходный контекст и аргументы (в этом примере объект события jQuery и элемент DOM как this).

ОБНОВЛЕНИЕ 2019-05-16

Я повторно реализовал функцию с использованием функций ES5 и ES6 для современных сред:

function delay(fn, ms) {
  let timer = 0
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(fn.bind(this, ...args), ms || 0)
  }
}

Реализация покрыта набором тестов .

Для чего-то более сложного, взгляните на плагин jQuery Typewatch .

CMS
источник
64
Другая альтернатива: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js . Он работает почти так же, как вы описали, я просто использовал этот шаблон, поэтому реализовал его как плагин jQuery, чтобы упростить синтаксис. Вот демонстрационная страница: briangrinstead.com/files/bindWithDelay
Брайан Гринстед
2
я изначально жестко закодировал количество набранных букв, этот подход гладкий, как меласса (без серфера)
Har
2
Когда я использую это с $ .post (), он отправляет все набранное одновременно, но отправляет столько раз, сколько у меня было keyup. Есть ли способ отправить ТОЛЬКО по истечении времени ожидания?
ntgCleaner
7
Но это не работает, если я вызываю две или более разные функции, которые требуют индивидуальной задержки. Я сделал это решение вместо stackoverflow.com/a/30503848/1834212
Мигель
4
Я рекомендую плагин «bindWithDelay», указанный в верхнем комментарии. Но я предлагаю @ Hazerider ответ на этот вопрос (ниже - stackoverflow.com/a/19259625/368896 ), который лучше этого ответа и заслуживает изучения, чтобы понять идею простой гладкой привязки, позволяющей использовать разные таймеры для разных элементов - тот же принцип, что и в коде плагина выше, но проще для понимания.
Дан Ниссенбаум
60

Если вы хотите выполнить поиск после завершения типа, используйте глобальную переменную для хранения времени ожидания, возвращенного вашим setTimoutвызовом, и отмените его, clearTimeoutесли он еще не произошел, чтобы он не запустил время ожидания, кроме последнего keyupсобытия.

var globalTimeout = null;  
$('#id').keyup(function(){
  if(globalTimeout != null) clearTimeout(globalTimeout);  
  globalTimeout =setTimeout(SearchFunc,200);  
}   
function SearchFunc(){  
  globalTimeout = null;  
  //ajax code
}

Или с анонимной функцией:

var globalTimeout = null;  
$('#id').keyup(function() {
  if (globalTimeout != null) {
    clearTimeout(globalTimeout);
  }
  globalTimeout = setTimeout(function() {
    globalTimeout = null;  

    //ajax code

  }, 200);  
}   
RHicke
источник
39

Еще одно небольшое улучшение в ответе CMS. Чтобы легко учесть отдельные задержки, вы можете использовать следующее:

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

Если вы хотите использовать ту же задержку, просто сделайте

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

Если вы хотите отдельные задержки, вы можете сделать

$(selector1).on('keyup', function() {makeDelay(250)(someCallback);});
$(selector2).on('keyup', function() {makeDelay(250)(someCallback);});
Paschover
источник
1
Интересно, что решение с более чем 600 ответами на день, когда я пишу этот комментарий, не так хорошо, как этот ответ, только с 2 ответами (включая мое). Совершенно очевидно, что он поддерживает несколько виджетов, которые могут совместно использовать или не совместно использовать задержку. Отличный ответ.
Дан Ниссенбаум
... Хотя плагин jQuery в комментарии под ответом также обрабатывает отдельные таймеры для отдельных элементов, а также добавляет дроссель и аргумент события: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js. Общий подход этого плагина такой же, как ваш код, поэтому ваш код стоит внимательно изучить, чтобы понять его простоту, прежде чем пытаться понять более сложный плагин. Тем не менее, я рекомендую этот плагин - но если вам не нужен газ или передача данных о событиях, ваш код великолепен! Два хороших варианта. Спасибо!
Дан Ниссенбаум
1
Хм, это задерживает для меня, но someApiCallвсе равно срабатывает несколько раз - теперь только с окончательным значением поля ввода.
Roelant
30

Вы также можете посмотреть на underscore.js , который предоставляет такие служебные методы, как debounce :

var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
meleyal
источник
То же самое можно найти и в Lodash - lodash.com/docs/#debounce
Адарш Мадреча,
14

Основываясь на ответе CMS, я сделал это:

Поместите код ниже после включения JQuery:

/*
 * 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);

И просто используйте так:

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

Осторожно: переменная $ (this) в функции, переданная в качестве параметра, не соответствует вводу

Gaten
источник
12

Задержка многофункциональных вызовов с использованием меток

Это решение, с которым я работаю. Это задержит выполнение ЛЮБОЙ функции, которую вы хотите . Это может быть поисковый запрос по нажатию клавиши, может быть, быстрый щелчок по предыдущей или следующей кнопке (который в противном случае отправлял бы несколько запросов, если быстро нажимать непрерывно, и не использовался бы в конце концов). При этом используется глобальный объект, который сохраняет каждое время выполнения и сравнивает его с самым последним запросом.

Таким образом, в результате будет вызван только последний щелчок / действие, поскольку эти запросы хранятся в очереди, то есть после вызова X миллисекунд, если в очереди не существует другого запроса с такой же меткой!

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);
  }

Вы можете установить собственное время задержки (необязательно, по умолчанию 500 мс). И отправьте аргументы вашей функции в «режиме закрытия».

Например, если вы хотите вызвать нижеуказанную функцию:

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

Чтобы предотвратить несколько запросов send_ajax, вы задерживаете их, используя:

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

Каждый запрос, использующий метку «дата проверки», будет запускаться только в том случае, если в течение 600 миллисекунд не было сделано ни одного другого запроса. Этот аргумент не является обязательным

Пометьте независимость (вызывая ту же целевую функцию), но запустите обе:

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

Приводит к вызову одной и той же функции, но задерживает их независимо, потому что их метки отличаются

Miguel
источник
Я использую ваш код в проекте, над которым я работаю, и испытываю трудности с ним. На самом деле ничего не откладывает. Мысль, которую вы, возможно, захотите увидеть / оказать помощь. stackoverflow.com/questions/51393779/…
KDJ
10

Если кому-то нравится задерживать ту же функцию и без внешней переменной он может использовать следующий скрипт:

function MyFunction() {

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

        //Execute the function code here...

    }, 500);
}
Рой Шоа
источник
1
Большой! Простое решение, которое отлично работает!
Феллипе Санчес
1
Мне нравится этот. Не совсем многоразового использования, но легко понять.
Винсент
10

Супер простой подход, разработанный для запуска функции после того, как пользователь закончил печатать в текстовом поле ...

<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!");
    }
});
</script>

<textarea name="text-input" class="text-input"></textarea>
HoldOffHunger
источник
1
Спасибо! Популярные ответы выше не сработали ... но ваше предложение сработало отлично.
user2662680
1
2020 все еще работает
Ромель Индемн
7

Эта функция немного расширяет функцию от ответа Гатена, чтобы вернуть элемент:

$.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/

fazzyx
источник
6

Я удивлен, что никто не упомянул проблему с множественным вводом в CMS.

По сути, вам придется определять переменную задержки индивидуально для каждого входа. В противном случае, если sb поместит текст в первый ввод и быстро перейдет к другому вводу и начнет печатать, обратный вызов для первого не будет вызван!

Посмотрите код ниже, с которым я пришел, основываясь на других ответах:

(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 внутри переменной inputTimer. Он также передает ссылку на элемент в функцию обратного вызова, как предложил fazzyx.

Протестировано в IE6, 8 (comp - 7), 8 и Opera 12.11.

jszoja
источник
Попробовал это, но не могу нормально работать с первым брелком.
Ларс Торен
6

Это сработало для меня, когда я задерживаю операцию логики поиска и проверяю, совпадает ли значение с введенным в текстовом поле. Если значение одно и то же, я продолжаю и выполняю операцию для данных, связанных с поиском значения.

$('#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);
});
Сагар Гала
источник
не работает должным образом - количество выборок одинаково, просто задерживается на 300 мс - вам нужно использовать функцию clearTimeout () при каждом нажатии клавиши перед выполнением setTimeout ()
Picard
Ну, вам не нужно использовать clearTimeout в этом случае. Условие searchValue == $ ('# searchText'). Val () внутри setTimeout будет гарантировать, что значение совпадает с тем, которое было 300 мс назад. Дайте мне знать, если это имеет смысл. Спасибо.
Сагар Гала
4

Функция задержки для вызова при каждом включении. Требуется jQuery 1.7.1 или выше

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 );

Виная Аггарвал
источник
3

Это решение, аналогичное CMS, но решающее для меня несколько ключевых вопросов:

  • Поддерживает несколько входов, задержки могут выполняться одновременно.
  • Игнорирует ключевые события, которые не изменили значение (например, Ctrl, Alt + Tab).
  • Решает условие гонки (когда обратный вызов выполнен и значение уже изменено).
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)
            }
        }
    }
}())

Применение:

signup.key.addEventListener('keyup', function() {
    delay(this).enqueue(300, function() {
        console.log(this.value)
    })
})

Код написан в стиле, который мне нравится, вам может понадобиться добавить несколько точек с запятой.

Что нужно иметь в виду:

  • Уникальный идентификатор генерируется на основе идентификатора формы и имени входа, поэтому они должны быть определены и уникальны, иначе вы можете настроить его в соответствии с вашей ситуацией.
  • delay возвращает объект, который легко расширить для собственных нужд.
  • Исходный элемент, используемый для задержки, привязан к обратному вызову, поэтому thisработает как положено (как в примере).
  • Пустое значение игнорируется во второй проверке.
  • Следите за постановкой в очередь , сначала ожидаются миллисекунды, я предпочитаю, но вы можете переключить параметры в соответствии setTimeout.

Решение, которое я использую, добавляет еще один уровень сложности, позволяя, например, отменить выполнение, но это хорошая основа для построения.

Хайме Гомес
источник
3

Объединение CMS ответ с одного Мигеля дает надежное решение , позволяющее одновременно задержек.

var delay = (function(){
    var timers = {};
    return function (callback, ms, label) {
        label = label || 'defaultTimer';
        clearTimeout(timers[label] || 0);
        timers[label] = setTimeout(callback, ms);
    };
})();

Когда вам нужно отложить разные действия независимо, используйте третий аргумент.

$('input.group1').keyup(function() {
    delay(function(){
        alert('Time elapsed!');
    }, 1000, 'firstAction');
});

$('input.group2').keyup(function() {
    delay(function(){
        alert('Time elapsed!');
    }, 1000, '2ndAction');
});
Фредерик
источник
3

Основываясь на ответе CMS, вот новый метод задержки, который сохраняет «this» в его использовании:

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

Применение:

$('input').keyup(function() {
    delay(function(){
      alert('Time elapsed!');
    }, 1000, this);
});
mga911
источник
2

использование

mytimeout = setTimeout( expression, timeout );

где expression - это сценарий, который нужно запустить, а timeout - это время ожидания в миллисекундах, прежде чем он запустится - это НЕ повреждает сценарий, а просто задерживает выполнение этой части до истечения времени ожидания.

clearTimeout(mytimeout);

сбросит / очистит тайм-аут, чтобы он не запускал скрипт в выражении (например, отмена), если он еще не был выполнен.

Марк Шультхайс
источник
2

Основываясь на ответе CMS, он просто игнорирует ключевые события, которые не меняют значение.

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

Используйте плагин bindWithDelay jQuery:

element.bindWithDelay(eventType, [ eventData ], handler(eventObject), timeout, throttle)
Вебьорн Лёса
источник
1
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
};
krut1
источник
1

Вот предложение, которое я написал, которое заботится о множественном вводе в вашей форме.

Эта функция получает объект поля ввода, вставленный в ваш код

function fieldKeyup(obj){
    //  what you want this to do

} // fieldKeyup

Это фактическая функция delayCall, которая заботится о нескольких полях ввода.

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

Применение:

$("#username").on("keyup",function(){
    delayCall($(this),500,fieldKeyup);
});
egpo
источник
1

Начиная с ES6 , можно также использовать синтаксис функции стрелки .

В этом примере код задерживает keyupсобытие на 400 мс после того, как пользователи закончили печатать, прежде чем вызывать searchFuncзапрос на запрос.

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);
})
Дат Нгуен
источник
1

JQuery :

var timeout = null;
$('#input').keyup(function() {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
      console.log($(this).val());
  }, 1000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<input type="text" id="input" placeholder="Type here..."/>

Чистый Javascript:

let input = document.getElementById('input');
let timeout = null;

input.addEventListener('keyup', function (e) {
    clearTimeout(timeout);
    timeout = setTimeout(function () {
        console.log('Value:', input.value);
    }, 1000);
});
<input type="text" id="input" placeholder="Type here..."/>

Али Наср
источник
0

Взгляните на плагин автозаполнения . Я знаю, что это позволяет указать задержку или минимальное количество символов. Даже если вы не используете плагин, просмотр кода даст вам некоторые идеи о том, как реализовать его самостоятельно.

tvanfosson
источник
0

Ну, я также сделал кусок кода для ограничения высокочастотного запроса ajax от Keyup / Keydown. Проверь это:

https://github.com/raincious/jQueue

Сделайте ваш запрос так:

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.

И связать событие, как это:

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

источник
0

Увидел это сегодня немного поздно, но просто хочу поместить это здесь на случай, если кому-то еще понадобится. просто разделите функцию, чтобы сделать ее многоразовой. код ниже будет ждать 1/2 секунды после ввода остановки.

    var timeOutVar
$(selector).on('keyup', function() {

                    clearTimeout(timeOutVar);
                    timeOutVar= setTimeout(function(){ console.log("Hello"); }, 500);
                });
chungtinhlakho
источник
0

Пользовательская библиотека lodash javascript и использование функции _.debounce

changeName: _.debounce(function (val) {
  console.log(val)                
}, 1000)
Амир Ур Рехман
источник
0
// Get an global variable isApiCallingInProgress

//  check isApiCallingInProgress 
if (!isApiCallingInProgress) {
// set it to isApiCallingInProgress true
      isApiCallingInProgress = true;

      // set timeout
      setTimeout(() => {
         // Api call will go here

        // then set variable again as false
        isApiCallingInProgress = false;     
      }, 1000);
}
Бхарат Чаудхари
источник