This question already has answers here:
Why is setTimeout(fn, 0) sometimes useful?
(19 answers)
Closed 3 years ago.
I notice that when I remove setTimeout from the code below, the animation stops working. Can someone explain to me what's happening under the hood?
Why is it necessary to insert this setTimeout here, even though the time value is 0? In my mind, it should execute right away, so why insert setTimeout(cb,0)?
setTimeout(() => {
... //see below for code
}, 0);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.message-ball {
font-size: 20px;
line-height: 200px;
text-align: center;
}
.circle {
transition-property: width, height, margin-left, margin-top;
transition-duration: 2s;
position: fixed;
transform: translateX(-50%) translateY(-50%);
background-color: red;
border-radius: 50%;
}
</style>
</head>
<body>
<button onclick="go()">Click me</button>
<script>
function go() {
showCircle(150, 150, 100, div => {
div.classList.add('message-ball');
div.append("Hello, world!");
});
}
function showCircle(cx, cy, radius, callback) {
let div = document.createElement('div');
div.style.width = 0;
div.style.height = 0;
div.style.left = cx + 'px';
div.style.top = cy + 'px';
div.className = 'circle';
document.body.append(div);
setTimeout(() => {
div.style.width = radius * 2 + 'px';
div.style.height = radius * 2 + 'px';
div.addEventListener('transitionend', function handler() {
div.removeEventListener('transitionend', handler);
callback(div);
});
}, 0);
}
</script>
</body>
</html>
Because transitions only happen when a property was changed. If you do
div.style.width = 0;
div style.width = "1000px";
That wil not animate anything as the property was directly changed. If you defer the second update, it will detect the change and cause a transition to happen.,
Related
I have some sort of poll where you vote either YES and NO and based on the votes it creates a poll chart (by creating two divs inside another div that has a set width and setting the width of the first two divs the percentage of YES and NO votes out of the total votes). You can see the project for a better understanding by clicking HERE.
I want it to appear animated as if it were in CSS with transition: width 100ms linear; just like here:
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
<style>
.poll{
height: 50px;
width: 300px;
background-color: black;
transition: all 300ms;
}
.poll:hover{
width: 500px;
}
</style>
</head>
<body>
<div class="poll"></div>
</body>
</html>
However, whenever I add something similar to the class of my divs I see no change. The divs in question are created in this function:
function renderPoll(){
container.innerHTML=''; //reset container
let poll1 = document.createElement('div');
let poll2 = document.createElement('div');
poll1.classList.add('poll-attr');
poll2.classList.add('poll-attr');
let innerTextPoll = Math.round(calcPerc()); //calcPerc() calculates the percent of YES votes with the equation percentage = (100*NumberOfYES)/NumberOfVotes
poll1.style.width = calcPerc() + '%';
poll2.style.width = 100-calcPerc() + '%';
poll1.innerText = innerTextPoll + '%';
poll2.innerText = 100-innerTextPoll + '%';
container.appendChild(poll1);
container.appendChild(poll2);
}
I am not nearly experienced enough to figure this out so any input is appreciated!
Bulding on your code and #Noel MarĂ³ti answer, indeed all you have to do is set interval for animating the polls after you add them to the container.
function renderPoll() {
container.innerHTML = ''; //reset container
let poll1 = document.createElement('div');
let poll2 = document.createElement('div');
poll1.classList.add('poll-attr');
poll2.classList.add('poll-attr');
let innerTextPoll = Math.round(calcPerc()); //calcPerc() calculates the percent of YES
poll1.innerText = innerTextPoll + '%';
poll2.innerText = 100 - innerTextPoll + '%';
container.appendChild(poll1);
container.appendChild(poll2);
var target_length = 300;
animation(poll1, 0, (calcPerc()) * target_length / 100);
animation(poll2, 0, (100 - calcPerc()) * target_length / 100);
}
function calcPerc() {
return 75;
}
function animation(elem, from, to) {
let id = null;
let width = from || 0;
var speed = 2.5;
requestAnimationFrame(frame);
function frame() {
if (width < to) {
width += speed;
elem.style.width = width + "px";
requestAnimationFrame(frame);
}
}
}
renderPoll();
.poll-attr {
border: 1px solid blue;
height: 50px;
background: lightyellow;
}
.poll {
height: 50px;
width: 300px;
background-color: black;
transition: all 300ms;
}
.poll:hover {
width: 500px;
}
<div class="poll"></div>
<div id="container"></div>
You can do it easily like this:
function animation () {
let id = null;
const elem = document.querySelector(".poll");
let width = 300; // default width
clearInterval(id);
id = setInterval(frame, 5); // changing the number will effect the speed of the animation
function frame() {
if (width == 500) { // if the width is 500px, then finish animation
clearInterval(id); // finish animation
} else {
width++;
elem.style.width = width + "px";
}
}
}
I'm still learning JS and I'm trying to move this div with arrows using Javascript but it moves only 1 step per click can someone please help me how to make it moves more steps
check my code please
let
div = document.getElementById("test");
function move(e){
if(e.keyCode ==40){
div.style.top = 10+"px"
}
if(e.keyCode ==38){
div.style.top = -10+"px" }
if(e.keyCode==39){
div.style.left = 10 +"px"
}
if(e.keyCode==37){
div.style.left = -10 +"px"
}
}
window.onkeydown = move;
div{
width: 200px;
height: 200px;
background-color: red;
position: absolute;
}
<!DOCTYPE html>
<html>
<head>
<title>index</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="test"></div>
<script src="test.js"></script>
</body>
</html>
Try this code, you need the current position
I use getBoundingClientRect
You might also look at code instead of keyCode
const div = document.getElementById("test");
function move(e) {
const pos = div.getBoundingClientRect()
let top = pos.top;
let left = pos.left;
const code = e.code;
switch (code) {
case "ArrowRight": left += 10; break;
case "ArrowLeft" : left -= 10; break;
case "ArrowUp" : top -= 10; break;
case "ArrowDown" : top += 10; break;
}
div.style.top = `${top}px`;
div.style.left = `${left}px`;
}
window.addEventListener("keydown",move);
div {
width: 200px;
height: 200px;
background-color: red;
position: absolute;
}
<div id="test"></div>
You can save the current position then move it again, set an offset and add/remove distance at every keydown
const div = document.getElementById("test");
let position = 0
const offset = 10;
function move(e) {
if (e.keyCode == 40) {
position += offset;
div.style.top = position + "px"
}
if (e.keyCode == 38) {
position -= offset;
div.style.top = position + "px"
}
if (e.keyCode == 39) {
position += offset;
div.style.left = position + "px"
}
if (e.keyCode == 37) {
position -= offset;
div.style.left = position + "px"
}
}
document.onkeydown = move;
div {
width: 200px;
height: 200px;
background-color: red;
position: absolute;
}
<!DOCTYPE html>
<html>
<head>
<title>index</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="test"></div>
<script src="test.js"></script>
</body>
</html>
You are setting its offset to a fixed value of 10 or -10.
You need to store the current position somehow and offset the new position based on the current one.
//Intiate once at the beginning of the script
let currentVerticalPosition = 0;
if(e.keyCode ==40){
currentVerticalPosition += 10;
div.style.top = currentVerticalPosition +"px";
}
I'm experimenting with adding divs to the DOM programmatically. I want to arrange them in a circle within a "master" div,
<body>
<div id="master">
</div>
<script src="js/main.js"></script>
</body>
thusly:
window.addEventListener('load',init);
let master;
function init() {
master = document.getElementById('master');
const count = 8;
const radius = 50;
for (let i=0; i<count;i++){
const diva = document.createElement('div');
diva.id = i;
master.appendChild(diva);
let {x,y} = returnCoords(100,100,degreesToRad((360/count) * i),radius);
diva.style.left = x + "px";
diva.style.top = y + "px";
console.log(diva.style.width); // empty!
}
}
function degreesToRad(degrees) {
return degrees * (Math.PI / 180);
}
function radToDegrees(rad) {
return radians * (180 / Math.PI);
}
function returnCoords(originX = 0, originY = 0,radians,radius) {
let x = originX + (Math.cos(radians) * radius);
let y = originY + (Math.sin(radians) * radius);
return ({x,y});
}
These divs are styled by SCSS:
#master {
background:pink;
height:300px;
width:300px;
position: relative;
div {
background: white;
height:20px;
width:20px;
border:blueviolet solid 1px;
position:absolute;
}
}
And this all works fine. However I can't get the width of the div, even after it's added into the DOM. The line console.log(diva.style.width); produces nothing. Thus I can't move the divs based on their width. What am I missing?
The .style property reads the inline styles of an element, not the ones assigned by CSS. Confusing, for sure. Read more about it here: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style
I am writing a small program where I move DOMs at a specified speed.
When I move it at the rate of 20px per second, the offset that gets added to the elem.style.top is about 0.3px per frame.
The problem is, when this offset is smaller than 0.5px, elem doesn't move!
I constructed a simplified example that can demonstrate the issue in my program:
var requestFrameAnimationId;
function myMove(offset) {
var elem = document.getElementById("animate");
requestFrameAnimationId = animationLoop(frame);
function frame() {
console.log(elem.offsetTop);
if (elem.offsetTop === 350) {
cancelAnimationFrame(requestFrameAnimationId);
} else {
elem.style.top = elem.offsetTop + offset + 'px';
elem.style.left = elem.offsetLeft + offset + 'px';
}
}
}
function animationLoop(render) {
var running, lastFrame = +new Date(); // casting Date to Number
function loop(now) {`enter code here`
requestFrameAnimationId = requestAnimationFrame(loop);
running = render(now - lastFrame);
lastFrame = now;
}
loop(lastFrame);
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<!DOCTYPE html>
<html>
<body>
<p>
<button onclick="myMove(0.3)">Move at 0.3px per frame</button>
<button onclick="myMove(0.5)">Move at 0.5px per frame</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
</body>
</html>
Try clicking on Move at 0.5px per frame. The rectangle should be moving.
Reset it by clicking on Run code snippet.
Now try clicking on Move at 0.3px per frame.
It should be moving the DOM more slowly, but you can see that the DOM is not moving.
It's strange because when I initially kept track of the top position in a javascript variable topPos, and applied ${topPos + offset} to elem.style.top, it worked at even slower speeds!
So my guess is that elem.offsetTop rounds the decimal values, so 0.3 becomes 0, and 0.5 becomes 1.
What can I do to make it so that the DOM moves precisely at the specified speed? I can't use any libraries for this one.
EDIT: I looked more into the problem and I believe it's offsetTop that rounds the numbers to integers.
However, I found out that CSS OM spec changed the type of offsetTop to float, and the Chromium team was working on applying the change on the browser more than 4 years ago, and it seems that it should be fixed by now.
Why is it not working on my program, and how can I make it work?
EDIT2: I found from CSSOM working draft that the type of offsetTop was integer.
readonly attribute long offsetTop;
I think they only changed the type of scrollTop and scrollLeft to a double precision number.
attribute unrestricted double scrollTop;
attribute unrestricted double scrollLeft;
HTMLElement.offset[Left | Top] return long typed value (i.e integer).
Use Element.getBoundingClientRect if you want float values.
var requestFrameAnimationId;
function myMove(offset) {
var elem = document.getElementById("animate");
requestFrameAnimationId = animationLoop(frame);
function frame() {
// build up our own high precision offsetTop
var parentRect = elem.offsetParent && elem.offsetParent.getBoundingClientRect() || {top: 0, left:0};
var elemRect = elem.getBoundingClientRect();
var rect = {
top: elemRect.top - parentRect.top,
left: elemRect.left - parentRect.left
};
if (rect.top >= 350) {
cancelAnimationFrame(requestFrameAnimationId);
} else {
// so we can substract it here
elem.style.top = (rect.top + offset) + 'px';
elem.style.left = (rect.left + offset) + 'px';
}
}
}
function animationLoop(render) {
var running, lastFrame = +new Date(); // casting Date to Number
function loop(now) {
requestFrameAnimationId = requestAnimationFrame(loop);
running = render(now - lastFrame);
lastFrame = now;
}
loop(lastFrame);
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove(0.3)">Move at 0.3px per frame</button>
<button onclick="myMove(0.5)">Move at 0.5px per frame</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
Or simply add up your values to a variable:
var requestFrameAnimationId;
function myMove(offset) {
var elem = document.getElementById("animate");
requestFrameAnimationId = animationLoop(frame);
var pos = 0;
function frame() {
pos += offset;
if (pos >= 350) {
cancelAnimationFrame(requestFrameAnimationId);
} else {
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function animationLoop(render) {
var running, lastFrame = +new Date(); // casting Date to Number
function loop(now) {
requestFrameAnimationId = requestAnimationFrame(loop);
running = render(now - lastFrame);
lastFrame = now;
}
loop(lastFrame);
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove(0.3)">Move at 0.3px per frame</button>
<button onclick="myMove(0.5)">Move at 0.5px per frame</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
This question already has answers here:
Why does jQuery or a DOM method such as getElementById not find the element?
(6 answers)
Closed 6 years ago.
This code below should change the position of the element but fails to do so.
//variables
var p1 = document.getElementById("player");
var px = 10;
var py = 10;
//#variables
//functions
function start() {
setInterval(update, 100);
}
function update() {
p1.style.left = (px + "px");
p1.style.top = (py + "px");
px = px + 10;
py = py + 10;
}
//#functions
//start
start();
//#start`
my html is:
<html>
<head>
<title> jumpy </title>
<style type="text/css">
#player {
position: absolute;
background-color: white;
width: 100px;
height: 100px;
border-radius: 50%;
}
#body {
background-color: black;
}
</style>
</head>
<body id="body">
<script src="jumpy.js"> </script>
<div id="player"> </div>
</body>
</html>
Even though the position is absolute it still does not work.
I have seen the answer at the bottom but it did not fix my problem.
Your problem is that your script is run before your element is created. As a result document.getElementById("player"); returns nothing (the element is not loaded yet).
Move the script tag at the end of your body, or wrap your code in
window.addEventListener("load", function(){
// Your code.
});
Appart from this, your code works fine (except that requestAnimationFrame should be used to create animation with js, not setInterval). If it does not, the problem is somewhere else.
window.addEventListener("load", function(){
//variables
var p1 = document.getElementById("player");
var px = 10;
var py = 10;
//#variables
//functions
function start() {
setInterval(update, 100);
}
function update() {
p1.style.left = (px + "px");
p1.style.top = (py + "px");
px = px + 10;
py = py + 10;
}
//#functions
//start
start();
//#start`
});
#player {
position: absolute;
background-color: white;
width: 100px;
height: 100px;
border-radius: 50%;
}
body {
background-color: black;
}
<div id="player"></div>