Como atrasar o manipulador .keyup () até o usuário parar de digitar?

643

Eu tenho um campo de pesquisa. No momento, ele pesquisa todas as chaves. Portanto, se alguém digitar “Windows”, ele fará uma pesquisa com AJAX para todas as configurações: “W”, “Wi”, “Win”, “Wind”, “Windo”, “Window”, “Windows”.

Quero ter um atraso, por isso ele só procura quando o usuário para de digitar por 200 ms.

Não há opção para isso na keyupfunção, e eu tentei setTimeout, mas não funcionou.

Como eu posso fazer isso?

ajsie
fonte
4
Duplicata de stackoverflow.com/questions/1620602/…
Roatin Marth
2
Se eu pudesse, fecharia isso como uma duplicata.
A432511
7
Não vejo o mal de ter duplicatas, desde que as respostas dadas e aceitas estejam corretas. A adição ao banco de dados de perguntas deve ser algo bom e algo pelo qual se esforçar.
Mattis
14
O mal é que as pessoas no futuro não serão capazes de se beneficiar das respostas brilhantes que todos compartilham se houver 100 da mesma pergunta; portanto, fechar os enganadores e redirecionar todos para a pergunta original é melhor para encontrar as melhores práticas e correções. Consulte stackoverflow.com/help/duplicates para obter mais informações sobre por que as duplicatas estão fechadas.
rncrtr
14
Isso é muito mais popular do que aquele que supostamente estava duplicando. É melhor redigido, tem melhores respostas, classifica mais alto no google, etc. Muitos se beneficiaram com esta resposta. Em retrospecto, teria sido uma pena se isso fosse encerrado. Existem alguns triviais que são ruins como duplicatas, mas isso não se enquadra nessa categoria.
usuário

Respostas:

1124

Uso essa pequena função para o mesmo objetivo, executando uma função após o usuário parar de digitar por um período especificado ou em eventos que são disparados a uma taxa alta, como 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>

Como funciona:

A delayfunção retornará uma função empacotada que manipula internamente um cronômetro individual; em cada execução, o cronômetro é reiniciado com o atraso de tempo fornecido; se várias execuções ocorrerem antes desse tempo passar, o cronômetro será redefinido e reiniciado.

Quando o timer finalmente termina, a função de retorno de chamada é executada, passando o contexto e os argumentos originais (neste exemplo, o objeto de evento do jQuery e o elemento DOM como this).

ATUALIZAÇÃO 16-05-2019

Reimplementei a função usando os recursos ES5 e ES6 para ambientes modernos:

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

A implementação é coberta com um conjunto de testes .

Para algo mais sofisticado, dê uma olhada no plugin jQuery Typewatch .

CMS
fonte
64
Outra alternativa: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js . Funciona da mesma maneira que você descreveu, eu acabei usando muito esse padrão, então o implementei como um plugin jQuery para tornar a sintaxe mais simples. Aqui está uma página de demonstração: briangrinstead.com/files/bindWithDelay
Brian Grinstead
2
i inicialmente codificado a quantidade de letras digitadas, esta abordagem é suave como melaço (sem sulfer)
Har
2
Quando eu uso isso com um $ .post (), ele envia tudo digitado ao mesmo tempo, mas o envia tantas vezes quanto eu tinha um keyup. Existe uma maneira de enviar SOMENTE quando o tempo limite terminar?
NtgCleaner
7
Mas isso não funciona se eu estiver chamando duas ou mais funções diferentes que exigem atraso individual. Eu fiz esta solução em vez stackoverflow.com/a/30503848/1834212
Miguel
4
Eu recomendo o plugin 'bindWithDelay' vinculado no comentário superior. Mas sugiro a resposta do @ Hazerider para esta pergunta (abaixo - stackoverflow.com/a/19259625/368896 ), que é melhor do que esta resposta e vale a pena estudar para entender a idéia por trás da ligação simples e lisa para permitir temporizadores diferentes para elementos diferentes - o mesmo princípio usado no código do plugin acima, mas mais fácil de entender.
Dan Nissenbaum 29/11
60

Se você deseja pesquisar após o término do tipo, use uma variável global para reter o tempo limite retornado da sua setTimoutchamada e cancele-o com um clearTimeoutse ainda não tiver ocorrido, para que não ative o tempo limite, exceto no último keyupevento

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

Ou com uma função anônima:

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

    //ajax code

  }, 200);  
}   
RHicke
fonte
39

Outro ligeiro aprimoramento na resposta do CMS. Para permitir atrasos separados facilmente, você pode usar o seguinte:

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

Se você deseja reutilizar o mesmo atraso, basta

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

Se você quiser atrasos separados, poderá fazer

