Animation issue on Firefox Quantum - javascript

I noticed an issue with animations on the new Firefox Quantum.
When you first load a page with some animated elements display: none;, when a script switches it to .display = "block"; you will miss the entire animation, or some parts of it at the beginning if it is longer than a few seconds.
View it in the snippet below:
var anims = document.getElementsByClassName("anim"),
time = document.getElementById("time"),
interval = null;
function animate() {
for (var i = 0; i < anims.length; i++)
anims[i].style.display = "block";
}
function timer(sec) {
time.textContent = sec--;
interval = setInterval(function () {
time.textContent = sec >= 0 ? sec-- : clearInterval(interval) || "";
}, 1000);
}
// Directly after click
button0.addEventListener("click", animate);
// One seconds after click
button1.addEventListener("click", function () {
timer(1);
setTimeout(animate, 1000);
});
// Two seconds after click
button2.addEventListener("click", function () {
timer(2);
setTimeout(animate, 2000);
});
// Three seconds after click
button3.addEventListener("click", function () {
timer(3);
setTimeout(animate, 3000);
});
// Hide the divs
reset.addEventListener("click", function () {
for (var i = 0; i < anims.length; i++)
anims[i].style.display = "none";
});
body {
font-family: arial;
}
body > div {
margin-bottom: 10px;
}
#result {
background-color: #e5f3ff;
height: 120px;
padding: 10px;
}
.anim {
display: none;
float: left;
margin: 10px;
width: 50px;
height: 50px;
border-radius: 5px;
animation: animate 1.5s;
}
#anim1 {
background-image: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
/* Only one iteration iteration (default) */
/* This one will not be animated */
}
#anim2 {
background-color: #fddb92;
animation-iteration-count: 3;
/* Three iterations */
/* Only one iteration will be seen */
}
#anim3 {
background-image: linear-gradient(45deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);
animation-iteration-count: infinite;
/* Infinite */
/* No visible problem */
}
#keyframes animate {
50% {
transform: translate(80%, 100%) rotate(-360deg);
}
}
<div>
<span><strong>Reload the snippet</strong>
before clicking another button for viewing the issue
<br/><strong>Or,</strong>
<em>Reset</em> (display: "none") before clicking a button to view with no issue: </span>
</div>
<div>
<button id="button0">On click</button>
<button id="button1">1 sec timeout</button>
<button id="button2">2 sec timeout</button>
<button id="button3">3 sec timeout</button>
<button id="reset">Reset</button>
<span id="time"></span>
</div>
<div id="result">
<div id="anim1" class="anim"></div>
<div id="anim2" class="anim"></div>
<div id="anim3" class="anim"></div>
</div>
You will notice that the infinite animation doesn't apparently have any problem, but the two others do obviously have.
What is the solution then?
Note:
You have to use Firefox Quantum in order to view this.
I have tried the same snippet on Google Chrome and everything is working good.

Tested it, pretty sure it is solved for all browsers by using classes. There are more ways to handle it but putting the animation inside a new class that only gets added after the button click does the trick.
In the CSS I've moved the animation property to a new class, and the new class also add the block style.
.anim-start {
display: block;
animation: animate 1.5s;
}
In the JS I only changed style.display='block' to
anims[i].classList.add('anim-start');
See:
https://jsfiddle.net/0mgqd2ko/1/
Using this method of a new class makes it easier. For example, what if you want to transition from opacity 0 to 1? It's hard to do that when starting from display none. And what if you just want to use visibility so the elements still take space?

Related

In what cases are CSS transitions removed?

