Problem statement
To move the square along the perimeter of the viewport on click of the button as can be seen in the example:
https://codepen.io/vineetrok/pen/XRowdB
What do I need?
I'm using this code in combination with the transition property in the CSS. I think combination of transition and setInterval() is causing a delay. Is there a better and efficient method to accomplish this only using javascript?
Following is my code:
HTML
<div class="box" style="left:0;top:0"></div>
<button type="button" name="button" onclick="init()">Start!</button>
CSS
.box{
transition: all 1s linear;
}
JS
var elem = document.querySelector(".box");
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var dimension = elem.clientWidth;
var deltaX = viewportWidth - dimension;
var deltaY = viewportHeight - dimension;
function move(x,y){
if(x <=0 && y==0){
elem.style.left=(deltaX)+"px";
}
else if(x==(deltaX) && y==0){
elem.style.top=(deltaY)+"px";
}
else if(x==(deltaX) && y==(deltaY)){
elem.style.left="0px";
}
else if(x==0 && y==(deltaY)){
elem.style.top="0px";
}
}
function getCoordinates(elem){
return {
x: elem.getBoundingClientRect().left,
y: elem.getBoundingClientRect().top
}
}
var init = function(){
var clearTimer = 1;
var startTimer = setInterval(function(){
move(getCoordinates(elem).x,getCoordinates(elem).y )
}, 1000);
clearTimer++;
if(clearTimer>=4){
clearInterval(startTimer);
}
}
I would generally say that using both css and javascript to manage a transition is going to cause trouble. Part of the problem is that javascript timers aren't very precise. If you set a timer for 1 second it doesn't actually sleep for exactly one second. The exact amount of time it sleeps can vary depending on how busy the CPU is, what the user is doing, etc. It is very easy for the javascript timer to take longer than the CSS animation.
Since you are using jQuery I would use the jQuery.animate function to run things. It has a callback function that is invoked when the animation completes, and you can use that to execute the next step of the animation without any timers at all. That will make sure there aren't any delays. It should also be fairly performant. CSS animations are usually the slowest in terms of computer performance, so I expect jQuery.anmiate to probably be a bit better. There are other libraries out there designed for high performance animations, but unless performance actually becomes a problem, I wouldn't worry about it. Right now your issue is likely the imprecise timing of the timeout method, and not any performance issues.
Here's my go at it (I developed something from scratch instead of reusing your code) :
let box=document.getElementById("box"),
isLeft = false,
isTop = false
const toggleLeft = () => {
box.style.left = (isLeft=!isLeft) ? "calc( 100% - 50px )" : "0";
setTimeout(toggleTop, 2000);
}
const toggleTop = () => {
box.style.top = (isTop=!isTop) ? "calc( 100% - 50px )" : "0";
setTimeout(toggleLeft, 2000);
}
setTimeout(toggleLeft, 1000)
#box {
width: 50px;
height: 50px;
background: #00f;
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 2s ease-in-out;
transition: all 2s ease-in-out;
}
<div id="box"></div>
And a more condensed and recursive version :
let box=document.getElementById("box"),
is = { left : false, top : false }
const toggle = what => {
box.style[what] = (is[what]=!is[what]) ? "calc( 100% - 50px )" : "0";
setTimeout(()=>toggle(what==="left"?"top":"left"), 2000);
}
setTimeout(()=>toggle("left"), 100)
#box {
width: 50px;
height: 50px;
background: #00f;
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 2s ease-in-out;
transition: all 2s ease-in-out;
}
<div id="box"></div>
Related
I've been trying to create a fadeIn & fadeOut animation using VanillaJS in my project but I literally don't understand what's the problem. I'm using SCSS. I made it simple for you.
I tried visibility but it didn't work too. like it appears e.g. for 200ms but then immediately disappears. In another way of explanation, it appears whenever I click on it (stable) and then goes away after 200ms (unstable).
const fade = () => {
const box = document.querySelector('#box');
box.classList.toggle('fade');
};
document.querySelector('#fadebtn').addEventListener('click', fade);
#box {
width: 70px;
height: 50px;
background: #FD7A6B;
display: none;
opacity: 0;
-webkit-transition: 200ms ease-in-out;
-moz-transition: 200ms ease-in-out;
-o-transition: 200ms ease-in-out;
transition: 200ms ease-in-out;
}
#box.fade {
display: block !important;
opacity: 1 !important;
}
// I also tried this, wondered it may work, but didn't.
// .fade {
// display: block !important;
// opacity: 1 !important;
// }
<button type="button" id="fadebtn">Fade</button>
<div id="box"></div>
I wrote this due to the title of the question: "Fade in ... pure javascript ... simple way."
tl;dr https://jsfiddle.net/nqfud4j0/
The following solution is a basic example of how you can use only Javascript to fade in/out to a desired value. You could also use this with other values/properties, but it also serves as an example for basic tweening.
It's intentionally using setInterval rather than requestAnimationFrame to demonstrate the example's use of time + controlled framerate rather than a delta or 'fast as possible.' A good solution would abstract this logic into a tweening library that combines both RAF + intervals to manage latency between frames.
function fadeTo(element, toValue = 0, duration = 200) {
// Store our element's current opacity (or default to 1 if null)
const fromValue = parseFloat(element.style.opacity) || 1;
// Mark the start time (in ms). We use this to calculate a ratio
// over time that applied to our supplied duration argument
const startTime = Date.now();
// Determines time (ms) between each frame. Sometimes you may not
// want a full 60 fps for performance reasons or aesthetic
const framerate = 1000 / 60; // 60fps
// Store reference to interval (number) so we can clear it later
let interval = setInterval(() => {
const currentTime = Date.now();
// This creates a normalized number between now vs when we
// started and how far into our desired duration it goes
const timeDiff = (currentTime - startTime) / duration;
// Interpolate our values using the ratio from above
const value = fromValue - (fromValue - toValue) * timeDiff;
// If our ratio is >= 1, then we're done.. so stop processing
if (timeDiff >= 1) {
clearInterval(interval);
interval = 0;
}
// Apply visual. Style attributes are strings.
element.style.opacity = value.toString();
}, framerate)
}
// Element reference
const element = document.querySelector('div');
// Fade in and out on click
element.addEventListener('click', e => {
// Animates our element from current opacity (1.0) to 0.25 for 1000ms
fadeTo(element, 0.25, 1000);
// Waits 1s, then animates our element's opacity to 1.0 for 500ms
setTimeout(() => {
fadeTo(element, 1.0, 500);
}, 1000);
});
This question already has answers here:
How can I force WebKit to redraw/repaint to propagate style changes?
(33 answers)
Closed 4 years ago.
Currently I am working on an animation for a website which involves two elements having their position changed over a period of time and usually reset to their initial position. Only one element will be visible at a time and everything ought to run as smoothly as possible.
Before you ask, a CSS-only solution is not possible as it is dynamically generated and must be synchronised. For the sake of this question, I will be using a very simplified version which simply consists of a box moving to the right. I shall be referring only to this latter example unless explicitly stated for the remainder of this question to keep things simple.
Anyway, the movement is handled by the CSS transition property being set so that the browser can do the heavy lifting for that. This transition must then be done away with in order to reset the element's position in an instant. The obvious way of doing so would be to do just that then reapply transition when it needs to get moving again, which is also right away. However, this isn't working. Not quite. I'll explain.
Take a look at the JavaScript at the end of this question or in the linked JSFiddle and you can see that is what I'm doing, but setTimeout is adding a delay of 25ms in between. The reason for this is (and it's probably best you try this yourself) if there is either no delay (which is what I want) or a very short delay, the element will either intermittently or continually stay in place, which isn't the desired effect. The higher the delay, the more likely it is to work, although in my actual animation this causes a minor jitter because the animation works in two parts and is not designed to have a delay.
This does seem like the sort of thing that could be a browser bug but I've tested this on Chrome, Firefox 52 and the current version of Firefox, all with similar results. I'm not sure where to go from here as I have been unable to find this issue reported anywhere or any solutions/workarounds. It would be much appreciated if someone could find a way to get this reliably working as intended. :)
Here is the JSFiddle page with an example of what I mean.
The markup and code is also pasted here:
var box = document.getElementById("box");
//Reduce this value or set it to 0 (I
//want rid of the timeout altogether)
//and it will only function correctly
//intermittently.
var delay = 25;
setInterval(function() {
box.style.transition = "none";
box.style.left = "1em";
setTimeout(function() {
box.style.transition = "1s linear";
box.style.left = "11em";
}, delay);
}, 1000);
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
Force the DOM to recalculate itself before setting a new transition after reset. This can be achieved for example by reading the offset of the box, something like this:
var box = document.getElementById("box");
setInterval(function(){
box.style.transition = "none";
box.style.left = "1em";
let x = box.offsetLeft; // Reading a positioning value forces DOM to recalculate all the positions after changes
box.style.transition = "1s linear";
box.style.left = "11em";
}, 1000);
body {
background-color: rgba(0,0,0,0);
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
See also a working demo at jsFiddle.
Normally the DOM is not updated when you set its properties until the script will be finished. Then the DOM is recalculated and rendered. However, if you read a DOM property after changing it, it forces a recalculation immediately.
What happens without the timeout (and property reading) is, that the style.left value is first changed to 1em, and then immediately to 11em. Transition takes place after the script will be fihished, and sees the last set value (11em). But if you read a position value between the changes, transition has a fresh value to go with.
Instead of making the transition behave as an animation, use animation, it will do a much better job, most importantly performance-wise and one don't need a timer to watch it.
With the animation events one can synchronize the animation any way suited, including fire of a timer to restart or alter it.
Either with some parts being setup with CSS
var box = document.getElementById("box");
box.style.left = "11em"; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = "1em";
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
animation: move_me 1s linear 4;
}
#keyframes move_me {
0% { left: 1em; }
}
<div id="box"></div>
Or completely script based
var prop = 'left', value1 = '1em', value2 = '11em';
var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = '#keyframes move_me {0% { ' + prop + ':' + value1 +' }}';
document.getElementsByTagName('head')[0].appendChild(s);
var box = document.getElementById("box");
box.style.animation = 'move_me 1s linear 4';
box.style.left = value2; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = value1;
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
Yesterday I asked a question (original question) that was promptly answered, but even though a solution was found, I don't understand why this is working the way it is. I can duplicate this solution for other things I need to do but before I continue on I would like to understand why this works the way it does.
So basically I made three functions that called each other. The 1st called the second upon "animationend" and the second called the third upon an "animationend" and the finally the third function called the first to start the cycle all over again - BUT My original code though lacked;
document.getElementById("rightBoxTwo").style.animation = "none";
which was needed in-order for the third function to call the first so the cycle starts all over again. Without the above code in each function the three functions would work only once and then stop. The answer that StackOverFlow user; ScientiaEtVeritas gave me included a CodePen which had a working example of what I needed and a brief explanation
So, I think you have several options: What could work is that you
reset the the animation of rightBox in function runTwo with
animation: none. If you assign scrollTextTwo 10s back to the
rightBox it should start again. Equivalent for the other ones.
So finally my question is WHY does the animation need to be cleared, and why does the .style.animation = "none"; accomplish this?
below is the working code after a solution was presented...
<body onload="runOne()">
function runOne() {
var x = document.getElementById("rightBox");
x.addEventListener("animationend",runTwo);
document.getElementById("rightBox").style.animation = "scrollTextTwo 10s";
document.getElementById("rightBoxTwo").style.animation = "none";
}
function runTwo() {
var x = document.getElementById("rightBoxTwo");
x.addEventListener("animationend",runThree);
document.getElementById("rightBoxTwo").style.animation =
"scrollTextTwo 10s";
document.getElementById("rightBoxThree").style.animation = "none";
}
function runThree() {
var x = document.getElementById("rightBoxThree");
x.addEventListener("animationend",runOne);
document.getElementById("rightBoxThree").style.animation =
"scrollTextTwo 10s";
document.getElementById("rightBox").style.animation = "none";
}
The simplest reason is because setting the animation to the same thing twice (or more times) in a synchronous manner like a for loop is the same as doing it once:
let box = document.getElementById('box');
// animation happens once
for (let i = 0; i < 5; i++) {
box.style.animation = 'fade .5s';
}
#keyframes fade {
from {
opacity: 1
}
to {
opacity: 0
}
}
#box {
height: 200px;
width: 200px;
margin: 50px;
background: #018bbc;
}
<div id="box"></div>
The behavior is the same even if you delay the animation so each time it runs after a possible render:
let box = document.getElementById('box');
// animation still happens once
for (let i = 0; i < 5; i++) {
setTimeout(function() {
box.style.animation = 'fade .5s';
}, i * 1000);
}
#keyframes fade {
from {
opacity: 1
}
to {
opacity: 0
}
}
#box {
height: 200px;
width: 200px;
margin: 50px;
background: #018bbc;
}
<div id="box"></div>
But if I reset the animation before each step, the engine has to re-set the animation, which in a way means to "install the animation again", meaning it will be animated again:
let box = document.getElementById('box');
box.addEventListener('animationend', function() {
box.style.animation = 'none';
});
// animation now happens every time
for (let i = 0; i < 5; i++) {
setTimeout(function() {
box.style.animation = 'fade .5s';
}, i * 1000);
}
#keyframes fade {
from {
opacity: 1
}
to {
opacity: 0
}
}
#box {
height: 200px;
width: 200px;
margin: 50px;
background: #018bbc;
}
<div id="box"></div>
You don't really need javascript for something like this. Keyframes let you define styles by percent complete. Using that you can time 2 animations for a similar result:
#keyframes progress {
0% { width: 0px;}
50% { width: 600px;}
100% {width: 600px;}
}
#keyframes progress2 {
0% { width: 600px;}
49% { width:600px;}
50% { width: 0px;}
100% {width: 600px;}
}
div {
width:600px;
height:50px;
background-color:black;
}
#rightBox {
animation: progress 4s infinite;
}
#rightBoxTwo {
animation: progress2 4s infinite;
}
<div id="rightBox"></div>
<div id="rightBoxTwo"></div>
As this question observes, immediate CSS transitions on newly-appended elements are somehow ignored - the end state of the transition is rendered immediately.
For example, given this CSS (prefixes omitted here):
.box {
opacity: 0;
transition: all 2s;
background-color: red;
height: 100px;
width: 100px;
}
.box.in { opacity: 1; }
The opacity of this element will be set immediately to 1:
// Does not animate
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.addClass('in');
I have seen several ways of triggering the transition to get the expected behaviour:
// Does animate
var $b = $('<div>')
.addClass('box b')
.appendTo('#wrapper');
setTimeout(function() {
$('.b').addClass('in');
},0);
// Does animate
var $c = $('<div>')
.addClass('box c')
.appendTo('#wrapper');
$c[0]. offsetWidth = $c[0].offsetWidth
$c.addClass('in');
// Does animate
var $d = $('<div>')
.addClass('box d')
.appendTo('#wrapper');
$d.focus().addClass('in');
The same methods apply to vanilla JS DOM manipulation - this is not jQuery-specific behaviour.
Edit - I am using Chrome 35.
JSFiddle (includes vanilla JS example).
Why are immediate CSS animations on appended elements ignored?
How and why do these methods work?
Are there other ways of doing it
Which, if any, is the preferred solution?
The cause of not animating the newly added element is batching reflows by browsers.
When element is added, reflow is needed. The same applies to adding the class. However when you do both in single javascript round, browser takes its chance to optimize out the first one. In that case, there is only single (initial and final at the same time) style value, so no transition is going to happen.
The setTimeout trick works, because it delays the class addition to another javascript round, so there are two values present to the rendering engine, that needs to be calculated, as there is point in time, when the first one is presented to the user.
There is another exception of the batching rule. Browser need to calculate the immediate value, if you are trying to access it. One of these values is offsetWidth. When you are accessing it, the reflow is triggered. Another one is done separately during the actual display. Again, we have two different style values, so we can interpolate them in time.
This is really one of very few occasion, when this behaviour is desirable. Most of the time accessing the reflow-causing properties in between DOM modifications can cause serious slowdown.
The preferred solution may vary from person to person, but for me, the access of offsetWidth (or getComputedStyle()) is the best. There are cases, when setTimeout is fired without styles recalculation in between. This is rare case, mostly on loaded sites, but it happens. Then you won't get your animation. By accessing any calculated style, you are forcing the browser to actually calculate it.
Using jQuery try this (An Example Here.):
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.css('opacity'); // added
$a.addClass('in');
Using Vanilla javaScript try this:
var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e);
window.getComputedStyle(e).opacity; // added
e.className += ' in';
Brief idea:
The getComputedStyle() flushes all pending style changes and
forces the layout engine to compute the element's current state, hence
.css() works similar way.
About css()from jQuery site:
The .css() method is a convenient way to get a style property from the
first matched element, especially in light of the different ways
browsers access most of those properties (the getComputedStyle()
method in standards-based browsers versus the currentStyle and
runtimeStyle properties in Internet Explorer) and the different terms
browsers use for certain properties.
You may use getComputedStyle()/css() instead of setTimeout. Also you may read this article for some details information and examples.
Please use the below code, use "focus()"
Jquery
var $a = $('<div>')
.addClass('box a')
.appendTo('#wrapper');
$a.focus(); // focus Added
$a.addClass('in');
Javascript
var e = document.createElement('div');
e.className = 'box e';
document.getElementById('wrapper').appendChild(e).focus(); // focus Added
e.className += ' in';
I prefer requestAnimationFrame + setTimeout (see this post).
const child = document.createElement("div");
child.style.backgroundColor = "blue";
child.style.width = "100px";
child.style.height = "100px";
child.style.transition = "1s";
parent.appendChild(child);
requestAnimationFrame(() =>
setTimeout(() => {
child.style.width = "200px";
})
);
Try it here.
#Frizi's solution works, but at times I've found that getComputedStyle has not worked when I change certain properties on an element. If that doesn't work, you can try getBoundingClientRect() as follows, which I've found to be bulletproof:
Let's assume we have an element el, on which we want to transition opacity, but el is display:none; opacity: 0:
el.style.display = 'block';
el.style.transition = 'opacity .5s linear';
// reflow
el.getBoundingClientRect();
// it transitions!
el.style.opacity = 1;
Anything fundamentally wrong with using keyframes for "animate on create"?
(if you strictly don't want those animations on the initial nodes, add another class .initial inhibitin animation)
function addNode() {
var node = document.createElement("div");
var textnode = document.createTextNode("Hello");
node.appendChild(textnode);
document.getElementById("here").appendChild(node);
}
setTimeout( addNode, 500);
setTimeout( addNode, 1000);
body, html { background: #444; display: flex; min-height: 100vh; align-items: center; justify-content: center; }
button { font-size: 4em; border-radius: 20px; margin-left: 60px;}
div {
width: 200px; height: 100px; border: 12px solid white; border-radius: 20px; margin: 10px;
background: gray;
animation: bouncy .5s linear forwards;
}
/* suppres for initial elements */
div.initial {
animation: none;
}
#keyframes bouncy {
0% { transform: scale(.1); opacity: 0 }
80% { transform: scale(1.15); opacity: 1 }
90% { transform: scale(.9); }
100% { transform: scale(1); }
}
<section id="here">
<div class="target initial"></div>
</section>
Rather than trying to force an immediate repaint or style calculation, I tried using requestAnimationFrame() to allow the browser to paint on its next available frame.
In Chrome + Firefox, the browser optimizes rendering too much so this still doesn't help (works in Safari).
I settled on manually forcing a delay with setTimeout() then using requestAnimationFrame() to responsibly let the browser paint. If the append hasn't painted before the timeout ends the animation might be ignored, but it seems to work reliably.
setTimeout(function () {
requestAnimationFrame(function () {
// trigger the animation
});
}, 20);
I chose 20ms because it's larger than 1 frame at 60fps (16.7ms) and some browsers won't register timeouts <5ms.
Fingers crossed that should force the animation start into the next frame and then start it responsibly when the browser is ready to paint again.
setTimeout() works only due to race conditions, requestAnimationFrame() should be used instead. But the offsetWidth trick works the best out of all options.
Here is an example situation. We have a series of boxes that each need to be animated downward in sequence. To get everything to work we need to get an animation frame twice per element, here I put once before the animation and once after, but it also seems to work if you just put them one after another.
Using requestAnimationFrame twice works:
Works regardless of how exactly the 2 getFrame()s and single set-class-name step are ordered.
const delay = (d) => new Promise(resolve => setTimeout(resolve, d));
const getFrame = () => new Promise(resolve => window.requestAnimationFrame(resolve));
async function run() {
for (let i = 0; i < 100; i++) {
const box = document.createElement('div');
document.body.appendChild(box);
// BEFORE
await getFrame();
//await delay(1);
box.className = 'move';
// AFTER
await getFrame();
//await delay(1);
}
}
run();
div {
display: inline-block;
background-color: red;
width: 20px;
height: 20px;
transition: transform 1s;
}
.move {
transform: translate(0px, 100px);
}
Using setTimeout twice fails:
Since this is race condition-based, exact results will vary a lot depending on your browser and computer. Increasing the setTimeout delay helps the animation win the race more often, but guarantees nothing.
With Firefox on my Surfacebook 1, and with a delay of 2ms / el, I see about 50% of the boxes failing. With a delay of 20ms / el I see about 10% of the boxes failing.
const delay = (d) => new Promise(resolve => setTimeout(resolve, d));
const getFrame = () => new Promise(resolve => window.requestAnimationFrame(resolve));
async function run() {
for (let i = 0; i < 100; i++) {
const box = document.createElement('div');
document.body.appendChild(box);
// BEFORE
//await getFrame();
await delay(1);
box.className = 'move';
// AFTER
//await getFrame();
await delay(1);
}
}
run();
div {
display: inline-block;
background-color: red;
width: 20px;
height: 20px;
transition: transform 1s;
}
.move {
transform: translate(0px, 100px);
}
Using requestAnimationFrame once and setTimeout usually works:
This is Brendan's solution (setTimeout first) or pomber's solution (requestAnimationFrame first).
# works:
getFrame()
delay(0)
ANIMATE
# works:
delay(0)
getFrame()
ANIMATE
# works:
delay(0)
ANIMATE
getFrame()
# fails:
getFrame()
ANIMATE
delay(0)
The once case where it doesn't work (for me) is when getting a frame, then animating, then delaying. I do not have an explanation why.
const delay = (d) => new Promise(resolve => setTimeout(resolve, d));
const getFrame = () => new Promise(resolve => window.requestAnimationFrame(resolve));
async function run() {
for (let i = 0; i < 100; i++) {
const box = document.createElement('div');
document.body.appendChild(box);
// BEFORE
await getFrame();
await delay(1);
box.className = 'move';
// AFTER
//await getFrame();
//await delay(1);
}
}
run();
div {
display: inline-block;
background-color: red;
width: 20px;
height: 20px;
transition: transform 1s;
}
.move {
transform: translate(0px, 100px);
}
Edit: the technique used in the original answer, below the horizontal rule, does not work 100% of the time, as noted in the comments by mindplay.dk.
Currently, if using requestAnimationFrame(), pomber's approach is probably the best, as can be seen in the article linked to in pomber's answer. The article has been updated since pomber answered, and it now mentions requestPostAnimationFrame(), available behind the Chrome flag --enable-experimental-web-platform-features now.
When requestPostAnimationFrame() reaches a stable state in all major browsers, this will presumably work reliably:
const div = document.createElement("div");
document.body.appendChild(div);
requestPostAnimationFrame(() => div.className = "fade");
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}
For the time being, however, there is a polyfill called AfterFrame, which is also referenced in the aforementioned article. Example:
const div = document.createElement("div");
document.body.appendChild(div);
window.afterFrame(() => div.className = "fade");
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}
<script src="https://unpkg.com/afterframe/dist/afterframe.umd.js"></script>
Original answer:
Unlike Brendan, I found that requestAnimationFrame() worked in Chrome 63, Firefox 57, IE11 and Edge.
var div = document.createElement("div");
document.body.appendChild(div);
requestAnimationFrame(function () {
div.className = "fade";
});
div {
height: 100px;
width: 100px;
background-color: red;
}
.fade {
opacity: 0;
transition: opacity 2s;
}
Hey guys the solution to this should be simple, but im having difficulty figuring out what's going on.
I have a timerScript.js file that looks like this
//global variables
var timerInterval = null; // the timer that changes opacity every 0.1 seconds.
function StartTimer()
{
//disable the button
document.getElementById('startOpacityTimerButton').disabled=true;
timerInterval = window.setInterval(ChangeOpacity(), 100);
}
function StopTimer()
{
window.clearInterval(timerInterval);
timerInterval = 0;
}
function ChangeOpacity()
{
var object = document.getElementById('opacityZone');
var currentOpacity = (+object.style.opacity);
var newOpacity = currentOpacity + 0.1;
object.style.opacity = newOpacity;
if(newOpacity == 1.0)
{StopTimer();}
}
This is what my code is supposed to do
Click button -> Calls StartTimer
StartTimer -> Disables button, calls ChangeOpacity every 100 milliseconds.
ChangeOpacity -> gets the div element(opacityZone), gets its current opacity,
increments by 0.1 and checks if it is at max opacity in which case it calls StopTimer.
StopTimer -> clears the timer.
This is what it does:
Timer starts, changes opacity to 0.1, and just seems to stop!?!
I tried debugging with safari Web Inspector, but im not too sure what's going on, maybe one of you JavaScript experts can help me out (im a noob at js). Thanks!
Your problem is here:
window.setInterval(ChangeOpacity(), 100);
Instead of passing a reference to the function, you're now executing it inline and scheduling its return value. Change it to:
window.setInterval(ChangeOpacity, 100);
Apart from that, you should really use CSS transitions for stuff like this.
Thanks guys, i'll take a look at the suggestions. Was just trying to do it with JavaScript for the purpose of learning the language, here are the JavaScript functions i came up with to solve the problem.
//global variables
var opacityIncreasing; //boolean to know if opacity is increasing or decreasing.
var animationInterval;//time in millseconds to do animation.
var timerInterval;//the timer that changes opacity depending on interval.
var object;//object we are doing the animation on.
var currentOpacity;//currentOpacity of object.
//var buttonMessage;//message to make object appear or dissapear depending on animation.
function init(elementName,rateOfAnimation)
{
var object = document.getElementById(elementName);
animationInterval = rateOfAnimation;
currentOpacity = Truncate((+object.style.opacity),1);
document.getElementById('messageContainer').innerHTML=currentOpacity;
if (currentOpacity==0)
{
opacityIncreasing = true;
}
else
{
opacityIncreasing = false;
}
StartTimer();
}
function StartTimer()
{
//disable the button
document.getElementById('startOpacityTimerButton').disabled=true;
timerInterval = window.setInterval(ChangeOpacity, animationInterval);
}
function StopTimer()
{
window.clearInterval(timerInterval);
timerInterval = 0;
//enable Button
document.getElementById('startOpacityTimerButton').disabled=false;
}
function Truncate (number, digits)
{
var multiplier = Math.pow(10, digits),
adjustedNum = number * multiplier,
truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);
return truncatedNum / multiplier;
}
function ChangeOpacity()
{
var object = document.getElementById('opacityZone');
var stringOpValue = "";
if(opacityIncreasing)
{
currentOpacity += 1/10;
stringOpValue = String(currentOpacity.toFixed(1));
object.setAttribute("style","opacity:"+currentOpacity+"; -moz-opacity:"+currentOpacity+";");// filter:alpha(opacity="++")");
document.getElementById('messageContainer').innerHTML= stringOpValue;
if(currentOpacity.toFixed(1) == 1.0)
{
document.getElementById('startOpacityTimerButton').value = "Disappear";
StopTimer();
}
}
else
{
currentOpacity -= 1/10;
stringOpValue = String(currentOpacity.toFixed(1));
object.setAttribute("style","opacity:"+currentOpacity+"; -moz-opacity:"+currentOpacity+";");// filter:alpha(opacity="++")");
document.getElementById('messageContainer').innerHTML= stringOpValue;
if(currentOpacity.toFixed(1) == 0.0)
{
document.getElementById('startOpacityTimerButton').value = "Appear";
StopTimer();
}
}
}
This is the HTML and CSS
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta charset="utf-8">
<title>Opacity Test</title>
<style>
body
{
text-align: center;
}
#opacityZone
{
width: 350px;
height: 25px;
background-color: #F50;
text-align: center;
margin:0 auto;
margin-top: 10px;
margin-bottom: 10px;
padding-top: 5px;
/*opacity number between 0.0 and 1.0*/
opacity: 0.0;
}
#messageContainer
{
width: 100px;
min-height: 100px;
background-color:red;
color: white;
font-weight: bolder;
font-size: 72px;
text-align: center;
margin:0 auto;
padding-top: 10px;
}
.roundedContainer
{
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
border-radius: 15px,15px,15px,15px;
}
</style>
</head>
<body>
<h2>Opacity Test</h2>
<form>
<input type="button" id="startOpacityTimerButton" value="Appear" onclick="init('opacityZone',50);" />
</form>
<div id="opacityZone">Do you see me?</div>
<p id="messageContainer" class="roundedContainer"></p>
</body>
</html>
pass a function reference to window.setInterval. so pass ChangeOpacity and not ChangeOpacity()
timerInterval = window.setInterval(ChangeOpacity, 100);
Have you considered using CSS3 transition effects instead of making it using JavaScript? Performance wise it should be much better:
For example:
-webkit-transition: opacity 1s ease-in-out;
-moz-transition: opacity 1s ease-in-out;
-o-transition: opacity 1s ease-in-out;
transition: opacity 1s ease-in-out;
What everyone else above has been saying I completely agree with.
Just use CSS3 Animations to change the opacity of the button.
Simply use something along these lines:
#keyframes opacityChange{
from {opacity: 0.1}
to {opacity: 1}
}
You can also declare the timeframe in which the change would take place.
And add a class via javascript/jquery to your button.
(class = "opacityChange")
And when clicking on a new button be sure to remove that class, so that it can be reimplemented to the button later on.
However, to fix your particular problem.
(If for some reason you can't use css3)
Simply add this to the Change Opacity function:
if(newOpacity == 1.0){
StopTimer();
}else{
ChangeOpacity();
}
Looking at how you have it set up, that should work, unless i'm looking over something.
I had same problem and after so many time searching this is my solution:
instead of this line
var currentOpacity = (+object.style.opacity);
var newOpacity = currentOpacity + 0.1;
you have to use this line:
let newOpacity = String(parseInt(window.getComputedStyle(Object).getPropertyValue('opacity'))+0.2))
for alternative answer you can do this (if you have a white background :) ):
let i =0 ;
let interval = setInterval(()=>{
i+=0.1
Object.style.color = `rgba(0,0,0,${i})`;
},1000)
if(Object.style.color === 'rgba(0,0,0,1)')
clearInterval(interval)
console.log()