Why don't if statments work when repeated in JavaScript? - javascript

I am trying to make a grid where the different boxes will blink based off of a binary value defined within my HTML document. I have created a grid in HTML, where the background colour is automatically green and what I'm trying to achieve is that if my value changes to from 0 to 1 for each of the grid items it will then change the colour to red and blink respectively.
I have managed to get the first one working and thought I could just repeat the code with different variables assigned, however this hasn't worked. The weird thing is, if I remove the code for the first box the second box will start working.
Do I need to add some extra code in JS to separate the if statments?
CSS'
.grid-container {
display: grid;
grid-gap: 50px;
grid-template-columns: auto auto auto;
background-color: grey;
padding: 10px;
}
.grid-item {
background-color: green;
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 50px;
font-size: 30px;
text-align: center;
}
HTML
<div class="grid-container">
<div class="grid-item" id = "blink1">A</div>
<div class="grid-item" id = "blink2">B</div>
</div>
<div class = "values">
<div id = "$box1value"> 1 </div>
<div id = "$box2value"> 1 </div>
</div>
JS
var $box1 = document.getElementById("$box1value").innerHTML;
if ($box1 > 0) {
document.getElementById("blink1").style.backgroundColor = '#ff0000';
// blink "on" state
function show() {
if (document.getElementById)
document.getElementById("blink1").style.visibility = "visible";
}
// blink "off" state
function hide() {
if (document.getElementById)
document.getElementById("blink1").style.visibility = "hidden";
}
for (var i = 900; i < 99999999999; i = i + 900) {
setTimeout("hide()", i);
setTimeout("show()", i + 450);
}
} else {
document.getElementById("blink1").style.backgroundColor = '#098700';
}
/////////////////////next box/////////////////////////////
var $box2 = document.getElementById("$box2value").innerHTML;
if ($box2 > 0) {
document.getElementById("blink2").style.backgroundColor = '#ff0000';// blink "on" state
function show() {
if (document.getElementById)
document.getElementById("blink2").style.visibility = "visible";
}
// blink "off" state
function hide() {
if (document.getElementById)
document.getElementById("blink2").style.visibility = "hidden";
}
for (var i = 900; i < 99999999999999999; i = i + 900) {
setTimeout("hide()", i);
setTimeout("show()", i + 450);
}
} else {
document.getElementById("blink2").style.backgroundColor = '#098700';
}

2 different solutions (all JS vs. mostly CSS)
Keeping the core functionality in JS
Leveraging CSS for core functionality
I see what you're trying to achieve here, and I see a couple of different ways to accomplish this. Both of the solutions below allow your code to dynamically loop through any number of box items— no need to write a separate block for each item.
The first example below is modeled more similar to yours, based on
your code but rewritten to work more dynamically. The second solution
further down greatly simplifies things by moving all initialization
scripting into CSS, leaving JS responsible for only boolean switching
if you need to make any real-time state switches.
#1. Keeping the core functionality in JS
This solution modifies your original code to dynamically read the values for however many values there are, and then looping through them. In order to perform the repeated blinking in JS, I would suggest using setInterval. You'll also need to move that outside the rest of the code when using a loop or you'll end up with a conflict between the loop's iterator and the setInterval's and setTimeout's timing. More on that here. You can see the working example below:
function blink(el) {
if (el.style) {
setInterval(function() {
el.style.visibility = "visible";
setTimeout(function() {
el.style.visibility = "hidden";
}, 450);
}, 900);
}
}
const $boxes = document.querySelectorAll('[id^="blink"]');
for (const $box of $boxes) {
var boxId = $box.id.match(/\d+/)[0]; // store the ID #
if (document.getElementById('$box' + boxId + 'value')) {
var boxValue = parseInt(document.getElementById('$box' + boxId + 'value').innerHTML);
if (boxValue) {
$box.style.backgroundColor = '#ff0000';
blink($box);
} else {
$box.style.backgroundColor = '#098700';
}
}
}
.grid-container {
display: grid;
grid-gap: 50px;
grid-template-columns: auto auto auto;
background-color: grey;
padding: 10px;
}
.grid-item {
background-color: #098700;
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 50px;
font-size: 30px;
text-align: center;
}
.values {
display: none;
}
<div class="grid-container">
<div class="grid-item" id="blink1">A</div>
<div class="grid-item" id="blink2">B</div>
<div class="grid-item" id="blink3">C</div>
</div>
<div class="values">
<div id="$box1value">1</div>
<div id="$box2value">0</div>
<div id="$box3value">1</div>
</div>
CodePen: https://codepen.io/brandonmcconnell/pen/ecc954bad5552962574c080631700932
#2. Leveraging CSS for core functionality
This solution moves all of your JS code (color and animation) to the CSS, moving the binary boolean switch 0/1 to data-attributes on the grid-items themselves instead of separate items and then trigger any boolean switches on those containers using JS by targeting them by another attribute such as ID, or as I used in my example below, another data-attribute I called data-blink-id. This is my recommended solution if you're able to move all of this logic into CSS. It'll be much easier to maintain and to manipulate in real-time, as all it requires to change state is a simple boolean switch.
.grid-container {
display: grid;
grid-gap: 50px;
grid-template-columns: auto auto auto;
background-color: grey;
padding: 10px;
}
.grid-item {
background-color: #098700;
border: 1px solid rgba(0, 0, 0, 0.8);
padding: 50px;
font-size: 30px;
text-align: center;
}
.grid-item[data-blink-status="1"] {
background-color: #f00;
animation: blink 900ms linear infinite forwards;
}
#keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
<div class="grid-container">
<div class="grid-item" data-blink-id="1" data-blink-status="1">A</div>
<div class="grid-item" data-blink-id="2" data-blink-status="0">B</div>
<div class="grid-item" data-blink-id="3" data-blink-status="1">C</div>
</div>
CodePen: https://codepen.io/brandonmcconnell/pen/5b4f3090b3590902b11d50af43361758
To trigger the binary boolean switch on an item (turn ON/OFF), use the below JS command. I've commented this out in the CodePen example linked above. Un-comment this JS line to activate it and switch ON the block with data-blink-id=2
document.querySelector('[data-blink-id="2"]').setAttribute('data-blink-status', 1);

