Very simple parallax code has performance issues - javascript

For a parallax-effect, I created a simple script in native Javascript, but it seems to fail somewhere I can't see. That's why I already added the requestAnimationFrame-functionality, but it doesn't seem to really help.
My relevant code is as follows:
var $parallax, vh;
$(document).ready(function() {
$parallax = $('.parallax');
vh = $(window).height();
$parallax.parallaxInit();
});
$(window).resize(function() {
vh = $(window).height();
$parallax.parallaxInit();
});
$.fn.parallaxInit = function() {
var _ = this;
_.find('.parallax-bg')
.css('height', vh + (vh * .8) );
}
//call function on scroll
$(window).scroll(function() {
window.requestAnimationFrame(parallax);
});
var parallaxElements = document.getElementsByClassName('parallax'),
parallaxLength = parallaxElements.length;
var el, scrollTop, elOffset, i;
function parallax(){
for( i = 0; i < parallaxLength; i++ ) {
el = parallaxElements[i];
elOffset = el.getBoundingClientRect().top;
// only change if the element is in viewport - save resources
if( elOffset < vh && elOffset + el.offsetHeight > 0) {
el.getElementsByClassName('parallax-bg')[0].style.top = -(elOffset * .8) + 'px';
}
}
}
I think it's weird that this script by Hendry Sadrak runs better than my script (on my phone) while that is not really optimised, as far as I can tell.
Update: I checked if getBoundingClientRect might be slower in some freak of Javascript, but it's about 78% faster: https://jsperf.com/parallax-test

So here is the downlow on JS animations on mobile devices. Dont rely on them.
The reason is that mobile devices have a battery and the software is designed to minimize battery load. One of the tricks that manufacturers use (Apple does this on all their mobile devices) is temporarily pause script execution while scrolling. This is particularly noticeable with doing something like parallax. What you are seeing is the code execution - then you scroll, it pauses execution, you stop scrolling and the animation unpauses and catches up. But that is not all. iOS uses realtime prioritization of the UI thread - which means, your scrolling takes priority over all other actions while scrolling - which will amplify this lag.
Use CSS animation whenever possible if you need smooth animation on mobile devices. The impact is seen less on Android as the prioritization is handled differently, but some lag will likely be noticeable.
Red more here: https://plus.google.com/100838276097451809262/posts/VDkV9XaJRGS

I fixed it! I used transform: translate3d instead, which works with the GPU instead of the CPU. Which makes it much smoother, even on mobile.
http://codepen.io/AartdenBraber/pen/WpaxZg?editors=0010

Creating new jQuery objects is pretty expensive, so ideally you want to store them in a variable if they are used more than once by your script. (A new jQuery object is created every time you call $(window)).
So adding var $window = $(window); at the top of your script and using that instead of calling $(window) again should help a lot.

Related

Performance improvement in scroll-linked animations

I've created some effects in JavaScript (and with simple, CSS controlled animations), and they are working well, but costs a lot of hardware resources (especially GPU), so on devices with less performance, the webpage appears laggy when scrolling. The interesting thing is that I opened some webpages also with a lot of scroll-linked animations on devices with weak performance, and the animations looks smooth and nice.
I searched for solutions with performance improvement in scroll-linked effects, but the only thing I found is server-side pre-rendering (make screenshots from the webpage, and this way the page appears in one gif/video which frames are controlled depending on scroll). I was consider that it's possible to the root of the problem is that I don't use frameworks (JQuery, CSS preprocessors), but it can't be the only reason for this amount of performance cost. I tried to play with will-change in CSS, which make the effects smoother, but not enough.
An example of the effects I've created:
window.onscroll = function() {
var element = document.querySelector(.exampleElement),
elementsContainer = element.parentNode,
effect = (element.offsetTop - window.pageYOffset) / window.innerHeight;
elementsContainer.style.perspective = "1000px";
element.style.transform = "rotateX(" + effect * 90 + "deg)";
}
My question is that the only way to improve performance (to make the animations smooth on any devices) is the server-side rendering, or there are another way to do it? How other pages which uses scroll-linked animations make their effects smooth on any device?
The most obvious improvement is to move as much as possible outside of the scroll handler. Cause the handler is very vital for performance as it is triggered very often:
var element = document.querySelector(.exampleElement),
elementsContainer = element.parentNode;
elementsContainer.style.perspective = "1000px";
window.onscroll = function() {
const effect = (element.offsetTop - window.pageYOffset) / window.innerHeight;
element.style.transform = "rotateX(" + effect * 90 + "deg)";
}

How to make a slider scrollable on touch screens?

