JS: how to sync timing of multiple animations? - javascript

When I dynamically add an item that animates with JS, how do I get them to be in sync on the same timeline as shown here: http://youtube.com/watch?v=AGiTmjFHs8M&t=9m23s ? I saw a tutorial that showed a benefit of using JS animation vs. CSS is they inherit the same timeline.
<div class="body"></div>
<button onclick="addItem()">Add</button>
function addItem() {
let body = document.querySelector('.body');
let newEl = document.createElement('div');
newEl.innerText = "I am a new Item";
newEl.animate([
{
transform: 'translate(0px, 0px)',
transform: 'translate(500px, 500px)',
}
],
{
duration: 2000,
iterations: Infinity,
});
body.appendChild(newEl);
}

If all your Animation objects do share the same duration and you want them to start and end at the same time, you can simply set the iterationStart EffectTiming of your new Animation object to the ComputedTiming .progress value of the already running one.
However beware you must wait for the new Animation object is ready before getting the computed value of the previous one or you'll be one frame late. For this, you can simply await the animation.ready Promise.
To get the previous Animation object, you can either store it when you create it through Element.animate(), or you can access the set of currently running Animations on the document through document.getAnimations(), and pick it from there.
let i = 0;
async function addItem() {
i++;
const body = document.querySelector(".body");
const newEl = document.createElement("div");
newEl.classList.add("item");
newEl.textContent = "I am a new Item " + i;
// get a previous Animation if any
const previous_animation = document.getAnimations()[0];
// create the new Animation object
// slightly offset on the x axis only
// to check if they are indeed in sync
const anim = newEl.animate([{
transform: "translate(" + (i * 10) + "px, 0px)",
transform: "translate(" + (i * 10 + 250) + "px, 250px)",
}], {
duration: 2000,
iterations: Infinity
});
// when it's ready to start
await anim.ready;
// get the current progress of the first Animation object
const computed_timing = previous_animation?.effect.getComputedTiming();
if( computed_timing ) {
// update our new Animation object to the same progress value
anim.effect.updateTiming( {
iterationStart: computed_timing.progress
});
}
body.appendChild(newEl);
}
.item {
position: absolute;
}
<div class="body"></div>
<button onclick="addItem()">Add</button>
Note that as pointed out by user brianskold in a comment below, the startTime property of the Animation can be set to an earlier time. Doing so will make it like the animation did start at this time.
So for synchronizing two animations, this is probably the best way:
let i = 0;
function addItem() {
i++;
const body = document.querySelector(".body");
const newEl = document.createElement("div");
newEl.classList.add("item");
newEl.textContent = "I am a new Item " + i;
// get a previous Animation if any
const previous_animation = document.getAnimations()[0];
// create the new Animation object
// slightly offset on the x axis only
// to check if they are indeed in sync
const anim = newEl.animate([{
transform: "translate(" + (i * 10) + "px, 0px)",
transform: "translate(" + (i * 10 + 250) + "px, 250px)",
}], {
duration: 2000,
iterations: Infinity
});
if( previous_animation ) {
// set the startTime to the same
// as the previously running's one
// note this also forces anim.ready to resolve directly
anim.startTime = previous_animation.startTime;
}
body.appendChild(newEl);
}
.item {
position: absolute;
}
<div class="body"></div>
<button onclick="addItem()">Add</button>

Related

Lerp from one position to another in a specified duration with javascript

Ok so I am trying to ease a box from one place to another using Linear Interpolation. But It doesn't seem to easing it is just moving without easing. So I have read the post here C# Lerping from position to position and maybe I am misunderstanding the equation. Here is what I have so far.
const lerp = (start, end, speed) => start + (end - start) * speed
const div = document.querySelector('div')
const btn = document.querySelector('button')
let pos = 0
let startTime = 0
const duration = 2000
let aF = null
const animate = () => {
const elapsed = Date.now() - startTime
const t = elapsed / duration
if(elapsed < duration) {
pos = lerp(0, 300, t)
aF = requestAnimationFrame(animate)
} else {
cancelAnimationFrame(aF)
aF = null
pos = 300
}
console.log(pos, 300 * t)
div.style.transform = `translateX(${pos}px)`
}
btn.addEventListener('click', () => {
pos = 0
startTime = Date.now()
aF = requestAnimationFrame(animate)
})
div {
width: 50px;
height: 50px;
background: green;
}
button {
margin-bottom: 10px;
}
<button>Run Animation</button>
<div></div>
So in the example you can see the box is just animating without any easing. In the console log you can se the values are the same even though I am lerping on value and not he other.
I know there is something I am not understanding here but I don't know what that is. Any help would be appreciated. Thanks.
Your example is working (a + (a-b) * t). It is just that the interpolation is missing.
Interpolation is the art of remapping a value (t in your case) between 0-1 to again 0-1 using a different function (so a + (a-b) * t becomes a + (a-b) * interpolate(t)). There are infinite amount of such functions, I chose some popular ones and included them in your example:
let interpolators = {
identity: function(t){
t = Math.max(0,Math.min(1,t));
return t;
},
cubic: function(t){
t = Math.max(0,Math.min(1,t));
if(2*t<<0){
return 4*(t-1)*(t-1)*(t-1)+1;
} else {
return 4*t*t*t;
}
},
elastic: function(t){
t = Math.max(0,Math.min(1,t));
var range = 10.5*Math.PI;
return (range - Math.sin(range*t)/t)/(range - 1);
}
};
the most important change is:
const t = interpolators[selected](elapsed / duration);
https://jsfiddle.net/ibowankenobi/6ctp9a0s/26/

