Fade In without jQuery (fadeIn) - CSS transition opacity and display - javascript

I'm trying to implement an effect identical to jQuery's fadeIn() function, where an element is displayed, and then it's opacity is animated from 0 to 1. I need to do it programmatically (WITHOUT jQuery), and I need the element to be able to fade out (display:none) and then fade back in.
The ideal solution will use a CSS transition to leverage hardware acceleration - I can get the element to fadeOut with great success by listening to the transitionend event. Fading back in, however is proving to be a challenge as the following bit of code is not working as intended:
fader.style.transition = 'opacity 1s';
const fadeIn = () => {
fader.style.display = 'block';
fader.style.opacity = 1;
};
When fadeIn() is called the element simply snaps back in, instead of smoothly animating. I have a codePen that I've been tinkering with to illustrate the problem.
My theory is that the transition is unable to execute on an element that's not in the DOM, as I can get the animation to work by setting height:0 instead of display:none. Perhaps there is a delay between when I set fader.style.display = 'block'; and when the DOM is actually being updated, during which I cannot transition?
On that idea: I also seem to be able to get the animation to work by delaying the opacity change with setTimeout(() => {fader.style.opacity = 1}, 20}. This seems to create a sort of race condition however because as the timeout duration gets closer to 0 the animation works less and less dependably.
Please note that I do not want to toggle the visibility attribute like the solutions to this question, as that does not effectively remove the element from the DOM.
Changing the height/width to 0 is a more viable option, but because the height and width of the element are not known, it will require the extra step of capturing those values before fading out so they can be re-applied when fading in. This seems flimsy if, say, a different part of the application tries to change those values (for example a media query, and the user rotates their device while the element is hidden)

The following code should effectively replace jQuery's fadeOut() and fadeIn() functions (Much thanks to #Kyle for the clue!).
const fadeIn = (el, ms, callback) => {
ms = ms || 400;
const finishFadeIn = () => {
el.removeEventListener('transitionend', finishFadeIn);
callback && callback();
};
el.style.transition = 'opacity 0s';
el.style.display = '';
el.style.opacity = 0;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.addEventListener('transitionend', finishFadeIn);
el.style.transition = `opacity ${ms/1000}s`;
el.style.opacity = 1
});
});
};
const fadeOut = (el, ms, callback) => {
ms = ms || 400;
const finishFadeOut = () => {
el.style.display = 'none';
el.removeEventListener('transitionend', finishFadeOut);
callback && callback();
};
el.style.transition = 'opacity 0s';
el.style.opacity = 1;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transition = `opacity ${ms/1000}s`;
el.addEventListener('transitionend', finishFadeOut);
el.style.opacity = 0;
});
});
};
This became super clear after digging into rAF (requestAnimationFrame) and watching this video on the event loop. Right around 21:00 is where I had the aha moment about why rAF needs to be nested inside another rAF.
Here is the working example on Codepen.
Please comment if you discover any edge cases that aren't solved for :)

Related

Full page scroll with parallax effect

I want to make a full page scroll effect like this site or this site (demo here).
It seems that divs are moving in the different speed or kind of some acceleration so that the later div may scroll first and the div before may scroll later, and makes the later div becomes like a mask that will hide the before div for a little while.
I tried to use scrollIntoView with smooth behavior like this:
const content = document.querySelectorAll('section');
let index = 0;
document.addEventListener('wheel', event => {
var delta = event.wheelDelta;
if (delta < 0) {
index++;
content.forEach((section, i) => {
if (i === index) {
toggleText(i, 'show');
section.scrollIntoView({behavior: "smooth"});
}
})
} else {
index--;
content.forEach((section, i) => {
if (i === index) {
toggleText(i, 'show');
section.scrollIntoView({behavior: "smooth"});
}
})
}
})
Now when scroll event fires, each section moves in the same speed, and the later section cannot hide the before section for a little while.
How to implement this "mask" like effect?
Easiest way to do this would just to add delays after clicking next or prev
var delay = 2500; //2.5 second
setTimeout(function() {
//your code here
}, delay);
Your code will execute in 2.5 seconds.

Run a method inside an animation or run a method between two animations