I have a site here:
www.completefaq.com
The main page contains a slider, which I built on my own. It changes the slides automatically after 8 secs. and on a click on forward or backward button. But, the problem is that I want it to scroll even when one tries to just slide it on a touchscreen.
Any help is appreciated. I can only use CSS, JavaScript, HTML and PHP, because other APIs such as JQuery, tend to slowdown the website.
Since you don't want a jQuery plugin, it's going to be non trivial due to the differences between the various touch platforms, and in the end you would probably end up in reinventing the wheel trying to get the abstraction you need. Above all, your effort will clearly depend on the level of accuracy you want to achieve, so it's advisable that you don't try to make the images respond to every minimal touch event.
I believe your best bet is to use Tocca.js, a very promising, standalone script which is "super lightweight" (only 1kB) and aims to normalize touch events among existing devices.
Hammer.js is more accurate, but could be a bit heavier in your case.
QuoJS is also good but it's not focused only on touch events.
You may find this and this SO question interesting. Finally, you can always take inspiration from the several existing touch-enabled javascript image sliders.
Here is a very lightweight script to start with.
Put this script somewhere below your div#featured, or execute it on dom ready.
You may want ajust the minimum swipe time (200ms) and minimum swipe distance (50px):
var featured = document.getElementById("featured");
if( "ontouchstart" in window ) {
var touchStart = function(evt) {
var startTime = (new Date()).getTime();
var startX = evt.changedTouches[0].pageX;
var touchEnd = function(evt) {
document.removeEventListener("touchend", touchEnd);
var diffX = evt.changedTouches[0].pageX - startX;
var elapsed = (new Date()).getTime() - startTime;
console.log( "Swiped " + diffX + " pixels in " + elapsed + " milliseconds" );
if( elapsed < 200 && Math.abs(diffX) > 50 ) {
( diffX < 0 ) ? slideright() : slideleft();
}
}
document.addEventListener("touchend", touchEnd);
};
featured.addEventListener("touchstart", touchStart);
}
I understand that you want to keep your website light, but if you wan't more advanced gesture recognition that this simple swipe, you will have to go with some kind of framework.

scrolling on mobile devices

