Javascript/CSS - animation duration in pixel per second - javascript

How can i set the duration of an transition/animation to pixel per second?
You see the two different wrappers, with a different total height depending on it's colored content. The total speed is the same, given from the css transition attribute, thats okay if you want several animations with the same duration. For a smoother look i want to set this transition/animation effect to pixel per second so it takes as long as many pixels there. More content = more pixel = longer animation.
How can i achieve this with vanilla javascript or even css?
var wrapper1 = document.getElementById('wrapper1');
var wrapper2 = document.getElementById('wrapper2');
var header1 = document.getElementById('header1');
var header2 = document.getElementById('header2');
var wrapper1CmputedHeight = wrapper1.scrollHeight;
var wrapper2CmputedHeight = wrapper2.scrollHeight;
header1.addEventListener('click', function() {
if (wrapper1.style.height === '60px') {
wrapper1.style.height = wrapper1CmputedHeight + 'px';
} else {
wrapper1.style.height = '60px';
}
})
header2.addEventListener('click', function() {
if (wrapper2.style.height === '60px') {
wrapper2.style.height = wrapper2CmputedHeight + 'px';
} else {
wrapper2.style.height = '60px';
}
})
#wrapper1,
#wrapper2 {
background: #fff;
border: 1px solid grey;
overflow: hidden;
transition: height .2s linear
}
#wrapper1 {
margin-bottom: 40px
}
#header1,
#header2 {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer
}
#content1 {
height: 20px;
background: blue
}
#content2 {
height: 600px;
background: green
}
<div id="wrapper1" style="height: 60px">
<div id="header1">
<span>header</span>
</div>
<div id="content1"></div>
</div>
<div id="wrapper2" style="height: 60px">
<div id="header2">
<span>header</span>
</div>
<div id="content2"></div>
</div>

The only way to do this with css transitions, is to dynamically calculate the duration of the transition using a little javascript. So, in your code, I would remove the duration for the transition rule in your css, i,e.
#wrapper1,
#wrapper2 {
background: #fff;
overflow: hidden;
transition: height linear
}
and I would instead set the duration in the click handler as follows:
header1.addEventListener('click', function () {
if(wrapper1.style.height === '60px') {
wrapper1.style.height = wrapper1CmputedHeight + 'px';
wrapper1.style.transitionDuration=(wrapper1CmputedHeight/100)+"s";
} else {
wrapper1.style.height = '60px';
}
})
So in this case, I've used a speed of 100px per second (this is the /100 part in the above code).