How can I run a method inside and animation? or run two animations and a method between them? I'm trying to add a div then change it opacity from 0 to 1, than run a method and then change the opacity again from 1 to 0, some kind of load screen.
I just tried to divide the animation and method in two and use eventlisteners with "animation end" but doesn't work properly (just works the first run, the second time the changeScreen method is used the classList say is null the error says: "Uncaught TypeError: Cannot read property 'classList' of null
at HTMLDivElement." in document.querySelector("#screen").classList.remove("screenOff");)
here the methods:
const screen = document.createElement('div'); //Const to add the load screen
screen.id = 'screen';
function changeScreen(token){
document.body.prepend(screen);
document.querySelector("#screen").classList.add("screenOff");
document.querySelector("#screen").addEventListener("animationend", function(){
loadScreen(loadFile(token));
document.querySelector("#screen").classList.remove("screenOff");
document.querySelector("#screen").removeEventListener("animationed", null);
retrieveScreen();
},false);
}
function retrieveScreen(){
document.querySelector("#screen").classList.add("screenOn");
document.querySelector("#screen").addEventListener("animationend", function(){
document.querySelector("#screen").classList.remove("screenOn");
document.querySelector("#screen").removeEventListener("animationed", null);
screen.remove();
},false);
}
the method obtain a token (number) then I add a div element called screen to the body and add the class screenOff, this starts the animation of the div to turn black. Then with the event listener I expect that when the screen is balck the method loadScreen do the changes, then I wanted that the class changes to screen on to turn the div from black to opacity 0 and in the end remove the div from screen.
All works except that the remove stops the second animation and without it the div will be there blocking the way, this method should be executed many times doing the same thing.
If there is a way to execute the method loadScreen inside in just one animation example:
0% {opacity: 0;}
50% {opacity: 1;}
----- run loadScreen() then -----
50% {opacity: 1; }
100% {opacity: 0;}
would be nice ...
I found a way to do it, using setinterval we can add and remove animations, I resolve my problem like this:
const screen = document.createElement('div'); //Const to add the load screen
screen.id = 'screen';
//Function that calls the loadScreen and loadFile methods and manages the
secuence of the screen changes.
function changeScreen(token){
let x = 0;
document.querySelector("#screen").classList.add("screenOff");
let intervalID = setInterval(function () {
if (x === 0) {
loadScreen(loadFile(token));
load.play();
document.querySelector("#screen").classList.remove("screenOff");
document.querySelector("#screen").classList.add("screenOn");
x++;
}else{
document.querySelector("#screen").classList.remove("screenOn");
window.clearInterval(intervalID);
}
}, 1400);
}

Fade in using plain javascript

Trying to do a simple fade in using the opacity property of an h1 element. I'm learning javascript, so would like to try this using plain javascript (yes, I know it is much easier using jQuery).
Pasting only relevant snippets:
<body onload="fadeIn()">
...
<div class = "container">
<div class = "row">
<div class = "col-md-3">
<img class = "img-responsive" src="icons/Website_Logo.png">
</div>
<div class = "col-md-9 page-header">
<h1 id="welcomeHeader" style="opacity:0">
Welcome to the world!
</h1>
</div>
</div>
</div>
...
<script>
function fadeIn() {
var el = document.getElementById("welcomeHeader");
var op = parseFloat(el.style.opacity);
var timer = (function () {
if(op >= 1.0)
clearInterval(timer);
op += 0.1;
el.style.opacity = op;
}, 50);
}
</script>
</body>
Help is much appreciated! Thanks!
jsFIDDLE
You need to call the setInterval function first in order to invoke a timer. Rest is fine. Here is a working fiddle
Code Snippet:
function fadeIn() {
var el = document.getElementById("welcomeHeader");
var op = parseFloat(el.style.opacity);
var timer = setInterval(function () {
console.log('here');
if(op >= 1.0)
clearInterval(timer);
op += 0.1;
el.style.opacity = op;
}, 50);
}
You need to change your function to use setInterval like so:
var timer = setInterval(function () { // notice the setInterval added.
if(op >= 1.0)
clearInterval(timer);
op += 0.1;
el.style.opacity = op;
}, 50);
Notes:
I give you this answer to help you LEARN javascript as you mentioned, otherwise,
it would be better done with pure css of course.
Also, make sure your opacity is set to 0 in your css as a starting point.
You don't need a timer for this - all you need to do is change the class. Here's an example:
the CSS:
element{
/* whatever styles you have */
}
element_faded{
transition: opacity .5s;
opacity: 50%; /* or whatever values you want */
}
the javascript
var element = document.getElementById('element');
// in order to trigger the fade, just change the class
element.className = "element_faded";
In the transition will happen between the values of the original and new class, so if you want a fade-in, have the original opacity be 0% and the new one be 100% or something higher than zero, depending on what you want the final opacity to be. Also, remember that the transition characteristics are determined by the transition attribute in the new class.
Doing this without CSS will just make things more complicated unless you need to do something more sophisticated than just plain fading in or out. If that's the case, then use setInterval or perhaps even something like requestAnimationFrame if you're feeling adventurous.
Honestly, this isn't really the kind of thing you need to learn when first learning javascript. Eventually this will be really easy once you get some confidence under your belt doing things that work more easily in javascript (setTimeout and the like can have their own weird caveats). Try to set a meaningful, practical goal and fulfill it first, using whatever mix of javscript/css/html you can and you'll soon have the basics down well enough to find things like this obvious.

