Using CSS Animations and animation-play-state
You can use a CSS animation to easily, continuously, and performantly rotate your image. In order to keep the animation from playing right away, you can set the animation-play-state CSS property to paused. When you want to make the animation play — to make the fan rotate — you can either unset animation-play-state or set it to running.
How do I make this accessible
You can set the alt attribute on the image according to what it looks like to the user (i.e. spinning or not). To let the user know if this change, you should set aria-live="polite" so that the assistive technology will read aloud the alternative text.
In order to make the on/off buttons more accessible, you have a few options. One option would be to set display: none on the button that was last pressed. Another option would be to set disabled to true on the last pressed button. Lastly, you could use one button that toggles it on and off, set aria-live="polite" on it, and change the text from "Turn on fan" to "Turn off fan" and vice versa.
let fanStatus = 'off'
on.addEventListener('click', () => {
if (fanStatus === 'off') {
rotateMe.classList.remove('paused')
rotateMe.alt = 'spinning bladed fan'
on.disabled = true
off.disabled = false
}
})
off.addEventListener('click', () => {
if (fanStatus === 'off') {
rotateMe.classList.add('paused')
rotateMe.alt = 'bladed fan'
on.disabled = false
off.disabled = true
}
})
img {
width: 90px;
height: 90px;
}
.rotate {
animation: rotate 0.75s infinite linear;
}
.paused {
animation-play-state: paused;
}
@keyframes rotate {
100% {
transform: rotate(-360deg);
}
}
<img
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcScQe7tlEzE58so4n8IIn3LjKI2JCq_HIckHg&usqp=CAU"
alt="bladed fan"
id="rotateMe"
class="rotate paused"
aria-live="polite"
/>
<button id="on" aria-live="polite">Turn on fan</button>
<button id="off" aria-live="polite" disabled>Turn off fan</button>