I'm pretty new to Javascript, and I have a webpage I'm trying to make that uses the same HTML file, and just cross-fades content instead of redirecting to new pages. I'm using event listeners to know when the current page has faded out and that triggers the new page to come in. Why is it that in the example below, the new pages don't fade in slowly (they just appear suddenly, ignoring the transition property)? Why is the content no longer responding to the CSS transition?
Edit: I'll try to clarify what I'm asking here. I'm aware that the display feature cannot be transitioned, and that's actually why I'm using the event listener at all. I'm trying to make it so that when the content of one page fades out, the next one fades in in the same place, which I believe cannot be achieved with visibility.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<!-- CSS -->
<style>
/* navagation bar style */
#navbar {
overflow: hidden;
background-color: #000000;
text-align: center;
width: 100%;
height: 36px;
}
#navbar a {
display: inline-block;
color: #ffffff;
padding: 10px 15px 10px 15px;
font-size: 16px;
}
/* content style*/
.page {
padding: 50px;
text-align: center;
transition: opacity 1s;
}
</style>
<!-- Javascript -->
<script>
window.onload = function() {setup()};
function setup() {
var page1link = document.getElementById("page1link");
var page1 = document.getElementById("page1");
page1.style["opacity"] = "1";
var page2link = document.getElementById("page2link");
var page2 = document.getElementById("page2");
page2.style["opacity"] = "0";
page2.style["display"] = "none";
var page3link = document.getElementById("page3link");
var page3 = document.getElementById("page3");
page3.style["opacity"] = "0";
page3.style["display"] = "none";
page1link.onclick = function() {fade(page1, page2, page3)};
page2link.onclick = function() {fade(page2, page1, page3)};
page3link.onclick = function() {fade(page3, page1, page2)};
}
function fade(page_1, page_2, page_3) {
let on_page;
if (page_2.style["opacity"] != "0") {
on_page = page_2
} else if (page_3.style["opacity"] != "0") {
on_page = page_3
} if (on_page != undefined) {
on_page.addEventListener('transitionend', fadePageIn)
on_page.style["opacity"] = "0";
function fadePageIn() {
on_page.style["display"] = "none";
page_1.style["display"] = "";
page_1.style["opacity"] = "1";
on_page.removeEventListener('transitionend', fadePageIn);
}
}
}
</script>
<title>Example</title>
</head>
<body>
<div id="navbar">
<a id="page1link" href="javascript:void(0)">Page 1</a>
<a id="page2link" href="javascript:void(0)">Page 2</a>
<a id="page3link" href="javascript:void(0)">Page 3</a>
</div>
<div class="page" id="page1">
page 1 content here
</div>
<div class="page" id="page2">
page 2 content here
</div>
<div class="page" id="page3">
page 3 content here
</div>
</body>
</html>
You can't animate the display property. So when you set opacity and display at the same time, the opacity will transition but the display value changes immediately.
As an alternative, the visibility property can be animated. Interpolation between its values happens at the halfway point, so if you want to make it work with transition that might complicate things. But I've had success in the past using CSS animations to change opacity and visibility at the same time. Using animations like this:
#keyframes becomeVisible {
0% { visibility: visible; }
100% { visibility: visible; }
}
#keyframes becomeHidden {
0% { visibility: visible; }
100% { visibility: visible; }
100% { visibility: hidden; }
}
#keyframes fadein {
0% { opacity: 0; }
100% { opacity: 1; }
}
#keyframes fadeout {
0% { opacity: 1; }
100% { opacity: 0; }
}
This is an interesting question.
Basically, as Mark points out, you can't animate it because setting the display property isn't possible to animate, thus nullifying your other transitions.
Therefore, as long as you can update the transition-able properties later in the event loop, then it will work as intended. A very easy way to do this is to use a setTimeout with 0 time.
function transitionEndCallback() {
oldPage.style["display"] = "none";
newPage.style["display"] = "block";
// add the setTimeout somewhere in this function
setTimeout(() => {
page_1.style["opacity"] = "1";
}, 0)
on_page.removeEventListener('transitionend', transitionEndCallback);
}
This way, it gets called in a different event loop callback, but still updates almost immediately afterwards. There maybe a better callback function or feature because it's a bit hacky, but I can verify it works.

Using JavaScript to populate text in div, but animation applies only to first text