javascript, div blinking instead of fading

I'm making a site that has an area that it's content disappear and re-appears. So when the user clicks certain button, the <div>'s content fades out and fades in the content relative to the clicked icon.
First time the function getabout is clicked it works OK, but whenever I click on clear() and then again on getabout it starts blinking. I've discovered that it does the clean to the div but it happens that the content re-appears again from nothing and becomes intermittent.
Here is my JavaScript code, it's commented so you could give me a hand here:
var check = null; //this will be checking the instance of div's content
const wait_time = 50; //the time it will take to fade
function getabout(id) {
/* prevent second call to the same function to bug */
if (check == id) return;
var titleOpacity = 0,
textOpacity = 0;
/* this changes the title first */
document.getElementById("title").style.opacity = 0;
document.getElementById("title").innerHTML = "this is the title";
// recursive call to the opacity changer, it
// increases opacity by 0.1 each time until it's 1
setInterval(function () {
titleOpacity = fadeIn(titleOpacity, 'title');
}, wait_time);
/* changes the content next to the title */
window.setTimeout(function () {
document.getElementById("dialog").style.opacity = 0;
document.getElementById("dialog").innerHTML = "this is the content";
setInterval(function () {
textOpacity = fadeIn(textOpacity, 'dialog');
}, wait_time);
}, 500);
check = id; // defines the instance "about" at the moment
}
function fadeIn(opacity, id) {
opacity += 0.1;
document.getElementById(id).style.opacity = opacity;
document.getElementById(id).style.MozOpacity = opacity;
if (opacity >= 1.0) clearInterval(listener);
return opacity;
}
function clear() {
var opacity = document.getElementById("title").style.opacity;
// supposed to decrease the opacity by 0.1 but it's not doing that
setInterval(function () {
opacity = fadeout(opacity);
}, wait_time);
//cleans the title and dialog to fill with the next button user clicked
document.getElementById("title").innerHTML = "";
document.getElementById("dialog").innerHTML = "";
}
function fadeout(opacity) {
opacity -= 0.1;
document.getElementById("title").style.opacity = opacity;
document.getElementById("dialog").style.MozOpacity = opacity;
if (opacity <= 0.0) clearInterval(listener);
return opacity;
}
function getregister(id) {
if (check == id) return;
clear(); // proceed to fade out the content and clean it
check = id;
}
I don't understand what is the error of the code. with the clear() function it should smoothly fade out the content and then clean it. But it just cleans the div. And next time I use getabout() function, instead of smoothly fade in again as it does the first time, it starts to blink.
I'm relatively new to web programming and I refuse JQuery for now. I want to understand deeply javascript before go to JQuery and this is why I would just like to know pure JavaScript solutions and considerations about this.
Ive managed to cock up my comment so trying again!
I think your problem is that you're not clearing the setInterval correctly - ensure you use listener = setInterval(...)
As it stands your clearInterval(listener); is doing nothing as 'listener' is not defined. So your fade out function continues to run.

How to improve image cross-fade performance?

