Should :hover pseudo state style change work after CSS animation completes - css

Should style changes specified on a pseudo state such as :hover work after a CSS animation has completed running on the element?
EDIT: Perhaps more pertinently, I should ask: why does applying 'forwards' on an animation prevent a more specific style change from overriding?
EDIT 2: Turns out that this is actually a cross browser issue. E.g. Chrome (I was running Version 38.0.2125.111) behaves incorrectly but Firefox handles it as per the specs.
Long story short: According to the specs (as quoted by chrona below) adding !important to the override should render the style. However, at present, only Firefox handles this correctly.
Here is a reduction:
#keyframes go {
0% {
background: green;
}
100% {
background: pink;
}
}
#-webkit-keyframes go {
0% {
background: green;
}
100% {
background: pink;
}
}
.box {
animation: go 3s forwards;
-webkit-animation: go 3s forwards;
}
.box:hover {
background: orange!important;
cursor: pointer;
}
<div class="box">Hover states don't work after animation</div>
I am unable to find information relating to this, nothing in the spec either: http://www.w3.org/TR/css3-animations/
Anybody know if a) this should be possible? b) how to make hover states work on an element once the animation ends?

a)
About why does it happen, I can't state for sure. But it obviously is related to the animation-fill-mode property that you're setting to be forwards. That, by definition, sets the visual style of the element to be the last keyframe of the animation:
forwards
After the animation ends (as determined by its animation-iteration-count), the animation will apply the property values for the time the animation ended.
MDN's definition is a bit more clear:
forwards
The target will retain the computed values set by the last keyframe encountered during execution. The last keyframe encountered depends on the value of animation-direction and animation-iteration-count:
But I don't know why does it not allow the :hover state to override the styles.
b)
Now, about how to make it work, you could remove the forwards property from the animation. In this case, you'd need to reverse the animation, so the original state of the element (when the animation ends, and removes the visual effect), is the color that you want it to be fixed:
#keyframes go {
0% {
background: pink;
}
100% {
background: green;
}
}
#-webkit-keyframes go {
0% {
background: pink;
}
100% {
background: green;
}
}
.box {
animation: go 2s;
-webkit-animation: go 2s;
-webkit-animation-direction: reverse;
animation-direction: reverse;
background: pink;
}
.box:hover {
background: orange;
cursor: pointer;
}
<div class="box">Hover states don't work after animation</div>

Quoted from the CSS Animations Working Draft
CSS Animations affect computed property values. During the execution of an animation, the computed value for a property is controlled by the animation. This overrides the value specified in the normal styling system. Animations override all normal rules, but are overriden by !important rules.
and a bit further down (Animation Duration):
[…] and an animation that fills forwards will retain the value specified at the 100% keyframe, even if the animation was instantaneous. Also, animation events are still fired.
As you are animating the background it cannot be overriden by default (except for !important rules). If you don't want to use !important you should go by LcSalazar's answer. (Currently only Firefox reacts as described in the specs [6th Nov 2014])
#keyframes go {
0% {
background: green;
}
100% {
background: pink;
}
}
#-webkit-keyframes go {
0% {
background: green;
}
100% {
background: pink;
}
}
.box {
animation: go 3s forwards;
-webkit-animation: go 3s forwards;
}
.box:hover {
background: orange !important;
cursor: pointer;
}
<div class="box">Hover states don't work after animation</div>

Actually I don't really understand your question, may be you need the effects like this? http://jsfiddle.net/abruzzi/5td8w6jx/
#keyframes go {
0% {
background: green;
}
100% {
background: pink;
}
}
#-webkit-keyframes go {
0% {
background: green;
}
100% {
background: pink;
}
}
.box {
animation: go 3s forwards;
-webkit-animation: go 3s forwards;
}
.box:hover {
-webkit-animation: none;
background: orange;
cursor: pointer;
}
However, with these codes, when you mouseout the text, the animation will be replay.

Related

How to use CSS variables as background color for keyframe animation in Edge browser

