Як затримати обробник .keyup (), поки користувач не перестане вводити текст?


642

У мене є поле пошуку. Зараз він шукає кожну клавіатуру. Тож якщо хтось набере "Windows", він здійснить пошук за допомогою AJAX для кожної клавіші: "W", "Wi", "Win", "Wind", "Windo", "Window", "Windows".

Я хочу затримати, тому він шукає лише тоді, коли користувач перестане друкувати протягом 200 мс.

У цій keyupфункції немає варіанту , і я спробував setTimeout, але це не вийшло.

Як я можу це зробити?



2
Якби міг, я закрив би це як дублікат.
a432511

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>

Як це працює:

The 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 .


64
Ще одна альтернатива: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js . Це майже працює так само, як ви описали, я просто знайшов себе за допомогою цього шаблону, тому реалізував його як плагін jQuery, щоб спростити синтаксис. Ось демонстраційна сторінка: briangrinstead.com/files/bindWithDelay
Брайан Грінстед

2
Я спочатку твердо кодував кількість набраних букв, цей підхід гладкий як патока (без сульфуру)
Хар

2
Коли я використовую це з $ .post (), він надсилає все введене одночасно, але воно надсилає стільки разів, скільки в мене була клавіша. Чи є спосіб відправити ТОЛЬКО, коли час закінчиться?
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);  
}   

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

1
Цікаво, що рішення з більш ніж 600 посилань на день, коли я пишу цей коментар, не настільки добре, як ця відповідь лише з двома оновленнями (включаючи мою). Зрозуміло, що це найкраще, оскільки він підтримує декілька віджетів, які можуть спільно використовувати або не поділяти затримку. Відмінна відповідь.
Dan Nissenbaum

... Хоча плагін jQuery в коментарі під відповіддю також обробляє окремі таймери для окремих елементів, а також додає аргумент дроселів та подій: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js. Загальний підхід цього плагіна такий самий, як і ваш код, тому ваш код варто уважно вивчити, щоб зрозуміти його для його простоти, перш ніж намагатися зрозуміти більш складний плагін. Я рекомендую цей плагін, але якщо вам не потрібен газ або передача даних про події, ваш код чудовий! Два хороших варіанти. Дякую!
Дан Ніссенбаум

1
Хм, це робить затримку для мене, але someApiCallвсе ще спрацьовує кілька разів - тепер лише з кінцевим значенням поля введення.
Roelant


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

Обережно: змінна $ (ця) у функції, переданій як параметр, не відповідає входу


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

Результати виклику однієї і тієї ж функції, але затримка їх незалежно, оскільки їх мітки відрізняються


Я використовую ваш код у проекті, над яким я працюю, і з ним виникають труднощі. Це насправді нічого не затримує. Думаю, що ви можете побачити / надати допомогу. stackoverflow.com/questions/51393779/…
KDJ

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>

1
Дякую! Популярні відповіді вище не спрацювали ... але ваша пропозиція спрацювала чудово.
користувач2662680

1
2020 як і раніше працює
Ромел Індемне

9

Якщо хтось любить затримувати ту саму функцію, і без зовнішньої змінної він може використовувати наступний скрипт:

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
Мені це подобається. Не зовсім багаторазовий, але простий для розуміння.
Вінсент

7

Ця функція трохи розширює функцію від відповіді Gaten, щоб повернути елемент:

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


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 в межах змінної delayTimer. Він також передає посилання елемента на зворотний виклик, як це запропонував fazzyx.

Перевірено в IE6, 8 (комп. - 7), 8 та Opera 12.11.


Спробували це, але не вдалося правильно працювати при першій клавіші.
Ларс Торен

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 ось новий метод затримки, який зберігає "це" у використанні:

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

2

Використовуйте

mytimeout = setTimeout( 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
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
};

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

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

Погляньте на плагін автозаповнення . Я знаю, що це дозволяє вказати затримку або мінімальну кількість символів. Навіть якщо ви не користуєтесь плагіном, перегляд коду дасть вам кілька ідей щодо того, як його самостійно реалізувати.


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


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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.