I'm trying to create a landing page where a series of texts are displayed on the screen and rotated through continuously. In addition, these texts need to be animated with a typewriter-like effect. I have the animation for the text working, but it only applies to the first text. When cycling through the rest of the texts, they are just shown on the page. Any idea how to fix this issue? I was thinking trying to add the animation CSS with JavaScript for every single text refresh, but this seems cumbersome and not the most efficient way.
document.addEventListener('DOMContentLoaded', function(event) {
// array with texts to type in typewriter
var dataText = ["Hi, I'm Ned.", "Developer.", "Writer."];
function typeWriter(i) {
// add next text to h1
document.querySelector("h1").innerHTML = dataText[i] + '<span aria-hidden="true"></span>';
// wait for a while and call this function again for next text
setTimeout(function() {
typeWriter((i + 1) % 3)
}, 10000);
}
// start the text animation
typeWriter(0);
});
.typewriter h1 {
font-weight: bold;
color: #000;
font-family: "Lucida Console";
font-size: 2em;
overflow: hidden;
/*Hide content before animation*/
border-right: .1em solid white;
/*Cursor*/
white-space: nowrap;
/*Keep text on same line*/
margin: 0 auto;
/*Scrolling effect while typing*/
letter-spacing: .1em;
animation: typing 3s steps(30, end), blink-caret .75s steps(1, end) infinite alternate;
}
/* The typing effect */
#keyframes typing {
from {
width: 0
}
to {
width: 100%
}
}
/* The typewriter cursor effect */
#keyframes blink-caret {
from,
to {
border-color: transparent
}
50% {
border-color: white;
}
}
<div class="jumbotron" id="jumbotron">
<div class="typewriter">
<h1></h1>
</div>
</div>
The problem here is, that once the CSS animation is finished it wont be triggered again for that element. The only ways you can achieve a repeat is by either removing a css class (with the animation) from that element, wait for a bit and then readd it, or to destroy that element and re-create it.
For your example, I would simply create a new h1-element, edit its content and add that to the DOM. (And obviously removing the old one) Using jQuery, you can easily do that with
var h = $("<h1>"); //creating the element
h.innerHTML = ...;
$(".typewriter").empty(); //remove all children
$(".typewriter").append(h); //add new h1 element
Plain JS Method:
var h = document.createElement("h1"); //creating the element
h.innerHTML = ...; //setting its content
var typewriter = document.querySelector(".typewriter");
typewriter.innerHTML = ''; //remove all children
typewriter.appendChild(h); //add the new h1 element
Also note that the way you have selected your query selector, these changes will apply to ALL <h1> elements.
You can use JQuery to make it easier, I added also a function to stop animation when your data is completely displayed.
<script type="text/javascript">
$(function() {
var dataText = [ "Hi, I'm Ned.", "Developer.", "Writer."];
function typeWriter(i) {
var content = '<h1>' + dataText[i] +'<span aria-hidden="true"></span></h1>'
$(".typewriter").append(content);
// wait for a while and call this function again for next character
var myFunc = setTimeout(function() {
typeWriter((i + 1)%3)
}, 10000);
if (i == dataText.length - 1) {
clearTimeout(myFunc);
}
}
// start the text animation
typeWriter(0);
})
</script>
<div class="jumbotron" id="jumbotron">
<div class="typewriter">
</div>
</div>
You can check it there : https://jsfiddle.net/z4cryxx0/2/
Let me know if I answered your question.
I think I could achieve what you want. Check it out:
var dataText = [ "Hi, I'm Ned.", "Developer.", "Writer."];
function typeWriter(i) {
// add next character to h1
var h1 = document.createElement("h1");
h1.innerHTML = dataText[i] +'<span aria-hidden="true"></span>';
document.querySelector(".typewriter").append(h1);
// wait for a while and call this function again for next character
setTimeout(function() {
document.querySelector(".typewriter").innerHTML = ''; // add if(i === 2) to display the whole phrase
typeWriter((i + 1)%3)
}, 10000);
}
// start the text animation
typeWriter(0);
.typewriter h1 {
font-weight: bold;
color: orange;
font-family: "Lucida Console";
font-size: 7em;
overflow: hidden; /*Hide content before animation*/
border-right: .1em solid white; /*Cursor*/
white-space: nowrap; /*Keep text on same line*/
margin: 0 auto; /*Scrolling effect while typing*/
letter-spacing: .1em;
animation:
typing 3s steps(30, end),
blink-caret .75s steps(1, end) infinite alternate;
}
/* The typing effect */
#keyframes typing {
from {
width: 0
}
to {
width: 100%
}
}
/* The typewriter cursor effect */
#keyframes blink-caret {
from, to {
border-color: transparent
} 50% {
border-color: white;
}
}
<div class="jumbotron" id="jumbotron">
<div class="typewriter">
<h1></h1>
</div>
The trick is to make css animation work again. To do that, every time I create a new h1 element. Once a part/the whole phrase is displayed I just remove all h1 elements and repeat the process.
You could set the animation as infinite and change the length of the animation to the delay you want between the change in the text, and then set it to finish after 3 seconds (which is at 30% here) or however long you want a single animation to last:
.typewriter h1 {
...
animation:
typing 10s steps(30, end) infinite,
blink-caret .75s steps(1, end) infinite alternate;
}
#keyframes typing {
0% {
width: 0;
}
30% {
width: 100%;
}
}
The downside of this approach is that if you want to change the length of the delay between the texts, you have to modify both the JavaScript delay and the CSS animation length.
JSFiddle

