7

I have a css transition that moves an element on hover and an animation that rotates the element on hover too. There's a delay on the animation equal to the transition duration so that after it's transitioned to it's correct position, the animation starts. And it works nice, however, when we mouse off, the animation stops but it doesn't transition back down.

Is it possible to get it to transition back after we mouse off and the animation ends?

You can see an example here: http://codepen.io/jhealey5/pen/zvXBxM

Simplified code here:

    div {
        width: 200px;
        height: 200px;
        margin: 40px auto;
        background-color: #b00;
        position: relative;

        &:hover {
            span {
                transform: translateY(-60px);
                animation-name: rotate;
                animation-duration: 1s;
                animation-delay: .5s;
                animation-iteration-count: infinite;
                animation-direction: alternate;
            }
        }
    }

    span {
        position: absolute;
        width: 20px;
        height: 20px;
        background-color: #fff;
        bottom: 10px;
        left: 0;
        right: 0;
        margin: auto;
        transition: .5s;
    }

    @keyframes rotate {
        from {
            transform: translateY(-60px) rotate(0);
        }
        to {
            transform: translateY(-60px) rotate(-90deg);
        }
    }
evu
  • 1,031
  • 2
  • 10
  • 29
  • 1
    I am using a slightly older version of Chrome and it doesn't transition on hover in also (the animation works fine). – Harry Nov 20 '15 at 14:54
  • And, I think the reason it doesn't work on Chrome is because both animation and transition are applied to the same property (like discussed [here](http://stackoverflow.com/questions/33636375/css-keyframe-animation-breaks-transition-when-both-are-applied-on-same-property/33652438#33652438)). I think it'd be better for you to do both using the animation itself. – Harry Nov 20 '15 at 15:00
  • Or, another option would be to use `bottom` for the `transition` and the `animation` only for `rotate` like in [this snippet](http://codepen.io/hari_shanx/pen/ojOLeX). I am not sure if this approach suits your needs. Let me know if it does and I'll add as answer. – Harry Nov 20 '15 at 15:12
  • 1
    Cheers, I did try to animate the top property separately from the translate, however it didn't work until I set a default top of 0 it seems. It's not the ideal thing to animate top, but I don't have access to the dom to wrap it either so. Working now at least :) – evu Nov 20 '15 at 15:22
  • That's another known problem. Browsers don't transition property values unless we have a initial value like mentioned in [my answer here](http://stackoverflow.com/questions/18440393/css-transition-does-not-work-on-top-property-in-ff/18441215#18441215). The problem is not specific to Firefox alone as the title there indicates. – Harry Nov 20 '15 at 15:23

2 Answers2

6

I have forked your project and adapted it so it works. You can find it here.

What I have changed is the following:

I give the white square a start position of top: 150px and let it, on hover of div, get a top: 0. The span gets a transition: top .5s and with that it goes to top: 0; on hover and back to top: 150px; when the mouse leaves.

I have removed the translateY(-60px); from the animation, because that would move it even more up when the animation would start.

Here's your new CSS:

div {
    width: 200px;
    height: 200px;
    margin: 40px auto;
    background-color: #b00;
    position: relative;

    &:hover {
        span {
            top: 0px;
            animation: rotate 1s infinite .5s alternate;
            animation-direction: alternate;
        }
    }
}

span {
    position: absolute;
    width: 20px;
    height: 20px;
    background-color: #fff;
    bottom: 10px;
    left: 0;
    right: 0;
    top: 150px;
    margin: auto;
    transition: top .5s;
}

@keyframes rotate {
    from {
        transform: rotate(0);
    }
    to {
        transform: rotate(-90deg);
    }
}

Edit: The problem is that an animation is time-based and not action-based, which means that as soon as you trigger an animation, a timer starts running and it will run through all the keyframes until the set time has passed. Hover-in and hover-out have no effect, except that the timer can be stopped prematurely, but the animation will not continue (or reversed, which you wanted) after that. transition is action-based, which means it gets triggered every time an action (for example :hover) is happening. On :hover, this means it takes .5s to go to top:0 and when the hover ends, it takes .5s to got to top:150px.

I hope the above addition makes sense :)

As you can see, I also cleaned up a bit in your animation-name: etc., since it can be combined into one line.

Community
  • 1
  • 1
Rvervuurt
  • 8,589
  • 8
  • 39
  • 62
  • Yep, that in my opinion (transitioning the positioning attributes) is the best bet because `animation` and `transition` on same property cannot co-exist. – Harry Nov 20 '15 at 15:19
  • 1
    Thanks, as it's the most detailed I marked it as the answer. As a side note I use long hand durations for both git and because I can never remember the order :) – evu Nov 20 '15 at 15:23
3

As Harry pointed out, the problem is that you are animating/transitioning the same property, in this case transform. It looks like the current versions of Chrome/FF will allow the animation to take control of the property, thereby breaking the transition. It seems like the only way to work around this is to transition/animation a different property. Since you need to continue rotating the element, you could translate/position the element by changing the bottom property instead. I know that doesn't produce the exact same results, but nonetheless, it does move the element (just not relative to the parent element).

Updated Example

div:hover  span {
  bottom: 80px;
}

As an alternative, you could also wrap the span element, and then translate that element instead.

In the example below, the .wrapper element is transitioned to translateY(-60px) on hover, and then the child span element is rotated and maintains the animation.

Example Here

div {
  width: 200px;
  height: 200px;
  margin: 40px auto;
  background-color: #b00;
  position: relative;
}
div:hover .wrapper {
  transform: translateY(-60px);
}
div:hover .wrapper span {
  animation-name: rotate;
  animation-duration: 1s;
  animation-delay: .5s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
.wrapper {
  display: inline-block;
  transition: .5s;
  position: absolute;
  bottom: 10px;
  left: 0;
  right: 0;
  text-align: center;
}
.wrapper span {
  display: inline-block;
  width: 20px;
  height: 20px;
  background-color: #fff;
}
@keyframes rotate {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(-90deg);
  }
}
<div>
  <span class="wrapper">
   <span></span>
  </span>
</div>
Community
  • 1
  • 1
Josh Crozier
  • 233,099
  • 56
  • 391
  • 304
  • 1
    Nice one Josh. Yet again we seem to have the same ideas :) I had just linked something similar in comments. – Harry Nov 20 '15 at 15:17
  • @evu I just updated the answer. You can wrap the `span` element and then apply the transformation transition to the parent element while maintaining the rotation animation on the child. – Josh Crozier Nov 20 '15 at 15:27
  • Your update seems overly complex? Or is that just me? – Rvervuurt Nov 20 '15 at 15:27
  • Adding a completely new div, when just transitioning top (or bottom, in your case) is enough? I get that it's necessary if using `transform:` is important, but it doesn't seem to be a problem to change the `translateY(-60px)` to `top: 0;`. – Rvervuurt Nov 20 '15 at 15:29
  • 1
    @Rvervuurt Agreed. But nonetheless, I wanted to demonstrate that it is still possible to transition/animate the `transform` property while producing the same output. For instance, if we wanted to make a scaling transition using `transform: scaleX(2)` and animate the `transform` property with something else, then we wouldn't have an alternative, and this method would resolve that. – Josh Crozier Nov 20 '15 at 15:35