I know its a bit to ask, but is the following possible without using jQuery? I have it running with jQuery now but it seems to be presenting performance issues. If you could help I will be most grateful. I am not lazy, just not very code knowledgable. Took me a while to even get this far.
//
// default speed ist the lowest valid scroll speed.
//
var default_speed = 1;
//
// speed increments defines the increase/decrease of the acceleration
// between current scroll speed and data-scroll-speed
//
var speed_increment = 0.01;
//
// maximum scroll speed of the elements
//
var data_scroll_speed_a = 2; // #sloganenglish
var data_scroll_speed_b = 5; // #image-ul
//
//
//
var increase_speed, decrease_speed, target_speed, current_speed, speed_increments;
$(document).ready(function() {
$(window).on('load resize scroll', function() {
var WindowScrollTop = $(this).scrollTop(),
Div_one_top = $('#image-ul').offset().top,
Div_one_height = $('#image-ul').outerHeight(true),
Window_height = $(this).outerHeight(true);
if (WindowScrollTop + Window_height >= (Div_one_top + Div_one_height)) {
$('#sloganenglish').attr('data-scroll-speed', data_scroll_speed_a).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_a * speed_increment);
$('#image-ul').attr('data-scroll-speed', data_scroll_speed_b).attr('data-current-scroll-speed', default_speed).attr('data-speed-increments', data_scroll_speed_b * speed_increment);
increase_speed = true;
decrease_speed = false;
} else {
$('#sloganenglish').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
$('#image-ul').attr('data-scroll-speed', '1').attr('data-current-scroll-speed', default_speed);
decrease_speed = true;
increase_speed = false;
}
}).scroll();
});
I don't see any performance issue in your code, although there is space for some optimization. And I don't think jQuery might be the problem.
First thing to notice is the CSS access.
The height attribute is very expensive to access because it causes the browser to process many rendering steps of the pipeline, as you can see in CSS Triggers.
You are retrieving the height of two elements in a scroll event, which means that they will be calculated many times. Is it really necessary?
If your #image-ul element doesn't change its height, maybe you can calculate it outside of the event only once.
In the case of the window height, I believe it won't change in the scroll event. How about to create different handlers, one for the events that need to (re)calculate the window height and another for the events that don't need that calculation?
Another noticeable point is that you set the 'data-current-scroll-speed' and the 'data-speed-increments' attribute always with the same constant value. No change, no unset. Is it really necessary?
Actually, it is not clear what you are really doing. Your performance issue might be somewhere else.
Related
I'm trying to animate my menu with jquery - simply change padding depending on scroll. It works fine, however takes extremely long to execute (1-10 seconds). Any ideas? I have tried .on('scroll') but no change.
Both on Chrome and Firefox.
Fun thing - if I add alert(v); after 3rd line to be sure on scroll position, it animates well, without any lag ;x
<script>
$(window).scroll(function() {
var v = $(window).scrollTop();
if (v >= 150) {
$('#header').animate({"padding": "15px 0px"},500);
}
else if (v < 150 ) {
$('#header').animate({"padding": "50px 0x"},500);
}
});
</script>
You can see the problem here:
http://monsiorski.com/_projects/robson/
<script>
var isScrolled = false;
$(window).scroll(function() {
var v = $(window).scrollTop();
if (v >= 150 && !isScrolled) {
$('#header').animate({"padding": "15px 0px"},500);
isScrolled = true;
}
else if (v < 150 && isScrolled) {
$('#header').animate({"padding": "50px 0x"},500);
isScrolled = false;
}
});
</script>
This is because of two reasons: A) The scroll event is fired every scrolled pixel, and B) you calculate a lot every time.
A) is fixed very easily, you can limit the amount of events fired by dethrottling or debouncing it. You can set it to fire every 20ms which is still extremely fast, but will save tremendous amounts of calls.
B) Limit the calculations you do. The more often you trigger a function, the more you have to doublecheck a function whether you can make it more lightweigth.
In your case you calculate how much is scrolled from the top, and set the value every call. Instead, you want to also store of you've already passed the threshold. If you've already passed it and the scrollvalue>threshold, there is no need to set it again to the same value. Same goes for scrolling back. Checking a boolean is extremely fast.
I have a section of code which is supposed to move this image ( id = 'background' ), which I have downloaded locally, and is quite large. It is supposed to move when I hover over top of a certain div(s). This then changes the opacity CSS value, which in turn is detected by the js, which then makes the image move. The js code looks like this:
setInterval(function(){
var element = document.getElementById('background'),
style = window.getComputedStyle(element),
left = style.getPropertyValue('left');
left = left.replace(/px/g, "")
left = parseInt(left,10)
if(getOpacity('rightbar') == .5){
document.getElementById('background').style.left = left - 8 + 'px'
}
if(getOpacity('leftbar') == .5){
document.getElementById('background').style.left = left + 8 + 'px'
}
},10)
The getOpacity(idName) function looks like this:
function getOpacity(param){
var element = document.getElementById(param),
style = window.getComputedStyle(element),
opacity = style.getPropertyValue('opacity');
return opacity
}
So the problem is that, no matter what movement values or setInteveral time I use, the animation always makes out to be laggy. Is there a way to make this smooth with vanilla js, or better yet, to scrap the opacity detection and do it all with CSS?
It works fine when I put the above code in a fiddle, but when it actually runs full browser (on my personal chrome window), it looks like this.
Note: I am running this full browser window on a 4k monitor, is this just too much for chrome to handle?
1. Use requestAnimationFrame instead of setInterval
This signals the browser you want to do something before the next redraw. The callback you provide is executed exactly once per frame.
(In case this matters: requestAnimationFrame does not work in IE9 and below.)
2. Don't increase by a fixed value per frame, tween between values
Both when using requestAnimationFrame and using setInterval, the time difference between frames vary.
You can verify that yourself by using something like this in the developer toolbar:
var last = new Date();
function onFrame(){
var now = new Date();
console.log(new Date() - last);
last = now;
requestAnimationFrame(onFrame);
}
onFrame();
The developer console will output the frame times in ms, like this:
16
17
17
15
19
...
If you increase position (not so noticeable on e.g. opacity) by a fixed amount on vaying intervals, the animation will look jagged. So Instead of doing left = left + 8;, calculate at which position in the animation you are, based on the current time, something like this:
var myElement = ...;
var initialOpacity = 1.0;
var targetOpacity = 0.5;
var duration = 2000;
var startTime = new Date();
function animation() {
var delta = Math.min(1, (new Date() - startTime) / duration);
// delta is now a number in the range [0 ... 1]
myElement.style.opacity = initialOpacity + delta * (targetOpacity - initialOpacity);
if (delta < 1) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
Yes, this example tweens opacity and not position, but you get the idea - and your teacher can't claim you copy-pasted ;-)
3. Don't read and write back-and-forth between JS and CSS
Assuming the initial position of your image is not viewport-related (say, left: -10%), there is no need to read the position on every frame.
When your JavaScript is the only thing changing the left property, why read it from CSS? Save it in a variable and set it to CSS from your JavaScript.
var initialX = ComputeCssXPosition(myElement);
...
function animate() {
...
myElement.style.left = computedNewXPosition;
}
If you want to change the postition when the user hovers an element, use mouse events in your JS.
myElement.addEventListener('mouseover', function (ev) { ... });
myElement.addEventListener('mouseout', function (ev) { ... });
Alternative: Use CSS transitions
Already covered in the answer by Shomz.
The best approach would be to use requestAnimationFrame instead of setInterval and not to check for the style changes, but use mouseover listeners (too much communication: CSS->JS->CSS->JS...). See here: https://jsfiddle.net/yqpun3eb/4/
However, if you still want to use setInterval, you can simply put the transition CSS rule on your background element. For example:
#background {transition: left 0.2s linear}
That will smooth out all the value changes because CSS performs way better, so it should be fine even on the 4K screens. The problem was your changes can jump by 8 pixels.
Seems to work nice with 0.2s on my machine: https://jsfiddle.net/yqpun3eb/3/
Oh, and btw. you want good performance, but why are you raping the system with this:
function getOpacity(param){
var element = document.getElementById(param),
style = window.getComputedStyle(element),
opacity = style.getPropertyValue('opacity');
return opacity
}
This doesn't create extra variables (which you don't need anyway):
function getOpacity(param){
return window.getComputedStyle(document.getElementById(param))
.getPropertyValue('opacity');
}
Finally, here's a slightly optimized version using requestAnimationFrame (that's how I would do it + use listeners instead of reading the style values): https://jsfiddle.net/yqpun3eb/4/
Here is the problem, I've got a tree structure of html blocks, global container is of a fixed width(X) and height(Y). When i click one of the blocks on a level, all other blocks shrink to some size, while the clicked one gets enlarged to the leftover space, and the sublevels show up on it's place.
For all the shrinking i'm using default animate function with easing effect, when shrinking 1 level, to avoid enlargement bugs i have to do something like this:
$tabs.not($obj).animate({height:32<<$obj.getVerUp().length+"px"},{duration:300,
step:function() {
$obj.height(function(){
var sum = 0;
$tabs.not($obj).each(function(){
sum += $(this).height();
});
return $obj.getCont().height()-sum+"px";
});
}
});
$tabs are all the tabs of current level, $obj - is the one tab that i want to enlarge
The main problem is:
When i open up a tab that is on a deep level, i have to animate all the tabs of higher levels to shrink a little bit more, thus the $obj X and Y would change, so the current animation has to use new values, but if i call 3 different animations on different levels i'm bound to get a bug, when one of the animations on a deeper level finishes 1 step earlier, while the one on the level above, would enlarge the object by 5-10 more pixels and that space wouldn't be used up.
The second problem is that there has to be about 50 object animating with easing at the same time, which is a little bit overkill.
And the last problem is when i call step callback on animation as shown above, i have a strange feeling that it calls the step separately for each animation of the $tabs collection, while i need 1 step for all the tabs in the list (to avoid unnecessary scripts)
There might be some other way to fix all that, but i have yet to discover all jQuery functions, so from what i see the only way is to simulate easing, and do everything in one single animation.
I don't really want to use setInterval and determining when do i need to clear it plus calculating each of the easing values, if there is a simple way doing it.
Does jQuery has some sort of empty animation easing, e.g.
$().css("height":starth+"px").animate({height:endh},{duration:300,
step:function(fn) {
// all the animation actions here via fn end value
}
});
Thanks in advance.
What I need - is not a completely working solution in code, just some enlightenment in those subjects:
Is there a legal way to call one step function for a collection of animated elements, or, maybe, it does call step once when I use one .animate on collection.
I'd be really appreciated if someone would shed some light over how does jquery handle multiple .animate, would they be used in one global function that works on .setInterval? or would they be having massive number of those .setIntervals that are equivalent to setTimeout (which most browsers can't handle in large amounts);
Is there a way to simulate 'animate' easing, some function name maybe, or a special trick to achieve that (the only thing I see is a hidden element or 'window' property to change maybe)
Or some directed pushes with functions I should study, that could help me achieve my goals
Guess i pretty much found the answer to my questions:
http://james.padolsey.com/javascript/fun-with-jquerys-animate/
Here's the empty animation from the link above with 1 step function with desired values, going to post the result later on if it all works out.
var from = {property: 0};
var to = {property: 100};
jQuery(from).animate(to, {
duration: 100,
step: function() {
console.log( 'Currently # ' + this.property );
}
});
Yes it all worked great, no desynch, and a good speed, since only 1 animate, found making one universal function for the animation - waste of resourses, so it is pretty specific, but still, here it is:
animate: function($obj) {
var T = this;
...
T.arr = new Array();
// gathering the array
$obj.each(function(i){
var size;
T.arr[i] = {obj:$(this), rest:$(this).getSibl(), cont:$(this).getCont()}
if($(this).hasClass("vert"))
{
size = "height";
T.arr[i].to = yto;
}
else
{
size = "width";
T.arr[i].to = xto;
T.arr[i].children = $(this).getChld();
}
T.arr[i].rest.each(function(){
$(this).attr("from",$(this)[size]());
});
});
// animating prop
jQuery({prop:0}).animate({prop:1}, {
duration: 300,
step: function() {
var i;
var P = this;
var newval;
var sum;
var size;
for(i = 0; i < T.arr.length; i++)
{
size = T.arr[i].obj.hasClass("vert") ? "height":"width";
sum = 0;
T.arr[i].rest.each(function(){
// new value of width/height, determined by the animation percentage
newval = parseInt($(this).attr("from")) + (T.arr[i].to-$(this).attr("from"))*P.prop;
$(this)[size](newval);
sum += newval;
});
T.arr[i].obj[size](T.arr[i].cont[size]()-sum);
}
}
});
},
For example:
var $myContainer = $('#myContainer');
$myContainer.html(someHtml);
var width = $myContainer.height();
var height = $myContainer.height();
If #myContainer was an empty div, width and height would still be zero. A solution is to use a timeout:
var $myContainer = $('#myContainer');
$myContainer.html(someHtml);
setTimeout(function () {
var width = $myContainer.height();
var height = $myContainer.height();
}, 500);
However, I don't like the magic number in there. What if its a really slow browser? Is there any reliable cross browser method available to tell me when the browser has rendered the changes?
You can use 0 for the timeout, reliably. It won't actually be 0ms, you understand, most browsers will make it at least 5 or 10, but just the act of yielding to the browser is sufficient.
That said, I'm not immediately finding a browser that doesn't get the (new) height right immediately, without a yield (even IE6!). But I wouldn't be surprised if, depending on markup and such, there were one...
Feels like a hack but might work: Add a script element to the DOM as the last element. All browsers should execute that after all the other elements have "settled."
Are you using ajax?
So you can use jQuery 'load'.
var $myContainer = $('#myContainer');
$myContainer.load('/url/for/someHtml', function(){
var width = $myContainer.height();
var height = $myContainer.height();
});
how can I scroll the page with javascript until the scrolled height will be some predefined number of pixels?
function pageScroll() {
// Next line is pseudocode:
if (window.ScrolledHeight != SOME_NUM)
window.scrollBy(0,50);
}
scrolldelay = setTimeout('pageScroll()',100);
There should be some logical checking of something. Could you help me, how to get scrolled height?
Could you use a variable outside of the function that would increment the value by 50 pixels everytime pageScroll ran and then check to see if that is equal to or greater than the value you are looking for?
e.g.
var scrollAmount = 0;
function pageScroll() {
window.scrollBy(0,50);
scrollAmount += 50;
if(scrollAmount < 200) {
scrolldelay = setTimeout('pageScroll()',100);
}
}
You could also make the function take in a parameter that you modify if you don't want to use the global variable.
You already had valid answers, but I will add a bit of flavor, it scrolls but slows down before reaching the targeted scroll point:
function pageScroll() {
var delta = min(SOME_NUM-window.ScrolledHeight)>>1,50);
if (delta > 0) {
window.scrollBy(0,delta);
setTimeout('pageScroll()',100);
}
}
A nice side effect is that it should reach exactly SOME_NUM regardless it is a multiple of 50 (your scroll amount) or not.
First off you shouldn't be using setTimeout you should be using setInterval. Basically because it fulfills your purpose better and it is more accurate as far as actually time. Google it if you want more info on that topic. But what you need is the clearInterval method. Also it is faster to use anonymous functions inside of setTimeout and setInterval.
startPageScroll(){
var scrollAmt = 0;
var id=setInterval(function(){
if(scrollAmt</*Whatever your limit is*/){
window.scrollBy(0,50);
scollAmt+=50;
}
else{clearInterval(id);}
},100);
}
There are ways of getting the actual scroll of the page, but you would have to do something like this which is quite a bit more code then just keeping a running total of how far you have moved.
var scroll;
if(window.pageYOffset){
scroll=window.pageYOffset;
}
else{
scroll=document.body.scrollTop;
}
Here is a link that might help http://codepunk.hardwar.org.uk/ajs02.htm.