¿Cómo retrasar el controlador .keyup () hasta que el usuario deja de escribir?

643

Tengo un campo de búsqueda. En este momento busca cada keyup. Entonces, si alguien escribe "Windows", realizará una búsqueda con AJAX para cada tecla: "W", "Wi", "Win", "Wind", "Windo", "Window", "Windows".

Quiero tener un retraso, por lo que solo busca cuando el usuario deja de escribir durante 200 ms.

No hay ninguna opción para esto en la keyupfunción, y lo he intentado setTimeout, pero no funcionó.

¿Cómo puedo hacer eso?

ajsie
fuente
44
Duplicado de stackoverflow.com/questions/1620602/…
Roatin Marth
2
Si pudiera, cerraría esto como un duplicado.
a432511
77
No veo el daño en tener duplicados, siempre que las respuestas dadas y aceptadas sean correctas. Agregar a la base de datos de preguntas debe ser algo bueno y algo por lo que luchar.
Mattis
14
El daño es que las personas en el futuro no podrán beneficiarse de las respuestas brillantes que todos comparten si hay 100 de la misma pregunta, por lo que cerrar engaños y redirigir a todos a la pregunta original es mejor para encontrar las mejores prácticas y soluciones. Consulte stackoverflow.com/help/duplicates para obtener más información sobre por qué se cierran los duplicados.
rncrtr
14
Esto es mucho más popular que el que supuestamente estaba duplicando. Está mejor redactado, tiene mejores respuestas, ocupa un lugar más alto en Google, etc. Muchos se han beneficiado de esta respuesta. En retrospectiva, habría sido una lástima que esto estuviera cerrado. Hay algunos triviales que son malos como duplicados, pero esto no cae en esa categoría.
usuario

Respuestas:

1124

Utilizo esta pequeña función para el mismo propósito, ejecutando una función después de que el usuario ha dejado de escribir durante un período de tiempo especificado o en eventos que se disparan a una velocidad 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>

Cómo funciona:

La delayfunción devolverá una función ajustada que maneja internamente un temporizador individual; en cada ejecución, el temporizador se reinicia con el tiempo de retraso provisto; si ocurren varias ejecuciones antes de que pase este tiempo, el temporizador simplemente se reiniciará y comenzará nuevamente.

Cuando finalmente termina el temporizador, se ejecuta la función de devolución de llamada, pasando el contexto y los argumentos originales (en este ejemplo, el objeto de evento de jQuery y el elemento DOM como this).

ACTUALIZACIÓN 2019-05-16

He vuelto a implementar la función utilizando las funciones de ES5 y ES6 para entornos modernos:

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

La implementación está cubierta con un conjunto de pruebas .

Para algo más sofisticado, eche un vistazo al complemento jQuery Typewatch .

CMS
fuente
64
Otra alternativa: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js . Funciona de la misma manera que usted describió, acabo de usar ese patrón mucho, así que lo implementé como un complemento jQuery para simplificar la sintaxis. Aquí hay una página de demostración: briangrinstead.com/files/bindWithDelay
Brian Grinstead
2
Inicialmente codifiqué la cantidad de letras escritas, este enfoque es suave como la melaza (sin sulfer)
Har
2
Cuando uso esto con un $ .post (), envía todo lo que se escribe al mismo tiempo, pero lo envía tantas veces como tuve un keyup. ¿Hay alguna forma de enviar SOLO cuando se acabe el tiempo de espera?
ntgCleaner
77
Pero esto no funciona si estoy llamando a dos o más funciones diferentes que requieren un retraso individual. Hice esta solución en su lugar stackoverflow.com/a/30503848/1834212
Miguel
44
Recomiendo el complemento 'bindWithDelay' vinculado en el comentario superior. Pero sugiero la respuesta de @ Hazerider a esta pregunta (a continuación: stackoverflow.com/a/19259625/368896 ), que es mejor que esta respuesta y que vale la pena estudiar para comprender la idea detrás de la unión simple y elegante para permitir diferentes temporizadores para diferentes elementos: el mismo principio utilizado en el código del complemento anterior, pero más fácil de entender.
Dan Nissenbaum
60

Si desea buscar después de que el tipo haya terminado, use una variable global para retener el tiempo de espera devuelto por su setTimoutllamada y cancélelo con un clearTimeoutsi aún no ha sucedido para que no se active el tiempo de espera excepto en el último 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 función anónima:

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

    //ajax code

  }, 200);  
}   
RHicke
fuente
39

Otra ligera mejora en la respuesta de CMS. Para permitir fácilmente retrasos por separado, puede usar lo siguiente:

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

Si desea reutilizar el mismo retraso, simplemente haga

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

Si desea demoras separadas, puede hacerlo

