In what cases are CSS transitions removed? - javascript

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.

Related

Smooth scrolling slider with play/pause button

I'm doing a project where I need a scrollable slider with play pause button like www.gap.com. I got this below code from W3C but not sure why multiple images are not showing fully. If I change the width value in CSS code, only first image portion scrolls but it totally ignores the 2nd image. Please anyone help me.
var speed=20 // speed of scroller
var step=3 // smoothness of movement
var StartActionText= "Scroll" // Text for start link
var StopActionText = "Pause" // Text for stop link
var x, scroll, divW, sText=""
function onclickIE(idAttr,handler,call){
if ((document.all)&&(document.getElementById)){idAttr[handler]="Javascript:"+call}
}
function addLink(id,call,txt){
var e=document.createElement('a')
e.setAttribute('href',call)
var linktext=document.createTextNode(txt)
e.appendChild(linktext)
document.getElementById(id).appendChild(e)
}
function getElementStyle() {
var elem = document.getElementById('scroller');
if (elem.currentStyle) {
return elem.currentStyle.overflow;
} else if (window.getComputedStyle) {
var compStyle = window.getComputedStyle(elem, '');
return compStyle.getPropertyValue("overflow");
}
return "";
}
function addControls(){
// test for CSS support first
// test for the overlow property value set in style element or external file
if (getElementStyle()=="hidden") {
var f=document.createElement('div');
f.setAttribute('id','controls');
document.getElementById('scroller').parentNode.appendChild(f);
addLink('controls','Javascript:clickAction(0)',StopActionText);
onclickIE(document.getElementById('controls').childNodes[0],"href",'clickAction(0)');
document.getElementById('controls').style.display='block';
}
}
function stopScroller(){clearTimeout(scroll)}
function setAction(callvalue,txt){
var c=document.getElementById('controls')
c.childNodes[0].setAttribute('href','Javascript:clickAction('+callvalue+')')
onclickIE(document.getElementById('controls').childNodes[0],"href",'clickAction('+callvalue+')')
c.childNodes[0].firstChild.nodeValue=txt
}
function clickAction(no){
switch(no) {
case 0:
stopScroller();
setAction(1,StartActionText);
break;
case 1:
startScroller();
setAction(0,StopActionText);
}
}
function startScroller(){
document.getElementById('tag').style.whiteSpace='nowrap'
var p=document.createElement('p')
p.id='testP'
p.style.fontSize='25%' //fix for mozilla. multiply by 4 before using
x-=step
if (document.getElementById('tag').className) p.className=document.getElementById('tag').className
p.appendChild(document.createTextNode(sText))
document.body.appendChild(p)
pw=p.offsetWidth
document.body.removeChild(p)
if (x<(pw*4)*-1){x=divW}
document.getElementById('tag').style.left=x+'px'
scroll=setTimeout('startScroller()',speed)
}
function initScroller(){
if (document.getElementById && document.createElement && document.body.appendChild) {
addControls();
divW=document.getElementById('scroller').offsetWidth;
x=divW;
document.getElementById('tag').style.position='relative';
document.getElementById('tag').style.left=divW+'px';
var ss=document.getElementById('tag').childNodes;
for (i=0;i<ss.length;i++) {sText+=ss[i].nodeValue+" "};
scroll=setTimeout('startScroller()',speed);
}
}
function addLoadEvent(func) {
if (!document.getElementById | !document.getElementsByTagName) return
var oldonload = window.onload
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload()
func()
}
}
}
addLoadEvent(initScroller)
body {font:1em verdana,sans-serif; color:#000; margin:0}
/* position:relative and overflow:hidden are required */
#scroller { position:relative; overflow:hidden; width:30em; border:1px solid #008080; }
/* add formatting for the scrolling text */
#tag { margin:2px 0; }
/* #testP must also contain all text-sizing properties of #tag */
#testP { visibility:hidden; position:absolute; white-space:nowrap; }
/* used as a page top marker and to limit width */
#top { width:350px; margin:auto; }
<div id="scroller">
<div id="tag">
<img src="https://picsum.photos/1500/600/?image=1"/>
<img src="https://picsum.photos/1500/600/?image=2"/>
</div>
</div>
I got a bit lost in the given JS, and wonder if it is necessary for this relatively simple task.
Here is a method using HTML and CSS for the continuous scrolling and with JS just for the pause/play part.
Because you want continuous scrolling with no gap or jump when the sequence of images goes back to the beginning you need two copies. The tag element is scrolled to the left by half of its width which means that the set of images overwrite themselves so giving a smooth effect.
The JS for the button uses the running value and toggles that.
.playpause {
top: 10%;
left: 10%;
position: absolute;
z-index: 1;
}
#scroller {
width: min(30em, 100vw);
height: min(20em, 100vh);
display: inline-block;
white-space: nowrap;
overflow: hidden;
position: relative;
}
#scroller #tag {
height: 100%;
animation: scroll 10s linear infinite;
animation-fill-mode: forwards;
display: inline-block;
font-size: 0;
}
#keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
img {
height: 100%;
width: auto;
display: inline-block;
}
<div id="scroller">
<button class="playpause" onclick="document.querySelector('#tag').style.animationPlayState = (document.querySelector('#tag').style.animationPlayState != 'paused') ? 'paused' : 'running';">PLAY/PAUSE</button>
<div id="tag">
<img src="https://picsum.photos/1500/600/?image=1" />
<img src="https://picsum.photos/1500/600/?image=2" />
<!-- second copy of all the imaages -->
<img src="https://picsum.photos/1500/600/?image=1" />
<img src="https://picsum.photos/1500/600/?image=2" />
</div>
</div>
Observation: the site linked to in the question (gap) has a slightly unpleasant 'jump' half way through the images so I think they must be using a different method to achieve continuous scrolling.