Moving Div Box just using pure javascript at certain time

I have saw the problem
Moving Div Box using javascript
and the top upvote answer is good .
But if I want to get the feacture like CSS3 does ,how can I do?
.cuble{
height: 100px;
width: 100px;
background-color: blue;
transition: all 3s ease-in-out;
}
.cuble:hover{
transform: translateX(500px);
}
<!Doctype html>
<html>
<body>
<div class="cuble">
</div>
</body>
I don't need ease-in-out effect .I just want hover the div once and even if I mouseout the div ,the div will also moving from left to right until the setting time. But the Moving Div Box using javascript don't meet the requirement.
I try to use Promise to achieve the goal ,here is my Javascript code .(anyway,maybe I just want to learn deep about javascript async performance)
var cuble = document.querySelector('.cuble');
cuble.onmouseover = function(e) {
for (var i = 0; i < 100; i++) {
var pixels = 5 * i + "px";
delay(100)
.then(() => cuble.style.marginLeft = pixels)
}
}
function delay(t) {
return new Promise(function(resolve) {
setTimeout(resolve, t)
});
};
How can I fix my Javascript code and meet the requirement?
The issue is you're creating one hundred promises each with a 100ms delay almost instantly, so they all firing almost instantly.
One way to remedy the issue is increase the delay for each subsequent promise. And you also have to pass the corresponding pixels for each resolve. Something like:
var cuble = document.querySelector('.cuble');
cuble.onmouseover = function(e) {
for (var i = 0; i < 100; i++) {
var pixels = 5 * i + "px";
delay(i * 20, pixels)
.then((p) => cuble.style.marginLeft = p)
}
}
function delay(t, pixels) {
return new Promise(function(resolve) {
setTimeout(() => resolve(pixels), t)
});
};
DEMO
An example with no promises and just one function being created and called, instead of creating new function for every step:
var cuble = document.querySelector('.cuble');
cuble.onmouseover = function() {
var left = 0,
ival = setInterval(move, 5);
function move() {
if (left === 500) clearInterval(ival);
cuble.style.marginLeft = left++ + 'px';
}
}
DEMO

Why does my setInterval function not work second time? (JavaScript)