Even though your functions are declared inside if statements, they are still global.
So, you essentially redeclare the show and hide functions, and they stop working.
To make those functions local to the if statement, you'll have to use one of the ES6 block scope declarations, let or const, like this:
const show = function(){ ... }
const hide = function(){ ... }
To do this, you should also replace setTimeout's first argument with a reference to the function (actually, you should always do that):
setTimeout(hide, i)
setTimeout(show, i + 450)
Other improvements you can make:
Avoid that loop that sets timeouts. It's ugly, takes long to execute, and doesn't work forever. Instead, replace setTimeouts with setIntervals.
Remove the if (document.getElementById) part. You can count on it to be defined (it has been around for a loooong time...)
So, you get to:
var $box1 = document.getElementById("$box1value").innerHTML;
if ($box1 > 0) {
document.getElementById("blink1").style.backgroundColor = '#ff0000';// blink "on" state
const show = function () {
document.getElementById("blink1").style.visibility = "visible";
}
// blink "off" state
const hide = function () {
document.getElementById("blink1").style.visibility = "hidden";
}
let flag = false //This is needed to keep track if the element is visible
setInterval(function(){
if(flag = !flag)
hide()
else
show()
}, 450);
} else {
document.getElementById("blink1").style.backgroundColor = '#098700';
}
/////////////////////next box/////////////////////////////
var $box2 = document.getElementById("$box2value").innerHTML;
if ($box2 > 0) {
document.getElementById("blink2").style.backgroundColor = '#ff0000';// blink "on" state
const show = function () {
document.getElementById("blink2").style.visibility = "visible";
}
// blink "off" state
const hide = function () {
document.getElementById("blink2").style.visibility = "hidden";
}
let flag = false //This is needed to keep track if the element is visible
setInterval(function(){
if(flag = !flag)
hide()
else
show()
}, 450);
} else {
document.getElementById("blink2").style.backgroundColor = '#098700';
}

Related

is there a way to change fixed element style when enter any section element on scroll

