skip to Main Content

I have tired a couple of solution, and this is the closest i ever got.
But I’m still not happy how it turned out becuse the element snaps back to its original rotation when the mouse leaves the element.
The idea is if u go over the element it triggers the animation even if the mouse leaves the element. if u hover over the element it plays the animation and stays at 0 degree, once the mouse leaves another animation plays where it rotates back to -8 degrees.

const elements = document.getElementsByClassName('rotate_left');

for (let i = 0; i <= elements.length; i++) {
  elements[i].addEventListener('animationend', function(e) {
    elements[i].classList.remove('animated')
    /*elements[i].classList.add('animatedback')*/
  });

  elements[i].addEventListener('mouseover', function(e) {
    elements[i].classList.add('animated')
    elements[i].classList.remove('animatedback')
  });
}
.rotate_left {
  -ms-transform: rotate(-8deg);
  -webkit-transform: rotate(-8deg);
  transform: rotate(-8deg);
  transition-duration: 1s;
  transition-delay: 0.25s;
}

.polaroid {
  width: 280px;
  height: 200px;
  padding: 10px 15px 100px 15px;
  border: 1px solid #BFBFBF;
  border-radius: 2%;
  background-color: white;
  box-shadow: 10px 10px 5px #aaaaaa;
}

.polaroid:hover {
  width: 280px;
  height: 200px;
  padding: 10px 15px -100px 15px;
  border: 1px solid #BFBFBF;
  border-radius: 2%;
  background-color: white;
  box-shadow: 10px 10px 5px #aaaaaa;
  -ms-transform: rotate(0deg);
  -webkit-transform: rotate(0deg);
  transform: rotate(0deg);
  transition-duration: 1s;
  transition-delay: 0.25s;
}

@keyframes animationBegining {
  from {
    -ms-transform: rotate(-8deg);
    /* IE 9 */
    -webkit-transform: rotate(-8deg);
    /* Safari */
    transform: rotate(-8deg);
  }
  to {
    -ms-transform: rotate(0deg);
    /* IE 9 */
    -webkit-transform: rotate(0deg);
    /* Safari */
    transform: rotate(0deg);
  }
}

@keyframes animatedBack {
  form {
    -ms-transform: rotate(0deg);
    /* IE 9 */
    -webkit-transform: rotate(0deg);
    /* Safari */
    transform: rotate(0deg);
  }
  to {
    -ms-transform: rotate(-8deg);
    /* IE 9 */
    -webkit-transform: rotate(-8deg);
    /* Safari */
    transform: rotate(-8deg);
  }
}

.animatedback {
  animation: animatedBack 2s;
}

.animated {
  animation: animationBegining 2s;
}
<div id="modalWrepper1" class="polaroid rotate_left">
  <p class="caption">Just a basic explanation of the picture.</p>
</div>

Thank you very much for the help.

Just a smooth animation back to the original roatation once mouse leaves the element.

2