I made a div and I want to make it animate in a specific direction every 1 second. In the code that I have provided there's a function called resetPosition() don't worry about that, it's from my other js file that I linked in the head section(That works perfectly). I just want to know why setInterval() doesn't work correctly?
Here's my code:-
<!DOCTYPE html>
<html>
<head>
<title>Animtion</title>
<link rel="icon" href="http://3.bp.blogspot.com/-xxHZQduhqlw/T-cCSTAyLQI/AAAAAAAAAMw/o48rpWUeg2E/s1600/html-logo.jpg" type="image/jpg">
<script src="easyJs.js"></script>
<style type="text/css">
div{height:100px; width:100px; background:cyan;}
</style>
</head>
<body onload="">
<div id="demo"></div>
</body>
<script type="text/javascript">
setInterval(function(){var x = new element('demo');
x.resetPosition('absolute', '100px', '10px');}, 1000);
</script>
</html>
Here's easyJs:-
function element(elementId){
this.myElement = document.getElementById(elementId);
this.resetColor = changeColor;
this.resetSize = changeSize;
this.resetBackground = changeBackground;
this.resetPosition = changePosition;
this.resetBorder = changeBorder;
this.resetFontFamily = changeFont;
this.resetFontSize = changeFontSize;
this.resetFontStyle = changeFontStyle;
this.resetZindex = changeZindex;
this.resetClass = changeClass;
this.resetTitle = changeTitle;
this.resetMarginTop = changeMarginTop;
this.resetMarginLeft = changeMarginLeft;
this.resetSource = changeSource;
this.resetInnerHTML = changeInnerHTML;
this.resetHref = changeHref;
this.resetTextDecoration = changeTextDecoration;
this.resetFontWeight = changeFontWeight;
this.resetCursor = changeCursor;
this.resetPadding = changePadding;
this.getValue = getTheValue;
this.getName = getTheName;
this.getHeight = getTheHeight;
}
function changeColor(color){
this.myElement.style.color = color;
}
function changeSize(height, width){
this.myElement.style.height = height;
this.myElement.style.width = width;
}
function changeBackground(color){
this.myElement.style.background = color;
}
function changePosition(pos, x, y){
this.myElement.style.position = pos;
this.myElement.style.left = x;
this.myElement.style.top = y;
}
function changeBorder(border){
this.myElement.style.border = border;
}
function changeFont(fontName){
this.myElement.style.fontFamily = fontName;
}
function changeFontSize(size){
this.myElement.style.fontSize = size;
}
function changeZindex(indexNo){
this.myElement.style.zIndex = indexNo;
}
function changeClass(newClass){
this.myElement.className = newClass;
}
function changeTitle(newTitle){
this.myElement.title = newTitle;
}
function changeMarginTop(top){
this.myElement.style.marginTop = top;
}
function changeMarginLeft(left){
this.myElement.style.marginLeft = left;
}
function changeSource(newSource){
this.myElement.src = newSource;
}
function changeHref(newHref){
this.myElement.href = newHref;
}
function changeInnerHTML(newHTML){
this.myElement.innerHTML = newHTML;
}
function changeTextDecoration(decoration){
this.myElement.style.textDecoration = decoration;
}
function changeFontWeight(weight){
this.myElement.style.fontWeight = weight;
}
function changeFontStyle(style){
this.myElement.style.fontStyle = style;
}
function changeCursor(cursor){
this.myElement.style.cursor = cursor;
}
function changePadding(padding){
this.myElement.style.padding = padding;
}
function getTheValue(){
var theValue = this.myElement.value;
return theValue;
}
function getTheName(){
var theName = this.myElement.name;
return theName;
}
function getTheHeight(){
var theHeight = this.myElement.offsetHeight;
return theHeight;
}
This might help you see what and how. (Scroll down to bottom of code):
Fiddle
// Create a new EasyJS object
var el = new element('demo');
// x and y position of element
var x = 0, y = 0;
// Interval routine
setInterval(function(){
x = x + 1; // Increment x by 1
y = y + 1; // Increment y by 1
// Move element to position xy
el.resetPosition('absolute', x + 'px', y + 'px');
// Add text inside element showing position.
el.resetInnerHTML(x + 'x' + y);
// Run five times every second
}, 1000 / 5);
Explanation of original code:
setInterval(function() {
// Here you re-declare the "x" object for each iteration.
var x = new element('demo');
// Here you move the div with id "demo" to position 100x10
x.resetPosition('absolute', '100px', '10px');
// Once every second
}, 1000);
The HTML div element demo initially has no positioning styling (CSS). As such it is rendered in the default position according to the browser defaults.
In first iteration you change the style option position to absolute. That means you can move it anywhere. Secondly you move it to offset 100x10.
On the second and every iteration after that the element has position set to absolute, and it reside at 100x10. Then you say it should be moved to 100x10 – as in same place as it is.
If you do not change either x or y position (left / top), it will stay at 100x10, no mather how many times you run the loop. Run it 100 times a second for a year and it will still be at 100x10 ;-)
Think about it. Everytime the interval runs, it creates a new instance of element "demo".
This demo variable has all the default values your elements object sets it to (if any), and runs the same function each time. That's why it only moves once.
Hoist your element higher and you won't be re-declaring each interval.
<script type="text/javascript">
var x = new element('demo');
setInterval(function(){
x.resetPosition('absolute', '100px', '10px');}, 1000);
</script>
The problem isn't in the setInterval, because a copypasta'd fiddle of the same expression worked properly, so it's probably either an issue with resetPosition or maybe easyJS, though I doubt the latter.
Also, it's unclear whether demo appears on-page, as it's not added anywhere visibly.
EDIT:
If demo is appended somewhere behind the scenes, it's still piling a new demo on that spot every second

webkit transition syntax in javascript?