White background during fadeOut() and fadeIn()

Currently I'm using this code to create a slideshow in my homepage:
var images = [
"/d/assets/images/IT/homepage/slider-1.jpg",
"/d/assets/images/IT/homepage/slider-2.jpg",
"/d/assets/images/IT/homepage/slider-3.jpg",
"/d/assets/images/IT/homepage/slider-4.jpg",
"/d/assets/images/IT/homepage/slider-5.jpg",
];
var imageHead = document.getElementById( "slider-home" );
var i = 0;
setInterval(function() {
$('#slider-home').fadeOut(200,
function() {
imageHead.style.backgroundImage = "url(" + images[i] + ")";
i = i + 1;
if (i == images.length) {
i = 0;
}
$('#slider-home').fadeIn(200)
}
);
}, 5000);
The code works perfectly and change the background-image every 5 seconds. Unfortunately between every change there is a small white background and the transition not appear to be "clean" and "fluid" like the Ryanair slideshow in homepage: https://www.ryanair.com/it/it/
Where is the problem that generate this and how I can solve it?
You're fading one image out, waiting for that transition to complete, then fading the next one in. That's effectively transitioning to the page background in between the images.
Making those transitions simultaneous by stacking two elements and transitioning between them would help, but it still wouldn't be perfect: you still wind up with the background peeking through during the transition while both images are partially transparent.
Instead, to get a smooth transition, just fade out the "top" image in the stack, revealing the one behind it:
yourswap = function() {
if ($('#div1').is(':visible')) {
fadeout = "#div1";
fadein = "#div2";
} else {
fadeout = "#div2";
fadein = "#div1";
}
// fade one out, then fade the other in
$(fadeout).fadeOut(function() {
$(fadein).fadeIn()
});
}
badswap = function() {
$('#div1, #div2').fadeToggle(); // transitions both elements in and out
}
goodswap = function() {
$('#div1').fadeIn(0); // make sure the background element is visible
$('#div2').fadeToggle(); // transitions the top element in and out
}
.block-div {
position: absolute;
height: 200px;
width: 400px;
color: #fff;
font-size: 40px;
text-align: center;
}
.container {
position: relative
}
#div1 {
background-color: red;
}
#div2 {
background-color: brown;
display: none;
}
#div3 {
background-color: blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<button onclick="yourswap()">Your transition</button>
<button onclick="badswap()">Simultaneous transition</button>
<button onclick="goodswap()">Single transition</button>
<div class="container">
<div class="block-div" id="div3">(This is the page background)</div>
<div class="block-div" id="div1">1</div>
<div class="block-div" id="div2">2</div>
</div>
Try this example
https://codepen.io/dudleystorey/pen/ehKpi
No need to write JS for that.
But through CSS it's possible.
CSS :
animation: 30s slidy infinite;
set property "animation:30s slidy infinite;" (number of seconds to change the Image in slider.)

What is the best way to detect if an element has a CSS animation applied