Answers


  1. Pure CSS solution (second animation subjects to prevention)

    You don’t need JS nor any fancy classes. transform and transition-duration will do just fine.

    Let’s start with HTML; this is still your code, but simplified:

    <div class="polaroid rotate_left">
      <p>Just a basic explanation of the picture.</p>
    </div>
    

    Now, the main part: CSS. Since your card is rotated by -8 degrees by default, we include a rule to implement that:

    .rotate_left {
      transform: rotate(-8deg);
      transition-duration: 1s;
      transition-delay: 0.25s;
    }
    

    If .polaroid is hovered over, it rotates back to 0 degree. That means we can use the pseudo-class :hover:

    .polaroid:hover {
      transform: rotate(0deg);
    }
    

    Once you move your mouse away from .polaroid, the :hover rule no longer applies. This means it will revert to the previous rule in 1 second, starting 0.25 seconds after your mouse left it.

    Try it:

    .rotate_left {
      transform: rotate(-8deg);
      transition-duration: 1s;
      transition-delay: 0.25s;
    }
    
    .polaroid:hover {
      transform: rotate(0deg);
    }
    
    /* Demo only */
    
    .polaroid {
      border: 1px solid #bfbfbf;
      border-radius: 2%;
      padding: 10px 15px;
      height: 200px;
      width: 280px;
      box-shadow: 10px 10px 5px #aaa;
    }
    <div class="polaroid rotate_left">
      <p>Just a basic explanation of the picture.</p>
    </div>

    Prevent second animation from being stopped

    To prevent the revert from being stopped, a bit of JS is needed.

    First, we declare some constants:

    const card = document.querySelector('.rotate_left');
    
    const duration = 1000; // Duration of animation, in milliseconds.
    const delay = 250; // Delay before animation, in milliseconds.
    
    const keyframes = [
      { transform: 'rotate(-8deg)' },
      { transform: 'rotate(0deg)' },
    ];
    const options = {
      duration, delay,
      fill: 'forwards'
    };
    

    We then create a handler:

    const handler = () => {
      // New mouse over & leave should not mess with current animation.
      if (card.classList.contains('rotating')) {
        return;
      }
      
      // Let ourselves know that an animation is playing.
      card.classList.add('rotating');
      
      let animation;
      
      if (card.classList.contains('not_rotated')) {
        // Rotated to 0 degree, reverting.
        animation = card.animate([...keyframes].reverse(), options);
      } else {
        animation = card.animate(keyframes, options);
      }
      
      // Make sure we clean after ourselves after animation.
      animation.finished.then(() => {
        card.classList.remove('rotating');
        card.classList.toggle('not_rotated');
      });
    };
    

    Add it as event handler for 'mouseover'/'mouseleave' and we’re done:

    card.addEventListener('mouseover', handler);
    card.addEventListener('mouseleave', handler);
    

    Try it:

    const card = document.querySelector('.rotate_left');
    
    const duration = 1000; // Duration of animation, in milliseconds.
    const delay = 250; // Delay before animation, in milliseconds.
    
    const keyframes = [
      { transform: 'rotate(-8deg)' },
      { transform: 'rotate(0deg)' },
    ];
    const options = {
      duration, delay,
      fill: 'forwards'
    };
    
    const handler = () => {
      if (card.classList.contains('rotating')) {
        return;
      }
      
      card.classList.add('rotating');
      
      let animation;
      
      if (card.classList.contains('not_rotated')) {
        animation = card.animate([...keyframes].reverse(), options);
      } else {
        animation = card.animate(keyframes, options);
      }
      
      animation.finished.then(() => {
        card.classList.remove('rotating');
        card.classList.toggle('not_rotated');
      });
    };
    
    card.addEventListener('mouseover', handler);
    card.addEventListener('mouseleave', handler);
    .rotate_left {
      transform: rotate(-8deg);
    }
    
    /* Demo only */
    
    .polaroid {
      border: 1px solid #bfbfbf;
      border-radius: 2%;
      padding: 10px 15px;
      height: 200px;
      width: 280px;
      box-shadow: 10px 10px 5px #aaa;
    }
    <div class="polaroid rotate_left">
      <p>Just a basic explanation of the picture.</p>
    </div>
    Login or Signup to reply.
  2. TLDR I think this is what you want:

    const rotateForwardAnimationName = 'rotate-forward';
    const rotateBackwardAnimationName = 'rotate-backward';
    
    const states = {
      forward: 'forward',
      backward: 'backward',
      rotatingForward: 'rotatingForward',
      rotatingBackward: 'rotatingBackward',
    };
    
    const elements = document.getElementsByClassName('polaroid');
    
    const stateMap = new Map();
    
    for (const element of elements) {
      stateMap.set(element, states.backward);
    
      element.addEventListener('animationstart', (e) => {
        if (e.animationName === rotateForwardAnimationName)
          stateMap.set(element, states.rotatingForward);
        if (e.animationName === rotateBackwardAnimationName)
          stateMap.set(element, states.rotatingBackward);
        onStateUpdate(element);
      });
    
      element.addEventListener('animationend', (e) => {
        if (e.animationName === rotateForwardAnimationName)
          stateMap.set(element, states.forward);
        if (e.animationName === rotateBackwardAnimationName)
          stateMap.set(element, states.backward);
        onStateUpdate(element);
      });
    
      element.addEventListener('mouseenter', () =>
        onStateUpdate(element)
      );
    
      element.addEventListener('mouseleave', () =>
        onStateUpdate(element)
      );
    }
    
    function onStateUpdate(element) {
      const hovered = element.matches(':hover');
      const state = stateMap.get(element);
      if (state === states.forward && !hovered) rotateBackward(element);
      if (state === states.backward && hovered) rotateForward(element);
    }
    
    function rotateBackward(element) {
      element.style.animation = rotateBackwardAnimationName + ' 2s forwards';
    }
    
    function rotateForward(element) {
      element.style.animation = rotateForwardAnimationName + ' 2s forwards';
    }
    .polaroid {
      width: 280px;
      height: 200px;
      padding: 10px 15px 100px 15px;
      border: 1px solid #bfbfbf;
      border-radius: 2%;
      background-color: white;
      box-shadow: 10px 10px 5px #aaaaaa;
      transform: rotate(-8deg);
    }
    
    @keyframes rotate-forward {
      from {
        transform: rotate(-8deg);
      }
      to {
        transform: rotate(0deg);
      }
    }
    
    @keyframes rotate-backward {
      from {
        transform: rotate(0deg);
      }
      to {
        transform: rotate(-8deg);
      }
    }
    <div class="polaroid">
      <p class="caption">Just a basic explanation of the picture.</p>
    </div>
    
    <div class="polaroid">
      <p class="caption">Just a basic explanation of the picture.</p>
    </div>

    Stackblitz Typescript Example: https://stackblitz.com/edit/typescript-bkyk66?file=index.ts


    You’re looking for more fine-grained animation control than CSS provides. That means you’ll have to keep track of your own animation state.

    In this case there are two types of state: animation state and hover state.

    Animation state has four mutually exclusive values: backward, forward, rotating backward, and rotating forward.

    Hover state is just true or false.

    I find it helps to make a truth table to discern what happens in all these combinations of state. I believe these are the actions you want, but I’m not sure:

    Animation State Hover State Action
    Forward True No Action
    Forward False Begin Rotating Backward
    Backward True Begin Rotating Forward
    Backward False No Action
    Rotating Forward True No Action
    Rotating Forward False No Action
    Rotating Backward True No Action
    Rotating Backward False No Action

    In other words, you want the animations to fully complete before starting another one. If that’s not correct, just let me know what you would like to change in this table, or even copy paste the table into your question with updated rows.

    In the example, I keep track of animation state with a simple map and string constants, mapping elements to their animation state. I use element.match(":hover") to check hover state.

    You need to update the animation state at animationstart and animationend events. You can use the animation name to determine the current state. For example if the rotate-forward animation just ended, the state is forward.

    Then you just need to perform checks on each state update to see if an action is required. State updates occur on the animationstart, animationend, mouseenter, and mouseleave events in this case.

    I chose to set the animation directly rather than using a class, so the old animation would be overwritten. You may prefer to use classes instead, you’ll just have to remember to remove the other class when starting a new animation.

    The forwards value for animation-fill-mode is also important, so the animation retains the rotation when it is finished.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search