How to change CSS animation property without breaking it? - html

I have the following CSS animation. I'd like to change the animation-iteration-count to 1 when the element in question has the property status added. Right now the animation breaks.
Example:
setTimeout(() => {
const e = document.querySelector('div')
e.classList.add('status')
}, 5000)
div {
position: relative;
}
div::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-bottom: 2px solid #00849b;
animation-name: border-animation;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
}
div.status::before {
animation-name: border-animation;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
#keyframes border-animation {
from {
width: 0;
}
to {
width: 100%;
}
}
<div>Hiiiiiiiii!</div>

Here's an example of what you can do to make sure the animation finishes before the animation-iteration-count is updated:
Added a CSS var to the :root for the iteration count so we can target it using JS since pseudo elements are not targetable.
I've added two ways that can add the status class to the element - click or using the original setTimeout with a value that is not on an exact second (1000,2000,3000 etc.)
The code is commented with all that's happening.
// The div.
const e = document.querySelector('div');
// Variable to hold the iteration count. This updates on the animationiteration event.
let iterationCount = 0;
// Function to check the iteration.
function check_iteration_count() {
// Listen to the animationiteration on the constant e element
e.addEventListener('animationiteration', () => {
// Updates the iteration count AFTER the current iteration is finished.
iterationCount++;
// Since this function starts the increment, once the iteration finishes, it will be 1.
if (iterationCount === 1) {
// updates the CSS var to the desired iteration count of 1 to stop the animation.
document.documentElement.style.setProperty('--iteration-count', '1');
}
});
}
// On click.
e.addEventListener('click', (event) => {
// Adds the status class to the div.
event.target.classList.add('status');
// Now run the function to increment the iterator.
check_iteration_count();
});
// Original timeout function. Uses a value that's not a direct second.
setTimeout(() => {
// Adds the status class to the div.
e.classList.add('status');
// Runs the function to update the iterator.
check_iteration_count();
}, 5100);
:root {
--iteration-count: infinite;
}
div {
position: relative;
}
div::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-bottom: 2px solid #00849b;
transition: border-bottom .3s ease;
animation-name: border-animation;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: var(--iteration-count);
animation-fill-mode: forwards;
}
/* just for fun to show the status class was added */
div.status::before {
border-bottom-color: red;
}
div.status::before {
animation-name: border-animation;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: var(--iteration-count);
animation-fill-mode: forwards;
}
#keyframes border-animation {
from {
width: 0;
}
to {
width: 100%;
}
}
<div>Hiiiiiiiii!</div>

Related

How to fade html element out and the fade new element in it's place