I am attempting to fire an event on a web component when a CSS animation has completed, however there is a possibility the user might clear the animation from the element using animation: none; meaning the transitionend event never fires:
// wait for dialog close animation to end before firing closed event
element.addEventListener('transitionend', function() {
// fire dialog closed event
self.dispatchEvent(new CustomEvent('pure-dialog-closed', {
bubbles: true,
cancelable: true
}));
});
To ensure my custom event always fires, I need to determine if the element or any of its children have an animation applied and if not, fire the pure-dialog-closed event immediately.
I have tried checking style.animationName and self.style.transition but it does not appear to be working. I need a simple way of checking if an element, or any of its children have a CSS animation applied.
You can use the getComputedStyle function. Following is an example which read the transition property of a div.
Read more about this here.
https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
function getTheStyle() {
var elem = document.getElementById("elem-container");
var theCSSprop = window.getComputedStyle(elem, null).getPropertyValue("transition");
document.getElementById("output").innerHTML = theCSSprop;
}
getTheStyle();
#elem-container {
position: absolute;
left: 100px;
top: 200px;
height: 100px;
transition: all 1s;
}
<div id="elem-container"></div>
<div id="output"></div>
Thank you #Thusitha. I used window.getComputedStyle along with animation-duration and transition-duration to determine if an animation existed as either would need to be greater than 0s for an animation/transition to play out.
The following inspects all elements including element passed in:
/**
* Determine if an element of any or its children have a CSS animation applied
* #param {HTMLElement} el - element to inspect
* #returns {boolean} true if an animation/transition detected, otherwise false
*/
function hasCssAnimation(el) {
// get a collection of all children including self
var items = [el].concat(Array.prototype.slice.call(el.getElementsByTagName("*")));
// go through each item in reverse (faster)
for (var i = items.length; i--;) {
// get the applied styles
var style = window.getComputedStyle(items[i], null);
// read the animation/transition duration - defaults to 0
var animDuration = parseFloat(style.getPropertyValue('animation-duration') || '0');
var transDuration = parseFloat(style.getPropertyValue('transition-duration') || '0');
// if we have any duration greater than 0, an animation exists
if (animDuration > 0 || transDuration > 0) {
return true;
}
}
return false;
}
var elementToTest = document.querySelector('.one');
var result = hasCssAnimation(elementToTest);
alert(result);
div {
font-size: 14px;
padding: 20px;
color: #fff;
}
.one {
background: red;
}
.two {
background: green;
animation: zoomIn 3s ease; /* <-- animation applied to child */
}
.three {
background: blue;
}
#keyframes zoomIn {
from {
opacity: 0;
transform: scale3d(.3, .3, .3);
}
50% {
opacity: 1;
}
}
<div class="one">
<div class="two"> <!-- animation on child element -->
<div class="three">
Hello World
</div>
</div>
</div>
You can listen for animationend and animationstart events.
let element = document.getElementById("square");
element.addEventListener("animationstart", function(event) {
element.innerHTML = "RED!"
element.setAttribute('data-animating', true);
}, false);
element.addEventListener("animationend", function(event) {
element.innerHTML = "YELLOW!"
element.setAttribute('data-animating', false);
}, false);
setInterval(() => {
console.log(element.getAttribute('data-animating'))
}, 500)
#square{
width: 100px;
height: 100px;
animation-name: anim;
animation-duration: 4s;
background-color: red;
animation-fill-mode: forwards;
text-align: center;
vertical-align: middle;
line-height: 100px;
}
#keyframes anim {
to {background-color: yellow;}
}
<div id="square"></div>

Is it possible to loop changing opacity values in HTML5 or css?

