Come ritardare il gestore .keyup () fino a quando l'utente smette di digitare?


643

Ho un campo di ricerca. In questo momento cerca ogni keyup. Quindi, se qualcuno digita "Windows", effettuerà una ricerca con AJAX per ogni keyup: "W", "Wi", "Win", "Wind", "Windo", "Window", "Windows".

Voglio avere un ritardo, quindi cerca solo quando l'utente smette di digitare per 200 ms.

Non esiste alcuna opzione per questo nella keyupfunzione e ho provato setTimeout, ma non ha funzionato.

Come lo posso fare?



2
Se potessi, lo chiuderei come duplicato.
a432511,

7
Non riesco a vedere il danno nell'avere duplicati, purché le risposte fornite e accettate siano corrette. L'aggiunta al database di domande deve essere qualcosa di buono e qualcosa per cui lottare.
Mattis,

14
Il danno è che le persone in futuro non potranno beneficiare delle risposte brillanti che tutti condividono se ci sono 100 della stessa domanda, quindi chiudere i duplicati e reindirizzare tutti alla domanda originale è meglio per trovare le migliori pratiche e correzioni. Vedi stackoverflow.com/help/duplicates per maggiori informazioni sul perché i duplicati vengono chiusi.
rncrtr

14
Questo è molto più popolare di quello che stava presumibilmente duplicando. Ha una formulazione migliore, ha risposte migliori, si posiziona più in alto su Google ecc. Quindi molti hanno beneficiato di questa risposta. In retrospettiva, sarebbe stato un peccato se questo fosse stato chiuso. Ci sono alcuni banali che sono cattivi come duplicati, ma questo non rientra in quella categoria.
utente

Risposte:


1124

Uso questa piccola funzione per lo stesso scopo, eseguendo una funzione dopo che l'utente ha smesso di digitare per un determinato periodo di tempo o in eventi che si attivano a una velocità elevata, come 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>

Come funziona:

La delayfunzione restituirà una funzione a capo che gestisce internamente un singolo timer, in ogni esecuzione il timer viene riavviato con il ritardo previsto, se si verificano più esecuzioni prima che questo tempo passi, il timer si reimposterà e si riavvierà.

Al termine del timer, viene eseguita la funzione di callback, passando il contesto e gli argomenti originali (in questo esempio, l'oggetto evento di jQuery e l'elemento DOM come this).

AGGIORNAMENTO 2019-05-16

Ho implementato nuovamente la funzione utilizzando le funzionalità ES5 ed ES6 per ambienti moderni:

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

L'implementazione è coperta da una serie di test .

Per qualcosa di più sofisticato, dai un'occhiata al plug-in jQuery Typewatch .


64
Un'altra alternativa: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js . Funziona praticamente nello stesso modo in cui mi hai descritto, mi sono appena trovato ad usare quel modello molto, quindi l'ho implementato come un plugin jQuery per semplificare la sintassi. Ecco una pagina demo: briangrinstead.com/files/bindWithDelay
Brian Grinstead

2
inizialmente ho codificato la quantità di lettere digitate, questo approccio è fluido come melassa (senza zolfo)
Har,

2
Quando lo uso con $ .post (), invia tutto digitato contemporaneamente, ma lo invia tutte le volte che ho avuto un keyup. C'è un modo per inviare SOLO al termine del timeout?
ntgCleaner,

7
Ma questo non funziona se sto chiamando due o più funzioni diverse che richiedono un ritardo individuale. Ho fatto questa soluzione invece stackoverflow.com/a/30503848/1834212
Miguel,

4
Raccomando il plugin 'bindWithDelay' collegato nel commento in alto. Ma suggerisco la risposta di @ Hazerider a questa domanda (di seguito - stackoverflow.com/a/19259625/368896 ), che è meglio di questa risposta e vale la pena studiare per comprendere l'idea alla base di una rilegatura semplice e liscia per consentire timer diversi per elementi diversi - il stesso principio usato nel codice plugin sopra, ma più facile da capire.
Dan Nissenbaum

60

Se si desidera cercare dopo che il tipo è terminato, utilizzare una variabile globale per contenere il timeout restituito dalla setTimoutchiamata e annullarlo con un clearTimeoutse non è ancora avvenuto in modo che non attivi il timeout tranne l'ultimo keyupevento

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

O con una funzione anonima:

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

    //ajax code

  }, 200);  
}   

39

Un altro leggero miglioramento sulla risposta di CMS. Per consentire facilmente ritardi separati, è possibile utilizzare quanto segue:

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

Se vuoi riutilizzare lo stesso ritardo, fallo e basta

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

