Looping css style with setTimeout: "in the blink of an eye" - javascript

I want to make a bar (#innerBar) to decrease 1% in width per second.
The loop doesn't seem to work. My bar drops from 100% to 0% in the blink of an eye.
function timer(){
var timer;
for(i=100;i>=0;i--){
timer = i.toString() + "%";
setTimeout(function({$('#innerBar').css("width", timer)}, ((100-i)*1000));
}
}
Note : #innerBar is a DIV with a css property (height:10px). ** + the width from timer(); **

As already said in the comments, you need to put it in the closure. Here's an example:
function timer() {
for (i = 100; i >= 0; i--) {
setTimeout(function(t) {
return function() {
var timer = t.toString() + "%";
$('#innerBar').css("width", timer);
};
}(i), ((100 - i) * 1000));
}
}
timer();
#innerBar {height: 50px; background: green; transition: width 0.2s linear}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="innerBar"></div>
EXPLANATION
So my question are: what is going throught function(t)? and why and how does }(i) work? Is it a multiplication of the fu?
Let's take the function body we're passing in to setTimeout:
function(t) {
return function() {
var timer = t.toString() + "%";
$('#innerBar').css("width", timer);
};
}(i)
Let's omit the inside part:
function(t) {
// do some stuff with t
}(i)
Looks familiar? It's like the function body is called right away and is called an IIFE, just like, say:
(function(a, b) {
return a + b;
})(2, 3) // returns 5
So back to the original function, it accepts one parameter, t, and when we're calling the function we're passing in the iterator i as an argument (so the value of i becomes t inside the function). As I said in the comment, this is necessary in order to "fetch" the current value of i instead of getting the post-loop value.

As #Shomz already posted. That is good solution. I simply want to add my solution because it does not create 100 functions. So it's slightly lighter on the memory. Also you don't have to look through the DOM for #innerBar over and over again. And I removed jQuery as a dependency.
var size = 100;
var bar = document.getElementById( "innerBar" );
function setSize() {
bar.style.width = size-- + "%";
if ( size > 0 ) setTimeout( setSize, 1000 );
}
setSize();
#innerBar {
width: 100%;
height: 50px;
background: green;
transition: width 0.2s linear;
}
<div id="innerBar"></div>

I think the following code does what you want. the input time should be 1000, which will decrease you width by 1% every second
var width = $('#innerBar').width();
function timeLoop(time){
width = width*0.99;
$('#innerBar').css("width", width);
if (width <= 0.01){
return;
}
else {
setTimeout(function() {
timeLoop(time);
}, time);
}
}

Related

setInterval for css sprite animation

