0

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>
Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
iskandarblue
  • 7,208
  • 15
  • 60
  • 130

3 Answers3

2

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>
Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
  • Thanks - I apologize but I forgot to include an important part of the question. I am also wrapping my slider in another span (to define the number inside the slider) - see edited question above. However, the above code doesn't work and the function still fires twice after applying your solution. Why could that be? – iskandarblue Feb 01 '18 at 08:14
  • @the_darkside It doesn’t work because your code turns out to be different. – Sebastian Simon Feb 01 '18 at 08:18
  • what would need to be included apart from `&& !e.originalEvent.pseudoElement` in this case? The `before:slider` pseudo element does not change in the edited question – iskandarblue Feb 01 '18 at 08:20
1

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>
ADreNaLiNe-DJ
  • 4,787
  • 3
  • 26
  • 35
0

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