I found this example here but it seems to do the trick for you (after some tweaking). In this case it implements a quartic interpolation, however you could adjust this algorithm to linear / other if so desired.
//
// Animate
//
var btn1 = document.querySelector('.animate');
btn1.addEventListener('click', function() {
reset();
animate();
btn1.disabled = true;
});
//
// http://easings.net/#easeInOutQuart
// t: current time
// b: beginning value
// c: change in value
// d: duration
//
function easeInOutQuart(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
function reset() {
document.querySelector('.square').style.width = Math.floor((Math.random() * 500) + 1) + "px";
}
function animate() {
var rect = document.querySelector('.square');
var from = 0;
var to = window.getComputedStyle(rect, null).getPropertyValue("width").split('px')[0];
var duration = to * 10;
var start = new Date().getTime();
var timer = setInterval(function() {
var time = new Date().getTime() - start;
var width = easeInOutQuart(time, from, to - from, duration);
rect.style.width = width + "px";
if (time >= duration) {
clearInterval(timer);
btn1.disabled = false;
}
}, 1000 / 60);
rect.style.width = from;
}
reset();
.square {
position: relative;
display: block;
width: 30px;
height: 30px;
background-color: #f00;
}
<div class="square"></div>
<button class="animate">Animate</button>

Related

How do I animate the change of the width of a div in JavaScript?

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

Rebuild Marquee with Js and CSS [duplicate]

I'm trying to make an Infinite marquee that speeds up on scroll, https://altsdigital.com/ you can see the effect on this website, the text says "Not your usual SEO agency" and when you scroll it speeds up.
Here's what I've tried but it does not work. It does not loop properly without overlapping (keep your eye on the left side of the page, you'll notice the text briefly overlaps and then translates left to create a gap) and I am unsure on how to fix it:
Here's the code (TEXT ONLY VISIBLE ON "FULL PAGE" view):
const lerp = (current, target, factor) => {
let holder = current * (1 - factor) + target * factor;
holder = parseFloat(holder).toFixed(3);
return holder;
};
class LoopingText {
constructor(DOMElements) {
this.DOMElements = DOMElements;
this.lerpingData = {
counterOne: { current: 0, target: 0 },
counterTwo: { current: 100, target: 100 },
};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.render();
this.onScroll();
}
onScroll() {
window.addEventListener("scroll", () => {
this.lerpingData["counterOne"].target += this.speed * 5;
this.lerpingData["counterTwo"].target += this.speed * 5;
});
}
lerp() {
for (const counter in this.lerpingData) {
this.lerpingData[counter].current = lerp(
this.lerpingData[counter].current,
this.lerpingData[counter].target,
this.interpolationFactor
);
}
this.lerpingData["counterOne"].target += this.speed;
this.lerpingData["counterTwo"].target += this.speed;
if (this.lerpingData["counterOne"].target < 100) {
this.DOMElements[0].style.transform = `translate(${this.lerpingData["counterOne"].current}%, 0%)`;
} else {
this.lerpingData["counterOne"].current = -100;
this.lerpingData["counterOne"].target = -100;
}
if (this.lerpingData["counterTwo"].target < 100) {
this.DOMElements[1].style.transform = `translate(${this.lerpingData["counterTwo"].current}%, 0%)`;
} else {
this.lerpingData["counterTwo"].current = -100;
this.lerpingData["counterTwo"].target = -100;
}
}
render() {
this.lerp();
window.requestAnimationFrame(() => this.render());
}
}
let textArray = document.getElementsByClassName("item");
new LoopingText(textArray);
#import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght#0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Poppins";
}
.hero-section {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
position: relative;
width: 100%;
}
.loop-container {
position: relative;
width: 100%;
display: flex;
/* padding-right: 24px; */
}
.item {
position: absolute;
font-size: 15rem;
white-space: nowrap;
margin: 0;
}
span {
transition: all 0.2s;
cursor: default;
}
.hover:hover {
color: gray;
transition: all 0.2s;
}
<body>
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text</div>
<div class="item">Infinite Horizontal Looping Text</div>
</div>
</section>
<section class="hero-section">
</section>
</body>
Your items are overlapping because you're not allowing any lerping diffing when the items should switch positions.
The current value should never equal the target value. If the values match, than the current value needs to catch up the target — giving that erratic movement and wrong calculations, additionally aggravated for the two sibling elements which should be perfectly in sync to give that immediate snap-back, perceived as a fluid continuous motion.
Solution
Instead of animating two (or more) children independently,animate only the parent .loop-container.
The container should be as wide as one child element exactly.
"Push" one child element to the far left using position: absolute; left: -100%
To allow the target value to be always greater than the current value:when the target value is greater than 100 — set current to the negative difference of the two values, and target to 0
Demo time:
const lerp = (current, target, factor) => current * (1 - factor) + target * factor;
class LoopingText {
constructor(el) {
this.el = el;
this.lerp = {current: 0, target: 0};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.direction = -1; // -1 (to-left), 1 (to-right)
// Init
this.el.style.cssText = `position: relative; display: inline-flex; white-space: nowrap;`;
this.el.children[1].style.cssText = `position: absolute; left: ${100 * -this.direction}%;`;
this.events();
this.render();
}
events() {
window.addEventListener("scroll", () => this.lerp.target += this.speed * 5);
}
animate() {
this.lerp.target += this.speed;
this.lerp.current = lerp(this.lerp.current, this.lerp.target, this.interpolationFactor);
if (this.lerp.target > 100) {
this.lerp.current -= this.lerp.target;
this.lerp.target = 0;
}
const x = this.lerp.current * this.direction;
this.el.style.transform = `translateX(${x}%)`;
}
render() {
this.animate();
window.requestAnimationFrame(() => this.render());
}
}
document.querySelectorAll(".loop-container").forEach(el => new LoopingText(el));
/* QuickReset */ * { margin: 0; box-sizing: border-box; }
body { min-height: 400vh; /* force some scrollbars */ }
.hero-section {
position: relative;
top: 50vh;
overflow: hidden;
font: 900 9vw/1 sans-serif;
min-height: 100vh;
}
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text </div>
<div class="item">Infinite Horizontal Looping Text </div>
</div>
</section>
PS:
When animating, (unless you want an element static / immovable) you should never put an elements transformations inside an if/else logic. The element should always receive the updated transformations. Put inside the conditional logic only the values that you actually want to modify (as I did in the example above).

How do I make an Infinite marquee with JS?

I'm trying to make an Infinite marquee that speeds up on scroll, https://altsdigital.com/ you can see the effect on this website, the text says "Not your usual SEO agency" and when you scroll it speeds up.
Here's what I've tried but it does not work. It does not loop properly without overlapping (keep your eye on the left side of the page, you'll notice the text briefly overlaps and then translates left to create a gap) and I am unsure on how to fix it:
Here's the code (TEXT ONLY VISIBLE ON "FULL PAGE" view):
const lerp = (current, target, factor) => {
let holder = current * (1 - factor) + target * factor;
holder = parseFloat(holder).toFixed(3);
return holder;
};
class LoopingText {
constructor(DOMElements) {
this.DOMElements = DOMElements;
this.lerpingData = {
counterOne: { current: 0, target: 0 },
counterTwo: { current: 100, target: 100 },
};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.render();
this.onScroll();
}
onScroll() {
window.addEventListener("scroll", () => {
this.lerpingData["counterOne"].target += this.speed * 5;
this.lerpingData["counterTwo"].target += this.speed * 5;
});
}
lerp() {
for (const counter in this.lerpingData) {
this.lerpingData[counter].current = lerp(
this.lerpingData[counter].current,
this.lerpingData[counter].target,
this.interpolationFactor
);
}
this.lerpingData["counterOne"].target += this.speed;
this.lerpingData["counterTwo"].target += this.speed;
if (this.lerpingData["counterOne"].target < 100) {
this.DOMElements[0].style.transform = `translate(${this.lerpingData["counterOne"].current}%, 0%)`;
} else {
this.lerpingData["counterOne"].current = -100;
this.lerpingData["counterOne"].target = -100;
}
if (this.lerpingData["counterTwo"].target < 100) {
this.DOMElements[1].style.transform = `translate(${this.lerpingData["counterTwo"].current}%, 0%)`;
} else {
this.lerpingData["counterTwo"].current = -100;
this.lerpingData["counterTwo"].target = -100;
}
}
render() {
this.lerp();
window.requestAnimationFrame(() => this.render());
}
}
let textArray = document.getElementsByClassName("item");
new LoopingText(textArray);
#import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght#0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Poppins";
}
.hero-section {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
position: relative;
width: 100%;
}
.loop-container {
position: relative;
width: 100%;
display: flex;
/* padding-right: 24px; */
}
.item {
position: absolute;
font-size: 15rem;
white-space: nowrap;
margin: 0;
}
span {
transition: all 0.2s;
cursor: default;
}
.hover:hover {
color: gray;
transition: all 0.2s;
}
<body>
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text</div>
<div class="item">Infinite Horizontal Looping Text</div>
</div>
</section>
<section class="hero-section">
</section>
</body>
Your items are overlapping because you're not allowing any lerping diffing when the items should switch positions.
The current value should never equal the target value. If the values match, than the current value needs to catch up the target — giving that erratic movement and wrong calculations, additionally aggravated for the two sibling elements which should be perfectly in sync to give that immediate snap-back, perceived as a fluid continuous motion.
Solution
Instead of animating two (or more) children independently,animate only the parent .loop-container.
The container should be as wide as one child element exactly.
"Push" one child element to the far left using position: absolute; left: -100%
To allow the target value to be always greater than the current value:when the target value is greater than 100 — set current to the negative difference of the two values, and target to 0
Demo time:
const lerp = (current, target, factor) => current * (1 - factor) + target * factor;
class LoopingText {
constructor(el) {
this.el = el;
this.lerp = {current: 0, target: 0};
this.interpolationFactor = 0.1;
this.speed = 0.2;
this.direction = -1; // -1 (to-left), 1 (to-right)
// Init
this.el.style.cssText = `position: relative; display: inline-flex; white-space: nowrap;`;
this.el.children[1].style.cssText = `position: absolute; left: ${100 * -this.direction}%;`;
this.events();
this.render();
}
events() {
window.addEventListener("scroll", () => this.lerp.target += this.speed * 5);
}
animate() {
this.lerp.target += this.speed;
this.lerp.current = lerp(this.lerp.current, this.lerp.target, this.interpolationFactor);
if (this.lerp.target > 100) {
this.lerp.current -= this.lerp.target;
this.lerp.target = 0;
}
const x = this.lerp.current * this.direction;
this.el.style.transform = `translateX(${x}%)`;
}
render() {
this.animate();
window.requestAnimationFrame(() => this.render());
}
}
document.querySelectorAll(".loop-container").forEach(el => new LoopingText(el));
/* QuickReset */ * { margin: 0; box-sizing: border-box; }
body { min-height: 400vh; /* force some scrollbars */ }
.hero-section {
position: relative;
top: 50vh;
overflow: hidden;
font: 900 9vw/1 sans-serif;
min-height: 100vh;
}
<section class="hero-section">
<div class="loop-container">
<div class="item">Infinite Horizontal Looping Text </div>
<div class="item">Infinite Horizontal Looping Text </div>
</div>
</section>
PS:
When animating, (unless you want an element static / immovable) you should never put an elements transformations inside an if/else logic. The element should always receive the updated transformations. Put inside the conditional logic only the values that you actually want to modify (as I did in the example above).

How do I make a progress bar?

I'm trying to create a progress bar for a product card track so by any click of the user on the prev and next buttons (which would scroll back or forward) the progress bar would advance or backup.
here's the code I came up with. the problem is the first click doesn't show any result and the prev button acts like the next button for the first time. It's like the code is one step behind.
I'm very new to javaScript and I can't figure out how this could happen.
const productScroll = () => {
rightButton.onclick = function () {
let scrollLeft = document.querySelector('#ProductSlider').scrollLeft;
let scrollPercent = ((scrollLeft - 0) / (5033 - 0)) * (100 - 0) + 0;
document.querySelector('div.progress-bar').style.width = `${scrollPercent}%`;
};
leftButton.onclick = function () {
let scrollLeft = document.querySelector('#ProductSlider').scrollLeft;
let scrollPercent = ((scrollLeft - 0) / (5033 - 0)) * (100 - 0) + 0;
document.querySelector('div.progress-bar').style.width = `${scrollPercent}%`;
};
If youre always 1 step behind, it could be that your percentage calculation is wrong. For example, if you have 5 steps and want to show progress for each step, starting at 1 and ending at 5, your progress bar needs to have 4 steps instead:
1 > 2 > 3 > 4 > 5 = 4 steps (total - 1)
In percentages, it looks like this for a 5 step progress bar:
1: 0%
2: 25%
3: 50%
4: 75%
5: 100%
Notice each increase is 25% (1/4) and not 20% (1/5).
So in abstract shape, your calculation would need to be:
((scroll / max) * (steps - 1)) / (steps - 1) * 100%
Which means your scrollLeft / 5033 needs to be between 0 and 4, divided by 4, then turned into a percentage:
const percentage = ((scrollLeft / 5033) * 4) / 4 * 100;
To create a progress bar, first create two “div” tag elements named id Progress_Status and myprogressbar.
To add a numeric label to indicate how far the user is in the process, the element inside or outside the progress bar is required which will display the progress status which in this case is myprogressbar.
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
</div>
Adding CSS:
#Progress_Status {
width: 50%;
background-color: #ddd;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
}
Adding JavaScript:
Next, Code below creates a dynamic progress bar(animated) using JavaScript functions update and scene.
function update() {
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 100) {
clearInterval(identity);
} else {
width++;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
Firstly, you fetch the element by id and then set starting width to 1. For the purpose of working example, i used setintervel to show the progression of progress bar.
All we are doing here is calling the scene function which checks if width is less than 100. If yes, then stop loader by clearing the interval. If not, then increment the width and add it to the with and progress label div for showing the progress in percentenge.
Complete Code:
function update() {
var element = document.getElementById("myprogressBar");
var width = 1;
var identity = setInterval(scene, 10);
function scene() {
if (width >= 100) {
clearInterval(identity);
} else {
width++;
element.style.width = width + '%';
element.innerHTML = width * 1 + '%';
}
}
}
#Progress_Status {
width: 50%;
background-color: #ddd;
}
#myprogressBar {
width: 1%;
height: 35px;
background-color: #4CAF50;
text-align: center;
line-height: 32px;
color: black;
}
<!DOCTYPE html>
<html>
<body>
<h3>Example of Progress Bar Using JavaScript</h3>
<p>Download Status of a File:</p>
<div id="Progress_Status">
<div id="myprogressBar">1%</div>
</div>
<br>
<button onclick="update()">Start Download</button>
</body>
</html>

HTMLElement.offsetTop doesn't work in subpixel precision

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>

Categories

Resources