I'm trying to use CSS variables as the background-color values. I got it to work in Chrome but not in Edge browser. I'm not sure if I did something wrong or it is a bug with the Edge browser.
In this example, the square change background-color from blue to red. But in Edge it's remains white.
:root {
--blue-color: blue;
--red-color: red;
}
#keyframes animatedBackground {
0% { background-color: var(--blue-color); }
100% { background-color: var(--red-color); }
}
#animate-area {
width: 100px;
height:100px;
animation: animatedBackground 1s linear infinite;
}
<div id="animate-area"></div>
Here is temporary work around if anyone needs it: If I use the CSS variable as the static background color, the animation will start to work. This feels totally random but it seems to solve the problem.
:root {
--blue-color: blue;
--red-color: red;
}
#keyframes animatedBackground {
0% { background-color: var(--blue-color); }
100% { background-color: var(--red-color); }
}
#animate-area {
width: 100px;
height:100px;
background-color: var(--blue-color); /* this fixed it! */
animation: animatedBackground 1s linear infinite;
}
<div id="animate-area"></div>

Pass argument to keyframes animation

So I saw this question (Add argument into #keyframes property Less) however it doesn't completely solve my problem.
.progress-anim(#percentage: 0%) {
#-webkit-keyframes progress {
to { width: #percentage }
}
#-moz-keyframes progress {
to { width: #percentage }
}
#-ms-keyframes progress {
to { width: #percentage }
}
#keyframes progress {
to { width: #percentage }
}
}
Above is what I'm trying to do. This, I understand. What I don't understand, is how to run it from a animation property.
.progress {
-webkit-animation: ? 2s 1 forwards;
-moz-animation: ? 2s 1 forwards;
-ms-animation: ? 2s 1 forwards;
animation: ? 2s 1 forwards;
}
What do I put in place for the ?? I don't understand this.
Do I put the animation calls within .progress-anim?
Please don't use Less mixins for vendor prefixing. It is better to leave that part to the libraries like auto-prefixer, prefix-free etc.
Coming to your questions,
What do I put in place for the ?? I don't understand this.
You should substitute the name of the animation in the place of that ?. The name of the animation is the one that is provided after the #keyframe directive. Here it is nothing but progress and so the CSS should look like this:
.progress {
animation: progress 2s 1 forwards;
}
If you already knew this and are trying to understand how Less can be used to avoid writing the name of the animation multiple times then you can use variables and parametric mixins like shown below:
.progress-anim(#name; #percentage: 0%) { /* name also as an extra param */
#keyframes #name { /* set the name dynamically */
to { width: #percentage }
}
}
.progress {
#name: progress; /* animation name */
.progress-anim(#name, 50%); /* pass it to mixin */
animation: #name 2s 1 forwards; /* use variable in animation prop */
}
Below is a sample demo using this code (some properties and settings are added for completeness):
.container {
position: relative;
height: 100px;
width: 500px;
border: 1px solid;
}
.progress {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 0%;
background-color: tomato;
animation: progress 2s 1 forwards;
}
#keyframes progress {
to {
width: 50%;
}
}
<div class='container'>
<div class='progress'></div>
</div>
Do I put the animation calls within .progress-anim?
No, specify the animation property within the selector that would refer to the progress element.

How to prevent a CSS keyframe animation from running on page load?

I have a div in which I animate the content:
#container {
position: relative;
width: 100px;
height: 100px;
border-style: inset;
}
#content {
visibility: hidden;
-webkit-animation: animDown 1s ease;
position: absolute;
top: 100px;
width: 100%;
height: 100%;
background-color: lightgreen;
}
#container:hover #content {
-webkit-animation: animUp 1s ease;
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
}
#-webkit-keyframes animUp {
0% {
-webkit-transform: translateY(0);
visibility: hidden;
opacity: 0;
}
100% {
-webkit-transform: translateY(-100%);
visibility: visible;
opacity: 1;
}
}
#-webkit-keyframes animDown {
0% {
-webkit-transform: translateY(-100%);
visibility: visible;
opacity: 1;
}
100% {
-webkit-transform: translateY(0);
visibility: hidden;
opacity: 0;
}
}
<div id="container">
<div id="content"></div>
</div>
On hover, the content slides into the container div.
When I refresh the page and the page loads, the #content's animDown animation will run, and I'd prefer it to run only after a hover event.
Is there a way to do this pure CSS, or I have to figure something out in JS?
http://jsfiddle.net/d0yhve8y/
I always set preload class to body with animation time value 0 and its working pretty well. I have some back going transitions so I have to remove load animation to them too. I solved this by temporary setting animation time to 0. You can change transitions to match yours.
HTML
... <body class="preload">...
CSS is setting animation to 0s
body.preload *{
animation-duration: 0s !important;
-webkit-animation-duration: 0s !important;
transition:background-color 0s, opacity 0s, color 0s, width 0s, height 0s, padding 0s, margin 0s !important;}
JS will remove class after some delay so animations can happen in normal time :)
setTimeout(function(){
document.body.className="";
},500);
Solution 1 - Add down animation on first hover
Probably the best option is to not put the down animation on until the user has hovered over the container for the first time.
This involves listening to the mouseover event then adding a class with the animation at that point, and removing the event listener. The main (potential) downside of this is it relies on Javascript.
;(function(){
var c = document.getElementById('container');
function addAnim() {
c.classList.add('animated')
// remove the listener, no longer needed
c.removeEventListener('mouseover', addAnim);
};
// listen to mouseover for the container
c.addEventListener('mouseover', addAnim);
})();
#container {
position:relative;
width:100px;
height:100px;
border-style:inset;
}
#content {
position:absolute;
top:100px;
width:100%;
height:100%;
background-color:lightgreen;
opacity:0;
}
/* This gets added on first mouseover */
#container.animated #content {
-webkit-animation:animDown 1s ease;
}
#container:hover #content {
-webkit-animation:animUp 1s ease;
animation-fill-mode:forwards;
-webkit-animation-fill-mode:forwards;
}
#-webkit-keyframes animUp {
0% {
-webkit-transform:translateY(0);
opacity:0;
}
100% {
-webkit-transform:translateY(-100%);
opacity:1;
}
}
#-webkit-keyframes animDown {
0% {
-webkit-transform:translateY(-100%);
opacity:1;
}
100% {
-webkit-transform:translateY(0);
opacity:0;
}
}
<div id="container">
<div id="content"></div>
</div>
Solution 2 - play animation hidden
Another way around this is to initially hide the element, make sure the animation plays while it is hidden, then make it visible. The downside of this is that the timing could be slightly off and it is made visible too early, and also the hover isn't available straight away.
This requires some Javascript which waits for the length of the animation and only then makes #content visible. This means you also need to set the initial opacity to 0 so it doesn't appear on load and also remove the visibility from the keyframes - these aren't doing anything anyway:
// wait for the animation length, plus a bit, then make the element visible
window.setTimeout(function() {
document.getElementById('content').style.visibility = 'visible';
}, 1100);
#container {
position:relative;
width:100px;
height:100px;
border-style:inset;
}
#content {
visibility:hidden;
-webkit-animation:animDown 1s ease;
position:absolute;
top:100px;
width:100%;
height:100%;
background-color:lightgreen;
opacity:0;
}
#container:hover #content {
-webkit-animation:animUp 1s ease;
animation-fill-mode:forwards;
-webkit-animation-fill-mode:forwards;
}
#-webkit-keyframes animUp {
0% {
-webkit-transform:translateY(0);
opacity:0;
}
100% {
-webkit-transform:translateY(-100%);
opacity:1;
}
}
#-webkit-keyframes animDown {
0% {
-webkit-transform:translateY(-100%);
opacity:1;
}
100% {
-webkit-transform:translateY(0);
opacity:0;
}
}
<div id="container">
<div id="content"></div>
</div>
Solution 3 - Use transitions
In your scenario, you can make this CSS only by replacing the keyframes with a transition instead, so it starts with opacity:0 and just the hover has a change in opacity and the transform:
#container {
position:relative;
width:100px;
height:100px;
border-style:inset;
}
#content {
position:absolute;
top:100px;
width:100%;
height:100%;
background-color:lightgreen;
/* initial state - hidden */
opacity:0;
/* set properties to animate - applies to hover and revert */
transition:opacity 1s, transform 1s;
}
#container:hover #content {
/* Just set properties to change - no need to change visibility */
opacity:1;
-webkit-transform:translateY(-100%);
transform:translateY(-100%);
}
<div id="container">
<div id="content"></div>
</div>
Is there a way to do this pure CSS ?
Yes, absolutely : See the fork http://jsfiddle.net/5r32Lsme/2/
There is really no need for JS.
and I'd prefer it to run only after a hover event.
So you need to tell CSS what happens when it is NOT a hover event as well - in your example :
#container:not(:hover) #content {
visibility: hidden;
transition: visibility 0.01s 1s;
}
But there are two things to note:
1) The transition delay above should match your animation duration
2) You can't use the property which you use to hide the animation onLoad in the animation.
If you do need visibility in the animation, hide the animation initially like e.g.
#container:not(:hover) #content {
top: -8000px;
transition: top 0.01s 1s;
}
A sidenote:
It is recommended to put native CSS properties after prefixed ones, so it should be
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
and now there is a native transform
-webkit-transform: translateY(0);
transform: translateY(0);
If you're looking at this after 2019, a better solution is this:
let div = document.querySelector('div')
document.addEventListener('DOMContentLoaded', () => {
// Adding timeout to simulate the loading of the page
setTimeout(() => {
div.classList.remove('prevent-animation')
}, 2000)
document.querySelector('button').addEventListener('click', () => {
if(div.classList.contains('after')) {
div.classList.remove('after')
} else {
div.classList.add('after')
}
})
})
div {
background-color: purple;
height: 150px;
width: 150px;
}
.animated-class {
animation: animationName 2000ms;
}
.animated-class.prevent-animation {
animation-duration: 0ms;
}
.animated-class.after {
animation: animation2 2000ms;
background-color: orange;
}
#keyframes animationName {
0% {
background-color: red;
}
50% {
background-color: blue;
}
100% {
background-color: purple;
}
}
#keyframes animation2 {
0% {
background-color: salmon;
}
50% {
background-color: green;
}
100% {
background-color: orange;
}
}
<div class="animated-class prevent-animation"></div>
<button id="btn">Toggle between animations</button>
Having had to solve a similar challenge, a neat CSS-only trick morewry posted already back in 2013 is to create an animation that initially is in a paused play-state on a keyframe hiding the element:
#content {
animation:animDown 1s ease, hasHovered 1ms paused;
animation-fill-mode: forwards; /* for both animations! */
}
#container:hover #content {
animation:animUp 1s ease, hasHovered 1ms;
}
/* hide #content element until #container has been hovered over */
#keyframes hasHovered {
0% { visibility: hidden; } /* property has to be removed */
100% { visibility: visible; } /* from the other animations! */
}
When hovering, the very brief animated transformation is applied and stays in the 100%-keyframe-state even after mouse-leave thanks to the animation-fill-mode.
For how to set animation sub-properties with multiple animations, see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations#setting_multiple_animation_property_values
This is not pure CSS but maybe someone will stumble across this thread as I did:
In React I solved this by setting a temporary class in ComponentDidMount() like so:
componentDidMount = () => {
document.getElementById("myContainer").className =
"myContainer pageload";
};
and then in css:
.myContainer.pageload {
animation: none;
}
.myContainer.pageload * {
animation: none;
}
If you are not familiar the " *" (n.b. the space) above means that it applies to all descendents of the element as well. The space means all descendents and the asterisk is a wildcard operator that refers to all types of elements.
It's always better a solution without relying on javascript.
The ones with CSS mentioned here are ok. The idea of hiding when not on mouse hover is fine for some situations, but I noticed that if I wanted the animation to happen when the mouse moves out of the element, it wouldn't happen because of the :not(:hover) rule.
The solution I came up worked best for me, by adding a animation to the parent element, that only adds opacity at the end with the same duration. Easier shown than explain:
I grabbed the fiddle made by #sebilasse and #9000 and I added the below code there:
https://jsfiddle.net/marcosrego/vqo3sr8z/2/
#container{
animation: animShow 1s forwards;
}
#keyframes animShow {
0% {
opacity: 0;
}
99% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Rotation animation that (appears) not to run until needed.
The CSS below allows for up and down arrows for showing menu items.
The animation does not appear to run on page load, but it really does.
#keyframes rotateDown {
from { transform: rotate(180deg); }
to { transform: rotate(0deg); }
}
#keyframes rotateUp {
from { transform: rotate(180deg); }
to { transform: rotate(0deg); }
}
div.menu input[type='checkbox'] + label.menu::before {
display :inline-block;
content : "▼";
color : #b78369;
opacity : 0.5;
font-size : 1.2em;
}
div.menu input[type='checkbox']:checked + label.menu::before {
display : inline-block;
content : "▲";
color : #b78369;
opacity : 0.5;
font-size : 1.2em;
}
div.menu input[type='checkbox'] + label.menu {
display : inline-block;
animation-name : rotateDown;
animation-duration : 1ms;
}
div.menu input[type='checkbox']:checked + label.menu {
display : inline-block;
animation-name : rotateUp;
animation-duration : 1ms;
}
div.menu input[type='checkbox'] + label.menu:hover {
animation-duration : 500ms;
}
div.menu input[type='checkbox']:checked + label.menu:hover {
animation-duration : 500ms;
}
From top to bottom:
Create the rotations. For this there are two... one for the down arrow and one for the up arrow. Two arrows are needed, because, after the rotation, they return to their natural state. So, the down arrow starts up and rotates down, while the up arrow starts down and rotates up.
Create the little arrows. This is a straight forward implementation of ::before
We put the animation on the label. There is nothing special, there, except that the animation duration is 1ms.
The mouse drives the animation speed. When the mouse hovers over the element, the animation-duration is set to enough time to seem smooth.
Working on my site
Building off of Tominator's answer, in React, you can apply it per component like so:
import React, { Component } from 'react'
export default class MyThing extends Component {
constructor(props) {
super(props);
this.state = {
preloadClassName: 'preload'
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.preloadClassName !== this.state.preloadClassName;
}
componentDidUpdate() {
this.setState({ preloadClassName: null });
}
render() {
const { preloadClassName } = this.state;
return (
<div className={`animation-class ${preloadClassName}`}>
<p>Hello World!</p>
</div>
)
}
}
and the css class:
.preload * {
-webkit-animation-duration: 0s !important;
animation-duration: 0s !important;
transition: background-color 0s, opacity 0s, color 0s, width 0s, height 0s, padding 0s, margin 0s !important;
}

CSS animation delay behaves differently in Chrome than in IE/Firefox

I'm having some issues when using a delay with CSS animation.
My desired effect in the example:
The red box starts transparent waits 1 second, then fades in.
This happens in Chrome.
However, the behaviour in IE and Firefox is different:
The box starts visible, waits 1 second, then disappears and fades back in.
Which behaviour is correct? It seems to me that if you're going to delay an animation, it makes sense to wait at the first frame of the animation, not the last frame.
Is there a workaround without Javascript?
#-webkit-keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.box {
height: 200px;
width: 200px;
background: red;
-webkit-animation: fadeIn 1s 1s;
animation: fadeIn 1s 1s;
}
<div class="box"></div>
You could use animation-fill-mode to determine how to 'fill' your animation when it ends. You can revert it to before, after, initial, etc... Its not the most intuitive naming convention, but it does allow you to set your animation to start with opacity : 0; and then retain the computed value you want after the animation using animation-fill-mode: forwards;.
MDN has a good explanation for it: https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode

