Toggle function fires twice on transitionend

1.3k Views Asked by At

I have a toggle whose checked status I am calling with jQuery. Every time I click the toggle from unchecked to checked, the console logs "do this!" twice. Why does this function fire twice and what is the workaround to get it to fire once?

$("#my-toggle").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function() {
  if (document.getElementById('my-checkbox').checked) {
    console.log("do this!")
  }
});
.switch {
  position: absolute;
  top: 15%;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {
  display: none;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider:before,
.slider .number {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider .number {
  background: none;
  font-size: 14px;
  left: 9px;
  top: 9px;
}

input:checked+.slider {
  background-color: #2196F3;
}

input:focus+.slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked+.slider:before,
input:checked+.slider .number {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}


/* Rounded sliders */

.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<label class="switch">
  <input type="checkbox" id="my-checkbox">
  <span class="slider round" id="my-toggle"></span>
</label>

My final HTML looks like this, for which the event fires three times:

<label class="switch">
  <input type="checkbox" id="my-checkbox">
  <span class="slider round" id="my-toggle">
    <span class="number">00</span>
  </span>
</label>
3

There are 3 best solutions below

3
Sebastian Simon On BEST ANSWER

There are two transitionend events firing: one from your #my-toggle <span>, one from your ::before pseudo element. You need to differentiate those two events. You’d need the event argument e for that.

The only difference is then found in e.originalEvent.propertyName (for the corresponding CSS property) and e.originalEvent.pseudoElement.

So let’s check for that last property in the if condition.

Also put another && e.target == this in there, since the transitionend event is also firing for children because the event bubbles up. Since you’re only listening to the event on the parent <span>, you can’t simply call e.stopPropagation which would only be useful to prevent the parent(s) from firing the event.

$("#my-toggle").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(e) {
  if (document.getElementById("my-checkbox").checked && !e.originalEvent.pseudoElement && e.target == this) {
    console.log("do this!");
  }
});
.switch {
  position: absolute;
  top: 15%;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {display:none;}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider:before,
.slider .number {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}
.slider .number {
  background: none;
  font-size: 14px;
  left: 9px;
    top: 9px;
}

input:checked + .slider {
  background-color: #2196F3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked + .slider:before,
input:checked + .slider .number {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label class="switch">
  <input type="checkbox" id = "my-checkbox">
  <span class="slider round" id = "my-toggle">
     <span class="number">00</span>
  </span>
</label>

2
ADreNaLiNe-DJ On

This comes from the elements that are concerned by the transition. If you put a console.log(event) (with putting it as a argument of your event handler), you will see that the concerned properties are background-color and trandform. These 2 properties are concerned by the transition, so your event hendler is called once per property.

Here is the snippet with "filtering" on transform property:

$("#my-toggle").bind("transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", function(event){
  if (event.originalEvent.propertyName === "transform" && document.getElementById('my-checkbox').checked){
    console.log("do this!");
  }
 });
.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {display:none;}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}

input:checked + .slider {
  background-color: #2196F3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label class="switch">
  <input type="checkbox" id="my-checkbox">
  <span class="slider round" id="my-toggle"></span>
</label>

0
Pacanaz On

Use .slider instead of .switch when looking for the click function, this worked for me and I don't know why.

$('.slider').on('click',function(){


alert("Test");
});