jQuery click events disparando varias veces

283

Estoy tratando de escribir un juego de video póker en Javascript como una forma de obtener los conceptos básicos, y me he encontrado con un problema en el que los controladores de eventos jQuery click se disparan varias veces.

Están unidos a botones para hacer una apuesta, y funciona bien para hacer una apuesta en la primera mano durante un juego (disparando solo una vez); pero al apostar por la segunda mano, dispara el evento de clic dos veces cada vez que se presiona un botón de apuesta o apuesta de lugar (por lo que se apuesta dos veces la cantidad correcta por cada presión). En general, sigue este patrón por la cantidad de veces que se dispara el evento de clic cuando se presiona un botón de apuesta una vez, donde el i-ésimo término de la secuencia es para apostar la i-ésima mano desde el comienzo del juego: 1, 2, 4 , 7, 11, 16, 22, 29, 37, 46, que parece ser n (n + 1) / 2 + 1 para lo que sea que valga la pena, y no era lo suficientemente inteligente como para entender eso, usé OEIS . :)

Aquí está la función con los controladores de eventos de clic que están actuando; espero que sea fácil de entender (avíseme si no, también quiero mejorar en eso):

/** The following function keeps track of bet buttons that are pressed, until place button is pressed to place bet. **/
function pushingBetButtons() {
    $("#money").text("Money left: $" + player.money); // displays money player has left

    $(".bet").click(function() {
        var amount = 0; // holds the amount of money the player bet on this click
        if($(this).attr("id") == "bet1") { // the player just bet $1
            amount = 1;
        } else if($(this).attr("id") == "bet5") { // etc.
            amount = 5;
        } else if($(this).attr("id") == "bet25") {
            amount = 25;
        } else if($(this).attr("id") == "bet100") {
            amount = 100;
        } else if($(this).attr("id") == "bet500") {
            amount = 500;
        } else if($(this).attr("id") == "bet1000") {
            amount = 1000;
        }
        if(player.money >= amount) { // check whether the player has this much to bet
            player.bet += amount; // add what was just bet by clicking that button to the total bet on this hand
            player.money -= amount; // and, of course, subtract it from player's current pot
            $("#money").text("Money left: $" + player.money); // then redisplay what the player has left
        } else {
            alert("You don't have $" + amount + " to bet.");
        }
    });

    $("#place").click(function() {
        if(player.bet == 0) { // player didn't bet anything on this hand
            alert("Please place a bet first.");
        } else {
            $("#card_para").css("display", "block"); // now show the cards
            $(".card").bind("click", cardClicked); // and set up the event handler for the cards
            $("#bet_buttons_para").css("display", "none"); // hide the bet buttons and place bet button
            $("#redraw").css("display", "block"); // and reshow the button for redrawing the hand
            player.bet = 0; // reset the bet for betting on the next hand
            drawNewHand(); // draw the cards
        }
    });
}

Avíseme si tiene alguna idea o sugerencia, o si la solución a mi problema es similar a una solución a otro problema aquí (he examinado muchos hilos con títulos similares y no tuve suerte en encontrar una solución que pudiera funcionar para mi).

Gregory Fowler
fuente
var amount = parseInt(this.id.replace(/[^\d]/g,''),10);Y si va a usar la misma propiedad de un elemento más de una vez, almacene esa propiedad, no siga buscando. La búsqueda es costosa.
David dice que reinstale a Mónica
Gracias por la respuesta y el consejo sobre las propiedades de almacenamiento en caché. Configuré player.money y player.bet a las variables locales dinero y aposté dentro de esa función y las manipulé en su lugar, y cambiaré el resto de mi código para hacer eso también. :) Si tiene tiempo, ¿podría explicar también lo que sugirió? la inicialización de la cantidad está haciendo; parece una expresión regular, pero no puedo entenderlo fácilmente.
Gregory Fowler
@GregoryFowler: no está relacionado con su pregunta, pero ... vale la pena analizar una declaración de cambio de JavaScript.
Clayton
2
Hombre, tu función es colocar un controlador de clic cada vez que se invoca. Si lo invoca en cada ronda, en la segunda ronda tiene dos controladores y así sucesivamente. Cada controlador hace su trabajo y en la ronda 100 obtienes 100 alertas.
Marco Faustinelli
Esto sucede porque en algún lugar de su código, está volviendo a vincular el controlador de eventos sin primero desvincularlo. Vea esta pregunta para un escenario similar y una buena explicación.
jpaugh