-webkit-animation-name css selector bug

Is this a bug? On safari and chrome -webkit-animation-name doesn't seem to override properly. The following CSS should cause a ghastly flashing effect on the h1 tag called title but the animation fails to run;
#-webkit-keyframes flash2 {
0% { background-color: red; }
100% { background-color: blue; }
}
#-ms-keyframes flash2 {
0% { background-color: red; }
100% { background-color: blue; }
}
h1 {
/* this breaks the animation on chrome,safari */
-webkit-animation-name: none;
-ms-animation-name: none;
}
h1#title {
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-name: flash2;
-ms-animation-duration: 2s;
-ms-animation-iteration-count: infinite;
-ms-animation-name: flash2;
}
http://jsfiddle.net/DQ96d/5/
Is this a bug?
Either way, I wanted to document what I've discovered since it cost me a lot of time to resolve. The main reason I couldn't see this at first was because the inspector in chrome (F12) indicated that the css selectors had properly prioritised and -webkit-animation-name was indeed showing flash2. HOWEVER - the animation fails to run.
Hopefully this may help someone else. The workaround would seem to be that you can't have that property in more than one CSS selector. Curiously, if you list it twice in one rule it doesn't break.
Instead of 'none' try giving the h1 a dummy name, like:
-webkit-animation-name: test;
Seems to work ok like this.

Resources