$(selector1).on('keyup', function() {makeDelay(250)(someCallback);});
$(selector2).on('keyup', function() {makeDelay(250)(someCallback);});
Pascua
fuente
1
Es interesante que la solución con más de 600 votos a favor en el día en que escribo este comentario no sea tan buena como esta respuesta con solo 2 votos a favor (incluido el mío). Claramente superior, porque admite múltiples widgets que pueden compartir o no compartir el retraso. Excelente respuesta
Dan Nissenbaum
... Aunque el complemento jQuery en el comentario debajo de la respuesta también maneja temporizadores separados para elementos separados, y también agrega argumentos de acelerador y evento: github.com/bgrins/bindWithDelay/blob/master/bindWithDelay.js. El enfoque general de ese complemento es el mismo que el de su código, por lo que vale la pena estudiarlo detenidamente para comprenderlo por su simplicidad antes de intentar comprender el complemento más sofisticado. Sin embargo, recomiendo ese complemento, pero si no necesita aceleración o el paso de datos de eventos, ¡su código es excelente! Dos buenas opciones ¡Gracias!
Dan Nissenbaum
1
Hmm, me demora pero someApiCalltodavía se dispara varias veces, ahora solo con el valor final del campo de entrada.
Roelant
30

También puede mirar underscore.js , que proporciona métodos de utilidad como debounce :

var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
melancólico
fuente
Lo mismo también está disponible en Lodash - lodash.com/docs/#debounce
Adarsh ​​Madrecha
14

Basado en la respuesta de CMS, hice esto:

Ponga el siguiente código después de incluir 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);

Y simplemente use así:

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

Cuidado: la variable $ (this) en la función pasada como parámetro no coincide con la entrada

Gaten
fuente
12

Retrasar llamadas multifunción usando etiquetas

Esta es la solución con la que trabajo. Retrasará la ejecución en CUALQUIER función que desee . Puede ser la consulta de búsqueda de keydown, tal vez el clic rápido en los botones anteriores o siguientes (que de lo contrario enviaría una solicitud múltiple si se hace clic rápidamente de forma continua, y no se usará después de todo). Esto utiliza un objeto global que almacena cada tiempo de ejecución y lo compara con la solicitud más actual.

Entonces, el resultado es que solo se llamará ese último clic / acción, porque esas solicitudes se almacenan en una cola, que después de X milisegundos se llama si no existe otra solicitud con la misma etiqueta en la cola.

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

Puede establecer su propio tiempo de retraso (es opcional, por defecto es de 500 ms). Y envíe sus argumentos de función en una "moda de cierre".

Por ejemplo, si desea llamar a la función de abajo:

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

Para evitar múltiples solicitudes send_ajax, las retrasa usando:

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

Cada solicitud que use la etiqueta "fecha de verificación" solo se activará si no se realiza ninguna otra solicitud en el plazo de 600 milisegundos. Este argumento es opcional.

Etiqueta de independencia (llamando a la misma función de destino) pero ejecuta ambos:

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

Resulta en llamar a la misma función pero las retrasa de forma independiente debido a que sus etiquetas son diferentes

Miguel
fuente
Estoy usando su código en un proyecto en el que estoy trabajando y tengo dificultades con él. En realidad no está retrasando nada. Pensé que es posible que desee ver / proporcionar asistencia. stackoverflow.com/questions/51393779/…
KDJ
10

Si a alguien le gusta retrasar la misma función, y sin una variable externa, puede usar el siguiente 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
fuente
1
¡Excelente! ¡Solución fácil que funciona bien!
Fellipe Sanches
1
Me gusta este. No es exactamente reutilizable pero fácil de entender.
Vincent
10

Enfoque súper simple, diseñado para ejecutar una función después de que un usuario haya terminado de escribir en un 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
fuente
1
¡Gracias! Las respuestas populares anteriores no funcionaron ... pero su sugerencia funcionó perfectamente.
user2662680
1
2020 todavía funciona
Romel Indemne
7

Esta función extiende un poco la función de la respuesta de Gaten para recuperar el 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
fuente
6

Me sorprende que nadie mencione el problema con la entrada múltiple en CMS muy bien recortado.

Básicamente, debería definir la variable de retardo individualmente para cada entrada. De lo contrario, si sb coloca el texto en la primera entrada y salta rápidamente a otra entrada y comienza a escribir, ¡NO se llamará a la devolución de llamada para la primera !

Vea el siguiente código con el que vine basado en otras respuestas:

(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 solución mantiene la referencia setTimeout dentro de la variable delayTimer de entrada. También pasa la referencia del elemento a la devolución de llamada como sugiere fazzyx.

Probado en IE6, 8 (comp - 7), 8 y Opera 12.11.

jszoja
fuente
He intentado esto pero no puedo trabajar correctamente en el primer keyup.
Lars Thorén
6

Esto funcionó para mí cuando retrasé la operación de lógica de búsqueda y verifiqué si el valor es el mismo que se ingresó en el campo de texto. Si el valor es el mismo, sigo adelante y realizo la operación para los datos relacionados con el valor de búsqueda.

$('#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
fuente
no funciona como se esperaba: el número de recuperaciones es el mismo, solo retrasado por los 300 ms; debe usar la función clearTimeout () en cada pulsación de tecla, antes de ejecutar setTimeout ()
Picard
Bueno, no necesitas usar clearTimeout en este caso. La condición searchValue == $ ('# searchText'). Val () dentro de setTimeout asegurará si el valor es el mismo que hace 300ms. Avísame si esto tiene sentido. Gracias.
Sagar Gala
4

Función de retraso para llamar en cada tecla. Se requiere jQuery 1.7.1 o superior

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
fuente
3

Esta es una solución similar a la de los CMS, pero resuelve algunos problemas clave para mí:

  • Admite múltiples entradas, los retrasos pueden ejecutarse simultáneamente.
  • Ignora los eventos clave que no cambiaron el valor (como Ctrl, Alt + Tab).
  • Resuelve una condición de carrera (cuando se ejecuta la devolución de llamada y el valor ya cambió).
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)
    })
})

El código está escrito en un estilo que disfruto, es posible que deba agregar un punto y coma.

Cosas a tener en cuenta:

  • Se genera una identificación única basada en la identificación del formulario y el nombre de entrada, por lo que deben ser definidos y únicos, o puede ajustarlo a su situación.
  • delay devuelve un objeto que es fácil de extender para sus propias necesidades.
  • El elemento original utilizado para el retraso está vinculado a la devolución de llamada, por lo que thisfunciona como se esperaba (como en el ejemplo).
  • El valor vacío se ignora en la segunda validación.
  • Tenga cuidado con la cola , espera milisegundos primero, prefiero eso, pero es posible que desee cambiar los parámetros para que coincidan setTimeout.

La solución que uso agrega otro nivel de complejidad, lo que le permite cancelar la ejecución, por ejemplo, pero esta es una buena base para construir.

Jaime Gómez
fuente
3

La combinación de la respuesta de CMS con la de Miguel produce una solución robusta que permite retrasos concurrentes.

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

Cuando necesite retrasar diferentes acciones de forma independiente, use el tercer 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
fuente
3

Sobre la base de la respuesta de CMS, aquí hay un nuevo método de retraso que conserva 'esto' en su 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
fuente
2

Utilizar

mytimeout = setTimeout( expression, timeout );

donde expresión es el script que se debe ejecutar y el tiempo de espera es el tiempo de espera en milisegundos antes de que se ejecute; esto NO obstaculiza el script, sino que simplemente retrasa la ejecución de esa parte hasta que finalice el tiempo de espera.

clearTimeout(mytimeout);

restablecerá / borrará el tiempo de espera para que no ejecute la secuencia de comandos en la expresión (como una cancelación) siempre que aún no se haya ejecutado.

Mark Schultheiss
fuente
2

Basado en la respuesta de CMS, simplemente ignora los eventos clave que no cambian el 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
fuente
1

Use el complemento bindWithDelay jQuery:

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

Aquí hay una sugerencia que he escrito que se encarga de múltiples entradas en su formulario.

Esta función obtiene el objeto del campo de entrada, ingrese su código

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

} // fieldKeyup

Esta es la función delayCall real, se encarga de múltiples 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
fuente
1

Desde ES6 , también se puede usar la sintaxis de la función de flecha .

En este ejemplo, el código retrasa el keyupevento durante 400 ms después de que los usuarios terminen de escribir antes de llamar para searchFuncrealizar una solicitud 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
fuente
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
fuente
0

Echa un vistazo al complemento de autocompletar . Sé que te permite especificar un retraso o un número mínimo de caracteres. Incluso si no termina usando el complemento, mirar el código le dará algunas ideas sobre cómo implementarlo usted mismo.

tvanfosson
fuente
0

Bueno, también hice un código para limitar la solicitud de ajax de alta frecuencia causada por Keyup / Keydown. Mira esto:

https://github.com/raincious/jQueue

Haga su consulta así:

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.

Y enlace evento como este:

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

fuente
0

Lo vi hoy un poco tarde, pero solo quiero poner esto aquí en caso de que alguien más lo necesite. simplemente separe la función para que sea reutilizable. el siguiente código esperará 1/2 segundo después de escribir stop.

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

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

Usuario lodash javascript library y uso _.debounce function

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