I'm trying to animate a sprite using the below code but instead of waiting about 1 second before going to the next iteration of the loop, the animation seems to jump from the first image in the sprite to the last within about a second. Can anyone tell me how I might be able to alter the code to make it work the way I have in mind? Thanks!
preview = setInterval(animation,1000);
counter = 0;
function animation() {
while (counter < 81){
position = counter * 1.25;
$('#ad_1').css({"background-position" : position + "% 0%"});
counter ++;
}
};
preview;
Your while loop is causing everything to happen in the first interval call.
Remove it, and you'll be depending solely on intervals:
preview = setInterval(animation,1000);
counter = 0;
function animation() {
position = counter * 1.25;
$('#ad_1').css({"background-position" : position + "% 0%"});
counter ++;
};
preview;
Live example:
var preview = setInterval(animation,100);
var counter = 0;
function animation() {
position = counter * 1.25;
$('#ad_1').css({"background-position" : position + "% 0%"});
counter ++;
};
div {
height: 317px;
width: 50px;
display: inline-block;
background: url(https://pbs.twimg.com/profile_images/2186972673/super_mario.jpg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="ad_1"></div>
Take the while portion of the loop out.
function animation() {
if (counter < 81){
position = counter * 1.25;
$('#ad_1').css({"background-position" : position + "% 0%"});
counter ++;
} else {
clearInterval(preview); /* this will stop the interval timer, since you won't need it after 80 repetitions. */
preview = null;
}
}

How can I make my setTimeouts get stopped here?

The behavior I'm trying to achieve is this:
On hover/mouseenter, change the background image from the placeholder to a gif whose positions changes in order to achieve an animated effect, then go back to the placeholder when the mouse leaves.
My code is
$('.filmstrip').mouseenter(function(){
var $that = $(this),
w = $that.width(),
fr = $that.attr('data-framerate');
$that.css('background-image','url('+$that.attr('data-gifurl')+')');
for ( var i = 1, n = $that.attr('data-ticks'); i <= n; ++i )
{
(function(j){
setTimeout(function(){
$that.css('background-position-x','-'+(w*j)+'px');
}, j*fr);
})(i);
}
$that.bind('mouseleave',function(){
$that.css('background-image','url('+$that.attr('data-placeholder')+')').css('background-position-x','0');
});
});
and the bug I'm having is that if the gif hasn't finished animating, then the
.css('background-position-x','0')
part of
$that.css('background-image','url('+$that.attr('data-placeholder')+')').css('background-position-x','0');
});
doesn't work because the background position is still being moved by the animation. So I need some way to first stop the setTimeout stuff if it isn't finished running. Any idea how I can do that?
This may be something better done with CSS rather than javascript.
Option #1 - Use an actual GIF
You could compile the frames which you want animated into an actual GIF file, and then have the background image change based on hover:
<div class="filmstrip"></div>
And then CSS
.filmstrip { background:transparent url('static_image.jpg') no-repeat 0 0 }
.filmstrip:hover { background-image:url( 'animated_image.gif' ) }
Option #2 - Use CSS3 Animation
You could keep the animated image as a strip of frames (of a known length) and then use something like:
<div class="filmstrip"></div>
With CSS
.filmstrip { background:transparent url('static_image.jpg') no-repeat 0 0 }
#keyframes animate-bg {
0% { background-position: 0 0 }
100% { background-position: -1000px 0 }
/* where 1000px is the length of the strip */
}
.filmstrip:hover { animation: animate-bg 5s steps(50) infinite }
/* where 5s is the overall loop length time and 50 is the number of frames in the strip */
Option #3 - Use Spritely
Spritely is a jQuery plugin which seems to manage all elements of turning a filmstrip/sprite image into an animation, including being able to start/stop the animation, reset to the first frame, change FPS, etc.
Add a stop variable :
$('.filmstrip').mouseenter(function(){
var isStopped = false;
var $that = $(this),
w = $that.width(),
fr = $that.attr('data-framerate');
$that.css('background-image','url('+$that.attr('data-gifurl')+')');
for ( var i = 1, n = $that.attr('data-ticks'); i <= n && !isStopped; ++i )
{
(function(j){
setTimeout(function(){
if (!isStopped) {
$that.css('background-position-x','-'+(w*j)+'px');
}
}, j*fr);
})(i);
}
$that.bind('mouseleave',function(){
isStopped = true;
$that.css('background-image','url('+$that.attr('data-placeholder')+')').css('background-position-x','0');
});
});
If isStopped is not accessible (because not tested) from the timeout, then just create a new variable in a inner scope which you affect isStopped value.
You can use an interval based solution like
$('.filmstrip').mouseenter(function() {
var $that = $(this),
w = $that.width(),
fr = +$that.attr('data-framerate'),
ticks = +$that.attr('data-ticks');
$that.css('background-image', 'url(' + $that.attr('data-gifurl') + ')');
var counter = 0;
var interval = setInterval(function() {
$that.css('background-position-x', '-' + (w * ++counter) + 'px');
if (counter >= ticks) {
clearInterval(interval);
}
}, fr);
$(this).data('bg-interval', interval)
}).mouseleave(function() {
clearInterval($(this).data('bg-interval'));
$(this).css('background-image', 'url(' + $(this).attr('data-placeholder') + ')').css('background-position-x', '0');
});
.filmstrip {
height: 64px;
border: 1px solid grey;
background-position: right;
background-position-y: inherit;
display: inline-block;
width: 64px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="filmstrip" data-framerate="400" data-ticks="10" data-gifurl="//cdn.sstatic.net/Sites/stackoverflow/img/sprites.svg?v=bc7c2f3904bf">
</div>
U can use clearTimeout to stop setTimeOut.
Working Demo-
var myVar;
function myFunction() {
myVar = setTimeout(function(){ alert("Hello"); }, 3000);
}
function myStopFunction() {
clearTimeout(myVar);
}
<p>Click the first button to alert "Hello" after waiting 3 seconds.</p>
<p>Click the second button to prevent the first function to execute. (You must click it before the 3 seconds are up.)</p>
<button onclick="myFunction()">Try it</button>
<button onclick="myStopFunction()">Stop the alert</button>
More-
Mozilla Developer Network
and
W3School

What causes my page to have so much paint time?

From what I read, opacity should be a save css property to change. The display property is only changed at the start and end of the animation. There are only a few css properties in the entire document yet this just baffles me. Why does my paint time still nearly hit the 60fps mark?
My paint graph
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() {
callback(currTime + timeToCall);
},
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
function ease(t) {
return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
}
(function(w, d) {
var toggle = 0,
fadeId,
overlay = d.getElementById('overlay'),
o_style = overlay.style;
function addEvent(elm, evt, fnc) {
return w.addEventListener ? elm.addEventListener(evt, fnc, false) : elm.attachEvent(evt, fnc);
}
var cur_opacity = 0,
factor = 400,
start_time,
time_Δ,
dur,
b,
Δ;
function loop(time) {
if (!start_time) start_time = time; // set time if not set
time_Δ = time - start_time + 16; // calculate passed time
cur_opacity = ease(time_Δ / dur) * Δ + b;
// calculate opacity
if (time_Δ < dur) {
fadeId = requestAnimationFrame(loop);
} else {
if (Δ > 0) {
cur_opacity = 1;
} else {
cur_opacity = 0;
o_style.display = 'none';
}
}
o_style.opacity = cur_opacity;
}
function fade() {
if (fadeId) cancelAnimationFrame(fadeId);
toggle ? toggle = 0 : toggle = 1;
b = cur_opacity; // set current opacity as base
Δ = toggle - cur_opacity; // set change we make
dur = factor * Math.abs(Δ); // set the animation duration
start_time = 0; // reset time
o_style.display = 'block'; // always show overlay first
fadeId = requestAnimationFrame(loop); // init animation
}
addEvent(d, 'click', function(event) {
if (fadeId) cancelAnimationFrame(fadeId);
fadeId = requestAnimationFrame(fade);
})
}(window, document))
html,
body {
margin: 0;
padding: 0;
background: #444;
height: 100%;
}
#overlay {
background: #fff;
height: 100%;
opacity: 0;
}
<div id="overlay"></div>
*edit: Removed toFixed(). Wrong implementation but not relevant to the issue;
*edit2: I found that adding transform:translateZ(0); to #overlay completely removed the paint time.
CSS reflows are CPU intensive as they are caused by the browser having to re-render the entire page in order to update any styling applied by to the UI. A CSS repaint is good. CSS Repaints are good as the browser is able to update just the elements that have changed and are much, much less CPU intensive.
Your 60fps number is good, more frames mean a smoother animation. From a quick look around it seems Chrome has a hardcoded 60fps cap in place, Chrome will render your animation as quickly as it can using resources at its disposal in order to achieve as smooth an animation as possible, being in mind the simplicity of the animation and how consistently its able to hit 60fps it is unlikely that this short animation actually puts any significant on your machine (in a brief test, spamming the transition on my machine, Chrome never goes over 4% CPU) and so corrective action is not necessary.

