apieceofbart apieceofbart - 16 days ago 5
CSS Question

Reversible transition with delay

What I'm trying to do is to have on hover transition or animation (can be triggered via javascript with

onmouseover
or
onmouseenter
) that will also be reversible (so the opposite animation should happen on mouse leave) but


  • the opposite animation should have a delay

  • it should be able to reverse in the middle of animation without delay



It's hard to describe without showing so please check this codepen that is pretty close to what I'm trying to achieve: http://codepen.io/anon/pen/xROOqO

There are two problems here:


  • I need to check for elapsed time in
    transitionend
    handler, so I would need to update both css and js to update transition time

  • It still has delay when you quickly hover in and out on the reverse animation - looks like it's stuck in the middle



Is this even possible using css transitions (perhaps keyframes animation) or should I stick to setting timers in javascript and leave out the delay from css?

Answer

Not sure if what I'm going to present is simpler, but it seems to address some of your issues, and matches my taste.

The main idea is to admit that the problem is complicated due to multiple states, and address it using a state machine. This allows for a declarative approach like this one:

const TRANSITIONS = {
  'small-inside' : {
    'transitionend' : 'big-inside',
    'mouseover' : 'small-inside',
    'mouseout' : 'small-outside',
  },
  'small-outside' : {
    'transitionend' : 'small-outside',
    'mouseover' : 'small-inside',
    'mouseout' : 'small-outside',
  },
  'big-inside' : {
    'transitionend' : 'big-inside',
    'mouseover' : 'big-inside',
    'mouseout' : 'big-outside',
  },
  'big-outside' : {
    'transitionend' : 'small-outside',
    'mouseover' : 'big-inside',
    'mouseout' : 'big-outside',
  },
}

And quite simple handling of events:

function step(e){
  box.className = TRANSITIONS[box.className][e.type];
}
box.addEventListener('transitionend', step);
box.addEventListener('mouseover', step);
box.addEventListener('mouseout', step);

Another insight is that you can specify the delay using CSS transition-delay:3s property:

div.small-inside,
div.big-inside {
  width: 300px;
}
div.small-outside,
div.big-outside {
  width: 150px;
}
div.big-outside {
  transition-delay:3s;
}

The proof of concept is here: http://codepen.io/anon/pen/pNNMWM.

What I do not like about my solution is that it assumes that the initial state is small-outside while actually the mouse pointer could be well located within the div when the page loads. You've mentioned ability to trigger state transitions manually from JS. I believe this is possible as long as you keep track of two separate boolean variables: "is mouse inside?" and "does js asked to grow?". You can not mix them into a one state and expect correct "counting". As you see I already have 2*2=4 states because I'm trying to keep track of {small,big}x{inside,outside} - one could imagine extending it to {small,big}x{inside,outside}x{js-open,js-close} in similar manner, with some extra "events" like 'open' and 'close'.

Comments