Parallax scroll choppy performance on Safari & Firefox - javascript

I'm building a onepager with a parallax intro. For the parallax effect I'm using the following piece of JS:
// Parallax
var layerBg = document.querySelector('.js-layer-bg');
var layerText = document.querySelector('.js-layer-text');
var sectionIntro = document.getElementById('section-intro');
var scrollPos = window.pageYOffset;
var layers = document.querySelectorAll('[data-type=\'parallax\']');
var parallax = function() {
for (var i = 0, len = layers.length; i < len; i++) {
var layer = layers[i];
var depth = layer.getAttribute('data-depth');
var movement = (scrollPos * depth) * -1;
var translate3d = 'translate3d(0, ' + movement + 'px, 0)';
layer.style['-webkit-transform'] = translate3d;
layer.style.transform = translate3d;
}
};
window.requestAnimationFrame(parallax);
window.addEventListener('scroll', function() {
// Parallax layers
scrollPos = window.pageYOffset;
window.requestAnimationFrame(parallax);
// Animate text layers
var vhScrolled = Math.round(window.pageYOffset / window.innerHeight * 100);
if (vhScrolled > 100 && layerText.classList.contains('is-hidden')) {
layerText.classList.remove('is-hidden');
} else if (vhScrolled <= 100 && !layerText.classList.contains('is-hidden')) {
layerText.classList.add('is-hidden');
}
});
Apart from this I'm animating a few other things on scroll using 2 libraries: ScrollMonitor and ScrollReveal. Nothing too special.
I've been developing this on Chrome and everything seems to be working smoothly enough. However, when I tested on Safari and especially Firefox, things got so laggy to the point of actually crashing my browser.
I really can't figure out what I am doing wrong and why performance is so different between browsers.
Hopefully you can help me out, thanks!

I'm not altogether certain about what's specifically causing the lag/choppiness issues, I seem to remember something similar in past projects. I'd look into any further image optimization to lower the weight of what's being rendered, that can make a huge difference. Otherwise, I've made a few suggestions for efficiency tweaks that might help it run a bit faster:
// Parallax
var layerBg = document.querySelector('.js-layer-bg');
var layerText = document.querySelector('.js-layer-text');
var sectionIntro = document.getElementById('section-intro');
var layers = document.querySelectorAll('[data-type=\'parallax\']');
var len = layers.length; // cache length
var layerarray = []; //create cache for depth attributes
var i = -1;
while(++i < len){
layerarray.push([layers[i], parseInt(layers[i].getAttribute('data-depth'))]); //create an array that stores each element alongside its depth attribute instead of requesting that attribute every time
}
var parallax = function() {
var scrollPos = window.pageYOffset; //define inside function instead of globally
var i = -1;
while(++i < len) { //while loop with cached length for minor speed gains
var layer = layerarray[i][0];
var depth = layerarray[i][1];
var movement = (scrollPos * depth) * -1;
var translate3d = ['translate3d(0, ', movement, 'px, 0)'].join(""); //join statement is much faster than string concatenation
layer.style['-webkit-transform'] = translate3d;
layer.style.transform = translate3d;
}
// Animate text layers
var vhScrolled = Math.round(scrollPos / window.innerHeight * 100);
if (vhScrolled > 100 && layerText.classList.contains('is-hidden')) {
layerText.classList.remove('is-hidden');
} else if (vhScrolled <= 100 && !layerText.classList.contains('is-hidden')) {
layerText.classList.add('is-hidden');
}
};
window.requestAnimationFrame(parallax);
window.addEventListener('scroll', function() {
// Parallax layers
window.requestAnimationFrame(parallax);
//moved text animation into the animationframe request
});

Related

Multiple Morphs on Scroll

