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)";
}
Related
Good Morning. I am a student of web development and I am curious to know with which technologies it is possible to realize this floating effect as in this site: https://www.fbiz.com.br/
Could any of you show me a code as an example?
As far as I can tell from the (minified) source code, they are using a JavaScript that applies a transform CSS style to the 'floating' elements as the user scrolls.
Their JavaScript code might look something like this (overly simplified):
let element = document.querySelector('.foo');
window.onscroll = function(){
let value = window.scrollY / 30 + 'px';
element.style.transform = 'translateY(' + value + ')';
}
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.
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.
I have a problem with my micro website. When I scroll, it's nice and smooth in all browsers except Safari. When I do scroll in Safari, the content div jumps or moves frequently (it should stay in place) and makes the scrolling look choppy. Do you have any idea what could be wrong?
This is the website:
http://beta.dynamicdust.com
I haven't checked to see how my answer compares to Jack's, but I think the problem is that Safari attempts to be very power efficient. As a result, it is hesitant to enable hardware acceleration unless it needs to. A common trick that people use to force hardware acceleration is to place
-webkit-transform: translate3d(0, 0, 0);
into the css for the divs which are moving. I tried it with the content class and it seemed a little better. You might try applying it to the other layers as well.
EDIT: I applied it to the left and right text holder divs as well, and the page seems just as smooth as Chrome now.
I took a look and did see the "choppy" scrolling you mentioned (looking at it more, it was hit or miss - sometimes it was smooth, other times it was VERY choppy).
It seems you've got some performance issues with your parallax callback on Safari (though it wouldn't surprise me if it's some buggy implementation with Safari...)
One thing I would recommend is taking a look at requestAnimationFrame for webkit. For a test, I wrapped the logic to update the offsets in a raf (and cached the window.pageYOffset value) and it seemed smoother on my end.
function parallax(e) {
window.webkitRequestAnimationFrame(function() {
var offset = window.pageYOffset;
a.style.top = (offset / 2) + "px";
b.style.top = (offset / 2) + "px";
textbox.style.top =- (offset * 0.7) + "px";
textbox2.style.top =- (offset * 0.7) + "px";
});
}
To be honest, you could probably use raf for all browsers (if they support it).
Another trick people use when animating elements is to accelerate the layer that the element you are animating is on. There are a few ways to do this, but the easiest is to use -webkit-transition and set translateZ(0). It wouldn't hurt to add the two additional lines as well:
-webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
Also, I noticed that some elements you offset (using style) are position: relative - Personally, I'd say that any element that's to be animated should be position: absolute. This will remove the element from the DOM, and offsetting it won't cause reflows to surrounding elements (which may contribute to your choppiness).
Edit - one other thing I noticed is that "choppiness/weirdness" happens when you encounter rubberbanding on safari (my guess are the negative values). That might be something you'll want to look at as well.
Good luck!
Just jotting this down, as I came across this today with an overflow auto element:
The solution was to add this rule to whatever element the scrollbar shows up on:
-webkit-overflow-scrolling: touch;
More detail can be found here:
https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-overflow-scrolling
Elements with heavy transitions on hover and active state can result in rendering issues.
In our case we had box-shadow transitions on some elements that were applied on hover. When the user scrolled the page and swiped the effected element, the transition got triggered. The browser then had to do the hard work of rendering the transition while scrolling.
I am looking for help / a point in the right direction / or a solution for a flicker/jump, when scrolling on a looping/infinite website, which can be seen in this fiddle.
What seems to be causing the jump is:
"$(window).scrollTop(half_way - child_height);", and what could also be a Chrome windows scrollTop bug, but it is happening in all browsers at the moment.
If I remove "- child_height" there is no longer a flicker but the page no longer scrolls correctly, which can be seen in this fiddle.
Also, on the very first scroll the right hand column jumps up by three boxes - also because of 'half_way', which I can fix by giving it a "bottom: -600px;"
The full code:
http://jsfiddle.net/djsbaker/j3d8r/1/
var num_children = $('#up-left').children().length;
var child_height = $('#up-left').height() / num_children;
var half_way = num_children * child_height / 2;
$(window).scrollTop(half_way);
function crisscross() {
$('#up-left').css('bottom', '-' + window.scrollY + 'px');
$('#down-right').css('bottom', '-' + window.scrollY + 'px');
var firstLeft = $('#up-left').children().first();
var lastLeft = $('#up-left').children().last();
var lastRight = $('#down-right').children().last();
var firstRight = $('#down-right').children().first();
if (window.scrollY > half_way ) {
$(window).scrollTop(half_way - child_height);
lastRight.appendTo('#up-left');
firstLeft.prependTo('#down-right');
} else if (window.scrollY < half_way - child_height) {
$(window).scrollTop(half_way);
lastLeft.appendTo('#down-right');
firstRight.prependTo('#up-left');
}
}
$(window).scroll(crisscross);
Okay - here is a 'working' version - and by works I mean it less flickery than before. I thought it was flicker free, and it was when I was on battery power, but plugged into the mains and the CPU is fast enough to get flicker.
As I mentioned, to get rid of the flicker you need to clone the objects, manipulate them and then replace them into the DOM, rather than just manipulating the DOM directly.
I did this by getting the contents of <div id="content"> manipulating them and then replacing them into that <div>.
Also, it's a good idea to only find things in the DOM once, and from then on use a reference to that object rather than searching repeatedly. e.g.
var leftSide = $(clone).find('.up-left');
....
lastRight.appendTo(leftSide);
....
$(leftSide).css('bottom', '-' + window.scrollY + 'px');
rather than:
lastRight.appendTo('#up-left');
$('#up-left').css('bottom', '-' + window.scrollY + 'px');
Searching the DOM is relatively slow, and so storing references can improve performance/reduce flicker.
Storing the object also makes the code easier to understand (imho) as you can easily see that you're referencing the same thing, rather than possibly different things.
I still get flickering in chrome on windows with Danack solution. For this site I would control all the scrolling (you already scroll manually one of the sides), and give elements absolute positions.
Or if you insist on using the browser scrolling, may be use animations: animate the height of the last elements till 0px then use appendTo, and then animato from 0px to the normal height...
This might be a long shot, but I had the same flickering when working with infinitescroll,
and ended up using imagesLoaded.I ended up appending the additional images (now loaded) with a fade in, and that prevented them from flickering because of the fact they were loaded.
So maybe by using the imagesloaded - or a callback on the images, you can solve the flickering. It does decrease the speed though. I can image that if you want to scroll through everything as fast as possible, this might not be the solution. Good luck!
A solution would be to not use the native scrolling functionality but to simulate scrolling. This would be done by setting the overflow of your content to "hidden" in addition with capturing the "mousewheel" event on it and triggering some action when it is called. I started to try this out here (using MooTools instead of jQuery since I'm more fimilar with it). It's currently just "working" on the left side by altering the margin-top of the first element.
My next steps would be:
Check if the negative margin-top of the first element is bigger than the height of it and move it to the right side if so.
Same logic for the last box on the right side with a negative margin-bottom.
This has some downsides, though. Simulating scrolling doesn't feel as natural as the native scrolling functionality and clicking the mousewheel doesn't work. These might be solveable but it would require some more coding to get it to work smoothly. Anyway, in the end you would have a solution without any flickering and with no sticky scrollbar at the side (An idea for a replacement could be a small area on the side that triggers the scrolling on mouseover).