I have a chat or go to top svg btn with white border and some of my sections element have blue and other white background
what I want to do is when the fixed btn enter the section while scrolling check its background
and add different classes in each case .
how can I do that in javascript or jquery ?
thank you
The easiest thing to do would be to have the fixed button have a background color that looks good on all sections. That way you can just style it and leave it alone.
If you have to have the color change at different sections, there are a few ways of doing that, none are easy, and only a few would have good performance.
The best way of doing this that I can think of would be:
Have the background of the fixed button be the default color.
Add a class modifier so that when you add one class it changes the style to the new color. Example: .button becomes .button.red
On each section that has to change the background of the button, add a custom data-attribute Example: <section change-button-bg="red">
Then on load
Set up a .querySelectorAll(*[change-button-bg]) so that you can run
a check on each of the sections.
Add a global variable called currentTarget
Set up an Intersection Observer on all the sections.
Have the callback function for .isIntersecting do a few things.
Update the currentTaget variable
Update the color of the button
Add a scroll listener
In the scroll listener watch the bounds.bottom of currentTarget to see which color it should be.
Then in the Intersection Observer, if it's no longer intersecting, remove the scroll listener to prevent memory leaks.
Here is a working example.
window.addEventListener('load', (event) => {
const changeBG = document.querySelectorAll('*[change-button-bg]');
let currentTarget = null;
const Observer = new IntersectionObserver((entries, Observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
currentTarget = entry.target;
addColor(true);
window.addEventListener('scroll', watchTarget);
} else {
addColor(false);
window.removeEventListener('scroll', watchTarget)
}
}
}, {threshold: 0.15});
for (const element of changeBG) {
Observer.observe(element);
}
function watchTarget() {
const bounds = currentTarget.getBoundingClientRect();
if (bounds.bottom < window.innerHeight - 80) {
addColor(false);
} else {
addColor(true);
}
}
function addColor(add) {
const btn = document.getElementById('button');
if (add) {
btn.classList.add('red');
} else {
btn.classList.remove('red');
}
}
});
body {
margin: 0;
}
section {
width: 100vw;
height: 100vh;
background: red;
}
section:nth-child(even) {
background: blue;
}
button {
position:fixed;
right: 50px;
bottom: 50px;
padding: 15px 25px;
border: none;
color: white;
background-color: blue;
cursor: pointer;
}
button.red {
background-color: red;
}
<html>
<body>
<section></section>
<section change-button-bg="red"></section>
<section></section>
<section change-button-bg="red"></section>
<section></section>
<button id="button">Top</button>
</body>
</html>
this is the solution I was looking for I did it using Intersection Observer
document.addEventListener('DOMContentLoaded',()=>{
let options = {
root:null,
rootMargin:"-570px 0px -100px 0px",
threshold:0.05
};
let Observer= new IntersectionObserver(changColor,options);
document.querySelectorAll("section").forEach(section => {
Observer.observe(section);
});
});
function changColor(elements) {
elements.forEach(el => {
if (el.isIntersecting) {
let elbg=el.target.dataset.bg;
if (elbg=="blue") { //if section data-bg== blue
// change svg button style
document.getElementById("chatting_path_7").style.fill = "#fff";
document.getElementById("to_top_Ellipse_4").style.stroke = "#fff";
} else {
document.getElementById("chatting_path_7").style.fill = "#034ea2";
document.getElementById("to_top_Ellipse_4").style.stroke = "#034ea2";
}
}
})
}

How to activate different functions if you are at certain part of the page?