I want to be able to do a cross fade transition on large images whose width is set to 100% of the screen. I have a working example of what I want to accomplish. However, when I test it out on various browsers and various computers I don't get a buttery-smooth transition everywhere.
See demo on jsFiddle: http://jsfiddle.net/vrD2C/
See on Amazon S3: http://imagefader.s3.amazonaws.com/index.htm
I want to know how to improve the performance. Here's the function that actually does the image swap:
function swapImage(oldImg, newImg) {
newImg.css({
"display": "block",
"z-index": 2,
"opacity": 0
})
.removeClass("shadow")
.animate({ "opacity": 1 }, 500, function () {
if (oldImg) {
oldImg.hide();
}
newImg.addClass("shadow").css("z-index", 1);
});
}
Is using jQuery animate() to change the opacity a bad way to go?
You might want to look into CSS3 Transitions, as the browser might be able to optimize that better than Javascript directly setting the attributes in a loop. This seems to be a pretty good start for it:
http://robertnyman.com/2010/04/27/using-css3-transitions-to-create-rich-effects/
I'm not sure if this will help optimize your performance as I am currently using IE9 on an amped up machine and even if I put the browser into IE7 or 8 document mode, the JavaScript doesn't falter with your current code. However, you might consider making the following optimizations to the code.
Unclutter the contents of the main photo stage by placing all your photos in a hidden container you could give an id of "queue" or something similar, making the DOM do the work of storing and ordering the images you are not currently displaying for you. This will also leave the browser only working with two visible images at any given time, giving it less to consider as far as stacking context, positioning, and so on.
Rewrite the code to use an event trigger and bind the fade-in handling to the event, calling the first image in the queue's event once the current transition is complete. I find this method is more well-behaved for cycling animation than some timeout-managed scripts. An example of how to do this follows:
// Bind a custom event to each image called "transition"
$("#queue img").bind("transition", function() {
$(this)
// Hide the image
.hide()
// Move it to the visible stage
.appendTo("#photos")
// Delay the upcoming animation by the desired value
.delay(2500)
// Slowly fade the image in
.fadeIn("slow", function() {
// Animation callback
$(this)
// Add a shadow class to this image
.addClass("shadow")
// Select the replaced image
.siblings("img")
// Remove its shadow class
.removeClass("shadow")
// Move it to the back of the image queue container
.appendTo("#queue");
// Trigger the transition event on the next image in the queue
$("#queue img:first").trigger("transition");
});
}).first().addClass("shadow").trigger("transition"); // Fire the initial event
Try this working demo in your problem browsers and let me know if the performance is still poor.
I had the same problem too. I just preloaded my images and the transitions became smooth again.
The point is that IE is not W3C compliant, but +1 with ctcherry as using css is the most efficient way for smooth transitions.
Then there are the javascript coded solutions, either using js straight (but need some efforts are needed to comply with W3C Vs browsers), or using libs like JQuery or Mootools.
Here is a good javascript coded example (See demo online) compliant to your needs :
var Fondu = function(classe_img){
this.classe_img = classe_img;
this.courant = 0;
this.coeff = 100;
this.collection = this.getImages();
this.collection[0].style.zIndex = 100;
this.total = this.collection.length - 1;
this.encours = false;
}
Fondu.prototype.getImages = function(){
var tmp = [];
if(document.getElementsByClassName){
tmp = document.getElementsByClassName(this.classe_img);
}
else{
var i=0;
while(document.getElementsByTagName('*')[i]){
if(document.getElementsByTagName('*')[i].className.indexOf(this.classe_img) > -1){
tmp.push(document.getElementsByTagName('*')[i]);
}
i++;
}
}
var j=tmp.length;
while(j--){
if(tmp[j].filters){
tmp[j].style.width = tmp[j].style.width || tmp[j].offsetWidth+'px';
tmp[j].style.filter = 'alpha(opacity=100)';
tmp[j].opaque = tmp[j].filters[0];
this.coeff = 1;
}
else{
tmp[j].opaque = tmp[j].style;
}
}
return tmp;
}
Fondu.prototype.change = function(sens){
if(this.encours){
return false;
}
var prevObj = this.collection[this.courant];
this.encours = true;
if(sens){
this.courant++;
if(this.courant>this.total){
this.courant = 0;
}
}
else{
this.courant--;
if(this.courant<0){
this.courant = this.total;
}
}
var nextObj = this.collection[this.courant];
nextObj.style.zIndex = 50;
var tmpOp = 100;
var that = this;
var timer = setInterval(function(){
if(tmpOp<0){
clearInterval(timer);
timer = null;
prevObj.opaque.opacity = 0;
nextObj.style.zIndex = 100;
prevObj.style.zIndex = 0;
prevObj.opaque.opacity = 100 / that.coeff;
that.encours = false;
}
else{
prevObj.opaque.opacity = tmpOp / that.coeff;
tmpOp -= 5;
}
}, 25);
}

Categories