I have a number of questions:
<div id='q1'>question...</div>
<div id='q2'>question...</div>
<div id='q3'>question...</div>
etc...
Only one question div should be visible at any time.
I want each question to fade out and the new question fade in exactly where the previous question was.
I am using this CSS for the fade transitions:
.fade-out {
opacity: 0;
animation-name: fadeOutOpacity;
animation-iteration-count: 1;
animation-timing-function: ease-out;
animation-duration: 1s;
}
.fade-in {
opacity: 1;
animation-name: fadeInOpacity;
animation-iteration-count: 1;
animation-timing-function: ease-in;
animation-duration: 2s;
transition-delay: 4s;
}
#keyframes fadeInOpacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes fadeOutOpacity {
0% {
opacity:1;
}
100% {
opacity:0;
}
}
But when the second question has the fade-in css class applied, it makes the first question jump up before it's faded out.
Ideally, I'd like the first question to fade out and the become display: none;
But I am struggling to get it to work.
Questions
How can I get the first question to 'fade-out' and the second 'fade-in' in it's place?
N.B I'd rather a pure CSS solution than a Jquery solution if possible...
Like in most pure CSS solutions you might want to utilize input elements. Here I am using radio types with labels inside to reach the next question.
As you are only changing opacity you might want to use a transition instead of an animation. I am using the shorthand here which can be expanded like this:
transition: property-name duration easing delay;
Here is a working example:
/* wrapper to hold absolute positioned children */
.wrapper {
position: relative;
}
/* hide actual radio buttons */
.wrapper input {
display: none
}
.question {
/* float above each other */
position: absolute;
/* transparency by default */
opacity: 0;
/* fade out without delay */
transition: opacity 1s ease-out;
}
input:checked + .question {
/* always put current question first */
z-index: 1;
/* fade in with delay */
opacity: 1;
transition: opacity 2s ease-in 1s;
}
<div class="wrapper">
<input id="question-1" type="radio" name="question" checked>
<div class="question">
<p>Question 1</p>
<label for="question-2">next</label>
</div>
<input id="question-2" type="radio" name="question">
<div class="question">
<p>Question 2</p>
<label for="question-3">next</label>
</div>
<input id="question-3" type="radio" name="question">
<div class="question">
<p>Question 3</p>
<label for="question-1">start over</label>
</div>
</div>
You can do it easily without needing an animation Instead you only need transition.
Run this code snippet example:
function doNext() {
let el = document.querySelector('.questions [showing]');
el.removeAttribute('showing');
let temp = el.nextElementSibling;
if (temp === null) {
temp = el.parentElement.firstElementChild;
}
temp.setAttribute('showing', '');
}
const btn = document.querySelector('#next');
btn.addEventListener('click', doNext);
.questions {
height: 60px;
position: relative;
}
.fader {
left: 0;
opacity: 0;
position: absolute;
top: 0;
transition: all 1s ease;
}
.fader[showing] {
opacity: 1;
transition: all 1s ease 0.75s;
}
<div class="questions">
<div id='q1' class="fader" showing>question...1</div>
<div id='q2' class="fader">question...2</div>
<div id='q3' class="fader">question...3</div>
<div id='q4' class="fader">question...4</div>
</div>
<button id="next">Next</button>
When the attribute showing is removed then the opacity of the question goes from 1 to 0 over 1 seconds. When the attribute showing is added then the element waits for 0.75 seconds and then changes opacity from 0 to 1 over 1 seconds.
The JavaScript I have added simple allows the changing of which element has the attribute showing. Your code would need to do something similar to change which question is showing.
I set the position of each question to absolute with top set to 0 so that all questions show in the same place. BUT doing this requires that you know that maximum size of your questions so the container can be set to the correct height.
Try this sample:
.fade-out {
opacity: 0;
animation-name: fadeOutOpacity;
animation-iteration-count: 1;
animation-timing-function: ease-out;
animation-duration: 2s;
height: 0;
width: 0;
}
.fade-in {
opacity: 1;
animation-name: fadeInOpacity;
animation-iteration-count: 1;
animation-timing-function: ease-in;
animation-duration: 4s;
animation-delay: 0s;
}
#keyframes fadeInOpacity {
0% {
opacity: 0;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes fadeOutOpacity {
0% {
opacity: 1;
height: auto;
width: auto;
}
100% {
opacity: 0;
}
}
<div id='q1' class="fade-out">question...1</div>
<div id='q2' class="fade-in">question...2</div>

run css animation when visible on scroll

I'm creating my test webpage and I ran into a problem, there are quite a few "answers" on my issue but none was I able to implement in my code. I know I have to use javascript but I was not able to get it working.
So, I need to run css animation of movement on chosen picture, when that picture is visible on screen when I scroll down to it. Basically like on this page: https://www.photoblog.com/
So I have this code in the html as for the picture:
<img class="movepic" src="pictures/test.jpg">
And then there is this simple code for the CSS movement:
.movepic {
position: relative;
animation-name: move;
animation-duration: 3s;
visibility: visible;
animation-fill-mode: forwards;
z-index:10;
}
#keyframes move {
0% { right:0px; top:150px;}
100% {right:700px; top:150px;}
}
Is there a way to make it work so I do not need to completely redo this? Or if so, could some please give me a advice how to do it maybe with code ilustration.
Thanks a lot
I use this code for this effect:
HTML:
<img class="movepic" src="pictures/test.jpg">
CSS:
.movepic {
opacity: 0;
margin: 25px 0 0;
color: white;
padding: 10px;
}
.FadeIn {
-webkit-animation: slideIn 0.8s ease 0.3s forwards;
animation: slideIn 0.8s ease 0.3s forwards;
}
#keyframes slideIn {
0% {
-webkit-transform: translateY(40px);
opacity: 0;
}
100% {
-webkit-transform: translateY(0px);
opacity: 1;
}
}
JQuery:
var $fade = $(".movepic"); //Calling the class in HTML
$(window).scroll(function () { //Using the scroll global variable
$fade.each(function () {
fadeMiddle = $(this).offset().top + (0.4 *$(this).height());
windowBottom = $(window).scrollTop() + $(window).height();
if (fadeMiddle < windowBottom) {
$(this).addClass("FadeIn");
}
});
});
/* On Load: Trigger Scroll Once*/
$(window).scroll();
Remove the animation-name from your style rule:
.movepic {
position: relative;
animation-duration: 3s;
animation-fill-mode: forwards
visibility: visible;
z-index:10;
}
and add this class to stylesheet:
.animation-class {
animation-name: move
}
Now add the jQuery:
var has_fired;
$("html").on("scroll", function () {
if (!has_fired && $(this).scrollTop() >= $("#imgContainer").offset().top) {
$("#imgContainer").addClass("animation-class");
has_fired = true; // use this if only want fired once
}
});
The animation will now run. BTW I would add an ID (imgContainer) to your container of interest and use this as selector for matching because unless .movepic is a unique class, this function will fire for any container with the .movepic class (if .movepic is the selector).

