0

With this toggle switch:

* { box-sizing: border-box; }
.switch { appearance: none; padding: 9px 18px; border-radius: 13px; background: radial-gradient(circle 9px, #007722 100%, transparent 100%) transparent -9px; border: 1px solid #007722; transition: all 0.3s; }
.switch:checked { background-position: 9px; }
<input type="checkbox" class="switch"></div>

I'd like the CSS smooth transition animation to happen if and only if the state change is done by the user.

On the other hand, if the state change is triggered by JS code (item.checked = false), then no transition happens.

Is there a more elegant solution than removing the transition CSS with JS, changing the state with JS, and set again the transition CSS with JS?

Maybe is there a CSS attribute for transition triggered by user only?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • Just use a class instead of the state – Sanmeet Jul 03 '23 at 17:31
  • I don't think such a thing exists, but I'm curious to know what others have to say. – Marco Jul 03 '23 at 17:34
  • Just tested and i think there's no need for anything . With js the transition occurs just fine ! So what's the issue ? – Sanmeet Jul 03 '23 at 17:36
  • 2
    The issue is that OP wants to not have the transition happen when changing the state via JS. – Marco Jul 03 '23 at 17:36
  • 1
    See: [Can I have an onclick effect in CSS?](https://stackoverflow.com/a/32721572/22152755). Personally, as a user speaking, I think no, you can't do such a thing. Do it via JavaScript, that's what JavaScript is made for, ***functionality***. – Sally loves Lightning Jul 03 '23 at 17:48
  • Just set up another class that doesn't include the transition. When the JS code needs to make the change (as opposed to a human), first remove the class that includes the transition and add the class that doesn't have it. Pretty simple. Have the classes set up in advance so all you're really using is `classList.remove()` and `classList.add()`. – Scott Marcus Jul 03 '23 at 17:56
  • You just did something wrong. Transition HAPPENS on programmatic change to true / false. – Vladislav Ladicky Jul 03 '23 at 18:43

3 Answers3

1

No, there is no simpler way to do this.

But, keep in mind that this is exactly why classList.add() and classList.remove() were added to the DOM API. Not only is there no simpler way than this already simple solution, but this is really the preferred way to handle any kind of dynamic CSS class changes that may need to occur in a page.

Just remove the transition from the baseline class and set it up in its own class and apply both classes by default. When the JS code needs to make the change (as opposed to a human), first remove the class that includes the transition, make the element change, and then add the class back (for possible human interaction later).

There is one caveat here, which is that the browser can sometimes act upon the UI faster or slower than JS can process a UI instruction so adding a slight imperceptible delay with .setTimeout() helps the UI get updated correctly.

You can see that without a short delay built-in, the effect doesn't work:

document.querySelector("button").addEventListener("click", function(event){
  const element = document.querySelector("input.transition");

  // JS code is going to affect the element: remove the transition class
  element.classList.remove("transition");

  element.checked = !element.checked; // Toggle the UI

  // Add the class back for futue possible human interaction
  element.classList.add("transition");  

});
* { box-sizing: border-box; }

/* This class applies all the time but does not include transition */
.switch { appearance: none; padding: 9px 18px; border-radius: 13px; background: radial-gradient(circle 9px, #007722 100%, transparent 100%) transparent -9px; border: 1px solid #007722;  }

/* This class is used by default but is removed when no transition is wanted. */
.transition { transition: all .3s; }

.switch:checked { background-position: 9px; }
<div>First, change the toggle switch manually. Then, use the button to do it.</div>
<input type="checkbox" class="switch transition">

<button type="button">Click to have JS change the value</button>

But, with the delay it does:

See comments inline.

document.querySelector("button").addEventListener("click", function(event){
  const element = document.querySelector("input.transition");

  // JS code is going to affect the element: remove the transition class
  element.classList.remove("transition");

  element.checked = !element.checked; // Toggle the UI

  // Adding a small delay ensures that the UI has enough time to update the UI
  setTimeout(function(){
    element.classList.add("transition");  // Now, add back the transition
  }, .1);
});
* { box-sizing: border-box; }

/* This class applies all the time but does not include transition */
.switch { appearance: none; padding: 9px 18px; border-radius: 13px; background: radial-gradient(circle 9px, #007722 100%, transparent 100%) transparent -9px; border: 1px solid #007722;  }

/* This class is used by default but is removed when no transition is wanted. */
.transition { transition: all .3s; }

.switch:checked { background-position: 9px; }
<div>First, change the toggle switch manually. Then, use the button to do it.</div>
<input type="checkbox" class="switch transition">

<button type="button">Click to have JS change the value</button>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • 1
    But this doesn't answers OP question! OP stated clearly that is there any other way then applying and then resetting the styles. OP wants a CSS transition to apply when the user interacts with the UI element, but no transition when the element is changed via code. And a simple solution too ? – Sanmeet Jul 03 '23 at 18:17
  • 1
    This is basically the code equivalent of what OP has already stated as a solution in his question. – Marco Jul 03 '23 at 18:18
  • I retract my downvote because you added _"No, there is no simpler way to do this."_ to the answer, now it's acceptable. – Marco Jul 03 '23 at 18:23
  • You can change `setTimeout(.., .1)` to `setTimeout(.., 0)` and it still works (executes the function after executing the event handler) - I don't think a 100μs delay is possible with `setTimeout` _anyway_. – Marco Jul 03 '23 at 18:27
  • @Marco You can, but in actuality, anything below a threshold of about 16 ms won't make any difference because there is a minimum amount of time that you'll never be able to act faster than. If you think about it, using 0 could never work as 0 ms because the currently running function must first complete and then the event queue must be examined and then whatever function is next on the queue must be invoked. My 16 ms number may not be 100% accurate, but I thought I had read that number somewhere. – Scott Marcus Jul 03 '23 at 18:28
  • @ScottMarcus I don't know why you would use `.1` over `0`. `0` is well defined: execute the function on the next event cycle, `.1` not so much, only if it gets rounded down to `0` (which is my suspicion). – Marco Jul 03 '23 at 18:30
  • @Marco You're splitting hairs over an inconsequential aspect of the code. You prefer 0, great! Your number or my number don't change anything about this answer. – Scott Marcus Jul 03 '23 at 18:31
  • @ScottMarcus It does, because reading your answer implies that `.1` is some kind of "special" value when its in fact not. I have never read any code that uses `.1` instead of `0`. `0` is acceptable and works. You may disagree, but I think it's less clean. – Marco Jul 03 '23 at 18:33
  • @Marco *but I think it's less clean* <-- I thought that's what I said with *You prefer 0, great!*. I don't because it's just as disingenuous as .1, since you can never have a delay of 0 and at least .1 indicates that there will be a delay. Again, you're splitting hairs over an aspect of the code that isn't actually relevant to the question or answer. – Scott Marcus Jul 03 '23 at 18:36
  • The difference is that `setTimeout(fn, 0)` is defined as "execute fn after this function has finished" (and **NOT** execute fn after 0 seconds) while `setTimeout(fn, 0.1)` is defined as "execute fn after 100μs or later". It gives the impression that there's something to the value of 100μs and that's what makes it unclean ; you don't need ANY delay to make it work, your code (and comment) suggests otherwise. I'm just pointing out that this is not an "ideal" idiomatic way of calling `setTimeout` - that's all. – Marco Jul 03 '23 at 18:42
  • I "kind-of" found a simpler way https://stackoverflow.com/a/76607386/6127393 – Arleigh Hix Jul 03 '23 at 18:46
  • @Marco **No, it does not!** Any number provided to `setTimeout()` or `setInterval()` indicates how much time (at a minimum) you want to wait ***after*** the currently executing function finishes, you should wait before invoking the specified function. Your last reply indicates that there is a different interpretation of what these methods do based on the number passed, which is wrong. And, it is needed to make the code function. All you have to do is try it without that code and see for yourself. – Scott Marcus Jul 03 '23 at 18:48
  • @Marco I've just added the code without the delay to the answer to show you that, indeed, the delay is needed. Anything else you want to argue about? *I'm just pointing out that this is not an "ideal" idiomatic way of calling setTimeout - that's all.* And I'm just pointing out that that is your opinion and is not one that is shared in general. – Scott Marcus Jul 03 '23 at 18:51
  • What I referred to when I said "you don't need ANY delay" is `0` which effectively means call this function as soon as possible, but not immediately (as `fn()` would do). I wasn't saying `setTimeout` isn't needed - it is, but it's noise to pass `.1` when you could just pass `0`. Maybe that's my opinion, but I'm hell of a lot sure I'm not the only one ;-) – Marco Jul 10 '23 at 18:39
  • *The difference is that setTimeout(fn, 0) is defined as "execute fn after this function has finished" (and NOT execute fn after 0 seconds) while setTimeout(fn, 0.1) is defined as "execute fn after 100μs or later".* <-- No, the definition of BOTH is "execute this function with at least a Nn millisecond delay once the event queue is clear." 0 is very often misinterpreted as "immediately" and that is why I didn't use it (because that actually can't happen), but at least .1 indicates that you will wait (which will happen). You may not be alone in your opinion, but it is certainly not mainsteam. – Scott Marcus Jul 10 '23 at 18:47
1

Note that I agree with Scott's answer (adding/removing a class is generally the way to go) but there is another, albeit limited, way.

This at least works when the user is using a mouse.
Only apply the transition to: .switch:hover

Keyboard users get supported by adding .switch:focus
Touchscreen users should be covered with .switch:active

I've updated my example to use all of the user action pseudo-class selectors found here. So it essentially only applies the transition when the element is in a state triggered by a user action. Note: using the keyboard to click the "flip" button while the mouse cursor is hovering the .switch still results in a transition.

const toggle = document.querySelector('input.switch')

document.querySelector('button').addEventListener('click', event => toggle.checked = !(toggle.checked))
* {
  box-sizing: border-box;
}

.switch {
  appearance: none;
  padding: 9px 18px;
  border-radius: 13px;
  background: radial-gradient(circle 9px, #007722 100%, transparent 100%) transparent -9px;
  border: 1px solid #007722;
}

.switch:hover,
.switch:active,
.switch:focus,
.switch:focus-visible,
.switch:focus-within {
  transition: all 0.3s;
}

.switch:checked {
  background-position: 9px;
}
<input type="checkbox" class="switch"></div>

<br/>
<button>flip</button>
Arleigh Hix
  • 9,990
  • 1
  • 14
  • 31
  • Would this work when the toggle is invoked via the keyboard or via speech which is what users of assistive technologies would use? It's a good solution for mouse users, but it won't work with the keyboard. – Scott Marcus Jul 03 '23 at 18:59
  • 1
    @ScottMarcus I clearly stated "This at least works when the user is using a mouse" and endorsed your answer over mine. I'm not trying to get in to that argument you got going on just presenting a different idea. – Arleigh Hix Jul 03 '23 at 19:04
  • Neither am I. I didn't down vote you and did say that it's a good solution. – Scott Marcus Jul 03 '23 at 19:16
  • @ScottMarcus, ok sorry I was still catching up on the thread when you commented and assumed I stepped in something I didn't want to be in. I've expanded my answer to support more user interactions, what do you think?. – Arleigh Hix Jul 03 '23 at 19:56
  • I tried testing on my iPhone in chrome but the toggle doesn't render. – Arleigh Hix Jul 03 '23 at 19:59
  • Great solution @ArleighHix! It seems to work even on touch screen, is that right? (I tried on Samsung phone with Chrome, and it works: JS via button => no transition, tap on the toggle => transition). Do you confirm? – Basj Jul 04 '23 at 06:53
  • 2/2 Your solution seems nearly perfect @ArleighHix, is there a case in which it doesn't work? It seems to be 100% working, even on touch screen. – Basj Jul 04 '23 at 06:57
-2

If you're talking about to do things triggered by user's hand, then you're refering to events. So, if you want to make some changes in element's CSS styles, you must attach those changes to some events like click and the like.

const element = document.querySelector("#Element's ID");

element.addEventListener("click", () => {
    //Do things
}, false);

On the other hand, you can manipulate transitions through style.transition property or just changing the class name linked to the element.

Alexis88
  • 297
  • 1
  • 11
  • No, this is not quite what the OP is asking about. The OP wants a CSS transition to apply when the user interacts with the UI element, but no transition when the element is changed via code. – Scott Marcus Jul 03 '23 at 18:11
  • @ScottMarcus And that's exactly what I answered: To make a class name change or affecting the transition property *when an user's event triggers*. – Alexis88 Jul 03 '23 at 18:17
  • That's not at all what you answered. Your answer discusses setting up an event handler that has code of "Do things" to allow a user to act upon a click. – Scott Marcus Jul 03 '23 at 18:23
  • @ScottMarcus And then I explained just below that part that he must manipulate class name or transition property. I think you expected an **exact** code line to be able to do the task, but that's not the only way to give some help. – Alexis88 Jul 03 '23 at 18:29
  • I'm not going to go round and round with you on this. I've been on SO for over a decade. Your answer talks primarily about needing events and casually mentions some possible things (without any context or example) you can do to address the actual question/problem being asked. That is NOT how a good answer works on SO. Your last sentence is what the entire answer should be addressing, not the event part. – Scott Marcus Jul 03 '23 at 18:33
  • @ScottMarcus Casually? Are you serious, Scott? Giving an exact code can be of temporary help, but explaining what is happening and what is needed is a form of help that transcends the need of the moment. By the way, that argument about how long you've been on SO is irrelevant. This is not a competition. – Alexis88 Jul 03 '23 at 18:38
  • *explaining what is happening and what is needed* <-- Where exactly did you do this? You can argue, or you can learn. You explained about events and took the time to show code to set one up. **This was not what was asked about.** Then you, yes, casually mentioned two options with no explanation or example. And, if you took the time to investigate my profile you'd see that my reputation and length of time here does quite clearly show that I understand how the site works and that others understand that too. That's called experience, which I don't think you have very much of here. – Scott Marcus Jul 03 '23 at 18:41
  • Please take the time to read [this](https://stackoverflow.com/help/how-to-answer). Here's one of the first things it says: ***What is the question asking for? Make sure your answer provides that – or at least a viable alternative.*** Does your answer do that? No. – Scott Marcus Jul 03 '23 at 18:44
  • @ScottMarcus Telling him that what he means when he says *"if and only if the state change is done by the user"* and *"triggered by user only"* is talking about user events, is not about what is happening and what is needed? Telling him that he only has to change the name of the class or manipulate the transition property is not the logical complement, in answer to the question, of what was previously said? – Alexis88 Jul 03 '23 at 18:51
  • @ScottMarcus I'm not interested in reviewing your profile, Scott. It's not the experience or the time a person has been in a forum that gives validity to his answers, but the arguments themselves that he offers. And I only see here that you are generating an unnecessary discussion in a useless spirit of what it seem to be a desire to compete, which is really absurd and meaningless. – Alexis88 Jul 03 '23 at 18:51
  • @ScottMarcus It doesn't actually seem like the person asking the question already knew about events. Otherwise, there would not be a question in the form of *how to do this in JS* appointing again and again that what he/she needs is a way to do the task *on user's interaction*? It's from that inference that I proceeded to explain that what he/she needs is to work with user events and modify the class name or the transition property. And that's it. – Alexis88 Jul 03 '23 at 19:10
  • @ScottMarcus If you consider that giving an exact line of code that solves that person's problem, that's fine. This time I wanted to help from another perspective and that's fine too. There's no need for further discussion about it. And just FYI, I'm not in the need to *look on someone's experience and reputation*, but thanks for the advice. – Alexis88 Jul 03 '23 at 19:12
  • Yeah, you're right. When discussing things, it's not useful to know anything about who you are discussing it with. Good luck to you. – Scott Marcus Jul 03 '23 at 19:18