I want to make two functions, "functionattoppage" and "functionat1000pxpage". I want to make the function "functionattoppage" activate when the user is at the top of a webpage and "functionat1000pxpage" activate when the user is down about 1000px from the top of the page, is this possible?
Here is the only thing I could come up with:
window.onscroll = function () {
scrollFunction()
};
function scrollFunction() {
if (document.body.scrollTop > 1000 || document.documentElement.scrollTop > 1000) {
document.body.classList.add("body.changed");
}
if (document.body.scrollTop > 0 || document.documentElement.scrollTop > 0) {
document.body.classList.remove("body.changed");
}
}
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
Sorry if I didn't explain this very well, I don't have much time right now. If you need more information please ask me.
After console logging document.body.scrollTopin the fiddle came out to be zero. Plus your conditions are bound to fail because when scrollTop > 1000 then it is > 0 too.
you should either make it a else or move the >0 condition to the first
window.onscroll = function() {
scrollFunction()
};
function scrollFunction() {
console.log(document.documentElement.scrollTop);
if (document.documentElement.scrollTop > 200) {
document.getElementsByTagName('h1')[1].classList.add("changed");
}
else{
document.getElementsByTagName('h1')[1].classList.remove("changed");
}
}
.changed {
color: red
}
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
<div style="height: 400px;background: grey;"></div>
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
<div style="height: 400px;background: grey;"></div>
Looks like you are trying to hide a class when the scroll position for the window is within 0 and 1000 pixels yes? The a conditional that will check if the scrollY position of the window, window.scrollY is greater than 0, but less than 1000, add the class else remove the class.
window.onscroll = () => {
scrollFunction()
}
const scrollFunction = () => {
window.scrollY > 0 && window.scrollY < 1000 ?
document.body.classList.add("changed") :
document.body.classList.remove("changed")
document.getElementById("scroll_position").textContent = window.scrollY;
}
#scroll_position {
position: fixed;
top: 0;
right: 0;
background: red;
color: #fff;
padding: 8px;
width: 100px;
}
#cont {
height: 3000px;
}
#cont h1 {
position: sticky;
top: 50px;
}
.changed {
background: black;
color: white;
}
<p id="scroll_position"></p>
<div id="cont">
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
</div>
You can use a div as your marker. And when it is >= 0 execute one function and when it gets to -1000px execute another. Use getBoundingClientRect() to get an elements coordinates.
HTML
<div id=“marker1”></div>
JS
window.addEventListener( "scroll", function(){
let marker1 = document.getElementById("marker1").getBoundingClientRect();
if (marker1.top >= 0) atTop();
if (marker1.top <= -1000) at1000px();
})
function atTop() {
document.body.style.backgroundColor = "blue";
}
function at1000px() {
document.body.style.backgroundColor = "red";
}
Be aware the atTop won’t execute until you start scrolling because of the type of event listener. If you want it executed on load also then you’ll need to add that.
You can do it through sensing the scroll event as you are doing, but it's quite overheady.
Instead you could use an IntersectionObserver on a couple of elements, This will tell you when they come into and go out of the viewport and you don't have to worry about any intermediate scrolling.
If you already have elements that you want to sense in those positions you could sense those going into and out of the viewport. If not you can 'plant' a couple of 1px divs at the top and at 1000px and sense them coming in and out.
This snippet just logs the comings and goings to the console but of course you put whatever code you want in their place.
function callback (entries) {
for (let i = 0; i < entries.length; i++) {
if (entries[i].isIntersecting) {console.log(entries[i].target.classList + ' is in the viewport'); }
else {console.log(entries[i].target.classList + ' is not in the viewport'); }
}
}
const observer = new IntersectionObserver(callback);
const sensors = document.querySelectorAll('.sensor');
for (let i = 0; i < sensors.length; i++) {
observer.observe(sensors[i]);
}
.talldiv {
width: 100vw;
height: 2000px;
background-image: linear-gradient(to bottom, red, blue, orange, green, purple, cyan); /*just so we notice scrolling */
}
.sensor {
position: absolute;
left: 50%;
top: var(--top);
width: 1px;
height: 1px;
background: transparent;
}
.sensetop {
--top: 0px;
}
.sense1000px {
--top: 999px;
}
<div class="sensor sensetop"></div>
<div class="sensor sense1000px"></div>
<div class="talldiv"></div>

how to stop a sound when an animation ends