Se vuoi ritardi separati, puoi farlo

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

1
È interessante notare che la soluzione con più di 600 voti a partire dal giorno in cui scrivo questo commento non è buona come questa risposta con solo 2 voti (incluso il mio). Chiaramente superiore, perché supporta più widget che possono condividere o non condividere il ritardo. Risposta eccellente.
Dan Nissenbaum,

... Anche se il plug-in jQuery nel commento sotto la risposta gestisce anche timer separati per elementi separati e aggiunge anche l'argomento throttle & event: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js. L'approccio generale di quel plugin è lo stesso del tuo codice, quindi vale la pena studiare attentamente il tuo codice per comprenderlo per la sua semplicità prima di provare a capire il plugin più sofisticato. Raccomando quel plugin, però - ma se non hai bisogno di accelerare o di passare i dati degli eventi, il tuo codice è fantastico! Due buone opzioni. Grazie!
Dan Nissenbaum,

1
Hmm, ritarda per me ma someApiCallsi innesca ancora più volte - ora solo con il valore finale del campo di input.
Roelant,


14

Sulla base della risposta di CMS, ho fatto questo:

Inserisci il codice seguente dopo includere 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);

E usa semplicemente così:

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

Attenzione: la variabile $ (questa) nella funzione passata come parametro non corrisponde all'input


12

Ritardare le chiamate multifunzione utilizzando le etichette

Questa è la soluzione con cui lavoro. Ritarderà l'esecuzione su QUALSIASI funzione desiderata . Può essere la query di ricerca del keydown, forse il clic rapido sui pulsanti precedenti o successivi (che altrimenti invierebbe una richiesta multipla se si fa clic rapidamente in modo continuo e non venga utilizzato dopo tutto). Questo utilizza un oggetto globale che memorizza ogni tempo di esecuzione e lo confronta con la richiesta più recente.

Quindi il risultato è che solo l'ultimo clic / azione verrà effettivamente chiamato, poiché quelle richieste sono memorizzate in una coda, che dopo X millisecondi viene chiamato se nella coda non esistono altre richieste con la stessa etichetta!

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

È possibile impostare il proprio tempo di ritardo (facoltativo, il valore predefinito è 500 ms). E invia i tuoi argomenti di funzione in un "modo di chiusura".

Ad esempio, se si desidera chiamare la seguente funzione:

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

Per impedire più richieste send_ajax, puoi ritardarle usando:

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

Ogni richiesta che utilizza l'etichetta "Controlla data" verrà attivata solo se non viene effettuata alcuna richiesta nel periodo di 600 millisecondi. Questo argomento è facoltativo

Indipendenza dall'etichetta (chiamando la stessa funzione target) ma esegui entrambi:

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

Risultati nel chiamare la stessa funzione ma ritardarli indipendentemente perché le loro etichette sono diverse


Sto usando il tuo codice in un progetto a cui sto lavorando e ho difficoltà con esso. In realtà non sta ritardando nulla. Ho pensato che potresti voler vedere / fornire assistenza. stackoverflow.com/questions/51393779/…
KDJ

10

Se a qualcuno piace ritardare la stessa funzione e senza una variabile esterna, può usare lo script successivo:

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
Grande! Soluzione semplice che funziona bene!
Fellipe Sanches,

1
Mi piace questa. Non esattamente riutilizzabile ma facile da capire.
Vincent

10

Approccio super semplice, progettato per eseguire una funzione dopo che un utente ha finito di digitare in un campo di testo ...

<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
Grazie! Le risposte popolari sopra non hanno funzionato ... ma il tuo suggerimento ha funzionato perfettamente.
user2662680,

1
2020 funziona ancora
Romel Indemne,

7

Questa funzione estende un po 'la funzione dalla risposta di Gaten per recuperare l'elemento:

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

Sono sorpreso che nessuno menzioni il problema con input multipli in CMS molto bello.

Fondamentalmente, dovresti definire la variabile delay singolarmente per ciascun input. Altrimenti, se sb inserisce il testo nel primo input e passa rapidamente all'altro input e inizia a digitare, richiamare per il primo NON VERRÀ verrà chiamato!

Vedi il codice qui sotto con cui sono arrivato sulla base di altre risposte:

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

Questa soluzione mantiene il riferimento setTimeout all'interno della variabile delayTimer dell'input. Passa anche il riferimento dell'elemento al callback come suggerito da Fazzyx.

Testato in IE6, 8 (comp - 7), 8 e Opera 12.11.


Ho provato questo, ma non riesco a funzionare correttamente al primo keyup.
Lars Thorén,

6