$(selector1).on('keyup', function() {makeDelay(250)(someCallback);});
$(selector2).on('keyup', function() {makeDelay(250)(someCallback);});
Paschover
fonte
1
Interessante que a solução com mais de 600 votos a partir do dia em que escrevo esse comentário não seja tão boa quanto esta resposta com apenas 2 votos (incluindo o meu). Claramente superior, porque suporta vários widgets que podem compartilhar ou não o atraso. Excelente resposta.
Dan Nissenbaum 29/11
... Embora o plug-in jQuery no comentário abaixo da resposta também lide com cronômetros separados para elementos separados e inclua também argumentos de aceleração e evento: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js. A abordagem geral desse plug-in é a mesma do seu código, portanto vale a pena estudá-lo com cuidado para entender por sua simplicidade antes de tentar entender o plug-in mais sofisticado. No entanto, eu recomendo esse plug-in - mas se você não precisar de aceleração ou transmissão de dados de eventos, seu código é ótimo! Duas boas opções. Obrigado!
Dan Nissenbaum 29/11
1
Hmm, isso demora para mim, mas someApiCallainda dispara várias vezes - agora apenas com o valor final do campo de entrada.
Roelant
30

Você também pode olhar no underscore.js , que fornece métodos utilitários como o debounce :

var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
meleyal
fonte
O mesmo também está disponível no Lodash - lodash.com/docs/#debounce
Adarsh ​​Madrecha
14

Com base na resposta do CMS, fiz o seguinte:

Coloque o código abaixo depois de incluir o 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 simplesmente use assim:

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

Cuidado: a variável $ (this) na função passada como parâmetro não corresponde à entrada

Gaten
fonte
12

Atraso em chamadas multifuncionais usando etiquetas

Esta é a solução com a qual trabalho. Isso atrasará a execução em QUALQUER função que você desejar . Pode ser a consulta de pesquisa de teclas pressionadas, talvez o clique rápido nos botões anterior ou próximo (que de outra forma enviariam várias solicitações se clicassem rapidamente continuamente e não fossem utilizados, afinal). Isso usa um objeto global que armazena cada tempo de execução e o compara com a solicitação mais atual.

Portanto, o resultado é que apenas o último clique / ação será chamado, porque essas solicitações são armazenadas em uma fila, que após o milissegundo X é chamado, se não houver outra solicitação com o mesmo rótulo na fila!

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

Você pode definir o seu próprio tempo de atraso (opcional, o padrão é 500ms). E envie os argumentos da sua função de uma "maneira de fechamento".

Por exemplo, se você deseja chamar a função abaixo:

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

Para impedir várias solicitações send_ajax, você as adia usando:

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

Qualquer solicitação que use o rótulo "data de verificação" será acionada apenas se nenhuma outra solicitação for feita no período de 600 milissegundos. Este argumento é opcional

Independência de rótulo (chamando a mesma função de destino), mas execute os dois:

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

Resulta em chamar a mesma função, mas atrasa-as de forma independente, porque seus rótulos são diferentes

Miguel
fonte
Estou usando seu código em um projeto no qual estou trabalhando e tendo dificuldades com ele. Na verdade, não está atrasando nada. Pensei que você poderia querer ver / fornecer assistência. stackoverflow.com/questions/51393779/…
KDJ
10

Se alguém gostar de atrasar a mesma função, e sem variável externa, poderá usar o próximo script:

function MyFunction() {

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

        //Execute the function code here...

    }, 500);
}
Roy Shoa
fonte
1
Ótimo! Solução fácil que funciona bem!
Fellipe Sanches
1
Eu gosto deste. Não é exatamente reutilizável, mas fácil de entender.
Vincent
10

Abordagem super simples, projetada para executar uma função depois que um usuário terminar de digitar em um campo de texto ...

<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
fonte
1
Obrigado! As respostas populares acima não funcionaram ... mas sua sugestão funcionou perfeitamente.
User2662680
1
2020 ainda funciona
Romel Indemne
7

Esta função estende a função da resposta de Gaten um pouco para recuperar o 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/

fazzyx
fonte
6

Estou surpreso que ninguém mencione o problema com várias entradas no snippet muito bom do CMS.

Basicamente, você teria que definir a variável de atraso individualmente para cada entrada. Caso contrário, se sb colocar texto na primeira entrada e pular rapidamente para outra entrada e começar a digitar, o retorno de chamada da primeira NÃO será chamado!

Veja o código abaixo com base em outras respostas:

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

Esta solução mantém a referência setTimeout na variável delayTimer da entrada. Ele também passa a referência do elemento ao retorno de chamada, como fazzyx sugerido.

Testado no IE6, 8 (comp - 7), 8 e Opera 12.11.

jszoja
fonte
Já tentei isso, mas não consigo trabalhar corretamente na primeira instalação.
Lars Thorén
6

Isso funcionou para mim, onde atraso a operação da lógica de pesquisa e faço uma verificação se o valor é o mesmo que o inserido no campo de texto. Se o valor for o mesmo, seguirei em frente e realizo a operação para os dados relacionados ao valor da pesquisa.