Respuestas:

526

Para asegurarse de que solo haga clic en acciones una vez use esto:

$(".bet").unbind().click(function() {
    //Stuff
});
Robar
fuente
31
Bien, ¿dónde estuviste ayer, Rob? ;) Eso es exactamente lo que estaba buscando, no sé por qué no lo encontré antes. Pero todavía fue una bendición disfrazada porque aprendí muchas otras cosas.
Gregory Fowler
1
;) lo siento. Hice girar mis ruedas durante unos días en este también. Todavía estoy buscando una razón lógica de por qué incluso sucede en primer lugar.
Rob
66
Hombre, después de tantas cosas, esto lo hizo. Excepto en mi caso, usé el equivalente de: $ (". Bet"). Off ().
On
77
Como jroi_web dice a continuación, este método está en desuso. Consulte la respuesta de @ trolle para obtener una solución más reciente.
Pascal
Un controlador de clics no es algo desechable. Especialmente cuando es el voluminoso si-si-si se muestra en la pregunta. Se supone que debe adjuntarlo y dejar que haga su trabajo mientras dure la página.
Marco Faustinelli
378

.unbind()está en desuso y debería usar el .off()método en su lugar. Simplemente llame .off()justo antes de llamar .on().

Esto eliminará todos los controladores de eventos:

$(element).off().on('click', function() {
    // function body
});

Para eliminar solo los controladores de eventos 'clic' registrados:

$(element).off('click').on('click', function() {
    // function body
});
mtl
fuente
9
esto debería usarse en la versión más nueva de jQuery ya que desunir, morir o vivir ya está en desuso
jroi_web
¡Funciona de maravilla! Mi función se ejecutaría una vez al hacer clic, luego dos veces, cuatro veces ... .off () antes de que .on () resolviera este problema.
jumpOnCommand
146

.uno()

Una mejor opción sería .one():

El controlador se ejecuta como máximo una vez por elemento por tipo de evento.

$(".bet").one('click',function() {
    //Your function
});

En el caso de varias clases y cada clase debe hacerse clic una vez,

$(".bet").on('click',function() {
    //Your function
    $(this).off('click');   //or $(this).unbind()
});
Shaunak D
fuente
8
mejor respuesta, me salvaste de un truco tonto, esto es mucho mejor ya que si tiene varios onclickeventos al mismo elemento, pero en diferentes lugares esto no va a afectar el resto, mientras que unbind()y off()sólo va a destruir otros onclicks gracias de nuevo
Fanckush
3
después de probar todas las respuestas, esta es la única que funciona para mí.
Neversion
10
Una cosa a tener en cuenta, si desea que la función solo se active una vez POR CLIC, pero continúe activando en los clics posteriores, esta literalmente solo se activará una vez durante la vida útil de la página. Por lo tanto, será necesario utilizar la respuesta de mtl con el método off (). On ().
Derek Hewitt
1
@munchschair, puede haber múltiples razones para esto. Múltiples elementos con la misma clase uno dentro del otro. O un controlador de clic se registra varias veces en una iteración.
Shaunak D
3
No funciona para mí, aún así disparos de múltiples clics, no tiene sentido ya que solo hay 1 elemento de botón con la ID y solo 1 evento está atrapado (el evento de clic). Pensé que esto era un error de jQuery, pero probablemente sea un error de diálogo modal de Bootstrap.
MC9000
78