Struggling to get multiple SVGs to animate on scroll. I followed the CodePen and the first animation works fine, but the second doesn't. I need to be able to run like 4-5 of these on the homepage.
var scrollMorph1 = new TimelineLite({paused:true})
.to("#headerBottomMask",1, {morphSVG:"#headerBottomMask2"},0)
.to("#headerBottom01a",1, {morphSVG:"#headerBottom01b"},0)
.to("#headerBottom02a",1, {morphSVG:"#headerBottom02b"},0)
.to("#headerBottom03a",1, {morphSVG:"#headerBottom03b"},0)
.to("#headerBottom04a",1, {morphSVG:"#headerBottom04b"},0)
$(window).scroll(function() {
var scrolled = $(window).scrollTop();
var diff = 50;
var object1 = $('#header h1');
var topOfRange1 = object1.offset().top + diff;
if (scrolled > topOfRange1 ) {
scrollMorph1.play().timeScale(1);
} else {scrollMorph1.reverse().timeScale(1);}
});
var scrollMorph2 = new TimelineLite({paused:true})
.to("#clientsMask",1, {morphSVG:"#clientsMask2"},0)
$(window).scroll(function() {
var scrolled = $(window).scrollTop();
var diff = 50;
var object2 = $('#weare a');
var topOfRange2 = object2.offset().top + diff;
if (scrolled > topOfRange2 ) {
scrollMorph2.play().timeScale(1);
} else {scrollMorph2.reverse().timeScale(1);}
});
Code works fine. Wasn't targeting the Path, but rather the SVG's ID on the second one. Have to target the path.

Issues with rotating jqueryUI modal

