So I have a span element whose textContent changes when I hover over it.
I want the old text to fade out, then fade in the new text.
Also, since the new text is longer than the old one, the width of the span needs to increase smoothly to the new value.
The solution below basically does what I want, but I am not satisfied with it for two reasons:
It splits the animation effect in two Animations that are played one after another.
This is not elegant and it causes a slight glitch in some browsers (Chrome and Edge, but not Firefox) at the point where the first animation finishes.
Determining the old and new width "manually", by simply changing the text and using getComputedStyle, also seems quite ugly.
Also I'm not sure it will always work because getComputedStyle might not return the width in pixels.
Concerning (1), there is a different solution where I only have one Animation and the text gets changed in the middle using setTimeout.
This doesn't have the glitch, but it is likewise not elegant.
Question: What is the best way to achieve the effect described above?
Below is my current solution:
const initialText = "Hover here";
const finalText = "Text changed on hover";
const dur = 1500; // in milliseconds
const element = document.getElementById("my-element");
element.textContent = finalText;
const finalWidth = parseFloat(window.getComputedStyle(element).width);
element.textContent = initialText;
const initialWidth = parseFloat(window.getComputedStyle(element).width);
const intermediateWidth = ( initialWidth + finalWidth ) / 2;
const keyframes_0 = new KeyframeEffect(
element,
[{ opacity: 1, width: initialWidth + "px" }, { opacity: 0, width: intermediateWidth + "px" }],
{ duration: dur / 2 }
);
const keyframes_1 = new KeyframeEffect(
element,
[{ opacity: 0, width: intermediateWidth + "px" }, { opacity: 1, width: finalWidth + "px" }],
{ duration: dur / 2 }
);
const animation_0 = new Animation( keyframes_0, document.timeline );
const animation_1 = new Animation( keyframes_1, document.timeline );
element.addEventListener('mouseenter', () => {
animateTextChange(element, animation_1, animation_0, finalText, 1.0);
});
element.addEventListener('mouseleave', () => {
animateTextChange(element, animation_0, animation_1, initialText, -1.0);
});
function animateTextChange(elem, anim0, anim1, text, rate) {
if( anim0.playState === "running" ) {
anim0.onfinish = () => { };
anim0.playbackRate = rate;
anim0.play();
} else {
anim0.onfinish = () => { };
anim1.onfinish = () => {
elem.textContent = text;
anim0.playbackRate = rate;
anim0.play();
};
anim1.playbackRate = rate;
anim1.play();
}
}
<span id="my-element" style="display:inline-block; white-space:nowrap; overflow:clip">
Hover here
</span>
… more text
Related
So I added code into Virtual box website, Works Awesome! One thing, In order for animation to take place you need to click and drag. (Which is fantastic because as a user it feels organic) That being said, I am having an issue finding out how to make the on click reaction occur after clicking but not dragging the cursor without refreshing the page
Basically you have to refresh page if you accidentally click and don't move cursor.
Thank you for help fam
const track = document.getElementById("image-track");
const handleOnDown = e => track.dataset.mouseDownAt = e.clientX;
const handleOnUp = () => {
track.dataset.mouseDownAt = "0";
track.dataset.prevPercentage = track.dataset.percentage;
}
const handleOnMove = e => {
if(track.dataset.mouseDownAt === "0") return;
const mouseDelta = parseFloat(track.dataset.mouseDownAt) - e.clientX,
maxDelta = window.innerWidth / 2;
const percentage = (mouseDelta / maxDelta) * -100,
nextPercentageUnconstrained = parseFloat(track.dataset.prevPercentage) + percentage;
nextPercentage = Math.max(Math.min(nextPercentageUnconstrained, 0), -100);
track.dataset.percentage = nextPercentage;
track.animate({
transform: `translate(${nextPercentage}%, -50%)`},
{ duration: 1200, fill: "forwards" });
for(const image of track.getElementsByClassName("image")) {
image.animate({
objectPosition: `${100 + nextPercentage}% center`},
{ duration: 1200, fill: "forwards" });
}
}
/* -- Had to add extra lines for touch events -- */
window.onmousedown = e => handleOnDown(e);
window.ontouchstart = e => handleOnDown(e.touches[0]);
window.onmouseup = e => handleOnUp(e);
window.ontouchend = e => handleOnUp(e.touches[0]);
window.onmousemove = e => handleOnMove(e);
window.ontouchmove = e => handleOnMove(e.touches[0]);
I thought it would react to on-click event every time I click to drag. It does not.
Completely new to JS and jQuery here. The code iterates through each one of the bars and when you press the Reset button, it should shrink the bars down to zero. However, I am trying to get this bar graph animation to stop but once you press the button, it goes through the animation and resets back again to where it was before. Any help would be appreciated!
function barGraph(data) {
//Create graph
var graph = document.createElement("div");
graph.id = "barGraph";
//inserting bar graph before previous 'label' div
const target = document.querySelector('#labels');
target.parentNode.insertBefore(graph, labels);
//Styling for graph
graph.style.position = "relative";
graph.style.marginTop = "20px";
graph.style.height = "500px";
graph.style.backgroundColor = "Gainsboro";
graph.style.borderBottomStyle = "solid";
graph.style.borderBottomWidth = "1px";
graph.style.overflow = "hidden";
//Iterating through each bar
var position = 50;
var width = 75;
for (i = 0; i < data.length; i += 1) {
var spendData = data[i];
var bar = document.createElement("div");
//set a unique id for each of the bars
bar.id = data[i].category;
//Styling for bar
bar.style.position = "absolute";
bar.style.left = position + "px";
bar.style.width = width + "px";
bar.style.backgroundColor = spendData.color;
bar.style.height = (spendData.amount) / 5 + "px";
bar.style.bottom = "0px";
bar.innerHTML = "$" + spendData.amount;
bar.style.fontSize = "11px";
bar.style.color = "Azure";
bar.style.fontWeight = "800";
bar.style.fontFamily = "Arial, Helvetica, sans-serif";
bar.style.textAlign = "center";
bar.style.padding = "1em";
//Appending to the graph
graph.appendChild(bar);
//Set bar width
position += (width * 2);
}
return graph;
}
window.onload = function() {
var data = [{
category: "Food and Dining",
amount: "2005.00",
color: "CadetBlue"
},
{
category: "Auto and Transport",
amount: "1471.31",
color: "CornflowerBlue"
},
{
category: "Shopping",
amount: "892.86",
color: "DarkCyan"
},
{
category: "Bills and Utilities",
amount: "531.60",
color: "DarkSeaGreen"
},
{
category: "Mortgage",
amount: "1646.00",
color: "LightSeaGreen"
},
{
category: "Entertainment",
amount: "179.52",
color: "YellowGreen"
}
];
document.getElementById("resetGraph").addEventListener("click", function() {
for (i = 0; i < data.length; i++) {
var bar = document.getElementById(data[i].category);
if (bar) {
bar.animate({
"height": "0px",
"padding": "0px"
}, 2000);
}
}
});
var graph = barGraph(data);
//document.div.appendChild(graph);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="labels"></div>
<button id="resetGraph">Reset graph</button>
<script src="js/spending-graph.js"></script>
Dom's animate() function will return an AnimationPlaybackEvent object. And we can use onFinish method to reset the height and width of the dom element or can hide it.
Can you please add following lines to your code after bar.animate() function.
.onfinish=function(e){
e.currentTarget.effect.target.style.visibility ="hidden"
};
like below snippet.
bar.animate({
"height": "0px",
"padding": "0px"
}, 2000).onfinish=function(e){
e.currentTarget.effect.target.style.visibility ="hidden"
};
Updated
Interesting question! AFAICT the problem is that you've mixed up jQuery's .animate() with the native Javascript .animate().
Your question is tagged jQuery-animate, you mention it explicitly, and the format of the parameter you are passing to .animate() is exactly what jQuery expects.
But your animate code is running against an HTML DOM element, not a jQuery element:
// bar here is an HTML DOM Element, not a jQuery object:
var bar = document.getElementById(data[i].category);
// That means this will run JS .animate, not jQuery's:
bar.animate( ... );
If you simply change that to run against a jQuery object, it works (almost) as expected:
var bar = $('#' + data[i].category);
bar.animate( ... );
I say almost because there is another problem:
bar.id = data[i].category;
This creates HTML IDs from your plain English categories, which include whitespace, like "Food and Dining". According to the spec:
id's value must not contain whitespace (spaces, tabs etc.).
And at least when trying to use IDs with spaces as jQuery selectors, this matters, and it does not work. So instead let's just use the array index, which is also guaranteed to be unique, with a prefix so we know what we're referring to:
bar.id = 'bar-' + i;
Here's a working snippet with those 2 changes.
Note: the last document.div.appendChild(graph); was throwing an error - maybe this was a debugging attempt, it isn't necessary and I've commented it out here.
function barGraph(data) {
//Create graph
var graph = document.createElement("div");
graph.id = "barGraph";
//inserting bar graph before previous 'label' div
const target = document.querySelector('#labels');
target.parentNode.insertBefore(graph, labels);
//Styling for graph
graph.style.position = "relative";
graph.style.marginTop = "20px";
graph.style.height = "500px";
graph.style.backgroundColor = "Gainsboro";
graph.style.borderBottomStyle = "solid";
graph.style.borderBottomWidth = "1px";
graph.style.overflow = "hidden";
//Iterating through each bar
var position = 50;
var width = 75;
for (i = 0; i < data.length; i += 1) {
var spendData = data[i];
var bar = document.createElement("div");
//set a unique id for each of the bars
// UPDATED - category includes spaces, just use array index
// bar.id = data[i].category;
bar.id = 'bar-' + i;
//Styling for bar
bar.style.position = "absolute";
bar.style.left = position + "px";
bar.style.width = width + "px";
bar.style.backgroundColor = spendData.color;
bar.style.height = (spendData.amount) / 5 + "px";
bar.style.bottom = "0px";
bar.innerHTML = "$" + spendData.amount;
bar.style.fontSize = "11px";
bar.style.color = "Azure";
bar.style.fontWeight = "800";
bar.style.fontFamily = "Arial, Helvetica, sans-serif";
bar.style.textAlign = "center";
bar.style.padding = "1em";
//Appending to the graph
graph.appendChild(bar);
//Set bar width
position += (width * 2);
}
return graph;
}
window.onload = function() {
var data = [{
category: "Food and Dining",
amount: "2005.00",
color: "CadetBlue"
},
{
category: "Auto and Transport",
amount: "1471.31",
color: "CornflowerBlue"
},
{
category: "Shopping",
amount: "892.86",
color: "DarkCyan"
},
{
category: "Bills and Utilities",
amount: "531.60",
color: "DarkSeaGreen"
},
{
category: "Mortgage",
amount: "1646.00",
color: "LightSeaGreen"
},
{
category: "Entertainment",
amount: "179.52",
color: "YellowGreen"
}
];
document.getElementById("resetGraph").addEventListener("click", function() {
for (i = 0; i < data.length; i++) {
// UPDATED: 1. use new format ID without spaces
// UPDATED: 2. use jQuery selector/animation
// UPDATED: 3. use bar.length to test if selector matched anything
var bar = $('#bar-' + i);
if (bar.length) {
bar.animate({
"height": "0px",
"padding": "0px"
}, 2000);
}
}
});
var graph = barGraph(data);
//document.div.appendChild(graph);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="labels"></div>
<button id="resetGraph">Reset graph</button>
So what is happening in your original code? I am not sure. It is the native Javascript .animate() that is running, not jQuery's. The first parameter of the Javascipt .animate() method should be:
Either an array of keyframe objects, or a keyframe object whose properties are arrays of values to iterate over.
Your code is passing an object, not an array, so .animate() will expect the latter, "a keyframe object". The Keyframe Formats spec gives a little more detail and some examples for what "a keyframe object" looks like:
element.animate({
opacity: [ 0, 1 ], // [ from, to ]
color: [ "#fff", "#000" ] // [ from, to ]
}, 2000);
Your object does not match this format though - each attribute is just a string, not an array of start/stop values. The Keyframe docs also describe Implicit to/from keyframes:
In newer browser versions, you are able to set a beginning or end state for an animation only (i.e. a single keyframe), and the browser will infer the other end of the animation if it is able to.
My guess is this is what is happening, somehow? The browser is inferring an implicit end frame of the start values?
I tried to update your code to use the right Keyframe format, so you can use the native .animate() and skip jQuery all together, but it does not work - the bars reset to their original height after the animation, just like in your original code, I don't know why:
document.getElementById("resetGraph").addEventListener("click", function() {
var bar, startHeight;
for (i = 0; i < data.length; i++) {
bar = document.getElementById('bar-' + i);
if (bar) {
startHeight = window.getComputedStyle(bar).height;
bar.animate({
height: [startHeight, '0px'],
padding: ['1em', '0']
}, 2000);
}
}
});
I'm looking to call a function right at the end of the wobble effect.
That is, at the end of the damping effect (when the wobble stops), I'd like to execute a GSAP timeline function. I'd assume this type of "onComplete" function would need to be called inside the onReady() of Curtains and perhaps by tracking the damping effect. I'm only familiar with GSAP's onComplete function, but don't know how I would implement it here. Maybe something that checks if deltas.applied is less than 0.001, then the function is called?
Below is the code snippet (without the fragment and vertex shaders). Full working code here:
CodePen
class Img {
constructor() {
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];
this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}
introAnim() {
deltas.max = 6;
//console.log("complete") <-- need an onComplete type function~!
}
}
window.onload = function() {
const img = new Img();
}
What you could use is some algebra :)
First off, you should simplify your deltas.max function like so:
deltas.max += (0 - deltas.max) * 0.07;
// Simplifies to
deltas.max -= deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max - deltas.max * 0.07;
// Rewrite to
deltas.max = deltas.max * (1 - 0.07);
// Simplifies to
deltas.max *= 0.93; // Much nicer :)
That is actually pretty important to do because it makes our work of calculating the end value of our time variable and the duration of our animation significantly Easier:
// Given deltas.max *= 0.93, need to calculate end time value
// endVal = startVal * reductionFactor^n
// Rewrite as
// n = ln(endVal / startVal) / ln(reductionFactor) // for more see https://www.purplemath.com/modules/solvexpo2.htm
// n = ln(0.001 / 8) / ln(0.93)
const n = 123.84;
// Assuming 60fps normally: n / 60
const dur = 2.064;
Once we have those values all we have to do is create a linear tween animating our time to that value with that duration and update the max and prog values in the onUpdate:
gsap.to(this.plane.uniforms.time, {
value: n,
duration: dur,
ease: "none",
onUpdate: () => {
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max *= 0.93;
this.plane.uniforms.prog.value = this.deltas.applied;
},
onComplete: () => console.log("complete!")
});
Then you get "complete!" when the animation finishes!
To make sure that your Curtains animations run at the proper rate even with monitors with high refresh rates (even the ones not directly animated with GSAP) it's also a good idea to turn off Curtain's autoRendering and use GSAP's ticker instead:
const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
curtains.render();
}
gsap.ticker.add(renderScene);
Altogether you get this demo.
This won't be the best answer possible but you can take some ideas and insights from it.
Open the console and see that when the animation gets completed it gets fired only once.
//Fire an onComplete event and listen for that
const event = new Event('onComplete');
class Img {
constructor() {
// Added a instance variable for encapsulation
this.animComplete = {anim1: false}
//Changed code above
const curtain = new Curtains({
container: "canvas",
watchScroll: false,
});
const params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
}
}
}
const planeElements = document.getElementsByClassName("plane")[0];
this.plane = curtain.addPlane(planeElements, params);
if (this.plane) {
this.plane
.onReady(() => {
this.introAnim();
document.addEventListener('onComplete', ()=>{
//Do damping effects here
console.log('complete')
})
})
.onRender(() => {
this.plane.uniforms.time.value++;
deltas.applied += (deltas.max - deltas.applied) * 0.05;
deltas.max += (0 - deltas.max) * 0.07;
this.plane.uniforms.prog.value = deltas.applied
if(deltas.applied<0.001 && !this.animComplete.anim1){
document.dispatchEvent(event)
this.animComplete.anim1 = true
}
})
}
// error handling
curtain.onError(function() {
document.body.classList.add("no-curtains");
});
}
introAnim() {
deltas.max = 6;
}
}
window.onload = function() {
const img = new Img();
}
I've found a solution to call a function at the end of the damping (wobble) effect, that doesn't use GSAP, but uses the Curtains onRender method. Because the uTime value goes up infinitely and the uProg value approaches 0, By tracking both the uTime and uProg values inside the Curtains onRender method we can find a point (2 thresholds) at which the damping effect has essentially completed. Not sure if this is the most efficient way, but it seems to work.
.onRender(() => {
if (this.plane.uniforms.prog.value < 0.008 && this.plane.uniforms.time.value > 50) { console.log("complete")}
})
Thanks to the Curtains docs re asynchronous-textures, I was able to better control the timing of the wobble effect with the desired result every time. That is, on computers with lower FPS, the entire damping effect takes place smoothly, with an onComplete function called at the end, as well as on comps with higher frame rates.
Although, as mentioned there is less control over the length of the effect, as we are not using GSAP to control the Utime values. Thanks #Zach! However, using a "threshold check" inside the curtains onRender this way, means the damping wobble effect is never compromised, if we were to disable the drawing at the on complete call.
By enabling the drawing at the same time the image is loaded we avoid any erratic behaviour. The following works now with hard refresh as well.
export default class Img {
constructor() {
this.deltas = {
max: 0,
applied: 0,
};
this.curtain = new Curtains({
container: "canvas",
watchScroll: false,
pixelRatio: Math.min(1.5, window.devicePixelRatio),
});
this.params = {
vertexShader,
fragmentShader,
uniforms: {
time: {
name: "uTime",
type: "1f",
value: 0,
},
prog: {
name: "uProg",
type: "1f",
value: 0,
},
},
};
this.planeElements = document.getElementsByClassName("plane")[0];
this.curtain.onError(() => document.body.classList.add("no-curtains"));
this.curtain.disableDrawing(); // disable drawing to begin with to prevent erratic timing issues
this.init();
}
init() {
this.plane = new Plane(this.curtain, this.planeElements, this.params);
this.playWobble();
}
loaded() {
return new Promise((resolve) => {
// load image and enable drawing as soon as it's ready
const asyncImgElements = document
.getElementById("async-textures-wrapper")
.getElementsByTagName("img");
// track image loading
let imagesLoaded = 0;
const imagesToLoad = asyncImgElements.length;
// load the images
this.plane.loadImages(asyncImgElements, {
// textures options
// improve texture rendering on small screens with LINEAR_MIPMAP_NEAREST minFilter
minFilter: this.curtain.gl.LINEAR_MIPMAP_NEAREST,
});
this.plane.onLoading(() => {
imagesLoaded++;
if (imagesLoaded === imagesToLoad) {
console.log("loaded");
// everything is ready, we need to render at least one frame
this.curtain.needRender();
// if window has been resized between plane creation and image loading, we need to trigger a resize
this.plane.resize();
// show our plane now
this.plane.visible = true;
this.curtain.enableDrawing();
resolve();
}
});
});
}
playWobble() {
if (this.plane) {
this.plane
.onReady(() => {
this.deltas.max = 7; // 7
})
.onRender(() => {
this.plane.uniforms.time.value++;
this.deltas.applied += (this.deltas.max - this.deltas.applied) * 0.05;
this.deltas.max += (0 - this.deltas.max) * 0.07;
this.plane.uniforms.prog.value = this.deltas.applied;
console.log(this.plane.uniforms.prog.value);
// ---- "on complete" working!! ( even on hard refresh) -----//
if (
this.plane.uniforms.prog.value < 0.001 &&
this.plane.uniforms.time.value > 50
) {
console.log("complete");
this.curtain.disableDrawing();
}
});
}
}
destroy() {
if (this.plane) {
this.curtain.disableDrawing();
this.curtain.dispose();
this.plane.remove();
}
}
}
const img = new Img();
Promise.all([img.loaded()]).then(() => {
console.log("animation started");
});
I made this small animation so I can practice, its purpose is to add the last letter to the beginning of the word.
I've thought that it should work with ".innerTEXT" instead of .data too, but it doesn't. Can you explain why and how does .data work? On w3schools I've learnt that .data returns a URL, so wouldn't this be supposed to work with .innerTEXT?
document.addEventListener('DOMContentLoaded', function() {
const div = document.getElementById('1');
const node = div.childNodes[0];
let text = node.data;
setInterval(() => {
text=text[text.length - 1] + text.substring(0, text.length-1);
node.data = text;
}, 100);
});
<div id="1">asdf</div>
See the working example with the innerText below.
document.addEventListener('DOMContentLoaded', function() {
const div = document.getElementById('1');
let text = div.innerText;
setInterval(() => {
text=text[text.length - 1] + text.substring(0, text.length-1);
div.innerText = text;
}, 100);
});
<div id="1">asdf</div>
You can also do this with the node like you did, but you should use textContent:
document.addEventListener('DOMContentLoaded', function() {
const div = document.getElementById('1');
const node = div.childNodes[0];
let text = node.textContent;
setInterval(() => {
text=text[text.length - 1] + text.substring(0, text.length-1);
node.textContent = text;
}, 100);
});
<div id="1">asdf</div>
I want to make one animation superimposed on another. The second (the third, the fourth) animation can start asynchronously. To do that I need to specify difference between new and old positions (not absolute 'left').
E.g.
// One moment
$( '#my_div' ).animate( { 'leftShift': "+20px", 2000, 'easeOutQuad' } );
// Another moment, may be a second later
$( '#my_div' ).animate( { 'leftShift': "+50px" }, 2000, 'easeOutQuad' );
Is it possible to add results of several animations?
The graph to clarify what I want (X-axis: time, Y-axis: speed of distance change).
I'd like to see that speeds of animations are added, not the one-by-one animations.
What did Roko C. Buljan offer?
But I don't want deferred animations, I want a real-time animation.
Note. The current syntax ("+20px", "+50px") is not supported now. It's just for the example.
I solved the problem for my case.
I need very smooth animation. So, when a user initiates a new push, it should be like a new impulse. So, an object mustn't be stopped before.
I choose an 'easing' function for smooth movement - 'easeInOutQuad'. I implemented my own version (I removed not necessary parameters which was for standart 'easing' options of jQuery):
// Some browsers works bad if there is float px
function asyncAnimate( obj, props, duration, easing )
{
function _getTime()
{
var d = new Date();
return d.getTime();
}
var startTime = _getTime();
var duration = duration;
var animNum = asyncAnimate._animNum;
var propInts = {}; // to increment only ints
var propFloats = {};
for( var iProp in props )
{
var sProp = obj.css( iProp );
propFloats[ iProp ] = 0.0; // in the beginning all floats is 0
}
var animInterval = setInterval(
function()
{
var curTime = _getTime();
if( curTime <= startTime + duration )
{
var timeDiff = curTime - startTime;
var step = easing( timeDiff / duration, timeDiff, 0, 1, duration );
var dStep;
var prevStep = asyncAnimate._prevSteps[ animNum ];
if( prevStep == null )
{
dStep = step;
}
else
{
dStep = step - prevStep;
}
asyncAnimate._prevSteps[ animNum ] = step;
for( var iProp in props )
{
var prop = props[ iProp ];
// we can increment int px only (for crossbrowser solution),
// so, we need to save a float part
var propStep = prop * dStep;
// calculate total int part
var savedFloatPart = propFloats[ iProp ];
var totalPropStep = propStep + savedFloatPart;
var totalPropStepInt = parseInt( totalPropStep );
var totalPropStepFloat = totalPropStep - totalPropStepInt;
if( Math.abs( totalPropStepInt ) > 0 )
{
obj.css( iProp, "+=" + String( totalPropStepInt ) + "px" );
}
// reset saved int/float parts
propFloats[ iProp ] = totalPropStepFloat;
}
}
else
{
clearInterval( animInterval );
}
},
10
);
asyncAnimate._animNum++;
}
asyncAnimate._prevSteps = {};
asyncAnimate._animNum = 0;
Examples of usage (do not forgent to include a js with easing functions) or create your own:
asyncAnimate( $( '#div1' ), { 'margin-left': 50 }, 1000, $.easing.easeInOutElastic );
// ...
asyncAnimate( $( '.green' ), { 'width': -50, 'height': -50 }, 250, $.easing.easeInOutQuad );
You can see how it works (try to press buttons in very short intervals).
I hope it's easy to adapt it for some other types of animations.
It seems that animate() of jQuery does not support the behavior currently.
LIVE DEMO
The .stop() method will clear your current animation. You can use += and -= to update the current element position
Just for example:
<input type="button" value="50" />
<input type="button" value="-50" />
<input type="button" value="150" />
<input type="button" value="-150" />
<div id="my_div"></div>
$(':button').click(function(){
$('#my_div').stop().animate( { left: "+="+ this.value }, 2000 );
});
Logically if you don't need to clear the previous animation just remove .stop()