Si encuentra que .off () .unbind () o .stopPropagation () aún no soluciona su problema específico, intente usar .stopImmediatePropagation () Funciona muy bien en situaciones en las que solo desea que su evento se maneje sin burbujas y sin efectuando cualquier otro evento que ya se esté manejando. Algo como:

$(".bet").click(function(event) {
  event.stopImmediatePropagation();
  //Do Stuff
});

¡Hace el truco!

Alfonse Pinto
fuente
Esto me ayudó, además de evitar que el evento se disparara dos veces, pero también me molestó mucho. Resulta que si usa event.stopImmediatePropagation () en un controlador de eventos adjunto a un campo de diálogo jQuery UI, entonces, por alguna razón, el diálogo ya no puede hacer referencia a la instancia más nueva de variables globales y en su lugar usa la versión de variables globales antes de la representación de jQuery UI Dialog. Lo que significa que si, por ejemplo, su variable global fue declarada, pero no inicializada en el momento de la representación de jQuery UI Dialog, entonces se mostrará como un controlador de evento jQuery UI Dialog no inicializado
UkraineTrain
1
o si asignó a alguna variable global un valor de 16 antes de representar jQuery UI Dialog, que luego cambió a, digamos, 55, entonces 16 aparecerá para esa variable global en el controlador de eventos jQuery UI Dialog, incluso si en ese momento es ya no 16. Por lo tanto, parece un error de jQuery UI que la gente debería tener en cuenta.
UkraineTrain
Para situaciones muy específicas que son incluso difíciles de describir. Funciona exactamente como Usted dijo: "Funciona sin afectar ningún otro evento". ¡Gracias! :)
Toms Bugna
Funciona completamente bien para mí. ¡Gracias!
Somnath Pawar
18

Si llama a esa función en cada "clic", entonces está agregando otro par de controladores en cada llamada.

Agregar controladores con jQuery simplemente no es como establecer el valor del atributo "onclick". Se pueden agregar tantos controladores como se desee.

Puntiagudo
fuente
44
Su respuesta me puso en la dirección correcta y aprendí mucho, pero desafortunadamente no lo suficiente como para resolver mi problema usando jQuery. :) Intenté usar on / off, live (y delegate) / die, y one, y luego addEventListener / removeEventListener, pero lo mejor que pude hacer fue frenar el crecimiento exponencial de los controladores. Finalmente acabo de resolver mi problema con onclick por el momento. Pero aún aprendí mucho sobre el modelo de eventos de Javascript, como capturar / burbujear, a partir de su respuesta, así que lo aprecio. Gracias. :)
Gregory Fowler
¡Muchas gracias, no puedo creer que no supiera esto!
Lenny
12

Un evento se disparará varias veces cuando se registre varias veces (incluso si se trata del mismo controlador).

por ejemplo, $("ctrl").on('click', somefunction)si este código se ejecuta cada vez que la página se actualiza parcialmente, el evento también se registra cada vez. Por lo tanto, incluso si se hace clic en ctrl solo una vez, puede ejecutar "alguna función" varias veces; cuántas veces se ejecute dependerá de cuántas veces se haya registrado.

Esto es cierto para cualquier evento registrado en JavaScript.

solución:

asegúrese de llamar "encendido" solo una vez.

y por alguna razón si no puede controlar la arquitectura, haga esto:

$("ctrl").off('click'); $("ctrl").on('click', somefunction);

Kalpesh Popat
fuente
¿Qué es lo que realmente quieres decir?
Somnath Kharat
Eso no explica lo que está sucediendo en este caso. El botón tiene una ID única y solo 1 evento (NO se llama varias veces, ya que solo se puede hacer clic una vez). Este es un error que solo aparece en los cuadros de diálogo de jQuery (y es muy fácil de reproducir).
MC9000
2
no sabemos si la función "pushBetButtons" se llama solo una o varias veces ... si se llama más de una vez, el evento se registra varias veces y, por lo tanto, también disparará múltiples tiempos ... incluso si se hace clic en el botón solo una vez .
Kalpesh Popat
4