$('#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);
});
Sagar Gala
fonte
não funciona como esperado - o número de buscas é o mesmo, apenas atrasado pelos 300ms - você precisa usar a função clearTimeout () em todas as teclas, antes de executar o setTimeout ()
Picard
Bem, você não precisa usar o clearTimeout neste caso. A condição searchValue == $ ('# searchText'). Val () dentro de setTimeout garantirá se o valor é o mesmo que era há 300 ms. Deixe-me saber se isso faz sentido. Obrigado.
Sagar Gala
4

Atraso na função para acessar cada keyup. jQuery 1.7.1 ou superior necessário

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

Vinay Aggarwal
fonte
3

Esta é uma solução semelhante à do CMS, mas resolve alguns problemas importantes para mim:

  • Suporta várias entradas, atrasos podem ser executados simultaneamente.
  • Ignora os principais eventos que não alteraram o valor (como Ctrl, Alt + Tab).
  • Resolve uma condição de corrida (quando o retorno de chamada é executado e o valor já foi alterado).
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)
    })
})

O código está escrito no estilo que eu gosto, talvez você precise adicionar vários pontos e vírgulas.

Coisas para manter em mente:

  • Um ID exclusivo é gerado com base no ID do formulário e no nome da entrada; portanto, eles devem ser definidos e exclusivos, ou você pode ajustá-lo à sua situação.
  • delay retorna um objeto fácil de estender para suas próprias necessidades.
  • O elemento original usado para o atraso está vinculado ao retorno de chamada, portanto this funciona conforme o esperado (como no exemplo).
  • O valor vazio é ignorado na segunda validação.
  • Cuidado com o enfileiramento , ele espera milissegundos primeiro, eu prefiro isso, mas convém alternar os parâmetros para corresponder setTimeout.

A solução que eu uso adiciona outro nível de complexidade, permitindo que você cancele a execução, por exemplo, mas essa é uma boa base para desenvolver.

Jaime Gómez
fonte
3

A combinação da resposta CMS com a de Miguel produz uma solução robusta que permite atrasos simultâneos.

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

Quando você precisar adiar ações diferentes independentemente, use o terceiro argumento.

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

$('input.group2').keyup(function() {
    delay(function(){
        alert('Time elapsed!');
    }, 1000, '2ndAction');
});
Frédéric
fonte
3

Com base na resposta do CMS, aqui está um novo método de atraso que preserva "isso" em seu uso:

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);
});
mga911
fonte
2

Usar

mytimeout = setTimeout( expression, timeout );

onde expressão é o script a ser executado e tempo limite é o tempo de espera em milissegundos antes de ser executado - isso NÃO assombra o script, mas simplesmente atrasa a execução dessa parte até que o tempo limite seja concluído.

clearTimeout(mytimeout);

redefinirá / limpará o tempo limite para que ele não execute o script na expressão (como um cancelamento) desde que ainda não tenha sido executado.

Mark Schultheiss
fonte
2

Com base na resposta do CMS, ele simplesmente ignora os principais eventos que não mudam de valor.

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


})
Anderson
fonte
1

Use o plugin jQuery bindWithDelay :

element.bindWithDelay(eventType, [ eventData ], handler(eventObject), timeout, throttle)
Vebjorn Ljosa
fonte
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
fonte
1

Aqui está uma sugestão que escrevi que cuida de várias entradas em seu formulário.

Esta função obtém o objeto do campo de entrada, insira seu código

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

} // fieldKeyup

Esta é a função delayCall real, cuida de vários campos de entrada

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);
});
egpo
fonte
1

No ES6 , também é possível usar a sintaxe da função de seta .

Neste exemplo, o código atrasa o keyupevento por 400 ms depois que os usuários terminam de digitar antes de chamar searchFuncfazer uma solicitação de consulta.

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);
})
Dat Nguyen
fonte
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..."/>

Ali Nasr
fonte
0

Dê uma olhada no plug-in de preenchimento automático . Eu sei que ele permite que você especifique um atraso ou um número mínimo de caracteres. Mesmo que você não use o plug-in, analisar o código fornecerá algumas idéias sobre como implementá-lo.

tvanfosson
fonte
0

Bem, eu também fiz um código para limitar a solicitação de ajax de alta frequência causada por Keyup / Keydown. Veja isso:

https://github.com/raincious/jQueue

Faça sua consulta assim:

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 evento bind como este:

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

fonte
0

Vi isso hoje um pouco tarde, mas só quero colocar isso aqui, caso alguém precise. basta separar a função para torná-la reutilizável. o código abaixo esperará 1/2 segundo após a digitação de parada.

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

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

Usuário biblioteca javascript lodash e use a função _.debounce

changeName: _.debounce(function (val) {
  console.log(val)                
}, 1000)
Amir Ur Rehman
fonte
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);
}
Bharat chaudhari
fonte