CSS Animation, ellipses starting from the end and repeating

I have a very simple animation setup, to show a loading three dots. I got a bunch of them from around and picked the simplest looking one. The problem I have with it, is that it starts from 0 like it's told to. I need it to start from the end.
CSS:
.loading {
font-size: 30px;
}
.loading:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
/* animation: ellipsis-dot steps(40, start) 9000ms infinite; */
animation: ellipsis-dot 1s infinite;
animation-fill-mode: fowards;
content: "\2026"; /* ascii code for the ellipsis character */
width: 0em;
}
#keyframes ellipsis {
to { width: 1.25em; }
}
Here's a fiddle.
I have these showing in a table with 100s of them showing together. Which all start from completely empty. I need them to start from 3 dots. Then go to 0 then do what it's doing right now.
Note: the duration 9000 is actually 900. It's slowed down to emphasize the start of the animation after I run the fiddle.
.loading {
font-size: 30px;
}
.loading:after {
content: "...";
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis-dot 1s infinite .3s;
animation-fill-mode: forwards;
width: 1.25em;
}
#keyframes ellipsis-dot {
25% {
content: "";
}
50% {
content: ".";
}
75% {
content: "..";
}
100% {
content: "...";
}
}
<div class="loading">Loading</div>
.loading {
font-size: 30px;
}
.loading:after {
content: "\2026";
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis-dot 1s infinite;
animation-fill-mode: fowards;
width: 1.25em;
}
#keyframes ellipsis-dot {
50% {
width: 0em;
}
100% {
width: 1.25em;
}
}
<div class="loading">Loading</div>
I'm seeing some common problems in your CSS, and I'll point them here to be more specific:
Your animation-fill-mode rule provides a invalid value. You need to correct it to forwards instead of "fowards".
The animation name differs from the animation name stated on your #keyframes rule. You'll need to correct that as well by changing one of those.
Suggestion: In order to maintain complete track of your animation, I suggest you to define the beginning point as well. Specifying both from and to in your #keyframes rule will save you some time, should you need to change it later.
Reference: Animation - CSS at MDN
That aside, you can apply animation-direction: reverse to your element's CSS. It will reverse the defined animation, and make it run backwards.
.loading:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis 1s infinite; /* Your previous rule */
animation: ellipsis 1s infinite reverse; /* You can reverse it like this */
animation-direction: reverse; /* Or like this */
content: "\2026";
width: 0em;
}
I've updated your JSFiddle using alternate-reverse, which feels cool.

Chrome animation makes text blurry