This is the code I'm currently working with. It works to my purposes of layering the two images. What I am trying to do is have the layer0 opacity lower to 0 as the layer1 opacity increases to 100 over a few seconds. {and then on to layer1 with layer2 and so on eventually looping back to layer0}
Any help would be appreciated.
<head>
<style>
div.layer0
{
width: 371px;
height: 345px;
background:url(image2.jpg);
opacity:1;
filter:alpha(opacity=100); /* For IE8 and earlier */
}
div.layer1
{
width: 371px;
height: 345px;
background:url(image3.jpg);
opacity:0;
filter:alpha(opacity=0); /* For IE8 and earlier */
}
</style>
</head>
<body>
<div class="layer0">
<div class="layer1">
</div>
</div>
</body>
To continually do this in a loop, you'll need some javascript to add an appropriate active class to the image you want displayed. Then using CSS transitions you can achieve the fading between images that you require.
I created a jsfiddle to give you an example of this working: http://jsfiddle.net/pacso/H6dqq/
The basics are as follows.
Some simple HTML divs which you'll be fading:
<div class='red square active'></div>
<div class='yellow square'></div>
<div class='green square'></div>
<div class='blue square'></div>
These are just going to be coloured squares, but yours could contain images.
Next, some CSS markup:
.red {
background-color: red;
}
.blue {
background-color: blue;
}
.green {
background-color: green;
}
.yellow {
background-color: yellow;
}
.square {
width: 200px;
height: 200px;
position: absolute;
top: 20px;
left: 20px;
opacity: 0;
transition: opacity 2s;
-webkit-transition: opacity 2s; /* Safari */
}
.active {
opacity: 1;
}
Note that my transition will alter the opacity of the div itself. You may need to change this as needed.
Now the javascript to make it work on an endless loop:
jQuery(function() {
window.setInterval(function () {
activeSquare = $('.active');
nextSquare = activeSquare.next()
if (nextSquare.length == 0) {
nextSquare = activeSquare.siblings().first();
}
nextSquare.addClass('active');
activeSquare.removeClass('active');
}, 3000);
});
Fairly straightforward. Click the link to my fiddle and hit the run button if you want to see a working demo.
Short answer: not easily.
You're probably better off with javascript for the looping. You could make a delayed keyframe animation, but that won't allow you to loop from the start again: jsfiddle.net/G4PTM (firefox/ie10) -- You could make a lot of keyframes with different timings and you can make it work, but it would require quite a bit of code and not scale well (say you wanted to add another layer/image the code would quickly become unmanagable)
With some javascript, you can just loop through the divs and add and remove a classname to trigger the transitions, like Jon mentioned. Here is a working demo (using jQuery for simplicity, let me know if you need vanilla js)
html
<div class="layer0">
</div>
<div class="layer1">
</div>
<div class="layer2">
</div>
css
div {
width: 371px;
height: 345px;
opacity: 0;
position: absolute;
transition: opacity 2s;
}
div.active {
opacity: 1;
}
div.layer0 {
background:url(http://lorempixel.com/373/345);
}
div.layer1 {
background:url(http://lorempixel.com/372/345);
}
div.layer2 {
background:url(http://lorempixel.com/374/345);
}
js+jquery
var firstDiv = $(".layer0");
var current;
function loopsie() {
// if first iteration or reached end, use first div
if (!current || !current.length) current = firstDiv;
current.addClass("active");
setTimeout(function() {
current.removeClass("active");
setTimeout(function() {
current = current.next();
loopsie(); // recurse
}, 2000);
}, 2000);
}
//initialize
loopsie();
Working demo at http://jsfiddle.net/G4PTM/2/
Plain JavaScript (Without jQuery):
var firstDiv = document.querySelector(".layer0"); // IE 8+
var current;
function loopsie() {
// if first iteration, use first div
if (!current) current = firstDiv;
current.classList.add("active"); // IE 10+, shim at https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
setTimeout(function() {
current.classList.remove("active");
// account for text node (if there is whitespace in html)
if (current.nextSibling && current.nextSibling.nodeName == "DIV") {
current = current.nextSibling;
} else if (current.nextSibling && current.nextSibling.nextSibling && current.nextSibling.nextSibling.nodeName == "DIV") {
current = current.nextSibling.nextSibling;
} else {
// reached end
current = firstDiv;
}
loopsie(); // recurse
}, 2000);
}
//initialize
loopsie();
http://jsfiddle.net/G4PTM/6/
You can use CSS transitions. The example below fades .layer0 in and out in a timespan of 500 ms:
div.layer0 {
opacity: 1;
-webkit-transition:opacity 500ms ease-out;
-moz-transition:opacity 500ms ease-out;
-o-transition:opacity 500ms ease-out;
transition:opacity 500ms ease-out;
}
div.layer0:hover {
opacity: 0;
}

Categories