Tuve un problema debido al marcado.

HTML:

<div class="myclass">
 <div class="inner">

  <div class="myclass">
   <a href="#">Click Me</a>
  </div>

 </div>
</div>

jQuery

$('.myclass').on('click', 'a', function(event) { ... } );

Te das cuenta de que tengo la misma clase 'myclass' dos veces en html, por lo que llama click para cada instancia de div.

Bobz
fuente
3

La mejor opción sería usar off

<script>
function flash() {
  $("div").show().fadeOut("slow");
}
$("#bind").click(function() {
  $( "body" )
    .on("click", "#theone", flash)
    .find("#theone")
      .text("Can Click!");
});
$("#unbind").click(function() {
  $("body")
    .off("click", "#theone", flash)
    .find("#theone")
      .text("Does nothing...");
});
</script>
arc_shiva
fuente
3

Todo lo relacionado con .on () y .one () es excelente, y jquery es excelente.

Pero a veces, desea que sea un poco más obvio que el usuario no puede hacer clic, y en esos casos puede hacer algo como esto:

function funName(){
    $("#orderButton").prop("disabled", true);
    //  do a bunch of stuff
    // and now that you're all done
    setTimeout(function(){
        $("#orderButton").prop("disabled",false);
        $("#orderButton").blur();
    }, 3000);
}

y tu botón se vería así:

<button onclick='funName()'>Click here</button>
rikkitikkitumbo
fuente
1
Eso resolvería el caso de un usuario que realmente haga clic varias veces, pero ... Recibo múltiples eventos de clic con la misma marca de tiempo. No hay forma de que pueda hacer clic 3 veces en el mismo milisegundo, incluso si fuera extremadamente rápido (y de alguna manera no supiera cuántas veces estoy presionando el botón).
jpaugh
2

Ocurre debido a que el evento particular está vinculado varias veces al mismo elemento.

La solución que funcionó para mí es:

Mata todos los eventos adjuntos usando el .die()método.

Y luego adjunte su método de escucha.

Así,