Everything works good on Firefox but chrome shows the animated text blurry. I did everything like -webkit-font-smoothing: subpixel-antialiased; , -webkit-transform: translate3d(0,0,0); and everything mentioned here before:
Webkit-based blurry/distorted text post-animation via translate3d
but the problem still exist.
I made very simple example to show you how it looks like. How can I fix this problem?
var text = 1;
function next() {
var next = (text == 2) ? 1 : 2;
document.getElementById('text' + text).className = 'out';
document.getElementById('text' + next).className = 'in';
text = next;
}
body {
padding: 0;
margin: 0;
font-family: tahoma;
font-size: 8pt;
color: black;
}
div {
height: 30px;
width: 100%;
position: relative;
overflow: hidden;
margin-bottom: 10px;
}
div div {
opacity: 0;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.in {
-webkit-animation: comein 1s 1;
-moz-animation: comein 1s 1;
animation: comein 1s 1;
animation-fill-mode: both;
}
#keyframes comein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.out {
-webkit-animation: goout 1s 1;
-moz-animation: goout 1s 1;
animation: goout 1s 1;
animation-fill-mode: both;
}
#keyframes goout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
<div>
<div class="in" id="text1">Hello! I'm Test Text. I'm Test Text jr Father!</div>
<div id="text2">Hi, I'm test text jr. I'm sharp and beautiful by nature but when I came in, Chrome made me blurry and I'm bad, I'm bad! ... Who's bad :)</div>
</div>
<button onclick="next();">Next</button>
You can also see the example at CodePen
Update 2020-10: this issue appears to be resolved in Chrome/Chromium 85+ in my testing. But it is not entirely fixed. You may still encounter blur in places.
Check this comment in the bug report that outlines continuing work to improve how Chrome handles this: https://bugs.chromium.org/p/chromium/issues/detail?id=521364#c103
This misrendering often appears.
You can try transform: translate3d(0, 0, 0) or transform: translateZ(0) und the element with the animation, but it doesnt works always.
-webkit-font-smoothing: antialised is another option but that never worked for me.
When the animation is being moved using percentage the text will become blurred due to the the browser guessing its exact location during the repaint phases. Using a different unit to move in such as 'px' will allow the browser to be specific during it's repaint phase and allow the text to be clean and smooth.
After reading the below I realized that this same concept may also have a factor when it comes to the blurry effect on the text.
Percentages are relative values, which means they have to depend on some other value in order to produce result. So every time you assign a percentage value it has to get it's relative value to perform a calculation. When doing a translation with pixels you only have to change the translation values, but with percentages you have to get element's dimensions first and then apply the translation. And that has to be done for every animation frame.
You can read more about this here: https://stackoverflow.com/a/50416761/4518455
In my testings this seems to fix the issue fully for all of my animations in my application. (10+)
The best solution for text blurring when adding an animation is add "z-index: 1;" on the style where animation is placed.
.in {
-webkit-animation: comein 0.5s 1;
-moz-animation: comein 0.5s 1;
animation: comein 0.5s 1;
animation-fill-mode: both;
z-index: 1;
}
you can check this link its animation time issue pls check down link
var text = 1;
function next() {
var next = (text == 2) ? 1 : 2;
document.getElementById('text' + text).className = 'out';
document.getElementById('text' + next).className = 'in';
text = next;
}
body {
padding: 0;
margin: 0;
font-family: tahoma;
font-size: 8pt;
color: black;
}
div {
height: 30px;
width: 100%;
position: relative;
overflow: hidden;
margin-bottom: 10px;
}
div div {
opacity: 0;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.in {
-webkit-animation: comein 0.5s 1;
-moz-animation: comein 0.5s 1;
animation: comein 0.5s 1;
animation-fill-mode: both;
}
#keyframes comein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.out {
-webkit-animation: goout 0.5s 1;
-moz-animation: goout 0.5s 1;
animation: goout 0.5s 1;
animation-fill-mode: both;
}
#keyframes goout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
<div>
<div class="in" id="text1">Hello! I'm Test Text. I'm Test Text jr Father!</div>
<div id="text2">Hi, I'm test text jr. I'm sharp and beautiful by nature but when I came in, Chrome made me blurry and I'm bad, I'm bad! ... Who's bad :)</div>
</div>
<button onclick="next();">Next</button>
http://codepen.io/anon/pen/kkpJaL

Remove/Hide div from DOM after animation completes using CSS?