How to implement a page loader animation only for webpages that take more than a certain amount of time to load?

I've got a page loader animation currently working successfully by calling window.onload() in my JavaScript, but I'd like to tweak the code so that the loader animation only triggers if the webpage takes more than a certain amount of time to load. I'm attempting to avoid having the animation display every time that the user navigates to a new page, and only when it's necessary.
Below is my current HTML, CSS, and JS code, if it's helpful.
HTML:
<div class="loader">
<img class="loading-gif" src="./assets/img/loading.gif" alt="Page Loading...">
</div>
CSS:
.loader {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: 100%;
height: 100%;
background: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
}
.loading-gif {
width: 100px;
}
.loader.fade-out {
animation: fadeOut var(--speed);
animation-fill-mode: forwards;
}
#keyframes fadeOut {
100% {
opacity: 0;
visibility: hidden;
}
}
JavaScript:
window.onload = function() {
const loader = document.querySelector(".loader")
loader.classList.add("fade-out")
}
Thank you so much for any help that you can provide!
In a word, this isn't possible. To achieve this effect you would need to know in advance the rate at which the client will download your webpage and the assets required for the webpage's initial render. You can't know, or even reliably predict, the client's download rate over time because the variables that affect network conditions are too complex. For example, the end user may enter a tunnel while downloading your webpage causing a sudden and unpredictable decrease in available download bandwidth, or other applications on the end user's device may make network requests that compete with the download in progress for bandwidth.
The best you can do to approximate this effect would be to use the onprogress event of XMLHttpRequest to determine the current download rate and the size of the asset being transferred to calculate when the asset will finish downloading, ASSUMING the download rate remains the same.
Here's a snippet you could use to figure out how to approximate the effect.
const xhr = new XMLHttpRequest();
xhr.open('GET', '/myasset.json}', true);
xhr.onprogress = e => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(percentComplete, 'percentComplete');
}
}
xhr.onload = function() {
console.log("loaded");
}
xhr.send();
You can try this, but I'm not 100% sure. Basically turn all html into text and inject it into a root div; if the slowest content isn't found, conditionally load page loader, and check again until found to fade loader.
window.onload = function() {
let yourContent = `<div>
Your site's content:
<div>Content A</div>
<div>Content B</div>
<div id="check">Content C</div>
</div>`
let loader = `<div class="loader">
<img class="loading-gif" src="./assets/img/loading.gif" alt="Page Loading...">
</div>`;
let wrap = document.getElementById("loader-wrap");
let root = document.getElementById("root");
//switch these for loader render
root.innerHTML = yourContent;
// setTimeout(()=>root.innerHTML = yourContent,1000);
let interval = setInterval( ()=> {
console.log("checking...")
if(root.children[0]){
let check = document.getElementById("check")
if(check.textContent === "Content C"){
const loader = document.querySelector(".loader");
if(loader) loader.classList.add("fade-out");
//don't forget to clear it
clearInterval(interval);
console.log("done")
}
} else {
if(!wrap.children[0]) wrap.innerHTML = loader;
}
},100)
}
.loader {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: 100%;
height: 100%;
background: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
}
.loading-gif {
width: 100px;
}
.fade-out {
animation: fadeOut 1s;
animation-fill-mode: forwards;
}
#keyframes fadeOut {
100% {
opacity: 0;
visibility: hidden;
}
}
<div id="root">
</div>
<div id="loader-wrap">
</div>

Animation issue on Firefox Quantum

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?

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.)

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