I am trying to build a modal that rotates to a particular element, $(.linkmoddet), based on a clicked element in the navbar $('.selectcircle') using the .switchClass function in jQueryUI.
However, I am having issues with the actual math involved, often causing:
only one or two elements to rotate at a time.
multiple elements gaining classes but not losing them.
occasionally losing all the classes involved, defaulting the element in question to a standard size and position in CSS.
Code
Edit: This has now been fixed.
http://codepen.io/yeasayer/pen/ZWxYZG
var selectcircle = $('.selectcircle');
var linkmoddet = $('.linkmoddet');
selectcircle.click(function(){
var circleindex = $(this).index()-1;
var centerindex;
console.log(circleindex);
selectcircle.each(function (index){
if (circleindex == index)
{
console.log($(this));
}
});
linkmoddet.each(function (index){
if ($(this).hasClass('moddetcenter'))
{
centerindex = $(this).index();
console.log("the center is index #"+centerindex);
}
var rotation = centerindex - circleindex;
//This is where I start having issues.
var i = $(this).index() + rotation;
var j;
if (i <= -1)
{
j = i + moddetids.length-1;
$(this).switchClass(classes[i+$(this).index()],classes[j]);
}
if (i >= moddetids.length)
{
j = i - moddetids.length;
$(this).switchClass(classes[i-$(this).index()],classes[j]);
}
else
{
if (rotation < 0)
{
j = i-1;
}
else
{
j = i+1;
}
$(this).switchClass(classes[i], classes[j]);
}
});
});
Does anyone have an idea on how to achieve the desired results, possibly in a simpler manner than described above?
Alright, it turns out that I figured it out by doing the following:
linkmoddet.each(function (index){
var classnum;
var newrot;
if ($(this).hasClass('moddetcenter'))
{
classnum = 2;
if (rotation < 0)
{
rotation += classes.length;
}
if (classnum + rotation >= classes.length)
{
newrot = classnum + rotation - classes.length;
$(this).switchClass(classes[classnum],classes[newrot]);
}
else if (rotation != 0)
{
$(this).switchClass(classes[classnum],classes[classnum+rotation]);
}
}
/* This is repeated for all the classes available in the classes array.
* ie: 0 being the first class, 1 being the second, etc. It's not an
* elegant solution, but it works for my current needs at the moment
* while I put it in a function in the future. */
Thanks!

Parallax scrolling cross browser compatibility

After watching this two part tutorial on (here's part two) I've got parallax scrolling up and running. Where the clip starts he introduces cross browser compatibility using Paul Irish's requestAnimationFrame and that's what I can't get to work. He just pastes the code right into the code and it works but I can't get it to run in any other browser except Chrome. Although, when pasted something is happening to the images so I suppose it does something...
Any idea what I'm doing wrong? One suggestion was moving the requestAnimationFrame before the other code but that didn't change anything. I've set up a JSFiddle here so please help yourself. Any pointer is helpful.
Here's my code:
(function () {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
}());
(function ($) {
var $container = $(".parallax");
var $divs = $container.find("div.parallax-background");
var thingBeingScroll = document.body;
var liHeight = $divs.eq(0).closest("li").height();
var diffHeight = $divs.eq(0).height() - liHeight;
var len = $divs.length;
var i, div, li, offset, scroll, top, transform;
var offsets = $divs.get().map(function (div, d) {
return $(div).offset();
});
var render = function () {
top = thingBeingScroll.scrollTop;
for (i = 0; i < len; i++) {
div = $divs[i];
offset = top - offsets[i].top;
scroll = ~~((offset / liHeight * diffHeight) * 3);
transform = 'translate3d(0px,' + scroll + 'px,0px)';
div.style.webkitTransform = transform;
div.style.MozTransform = transform;
div.style.msTransform = transform;
div.style.OTransform = transform;
div.style.transform = transform;
}
};
(function loop() {
requestAnimationFrame(loop);
render();
})();
})(jQuery);
Well apart from jQuery not getting loaded into jsFiddle properly, I think your problem was with scrollTop support. Try this updated fiddle which uses the jquery shim for scrollTop and the window property intead;
var $thingBeingScroll = $(window);
and
top = $thingBeingScroll.scrollTop();
But now it looks like you have the same problem I'm currently having. Namely, the scroll is jumpy on IE and FF compared to Chrome.
It's as if the smooth scrolling on FF and IE (which chrome doesn't have) is somehow moving the background slab on scroll before we get a chance to update it. It also issues an array of scroll changes which means that after you let go of the scroll bar, it then has to redraw the positions starting back at the begining and working its way back to current position. I believe that's what causes the jerkiness.
I believe requestAnimationFrame will stack up requests, so it may be that we need to cancel any previous outstanding ones if we've a more recent one and/or use higher resolution updates like mousemove.

Javascript Resize to reset variables

I'm trying to have a bunch of variables reset themselves on a resize (I'm just a little crazy like that. Yes, I know virtually no one will do it as their using the page). I want to be able to have the plugin that I created (hScroll) reset it's variables when the user resizes the page. I only want to declare them and set them within one line, so I tried using the window.variableName = ... but that didn't seem to work. Once again, all I want to be able to do is have to declare and set the variable within one line, and on resize, have the variables reestablish themselves, since a few are size dependent. As you can see, I have also tried the triggerHandler method as well, but it does not seem to be working.
(function($) {
$.fn.extend({
hScroll: function(options) {
var defaults = {
container: "nav",
sliderName: ".sliderName",
partContainer: ".beep"
};
var o = $.extend(defaults, options);
return this.each(function() {
// I want to set global variables and have them reset... START
var slider = $(o.container + " " + o.sliderName),
sliderWidth = slider.outerWidth(),
container = $(o.container),
containerWidth = container.outerWidth(),
containerInnerWidth = containerWidth - (2 * parseInt(container.css("padding-left"))),
sliderPieces = slider.children(o.partContainer),
numberOfPieces = sliderPieces.length,
containerWidth = $(o.container).width(),
piecesWidth = 0;
// set slide widths
if (containerInnerWidth > sliderWidth / numberOfPieces) {
piecesWidth = sliderWidth / numberOfPieces
} else {
piecesWidth = containerInnerWidth;
}
sliderPieces.width(piecesWidth);
// set gutter and how many pieces can be seen at once.
var wholePiecesSeen = Math.floor(containerInnerWidth / piecesWidth),
gutter = parseInt((containerInnerWidth - (wholePiecesSeen * piecesWidth)) / 2);
// END - I want this block to be reset when the window is resized.
var isContainerBigEnough = function() {
if (containerInnerWidth > sliderWidth) {
$(o.container).removeClass("tooBig");
} else {
$(o.container).addClass("tooBig");
}
}
isContainerBigEnough();
// arrow variables
$(o.container + " .previous").click(function() {
var addOn = 0,
newPosition = 0,
thisFarGone = parseInt(slider.css("left")),
moveThisFar = piecesWidth,
allTheWay = containerInnerWidth - sliderWidth;
// always make sure to center your tiles
if (thisFarGone == allTheWay) {
moveThisFar = moveThisFar - gutter;
console.log(moveThisFar);
console.log("in");
};
newPosition = -thisFarGone - moveThisFar;
//make sure it doesn't go too far
if (newPosition < 0) {
newPosition = 0;
}
newPosition = parseInt(newPosition); // - addOn);
slider.css({
"left": (-newPosition) + "px"
});
});
$(o.container + " .next").click(function() {
var addOn = 0,
newPosition = 0,
thisFarGone = parseInt(slider.css("left")),
moveThisFar = piecesWidth;
// always make sure to center your tiles
if (thisFarGone == 0 ||
thisFarGone == -0 ||
thisFarGone == undefined) {
moveThisFar = moveThisFar - gutter;
};
newPosition = moveThisFar - thisFarGone;
//make sure it doesn't go too far
if (newPosition > sliderWidth - containerInnerWidth) {
newPosition = sliderWidth - containerInnerWidth;
}
newPosition = parseInt(newPosition); // - addOn);
slider.css({
"left": (-newPosition) + "px"
});
});
$(window).resize(function() {
console.log("working");
$(o.container).triggerHandler("hScroll");
});
});
}
});
})(jQuery);
$(document).ready(function() {
$(".posts-timeline").hScroll({
container: ".posts-timeline",
sliderName: "#slider",
partContainer: ".post"
});
});
Something like this?
console.log("working");
$(o.container).hScroll("hScroll");
Maybe not exactly that, but if you want those variables to be reassigned, I'm guessing that would wind up being done somewhere in $(window).resize(function() {

How to add scroll background effect to multiple elements with different settings?

In this demo http://www.htmldrive.net/items/demo/527/Animated-background-image-with-jQuery
This code is for one background only. I want to add multiple background with different direction and speed.
var scrollSpeed = 70;
var step = 1;
var current = 0;
var imageWidth = 2247;
var headerWidth = 800;
var restartPosition = -(imageWidth - headerWidth);
function scrollBg(){
current -= step;
if (current == restartPosition){
current = 0;
}
$('#header').css("background-position",current+"px 0");
}
var init = setInterval("scrollBg()", scrollSpeed);
Currently it has settings for
$('#header').css("background-position",current+"px 0");
In a website I want to use this effect on #footer or #content background also. but with different speed and direction.
And is there any better and more optimized jquery method to achieve same effect?
And can we get same effect using CSS 3, without javascript?
Just saw the OP's answer, but decided to post anyway:
I've created a jQuery plugin to do this:
(function($) {
$.fn.scrollingBackground = function(options) {
// settings and defaults.
var settings = options || {};
var speed = settings.speed || 1;
var step = settings.step || 1;
var direction = settings.direction || 'rtl';
var animStep;
// build up a string to pass to animate:
if (direction === 'rtl') {
animStep = "-=" + step + "px";
}
else if (direction === 'ltr') {
animStep = '+=' + step + "px";
}
var element = this;
// perform the animation forever:
var animate = function() {
element.animate({
backgroundPosition: animStep + " 0px"
}, speed, animate);
};
animate();
};
})(jQuery);
Usage:
$("#header").scrollingBackground({
speed: 50,
step: 50,
direction: 'ltr'
});
This is pretty basic, and assumes that you're background-repeat is 'repeat-x' on the element you call it on. This way, there's no need to reset the background position every so often.
Working example: http://jsfiddle.net/andrewwhitaker/xmtpr/
I could work out the following solution. Am not sure if it is efficient. Will wait for anyone to comment or provide a better option.
Till then...:
var scrollSpeed = 70;
var step = 1;
var current = 0;
var images =
[
{
imageWidth:2247,
imagePath:"images/image1"
},
{
imageWidth:1200,
imagePath:"images/image2"
}
]
var headerWidth = 800;
var imageRotateCount = 0;
var imagesLength = images.length;
$('#header').css("background-image", images[0].imagePath);
function scrollBg(){
var curIndex = imageRotateCount%imagesLength;
var curImage = images[curIndex];
current -= step;
var restartPosition = -(curImage.imageWidth - headerWidth);
if (current == restartPosition){
current = 0;
imageRotateCount++;
curIndex = imageRotateCount%imagesLength;
curImage = images[curIndex];
$('#header').css("background-image", curImage.imagePath);
}
$('#header').css("background-position",current+"px 0");
}
var init = setInterval("scrollBg()", scrollSpeed);

Categories

Resources