This question is more of an advice research, I do hope that it will be helpful for others and it won't closed, as I'm not quite sure where to ask for advice on this matter.
I've been developing for mobile for the past 6 months and I had the occasion to deal with all kinds of situations and bugs on various devices.
The most troubling was the scrolling issue, when it comes to scrolling in multiple areas of the website. On three projects that I have been working on I've been building a navigation that behaves the same way that the native iOS Facebook app has, or the Google website on mobile, etc. And for each one I came up with different solutions.
But a few days ago I have just released a new JavaScript library, drawerjs, that can be used to generate such navigation (so called off canvas concept). The difference between the other libs and this one is that is library agnostic, and it acts on touch behavior (the same way that the Facebook app behaves) not just open / close on click.
One of the things that I have left to implement is a solution for scrolling inside the menu and the navigation without affecting one another (most of the time when you scroll in such way, the content tends to scroll together with you menu or after you have reached the end of the menu scrolling).
I have two solutions in mind:
One approach would be to use the same principle I'm using for dragging the content and showing the navigation, on touchmove I prevent the default scrolling on document / content and I start translating the contents with the same amount you scroll. And with the same resistant behavior as a touch slider would have (when you exceed the boundaries and let go, the contents would translate back so it doesn't exceed the boundary anymore, or on swipe with the same behavior).
A second approach would be using the native overflow-scrolling that iOS has and would offer the same feel as described in the first approach. The downside of this would be that only iOS devices would have the nice resistant feature, but it would be, supposedly, less of a hassle the the first approach.
So I'm not quite sure which approach I should take, or if there any better solutions for that. I'm also trying to keep in mind that some users would like to hide the url bar, so scrolling on the body / html would have to be kept (on the y axis).
You could do touchmove . But as far as I understand, you want something like this?
http://jsfiddle.net/2DwyH/
using
var menu = $('#menu')
menu.on('mousewheel', function(e, d) {
if((this.scrollTop === (menu[0].scrollHeight - menu.height()) && d < 0) || (this.scrollTop === 0 && d > 0)) {
e.preventDefault();
}
});
Using this plugin from Brandon Aaron - github : https://github.com/brandonaaron/jquery-mousewheel
And it should work with Android: What DOM events are available to WebKit on Android?
Some more info here: Prevent scrolling of parent element?
Also without using the plugin above , using only jQuery you could do this like it says on the link above - answer from Troy Alford
$('.Scrollable').on('DOMMouseScroll mousewheel', function(ev) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.height(),
delta = (ev.type == 'DOMMouseScroll' ?
ev.originalEvent.detail * -40 :
ev.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
ev.stopPropagation();
ev.preventDefault();
ev.returnValue = false;
return false;
}
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
The JS Fiddle he mentions: http://jsfiddle.net/TroyAlford/4wrxq/1/
Why not just provide a fixed height to your widget (min and max will also do). Then define like these -
height: x px;
overflow-y: auto;
This way till the focus is inside the widget, it'll only overflow the widget, once outside the page will scroll without affecting widget content at all.

Adjusting window scroll position in JavaScript to counteract element's resizing above

I'm trying to counteract an adjustment to the height of an element which is above the scroll offset by calculating the difference in height and then updating the current scroll position to account for it.
The problem is that there's no way that I can prevent a very quick flickering artefact. Whether I adjust the element's height and then the scroll position, or vice versa, I can't seem to prevent a quick visual jump.
Does anyone know how this could be overcome? I want these to operations to happen at the same time with no rendering in-between but I'm not sure if it's possible.
// Setup
...
var myElement = ...
var oldHeight = ...
var scrollOffset = window.scrollY;
var newHeight = 100;
var diff = newHeight - oldHeight;
// Determine if we need to counteract new size
var adjustScroll = (absoluteOffset(myElement) < scrollOffset);
// Adjust size
myElement.style.height = newHeight+'px';
// Adjust scroll to counteract the new height
if (adjustScroll) window.scrollTo(0, scrollOffset+diff);
I'm working with WebKit, specifically on iOS.
for webkit you can use CSS transitions/animations to smooth this but it's still sound like you are going the wrong way to begin with. I am sure that whatever is it you are trying to do can be solved purely with CSS (maybe with some very minimal Javaqscript). Post an example of you HTML + CSS + JS.
You could use scrollIntoView with timers to simulate multiple threads.
Or you could do it inside a document fragment beforehand.
Sorry to be reviving an old post here, but i came across this looking for a solution to a similar problem to do with browser resizing.
Stackoverflow user James Kyle created this little jsfiddle using jQuery that attempts to maintain scroll position as best as possible when a page is resized
var html = $('html'),
H = html.outerHeight(true),
S = $(window).scrollTop(),
P = S/H;
$(window).scroll(function() {
S = $(window).scrollTop();
P = S/H;
});
$(window).resize(function() {
H = html.outerHeight(true);
$(window).scrollTop(P*H);
});
http://jsfiddle.net/JamesKyle/RmNap/
you could try using this same code and trigger a 'resize' event on the html when the image has loaded by using a jQuery library like imagesLoaded

Custom Animation With JavaScript

I'm trying to do very basic, custom animation with JavaScript. I'm using a library called Modernizr to detect HTML5 support, and if a browser doesn't support CSS3 Transitions, I'd like to have my own custom (non jQuery) script that recreates the same effect of CSS3 Transitions.
My idea for this code is this:
JS:
function slide() {
var cur = 0;
while (cur <= 50) {
setInterval("document.getElementById('slider').style.marginLeft=cur + 'px'",100);
cur = cur++;
}
}
HTML (for hovering):
<div id="slider" onmouseover="slide()">
This should slide left.
</div>
This doesn't work (I know it doesn't loop, that's one of the issues that I can't figure out). I'm pretty new with JS so I don't know some pretty basic stuff.
The main reason that I don't want to use jQuery is for educational purposese. I want to know basic JS before I learn jQuery, just so I know how to do the things I want to do.
I'd like this to work for two different cases, one being automatically, one being only on hover (and the hover version doesn't have to loop). I had something kind of working on hover, but the more you hovered over it, the faster it would move.
Thanks!
function slide() {
var cur = 0;
while (cur >= 50) {
setInterval("document.getElementById('slider').style.marginLeft=cur + 'px'",100);
cur = cur++;
}
}
Your code sets cur to 0, and then the while condition is cur >= 50 (*while cur variable is larger or equal to 50) which will never be true because it is 0.
Also, animating with a loop is not a good idea, as the browser likely won't render the results in a manner that is pleasant to look at (or at all). Use setInterval() without the loop.
If I was tasked with writing this, it may look like...
function slide(element, newHeight) {
var currentHeight = element.offsetHeight;
var increaseHeight = function() {
currentHeight += 10;
element.style.height = currentHeight + 'px';
}
var interval = setInterval(function() {
if (currentHeight >= newHeight) {
clearInterval(interval);
}
increaseHeight();
}, 100);
}
jsFiddle.
There are many examples of doing sliding in javascript.
Here is one of them.
http://www.harrymaugans.com/2007/03/06/how-to-create-an-animated-sliding-collapsible-div-with-javascript-and-css/
In your example, if you use setInterval you will also want to call clearInterval. For a description of both you can look at: https://developer.mozilla.org/en/window.setInterval
Hopefully you aren't actually using the onmouseover function in the element, as shown, but are instead checking if you need this, then setting it after the page has loaded.
You may want to look at unobtrusive javascript to get an idea how this can be done well.
There are various articles on this, but you could start with:
http://labs.adobe.com/technologies/spry/articles/best_practices/separating_behavior.html

Categories

Resources