using requestAnimationFrame with wait time - javascript

I've some basic script to scroll a text to the right and left which I tried to convert it from timeout to requestAnimationFrame, however, I can't make it work.
function slideHorizontal(e, amount, time) {
var waitTime = 500;
e.animate({
marginRight: '-'+amount+'px'
}, time, 'linear', function() {
setTimeout(function() {
e.animate({
marginRight: 0
}, time , 'linear', function() {
setTimeout(function() {
slideHorizontal(e, amount, time);
}, waitTime);
});
}, waitTime);
});
}
any suggestion how I can apply the wait time with requestFrameAnimation? BTW, should I even use jQuery.animate() if I'm going to use the requestFrameAnimation?

No, requestAnimationFrame() does not make any sense when you use jQuery's animate(). jQuery may use requestAnimationFrame() internally though.
You can use requestAnimationFrame() to replace setTimeout() when you run the main animation loop with setTimeout(f, x). Instead of having a animation frame every x ms, requestAnimationFrame() lets the browser control the frame rate. The actual rate will then be based on whatever the browser deems necessary for a smooth animation, depending on the hardware and other factors. The browser can also lower the refresh rate when the window/tab is not visible, to conserve CPU time and reduce power usage of animations that are not visible.
But that's not what you are using setTimeout() for. You use it to wait between two animations. There are more elegant ways of doing this, but requestAnimationFrame() isn't one of them.

here's how you would use requestAnimationFrame:
var i = 0;
var forward = true;
function draw() {
requestAnimationFrame(draw);
if(i < 100 && forward){
i+=2;
}
else if(i > 0){
forward = false;
i-=2;
}
else{i = 0; forward = true;}
document.getElementById("div").style.left = i + "px";
}
draw();
div {
width: 80px;
height: 80px;
background: black;
position: absolute;
}
<div id="div"></div>
The setInterval equivalent would be:
var i = 0;
var forward = true;
function draw() {
setInterval(function(){
if(i < 100 && forward){
i+=2;
}
else if(i > 0){
forward = false;
i-=2;
}
else{i = 0; forward = true;}
document.getElementById("div").style.left = i + "px";},10);
}
draw();
div {
width: 80px;
height: 80px;
background: black;
position: absolute;
}
<div id="div" onload="draw"></div>

Related

Using requestAnimationFrame instead of setInterval

I ran across an issue where my code worked just fine in Chrome but in Safari it began to stutter. I read somewhere that "Safari caps intervals at 1000ms and then adds its own exponential delay, doubling every iteration." That being said I am trying to change my code to work both on Safari and Chrome using requestAnimationFrame but am having issues wrapping my head around the subject.
Essentially what I am trying to do is ease a div across a page with each click using requestAnimationFrame instead of setInterval.
Any guidance would be appreciated.
Here is the Javascript:
let progressAvatar = document.querySelector('.box');
progressAvatar.addEventListener('click', checkClick);
var clicks = 0;
function checkClick() {
clicks += 1;
if (clicks == 1) {
startingLocationOfAvatar();
} else if (clicks == 2) {
locationOfAvatar();
}
}
function startingLocationOfAvatar() {
let speed = 10;
// Initial location of Avatar
let pos = 0;
// Ending location of Avatar
let progressBarWidthDivided = 53;
let id = setInterval(frame, speed);
function frame() {
if (pos == progressBarWidthDivided) {
clearInterval(id);
} else {
pos++;
progressAvatar.style.marginLeft = pos + "px";
}
}
}
// Move the avatar based on its previous location
function locationOfAvatar() {
let speed = 10;
// Pos becomes last location of movePixels
let pos = 53;
let id = setInterval(frame, speed);
// movePixels adds last location by its new location
let movePixels = pos * 2;
function frame() {
if (pos == movePixels) {
clearInterval(id);
} else {
pos++;
progressAvatar.style.marginLeft = pos + "px";
}
}
}
Here is the Html:
<div class="box">
</div>
Here is the Css:
.box {
height: 100px;
width: 100px;
background-color:red;
}
Lastly here is a jsfiddle:
Move Div Across Screen
I will let experts answer, but as a 2 weeks coding noob this is what i know, you can compile all individual animation functions into a grand Animation function. And call that function with request animation frame. Trying to make a sensible game myself, i have totally given up on manipulating css values and coding in pure Javascript.
Note, dependent where you will put your mini-functions - the photos/graphical items will display either on top or under eachother visually.
It should look something like this:
function drawEverything(){
context.clearRect(0,0,width,height);
context.save();
draw();
draw1();
draw2();
context.restore();
requestAnimationFrame(drawEverything);
}
requestAnimationFrame(drawEverything);

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

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

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

Slide Up Linear Easing First Step Does Nothing

I'm developing an accordion plugin, and it's mostly done except for one bug where for the first few steps of the slideUp/slideDown, the accordion is 1px taller than it's meant to be, causing a visual bug. I've narrowed it down to the fact that the first step in the slideUp animation doesn't do anything, and I can't figure out why. Here's an example:
console.log('Start');
var diff = 0;
var upNow = 100;
var downNow = 0;
$.fx.interval = 1000;
var duration = $.fx.interval * 100;
$("#div1").slideUp({
easing: 'linear',
duration: duration,
step: function(now) {
if (now != 0 && now > 90) {
console.log("Slide Up: " + now);
upNow = now;
}
}
});
$("#div2").slideDown({
easing: 'linear',
duration: duration,
step: function(now) {
if (now != 0 && now < 10) {
downNow = now;
diff = 100 - (upNow + downNow);
console.log("Slide Down: " + now);
console.log("Slide Difference:" + diff);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div style='height: 100px; background-color: red;' id='div1'>
</div>
<div style='height: 100px; background-color: blue; display: none;' id='div2'>
</div>
http://jsfiddle.net/hbh6U/
The problem is that I need these to be in sync, and I can't figure out why they're not, or how to get them in sync. One idea I've had is to skip the first step of the slideDown animation, but I'm not sure how to do that either. Has anyone got any ideas, or faced this bug before?
The problem comes down to this line in jQuery's internal defaultPrefilter method:
tween.start = prop === "width" || prop === "height" ? 1 : 0;
This causes the animation for the second div (from 1px to 100px) to be shorter than that of the first div (from 0 to 100px).
To solve this modify your step function like this:
function linearStep(now, animation){
var animationStart = animation.start;
if (animationStart === 1){
animationStart = 0;
}
animation.now = (animation.end - animationStart ) * animation.pos + animationStart;
}
It overwrites the calculated now value by doing the same calculation with a fixed animationStart, which is 0 instead of 1.
This will break if the animation actually starts at 1, but there'd be other ways to handle it then.
Side-by-side Fiddle: http://jsfiddle.net/Nd3w2/3/
i don't exactly know where is this issue coming from... Sunday morning... not too much time to investigate... But i found two possible solution based on your fiddle...
First one was to wrap these two DIVs in another DIV with overflow:hidden.
Second one... probably more appropriate is to call "slide" function only on one of the divs and then update the size of second one in callback, something like that:
console.log('Start');
var diff = 0;
var upNow = 100;
var downNow = 0;
$.fx.interval = 1000;
var duration = $.fx.interval * 100;
$("#div1").slideUp({ easing: 'linear', duration: duration, step: function(now)
{
if(now != 0 && now > 90)
{
console.log("Slide Up: " + now);
upNow = now;
}
$("#div2").height(100- $("#div1").height());
}});
Also remove "disply:none" form div2 styles...
It fixes the issue and is a bit more elegant solution in my opinion... Calling two separate animation functions can lead to possible sync problems... Hope that helps...

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