Questo ha funzionato per me, dove ritardare l'operazione della logica di ricerca e controllo se il valore è uguale a quello inserito nel campo di testo. Se il valore è lo stesso, vado avanti ed eseguo l'operazione per i dati relativi al valore di ricerca.

$('#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);
});

non funziona come previsto - il numero di recuperi è lo stesso, appena ritardato di 300 ms - è necessario utilizzare la funzione clearTimeout () su ogni sequenza di tasti, prima di eseguire setTimeout ()
Picard

Bene, in questo caso non è necessario utilizzare clearTimeout. La condizione searchValue == $ ('# searchText'). Val () all'interno di setTimeout assicurerà se il valore è lo stesso di 300ms fa. Fammi sapere se questo ha senso. Grazie.
Sagar Gala,

4

Funzione di ritardo per richiamare ad ogni keyup. jQuery 1.7.1 o versioni successive richieste

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

Uso: $('#searchBox').keyupDelay( cb );


3

Questa è una soluzione sulla falsariga di CMS, ma risolve alcuni problemi chiave per me:

  • Supporta più input, i ritardi possono essere eseguiti contemporaneamente.
  • Ignora gli eventi chiave che non hanno modificato il valore (come Ctrl, Alt + Tab).
  • Risolve una condizione di competizione (quando viene eseguito il callback e il valore è già cambiato).
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)
            }
        }
    }
}())

Uso:

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

Il codice è scritto in uno stile che mi piace, potrebbe essere necessario aggiungere un punto e virgola.

Cose da tenere a mente:

  • Un ID univoco viene generato in base all'ID del modulo e al nome di input, quindi devono essere definiti e univoci, altrimenti è possibile adattarlo alla propria situazione.
  • delay restituisce un oggetto facile da estendere per le proprie esigenze.
  • L'elemento originale utilizzato per il ritardo è associato al callback, quindi this funziona come previsto (come nell'esempio).
  • Il valore vuoto viene ignorato nella seconda convalida.
  • Fai attenzione all'accodamento , prevede prima i millisecondi, preferisco quello, ma potresti voler cambiare i parametri in modo che corrispondano setTimeout.

La soluzione che utilizzo aggiunge un altro livello di complessità, che consente di annullare l'esecuzione, ad esempio, ma questa è una buona base su cui basare.


3

Combinando la risposta CMS con quella di Miguel si ottiene una soluzione solida che consente ritardi simultanei.

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

Quando è necessario ritardare diverse azioni in modo indipendente, utilizzare il terzo argomento.

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

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

3

Sulla base della risposta di CMS, ecco il nuovo metodo di ritardo che conserva "questo" nel suo utilizzo:

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

Uso:

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

2

Uso

mytimeout = setTimeout( expression, timeout );

dove espressione è lo script da eseguire e il timeout è il tempo di attesa in millisecondi prima che venga eseguito: questo NON ha lo script, ma semplicemente ritarda l'esecuzione di quella parte fino al termine del timeout.

clearTimeout(mytimeout);

reimposterà / azzererà il timeout in modo che non esegua lo script in espressione (come un annullamento) fintanto che non è stato ancora eseguito.


2

Basato sulla risposta di CMS, ignora semplicemente gli eventi chiave che non cambiano valore.

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

Ecco un suggerimento che ho scritto che si occupa di più input nel tuo modulo.

Questa funzione ottiene l'oggetto del campo di input, inserisci il tuo codice

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

} // fieldKeyup

Questa è la vera funzione delayCall, si occupa di più campi di input

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

Uso:

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

1

Da ES6 , è possibile utilizzare anche la sintassi della funzione freccia .

In questo esempio, il codice ritarda l' keyupevento di 400 ms dopo che gli utenti hanno terminato di digitare prima di chiamare per searchFunceffettuare una richiesta di query.

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 puro:

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

Dai un'occhiata al plugin di completamento automatico . So che ti consente di specificare un ritardo o un numero minimo di caratteri. Anche se non finisci per usare il plug-in, guardare attraverso il codice ti darà alcune idee su come implementarlo da solo.


0

Bene, ho anche creato un codice per limitare la richiesta di ajax ad alta frequenza causata da Keyup / Keydown. Controllalo:

https://github.com/raincious/jQueue

Fai la tua query in questo modo:

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.

E associa un evento come questo:

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

0

L'ho visto oggi un po 'tardi, ma voglio solo metterlo qui nel caso qualcun altro ne avesse bisogno. basta separare la funzione per renderla riutilizzabile. il codice seguente attenderà 1/2 secondo dopo aver digitato stop.

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

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

0

La libreria javascript dell'utente lodash e usa la funzione _.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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.