I have an animation where a div slides out the view, however when the animation is completed, the div just returns to its origin position in the view. How do I totally remove the div or hide it after the animation ends using just CSS?
Here is the markup:
<div class="container">
<div class="slide-box" id="slide-box""></div>
</div>
and the css:
.slide-box {
position: relative;
width: 100px;
height: 100px;
background-image: url(../pics/red.png);
background-position: center;
background-size: cover;
background-repeat: no-repeat;
animation: slide 5s linear 1;
}
#keyframes slide {
0% {
left: 0;
}
20% {
left: 20%;
}
40% {
left: 40%;
}
60% {
left: 60%;
}
80% {
left: 80%;
}
100% {
left: 100%;
overflow: hidden;
visibility: hidden;
}
}
I don't want it to fade out over the duration of the animation, i just want it to disappear once it hits 100% in the keyframe. Thanks ahead of time!
Use the animation-fill-mode option. Set it to forwards and the animation ends at it's final state and stay like that.
Altered based upon comments Set opacity fade to just last 1% of animation... simplified keyframes. Added a jquery option to literally remove the div from the DOM. CSS alone won't alter the markup, where jQuery will.
Although you can't animate the display property. If you want the div totally gone, after the opacity fades to zero, you can then add the display property to remove the div. If you don't wait for opacity to end, the div will just vanish without any transition.
/*
This jquery is added to really remove
the div. But it'll essentially be
VISUALLY gone at the end of the
animation. You can not use, or
delete the jquery, and you really
won't see any difference unless
you inspect the DOM after the animation.
This function is bound to animation
and will fire when animation ends.
No need to "guess" at timeout settings.
This REMOVES the div opposed to merely
setting it's style to display: none;
*/
$('.slide-box').bind('animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd', function(e) { $(this).remove(); });
.slide-box {
display: block;
position: relative;
left: 0%;
opacity: 1;
width: 100px;
height: 100px;
background: #a00;
animation: slide 1s 1 linear forwards;
/*
animation-name: slide;
animation-duration: 1s;
animation-iteration-count: 1;
animation-timing-function: linear;
animation-fill-mode: forwards;
*/
}
#keyframes slide {
0% {
left: 0%;
opacity: 1;
}
99% {
left: 99%;
opacity: 1;
}
100% {
left: 100%;
opacity: 0;
display: none;
}
}
#-webkit-keyframes slide {
0% {
left: 0%;
opacity: 1;
}
99% {
left: 99%;
opacity: 1;
}
100% {
left: 100%;
opacity: 0;
display: none;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="slide-box" id="slide-box"></div>
</div>
animation: slide 5s linear forwards;
at 100%
opacity: 0;
display: none;
Try this.
Working fiddle: https://jsfiddle.net/jbtfdjyy/1/
UPDATE: JS mani
var slideBox = document.getElementById('slide-box');
setTimeout(function(){
slideBox.style.display = 'none';
}, 5000);
Try this. https://jsfiddle.net/jbtfdjyy/2/
Add something at 99% or so to your keyframes, and set opacity to 1 in that. If you have opacity: 1 at the start, then it will stay that way until 99%. Only at 100% will it change.
It's not technically fired at 100%. If you want that, I'd recommend using some JavaScript here, but this will at least give the illusion you want.
#keyframes slide {
0% {
left: 0;
}
20% {
left: 20%;
}
40% {
left: 40%;
}
60% {
left: 60%;
}
80% {
left: 80%;
}
99% {
opacity: 1;
}
100% {
left: 100%;
overflow: hidden;
opacity: 0;
display: none;
visibility: hidden;
}
}
UPDATE:
As per your request, here is a JavaScript version. Keep in mind, there are endless ways to accomplish such a task. I am using vanilla JS (no jQuery, etc.), and using ES6 syntax.
What we do here is set a timeout, and at the end of that timeout I broadcast an event animation_end. That event listener will handle the end of the animation (in this case, it adds a class which will handle the fading out). This is much more granular than you need it to be, you could simply do the adding of the class within the setTimeout, but I think it is slightly better this way as you can abstract you can do other things with events such as animation start, etc.
Here is the fiddle: https://jsfiddle.net/vmyzyd6p/
HTML:
<div class="container">
<div class="slide-box" id="slide-box""></div>
</div>
CSS:
.slide-box {
position: relative;
width: 100px;
height: 100px;
background-color: blue;
animation: slide 3s linear 1;
-webkit-transition: opacity 2s ease-in-out;
-moz-transition: opacity 2s ease-in-out;
transition: opacity 2s ease-in-out;
}
.animationEnd {
opacity: 0;
}
#keyframes slide {
0% {
left: 0;
}
20% {
left: 20%;
}
40% {
left: 40%;
}
60% {
left: 60%;
}
80% {
left: 80%;
}
100% {
left: 100%;
}
}
JavaScript:
// Create a function that handles the `animation_end` event
const animationEnd = () => {
// Grab the slidebox element
let slideBox = document.getElementById('slide-box');
// Get the class of the slidebox element
let slideClass = slideBox.getAttribute('class');
// Add the animation end class appended to the previous class
slideBox.setAttribute('class', slideClass + ' animationEnd');
};
// Create the animation end event
let animationEndEvent = new Event('animation_end');
// Cross browser implementation of adding the event listener
if (document.addEventListener) {
document.addEventListener('animation_end', animationEnd, false);
} else {
document.attachEvent('animation_end', animationEnd);
}
// Set the timeout with the same duration as the animation.
setTimeout(() => {
// Broadcast the animation end event
document.dispatchEvent(animationEndEvent);
}, 3000);