I have very little experience in coding in general. But I've somehow managed to get this far with this, and I'm stuck on the very last thing.
This is for a Twitch alert, I'm doing this through 'Stream Elements'
The thing I'm having issues with is stopping the sound once the typing letters have fully appeared, I have no idea how to do this. Is it even possible?
I Forgot to mention, the Typekit links are intentionally broken, as I didn't want to share the link (Since I'm assuming they're all unique and based off your adobe account)
$(document).ready(function() {
var timer, fullText, currentOffset, onComplete, hearbeat = document.getElementById('heartbeat');
heartbeat.play();
function Speak(person, text, callback) {
$("#usernamean-container").html(person);
fullText = text;
currentOffset = 0;
onComplete = callback;
timer = setInterval(onTick, 120
);
}
function onTick() {
currentOffset++;
if (currentOffset == fullText.length) {
complete();
return;
}
var text = fullText.substring(0, currentOffset);
$("#message").html(text);
}
function complete() {
clearInterval(timer);
timer = null;
$("#message").html(fullText);
onComplete()
;
}
$(".box").click(function () {
complete();
});
Speak("{{name}}",
"{{name}} Is now a Witness",
)
//get data from the 🤟 StreamElements 🤟 data injection
const name = '{{name}}';
// vanilla es6 query selection (can use libraries and frameworks too)
const userNameContainer = document.querySelector('#username-container');
// change the inner html to animate it 🤪
userNameContainer.innerHTML = stringToAnimatedHTML(name, animation);
/**
* return an html, with animation
* #param s: the text
* #param anim: the animation to use on the text
* #returns {string}
*/
function stringToAnimatedHTML(s, anim) {
let stringAsArray = s.split('');
stringAsArray = stringAsArray.map((letter) => {
return `<span class="animated-letter ${anim}">${letter}</span>`
});
return stringAsArray.join('');
}
heartbeat.pause();
heartbeat.currentTime = 0;
});
#import url(#import url("https://use.typekit.net/.css");
.awsome-text-container {
font-family: typeka, sans-serif;
font-size: 42px;
font-weight: 400;
}
.image-container {
margin: auto;
display: table;
}
.text-container {
font-family: typeka, sans-serif;
font-size: 26px;
color: rgb(204, 10, 33);
text-align: center;
margin: auto;
text-shadow: rgba(0, 0, 0, 0.5) 1px 1px 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="heart" class="heart">
<audio id="heartbeat" src="https://cdn.discordapp.com/attachments/135995830279733248/733547597305741332/typewriters.mp3" preload="auto"></audio>
<link rel="stylesheet" href="https://use.typekit.net/.css">
<div class="text-container">
<div class="image-container">
<img src="https://media.tenor.com/images/83d6a5ed40a24164dfe1e4e19fad23d9/tenor.gif">
</div>
<div>
<div class="awsome-text-container">
<span id="message"></span>
<br>
</div>
</div>
</div>
Hello and welcome to Stack Overflow!
I have seen messier code and was therefor disappointed ;-). Regarding your question:
Main problem would be that you have a typo in your code and you call the heartbeat.pause(); in the complete method and not at the end of script (as this would be called independently of the completion of the animation).
Typo:
hearbeat = document.getElementById('heartbeat');
Changed method:
function complete() {
clearInterval(timer);
timer = null;
$("#message").html(fullText);
heartbeat.pause();
heartbeat.currentTime = 0;
}
and remove the lines from the bottom of your script.

How to load an initial set of images, then animate between them randomly without jQuery

On my page I have a gallery (just a div) with several images on it. I want to show the first 9 images immediately, then load more images and use CSS transitions to animate between the existing images.
Loading the initial images is easy but I do not know the best way to load the next set of images and then start animating (using the CSS Transform property). So far this is what I have:
HTML (abbreviated):
<div id="mainContainer">
<div class="imageHolder"><img class="homeImages" src="test.png"></div>
<div class="imageHolder"><img class="homeImages" src="test1.png"></div>
<div class="imageHolder"><img class="homeImages" src="test3.png"></div>
</div>
CSS (abbreviated):
img {
display: inline;
position: relative;
margin: 0;
padding: 0;
width: 30%;
}
.changed.opaque {
opacity: 0;
border: 2px solid red;
}
I am looking to do a variety of effects, the most simple one would be to change the opacity and fade one image over the other. To load the next set of images I have this:
Javascript:
var imageArray = [
'test2.png',
'test3.png',
'test4.png',
'test5.png',
'test6.png',
];
var imageNodeArray = [];
for(var i = imageArray.length - 1; i >= 0; i -= 1) {
var img = new Image();
img.onload = function() {
imageNodeArray.push(this);
};
img.src = imageArray[i];
}
document.onclick = function() {
imageNodeArray[0].setAttribute('class', 'changed.opaque');
divs[0].appendChild(imageNodeArray[0])
}
This does add an image to my mainContainer however, even though I can tell from devTools that it has the changed.opaque class applied to it, no opacity is shown on the added image.
I am curious about this. I would also like to know the best way to "stack" images to have a bunch to animate through. I am not sure that appending child is right.... Thank you
function animate() {
var index = Math.floor((Math.random() * document.querySelectorAll('#mainContainer > .imageHolder').length + 1));
var current = document.querySelector('.top');
var next = document.querySelector('.imageHolder:nth-of-type(' + index + ')');
current.className = "imageHolder";
next.className += "top";
}
Should be able to handle and switch between any dynamically inserted images.
Currently using:
.imageHolder {
display: none;
}
.top {
display: inherit;
}
to switch the image is just a simple implementation.
Here's the working fiddle: http://jsfiddle.net/e9dxN/1/
Alternative implementation: http://jsfiddle.net/e9dxN/6/

Trigger CSS transition on appended element

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;
}

Categories

Resources