some time ago I started to write some code in JavaScript to learn it a little bit. I picked a rollin/rollout animation as 'project'. (I know about JQuery's slideDown/slideUp, but I wanted to work with pure JavaScript.)
I finished my effect, and the result looks pretty good in all major browsers except Firefox (tested versions 22.x to the latest (25.0.1)). In Firefox, the rolling (in and out) stutters while it rolls smoothly in Chrome, Opera, and Internet Explorer.
The general approach is (unsurprisingly) to have an element's style.height (or width) attribute increased/decreased several times by some pixels over a given time. To avoid calculating sizes every time the effect takes place, I calculate them one time and place them in an array (first item (0 + stepSize), last item wanted height/width). The decrease of the element's height is done by this function:
var verticalRollInWorker = function(step) {
if (step > 0) {
$(btt).style.height = stepSizes[step - 1];
setTimeout(function() { verticalRollInWorker(step - 1); }, delay);
} else {
$(btt).style.display = "none";
$(btt).style.height = 0;
// Enable roll out effect:
stateChange(false);
if (afterFullRollIn != null) {
afterFullRollIn();
}
}
}
In the particular example, I'm using 20 steps over 400ms. The step sizes in the array are rounded to integers, that's why the last step just sets 0 - to handle rounding differences.
(For convenience, I wrote my own $(element) helper, there's no JQuery involved here.)
I tested Firefox without Add-Ons, no difference.
I highly appreciate any help you can provide :)
One problem that I noticed in the above code is that you used $(btt). So, every 20s when the function is iterated, the browser needs to obtain the jQuery object. You could rather store it into a variable say 'var BTT=$(btt);' and use this BTT. Fetching jQuery object is a time consuming task.
Since you are using setTimeout(), the function will be executed every 20ms regardless of the completion of the current execution, this may also cause a drag. As Dagg Nabbit said, you could use setInterval() instad of setTimeout.
Another possible reason might be browser-reflow. I made a personalised scrollbar, and found browser reflow was noticeably greater in my FF than Chrome or IE. This depends on the size of the element, DOM tree depth, overflow property, and more...
And again use this code and see if it is fixed. reduces the subtraction into 1 code.
var BTT=$(btt).get(0);
var verticalRollInWorker = function(step) {
if (step > 0) {
step--;
BTT.style.height = stepSizes[step];
setTimeout(function() { verticalRollInWorker(step); }, delay);
}
else {
BTT.style.display = "none";
BTT.style.height = 0;
// Enable roll out effect:
stateChange(false);
if (afterFullRollIn != null) {
afterFullRollIn();
}
}
}
Further Comments can be made only after seeing a live example.
Regards.
Related
I am trying to slow down an animation of a picture shrinking so that you can actually see it shrink instead of just jump to a smaller size, but what instead happens is that my console log updates as it should-with intervals in-between each update, but the image on the screen waits until the for loop is done and then jumps to the end size instead of gradually getting smaller. Is there something wrong with my code or am I missing some behind the scenes stuff on how the page updates?
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
function makeSmaller(){
for (let i = 100; i>50; i--){
rockButtonImage.style.width = i+'%';
console.log(rockButtonImage.style.width);
sleep(50);
}
}
Modern browsers avoid unnecessary renderings of a page and wait while a JavaScript execution iteration is complete before doing a rendition.
The sleep() function in the provided code does not really cause a browser to sleep but rather to do a processor-intensive computation, forbidding the browser from rendering anything.
You can try using the setTimeout() function instead, e.g.:
var i = 100;
function makeSmaller(){
console.log(rockButtonImage.style.width);
rockButtonImage.style.width = i+'%';
i--;
if (i > 50) {
setTimeout(makeSmaller, 50);
}
}
You might also find it useful to rely on CSS animations or CSS transitions instead of JavaScript for such visual effects as JS is not nearly as efficient as they are.
See also:
More on setTimeout: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
More about the CSS animations: https://developer.mozilla.org/en-US/docs/Web/CSS/animation
You have to dynamically get the DOM element getElementById and then change the width. Im not sure in which context the RockButton variable lives. Its all about the state.
I'm trying to get the position of a div being translated using CSS3 inside another div with a overflow: hidden property.
Here's a jsFiddle that demonstrates it: https://jsfiddle.net/meeuz3w9
The top position of the element is not updated when trying to get it with jQuery's position().top.
I have no idea why this is happening, does anyone have a solution?
Updated: This doesn't work on Chrome 44 on OSX, but works on other browsers
After playing with it a while, I guess I have an answer:
This is a performance issue
Chrome uses the resources to make the animation more fluid making the script to get less callbacks called and less positions updated.
While Firefox uses the resources to keep both updated, wich makes the animation not so fluid, but the script gets more updated positions.
In Chrome the profile of the calls is also very irregular (between 2 and 100 calls per position), while in Firefox keeps under 4 calls per position.
In this fiddle I tried to get better performance, using a native calculus in a global variable:
var position = function() {
return this.getBoundingClientRect().top -
this.offsetParent.getBoundingClientRect().top;
};
and avoiding the use of console.log ...
var callback = function(){
var top = position.call(callback.target);
if(benchmark[top] === undefined){
benchmark[top] = 0;
} else {
benchmark[top] += 1;
}
};
then I found this perfomance difference.
How to solve it
You can get more info here about the differences between JavaScript animations and CSS animations:
https://css-tricks.com/myth-busting-css-animations-vs-javascript/
So after this reading, I propose the following solution:
Do it with JavaScript
The animation you describe is as easy as:
$('#bar').animate({
'translate3d': '-2000'
}, {
step: function (now, fx) {
console.log(now);
$(this).css({"transform": "translate3d(0px, " + now + "px, 0px)"});
},
duration: 1000,
easing: 'linear',
queue: false
},
'linear');
this way you will be able to handle the position on javascript on every tick instead of asking it to the CSS.
I hope it helps.
I have got an issue with the javascript method setTimeout() executing correctly on mobile safari.
My code is as follows:
function addBlock() {
if(i < full) {
$('#box-'+i).removeClass('empty');
$('#box-'+i).addClass('full');
i++;
setTimeout(addBlock, 20);
}
else {
if(fullcheck != Math.round(fullcheck)) {
i = i++;
$('#box-'+i).removeClass('empty');
// $('#box-'+i).addClass('halfbox');
$('#total-count').animate({height: barheight}, 5000);
}
if(usergiven) {
$('#box-'+randbox).css('border', '1px SOLID #FF0000');
$('#box-'+randbox).css('background-color', '#FF0000');
}
}
}
No matter what timeout value I provide the setTimeout function with, it always seems to run at the same speed.
The idea is that it populates a set of blocks at a faster speed than 1 every 2 seconds ( the actual amount should be about 50 a second iirc ).
Can anyone tell me why the mobile safari is not properly executing this function or what I am doing wrong?
Thanks!
There are quite a few things that are hard to reason with regarding this question. One is the amount of variables without declaration or expressions without comments on what they should do (as in fullcheck != Math.round(fullcheck). WTF!?), the other is how this code behaves as opposed to how you would like to have it behave.
Since the code is incomplete and not runnable one just has to guess as to what you mean. I am guessing you are saying that the code completes in about two seconds as it is now, and you would like it to complete in fifty seconds.
Without knowing the values of i and full it is impossible to do any calculations on the right timeouts, so I have to guess what you are doing wrong. With the current rate of 20ms in the timeout, you will process about 500 rows per second. If it currently runs in two seconds, that means you have 4000 rows. If they should be filled in fifty seconds, you will have to up your timeout to 500ms.
I've tried everything that is supposed to invoke a reflow but that isn't happening. I'm calling my test function 10 times to draw some element on my screen and I move that element through each iteration. That loop is executed immediately and in the end I get one picture instead of seeing the movement of the element on the screen.
It's as if when all work is done, reflow and drawing on the screen is invoked. But i want to see each drawing.
All the things I've tried didn't give any results. The only thing that works is alert(), but i don't need an interaction with user.
I'm using an webkit 1.2.5 if that helps.
If I'm not understandable enough I will try to explain better.
This the code I'm forcing to reflow
var i = 0;
for(;i<500;i+=50){
fTestInfo(i);
console.log("Test loop!!! "+i);
}
The thing I nedd is to see a picture on my screen each time fTestInfo(i) is executed but instead, i only see the ending result.
fTestInfo depends on i it moves in left by the value of i.
I see you are using a for loop which typically means you misunderstand how timers work. The for loop is synchronously executed and you are probably setting all the timers at once.
Try this:
(function loop(i) {
if (i >= 500) {
return;
}
document.querySelector("div").style.left = i + "px";
setTimeout(function() {
loop(i + 1);
}, 16);
})(0);
demo http://jsfiddle.net/UCfmF/
I suppose you mean getting a value like .offsetWidth? This is not guaranteed to make a visible reflow on the screen, browsers may wait for some time (read: until javascript execution stops) before actually attempting to paint anything on the screen even if you are doing actions that trigger reflows.
This means that if you append 1000 elements to the document, it will not trigger 1000 reflows. Even if you fetch .offsetWidth in between each iteration. It will just be calculated for you but not necessarily painted.
You need to move the elements with a timer as the end of javascript execution is when browsers flush out any queued reflows.
See http://dev.opera.com/articles/view/efficient-javascript/?page=3#reflow
As stated earlier, the browser may cache several changes for you, and
reflow only once when those changes have all been made. However, note
that taking measurements of the element will force it to reflow, so
that the measurements will be correct. The changes may or may not not
be visibly repainted, but the reflow itself still has to happen behind
the scenes.
You need to give the browser the opportunity to enter its event loop between each iteration.
Use setTimeout to schedule each iteration of the drawing:
function scheduledTestInfo(i) {
setTimeout(function() {
fTestInfo(i);
}, i); // calls each function 50ms apart
}
var i = 0;
for ( ; i < 500 ; i += 50) {
scheduledTestInfo(i);
}
I'm using OpenX at work, and one of my boss requirements is a expandable banner. For that (and made a horrible simplification of the whole story) I made this script.
function retro(){
var acs = jQuery('#trial_center').height() - 5;
jQuery('#trial_center').css('height', acs + 'px');
}
jQuery(document).ready(function(){
jQuery("#trial_center").mouseover(function(){
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
})
jQuery("#trial_center").mouseleave(function(){
var c = 89;
while (c > 0) {
setTimeout("retro()", 1000);
c--;
}
})
});
The problem I have is in the mouseleave event: the original idea was to made this loop several times (89 times), and each time, decrease the height of the banner until it get his original size. Why this? Because my boss want an "effect", and this effect must be in sync with the customer's flash.
The problem is that instead of decrease his size progressively, apparently the script made all the operations an "after" the sum of setTimeout calls, updated the page. So, the result is exactly as the banner shrinks one time from the expanded size to the original size.
I don't know what is wrong with this, or if exists other more intelligent solution.
Any help will be very appreciate.
Thanks in advance
Your loop setting the timeout is just setting 89 timers for one second later than the loop runs, and the loop will run in milliseconds — so they'll all fire about a second later. That doesn't sound like what you want to do.
Two options for you:
1. Use animate
jQuery's animate function seems like it does what you want. You can tell jQuery to animate the size change, and you tell it how long to take to do so:
jQuery('#trial_center').animate({
height: "500px" // Or whatever the desired ending height is
}, 1000);
That will animate changing the height of the container from whatever it is at the point that code runs to 500px, across the course of 1,000 milliseconds (one second). Obviously you can change the duration to whatever you like.
2. Set up the timer loop manually
If for whatever reason you don't want to use animate, you can do this manually (of course you can; jQuery can't do anything you can't do yourself, it just makes things easier). Here's how to set up a timer loop:
jQuery("#trial_center").mouseleave(function(){
var c = 89;
// Do the first one right now, which will schedule the next
iteration();
// Our function here lives on until all the iterations are
// complete
function iteration() {
// Do one
retro();
// Schedule this next unless we're done
if (--c > 0 {
setTimeout(iteration, 100); // 100ms = 1/10th second
}
}
});
That works because iteration is a closure over c (amongst other things). Don't worry about the term "closure" if it's unfamiliar, closures are not complicated.
Separately: You're using mouseover to set the height of the trial_center element a second later; you probably wanted mouseneter rather than mouseover. mouseover repeats as the mouse moves across it.
Off-topic:
It's best not to use strings with setTimeout; just pass it a function reference instead. For example, instead of
setTimeout("retro()", 1000);
you'd use
setTimeout(retro, 1000); // No quotes, and no ()
And for the other place you're using, instead of
setTimeout("jQuery('#trial_center').css('height', '500px')", 1000);
you'd use
setTimeout(function() {
jQuery('#trial_center').css('height', '500px');
}, 1000);