CSS for Rotating on Hover and Reversing on Click

CSS
2023/11/12
This article was translated with AI and may contain inaccuracies.

This article is about creating CSS that rotates clockwise (or counterclockwise) on hover, and reverses the rotation direction on click, like this site’s homepage.

Let’s Try Writing It

This seems straightforward, but let’s just write it first. The following example rotates an image on hover. We set the default to paused and change it to running on hover.

<img src="itika.jpg" class="image-circle">

<style>
  .image-circle {
    border-radius: 50%;
    animation: rotate-image 2s linear infinite paused;
  }
  .image-circle:hover {
    animation-play-state: running;
  }
  @keyframes rotate-image {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
</style>

↓Here’s how it looks. It works correctly.

Now let’s try reversing the rotation on click. We’ll use a checkbox to track the clicked state and manage the rotation direction.

<label>
  <input type="checkbox" class="checkbox" />
  <img
    src="itika.jpg"
    class="image-circle" />
</label>

<style>
  .image-circle {
    border-radius: 50%;
    animation: rotate-image 2s linear infinite paused;
  }
  .image-circle:hover {
    animation-play-state: running;
  }
  .checkbox:checked ~ .image-circle {
    animation-direction: reverse;
  }
  @keyframes rotate-image {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
</style>

↓Here’s how it looks. Click it and it reverses, right?

…Hmm? Yes, it does reverse, but on click it jumps to a mirror-symmetrical position. How can we fix this?

The Solution

I solved this by adding another animation to the parent element. First, we set up an animation on the parent that rotates clockwise at twice the intended speed, and an animation on the child that rotates counterclockwise at normal speed. Then, on hover, we set both animations to running, and when checked is true, we pause the clockwise animation.

This way, when both animations are running, they cancel each other out, resulting in clockwise rotation at normal speed. When checked is true, the clockwise animation stops, leaving only the counterclockwise animation, resulting in counterclockwise rotation at normal speed.

Here’s the specific code:

<label>
  <input type="checkbox" class="checkbox" />
  <div class="image-wrapper">
    <img
      src="itika.jpg"
      class="image-circle" />
  </div>
</label>

<style>
  .image-wrapper {
    display: inline-block;
    animation: rotate-image 1s linear infinite paused;
  }
  .image-wrapper:hover {
    animation-play-state: running;
  }
  .checkbox:checked ~ .image-wrapper {
    animation-play-state: paused;
  }
  .image-circle {
    border-radius: 50%;
    animation: rotate-image 2s linear infinite paused reverse;
  }
  .image-circle:hover {
    animation-play-state: running;
  }
  @keyframes rotate-image {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
</style>

↓Here’s how it looks. Now clicking reverses the rotation smoothly without any jumping.

If you know of any other solutions, I’d love to hear about them.