I'm looking for the webkitTransition object reference here
function spawnAnimation(what){
//sets the moving element
var moveingEl = document.getElementById(what);
//gives temp transition property
moveingEl.style.WebkitTransition = "left 2s";
// moveingEl.style.webkitTransition = "top 500ms";
var cLeft = moveingEl.style.left
var cleft = Number(cLeft.slice(0, -2));
var cTop = moveingEl.style.top
var cTop = Number(cTop.slice(0, -2));
moveingEl.style.left = cLeft+200 + "px";
}
This does not work.I would like to give the element a transition property, then make it move to the right. When this code is called it just immediately moves to the right with no animation. bummer :(. I don't want to predefine it in CSS, I would like to dynamically add it and then remove it.
You can use style.setProperty to modify any property using its CSS name as string, including -moz-* and -webkit-* properties.
const style = document.getElementById('my-div').style
const prop = (k, v) => style.setProperty(k, v)
function bounce() {
prop("-webkit-transition", "top .5s ease-in");
prop("top", "50px");
setTimeout(() => {
prop("-webkit-transition", "top .75s cubic-bezier(0.390, 0.575, 0.565, 1.000)");
prop("top", "0px");
}, .5 * 1000)
}
prop("-webkit-transition", "top .5s ease-in");
setInterval(bounce, (.75 + .5) * 1000);
#my-div {
background: red;
border-radius: 50%;
width:50px;
height:50px;
position:absolute;
top: 0px;
}
<div id="my-div"></div>
Allow 1ms for the rendered to get the thread back.
setTimeout(function() {
myElement.style.height = '200px'; /* or whatever css changes you want to do */
}, 1);​
You can use:
element.style.webkitTransition = "set your transition up here"
I know it's a workaround, but can you use jQuery?
$(moveingEl).css('-webkit-transform', 'translateX(200px)');
<script>
$(document).ready(function(){
var x = 100;
var y = 0;
setInterval(function(){
x += 1;
y += 1;
var element = document.getElementById('cube');
element.style.webkitTransform = "translateZ(-100px) rotateY("+x+"deg) rotateX("+y+"deg)"; //for safari and chrome
element.style.MozTransform = "translateZ(-100px) rotateY("+x+"deg) rotateX("+y+"deg)"; //for firefox
},50);
//for other browsers use: "msTransform", "OTransform", "transform"
});
</script>

Javascript plugin only firing once

I've built a very basic jQuery plugin that essentially positions a sprite image left by a set amount every x milliseconds to create an animation effect. The plugin at this stage is very basic and only has a few options, and it works pretty well.
Apart from that fact that it only fires once! I have multiple instance of the animation on one page and they all fire, but only ever once each.
Now I'm not expert on Javascript and only just managed to cobble this together but here's the code anyhow:
// Animation Plugin
(function($){
$.fn.anime = function(customOptions) {
// Default Options
var defaultOptions = {
slideWidth : 100,
frames : 10,
speed : 40,
minCycles : 1,
stopFrame : 0
};
// Set options to default
var options = defaultOptions;
// Merge custom options with defaults using the setOptions func
setOptions(customOptions);
// Merge current options with the custom option
function setOptions(customOptions) {
options = $.extend(options, customOptions);
};
return this.each(function() {
// Initialize the animation object
var $elem = $('img', this);
var frameCount = 0;
var currentSlideWidth = options.slideWidth;
var intervalID = null;
var cycleCount = 1;
var animate = function() {
if (frameCount < (options.frames -1)) {
$elem.css('left', '-' + currentSlideWidth + 'px');
frameCount++;
currentSlideWidth += options.slideWidth;
}
else {
if (cycleCount < options.minCycles)
{
frameCount = 0;
currentSlideWidth = options.slideWidth;
$elem.css('left', '-' + currentSlideWidth + 'px');
cycleCount++;
}
else
{
window.clearInterval(intervalID);
$elem.css('left', '0px');
}
}
cycleCount = 1;
};
$(this).bind('mouseover', function(){
var intervalID = window.setInterval(animate, options.speed);
});
});
};
})(jQuery);
The code used to call the actual plugin on a dom element is:
$('#animeBolt').anime({
frames: 50,
slideWidth: 62,
minCycles: 1,
speed: 30,
});
This is the html it is called on:
<div class="anime" id="animeBolt">
<img src="_assets/img/anime-bolt.png" alt="Lightning Bolt" width="3100" height="114">
</div>
And finally the css:
.anime {
position: absolute;
overflow: hidden; }
.anime img {
position: absolute;
left: 0;
top: 0; }
#animeBolt {
top: 2669px;
left: 634px;
width: 62px;
height: 116px; }
How do I get the plugin to fire repeatedly?
I've created and modified jsfiddle example using your code. It's working http://jsfiddle.net/9Yz9j/16/
I've change a couple of things:
added clearInterval on mouseover to preven multiple overlapping intervals
moved intervalID variable to outside of each function, and removed var keyword from mousover handler so the script will remember intervalID set on last mouseover
reseting the frameCount, cycleCount and currentSlideWidth variables on animation end (that was actually a clue thing to get animation going more than just once)
Hope that helps

Categories