$('.arrow').click(function() {
// FUNCTION BODY HERE
}

debiera ser:

$('.arrow').die("click")
$('.arrow').click(function() {
// FUNCTION BODY HERE
}
Alumno
fuente
2

Debemos hacerlo stopPropagation()Para evitar clics desencadena el evento demasiadas veces.

$(this).find('#cameraImageView').on('click', function(evt) {
   evt.stopPropagation();
   console.log("Camera click event.");
});

Impide que el evento forme burbujas en el árbol DOM, evitando que cualquier controlador principal sea notificado del evento. Este método no acepta ningún argumento.

Podemos usar event.isPropagationStopped()para determinar si este método fue llamado alguna vez (en ese objeto de evento).

Este método también funciona para eventos personalizados activados con trigger (). Tenga en cuenta que esto no impedirá que se ejecuten otros controladores en el mismo elemento.

Daniel Raja Singh
fuente
2

En mi caso, estaba usando 'delegado', por lo que ninguna de estas soluciones funcionó. Creo que fue el botón que apareció varias veces a través de llamadas ajax lo que estaba causando el problema de clics múltiples. Las soluciones estaban usando un tiempo de espera, por lo que solo se reconoce el último clic:

var t;
$('body').delegate( '.mybutton', 'click', function(){
    // clear the timeout
    clearTimeout(t);
    // Delay the actionable script by 500ms
    t = setTimeout( function(){
        // do something here
    },500)
})
Trevor Lettman
fuente
1
$(element).click(function (e)
{
  if(e.timeStamp !== 0) // This will prevent event triggering more then once
   {
      //do your stuff
   }
}
Rajan Rajan M
fuente
1

.one solo se dispara una vez durante toda la vida de la página

Entonces, en caso de que desee hacer la validación, esta no es la solución correcta, porque cuando no abandona la página después de la validación, nunca regresa. Mejor usar

$(".bet").on('click',function() 
{ //validation 
   if (validated) { 
      $(".bet").off('click'); //prevent to fire again when we are not yet off the page
      //go somewhere
    }
});
Pieter van Kampen
fuente
1

Cuando trato con este problema, siempre uso:

$(".bet").unbind("click").bind("click", function (e) {
  // code goes here
}

De esta manera, me desengancho y vuelvo a unir de la misma manera.

Andy
fuente
0

https://jsfiddle.net/0vgchj9n/1/

Para asegurarse de que el evento siempre se active solo una vez, puede usar Jquery .one (). JQuery one asegura que su controlador de eventos solo llame una vez. Además, puede suscribir su controlador de eventos con uno para permitir más clics cuando haya finalizado el procesamiento de la operación de clics actual.

<div id="testDiv">
  <button class="testClass">Test Button</button>
</div>

...

var subscribeClickEvent = function() {$("#testDiv").one("click", ".testClass", clickHandler);};

function clickHandler() {
  //... perform the tasks  
  alert("you clicked the button");
  //... subscribe the click handler again when the processing of current click operation is complete  
  subscribeClickEvent();
}

subscribeClickEvent();
Razan Paul
fuente
0

Intenta de esa manera:

<a href="javascript:void(0)" onclick="this.onclick = false; fireThisFunctionOnlyOnce()"> Fire function </a>
Adam Kozlowski
fuente
0

En mi caso, el evento onclick se disparó varias veces porque había hecho un controlador de eventos genérico comparativamente como

  `$('div').on("click", 'a[data-toggle="tab"]',function () {
        console.log("dynamic bootstrap tab clicked");
        var href = $(this).attr('href');
        window.location.hash = href;
   });`

cambiado a

    `$('div#mainData').on("click", 'a[data-toggle="tab"]',function () {
        console.log("dynamic bootstrap tab clicked");
        var href = $(this).attr('href');
        window.location.hash = href;
    });`

y también tienen que hacer manejadores separados para clics estáticos y dinámicos, para clics en pestañas estáticas

    `$('a[data-toggle="tab"]').on("click",function () {
        console.log("static bootstrap tab clicked");
        var href = $(this).attr('href');
        window.location.hash = href;
    });`
Awais Nasir
fuente
0

En mi caso, había cargado el mismo *.jsarchivo en la página dos veces en una <script>etiqueta, por lo que ambos archivos adjuntaron controladores de eventos al elemento. Eliminé la declaración duplicada y eso solucionó el problema.

inostia
fuente
0

Otra solución que encontré fue esta, si tienes varias clases y estás lidiando con botones de radio mientras haces clic en la etiqueta.

$('.btn').on('click', function(e) {
    e.preventDefault();

    // Hack - Stop Double click on Radio Buttons
    if (e.target.tagName != 'INPUT') {
        // Not a input, check to see if we have a radio
        $(this).find('input').attr('checked', 'checked').change();
    }
});
Andrew Vink
fuente
0

Estaba teniendo este problema con un enlace generado dinámicamente:

$(document).on('click', '#mylink', function({...do stuff...});

Encontré reemplazar documentcon 'body'solucionado el problema para mí:

$('body').on('click', '#mylink', function({...do stuff...});

nateM
fuente
0

Unbind () funciona, pero eso puede causar otros problemas en el futuro. El controlador se dispara varias veces cuando está dentro de otro controlador, por lo tanto, mantenga su controlador fuera y, si desea los valores del controlador que se anidó, asígnelos a una variable global que sea accesible para su controlador.

Praveen Poonja
fuente
0

En caso de que esto funcione bien

$( "#ok" ).bind( "click", function() {
    console.log("click"); 
});
santosh vishwakarma
fuente
-1

El código siguiente funcionó para mí en mi aplicación de chat para manejar múltiples eventos de activación del clic del mouse más de una vez. if (!e.originalEvent.detail || e.originalEvent.detail == 1) { // Your code logic }

usuario2792303
fuente