setInterval stopping prematurely

I'm trying to increment the width of a div every half a second. It should continue to expand as long as it is less than 100% wide. However, it is stopping at 10%:
JSFIDDLE
$('.button').click(function(){
var progress = setInterval(function(){
if( $(".bar").css('width') < '100%') {
$('.bar').animate({ width: '+=10%' });
} else {
clearInterval(progress);
}
}, 500)
});
Would anyone know why?
Try this, simply compares bar to it's parent width:
$('.button').click(function () {
var $bar = $(".bar"),
parentW = $bar.parent().width();
var progress = setInterval(function () {
if ($bar.width() < parentW) {
$bar.animate({
width: '+=10%'
});
} else {
clearInterval(progress);
}
}, 500)
});
The problem with approach you had is css('width') returns string width including px and comparing that to string 100% wasn't working .
Easy check to see what it looks like:
console.log($bar.css('width'))
Note that jQuery.width() returns numerical value of pixel width
DEMO
Why are you playing with % of width. Just make it simple.
For example :
increase the width by 10px each time while width is less than 100.
$('.button').click(function(){
var progress = setInterval(function(){
var width=$(".bar").width();
if( width < 100) {
$('.bar').animate({ width: width + 10 });
} else {
clearInterval(progress);
}
}, 500)
});
Check fiddle here

Web technique to resize an image over a given time interval

Is there a technique to resize an image over a given time interval?
What I want to do is have an image and when the mouse rolls overs it, it should resize the image making it larger. All I can find are simple rollover scripts that instantly resize the image. I want to do it over a period of about a second.
And as a must it cannot lag and destroy the visual experience. I am looking for an approach in javascript, jQuery, or HTML5 if it's possible; other suggestions appreciated but no flash.
It's very easy with CSS3 Transitions:
.myImg
{
width: 200px;
transition-duration: 1s;
-webkit-transition-duration: 1s;
}
.myImg:hover
{
width: 300px;
}
Demo: jsfiddle.net/yyDd4
You can do it in jQuery in this way.
var factor = 2;
$('#foo').mouseover(function() {
$(this).animate({
top: '-=' + $(this).height() / factor,
left: '-=' + $(this).width() / factor,
width: $(this).width() * factor
});
});
and the other techniques are here.
You can do this in plain javascript, though animation is always surprisingly complicated, especially if you want the image to shrink back after the mouse moves off it. Making an object to store the state is possibly the best solution and is also quite adaptable (other images, other types of animation).
http://jsfiddle.net/VceD9/6/
new GrowingImage('myImage', 2, 1000);
function GrowingImage(id, factor, duration) {
var el = document.getElementById(id),
originalWidth = el.offsetWidth,
originalHeight = el.offsetHeight,
timer,
stage = 0,
frameRate = 17,
maxStage = duration / frameRate;
el.onmouseover = function () {
animate(1);
};
el.onmouseout = function () {
animate(-1);
};
function animate(direction) {
clearInterval(timer);
timer = setInterval(function() {
stage += direction;
if (stage <= 0) {
stage = 0;
clearInterval(timer);
} else if (stage >= maxStage) {
stage = maxStage;
clearInterval(timer);
}
var scale = 1 + (factor - 1) * stage / maxStage;
el.style.width = originalWidth * scale + 'px';
el.style.height = originalHeight * scale + 'px';
}, frameRate);
}
}
If exact timing is important to you, you may need to adjust this so that it keeps track of the amount of time that the current animation has been running.

Categories