I'm currently working on a new site build, currently located at http://weve.wpengine.com/. As part of the site design, I've implemented a simple backbone.js view that scrolls the background at a different speed to the rest of the content to create a perspective illusion.
weve.ScrollTransitionView = Backbone.View.extend({
templateId: 'template-page-background',
className: 'page-background',
initialize: function () {
var scope = this;
var factor = 1.3;
jQuery(window).scroll(function (e) {
var scrollTop = jQuery(this).scrollTop();
var position = 'top ' + (scrollTop / factor) + 'px center';
scope.$el.css({ 'background-position': position });
});
}
});
This works fine in Firefox. However, in Chrome, there is a ripple effect upon scrolling - the background seems to only partially render in places and bits of the image get rendered out of place - and in I.E. the effect is jittery.
Initially I thought this may be an issue with the skewed elements on the page but after creating a mockup using partially transparent images rather than CSS skewed DOM elements I experienced the same problems.
Also, the mockup appeared to be fine when there were only a few elements on the page but when I translated this to the site where there were more text/images/elements on the page the rendering issue persisted.
I've found hints here and there in this and other forums for related rendering issues but nothing which seems to match the problem I'm experiencing.
Can anybody help?
I eventually found a solution to this. The problem (for me) was related to the way that the Chrome rendering engine works (See: http://www.html5rocks.com/en/tutorials/speed/layers/).
Essentially, the foreground of my page was divided into a series of sections that contained transformed (skewed) elements. Even though the sections themselves appeared as diagonals, the rendering engine treated them as squares with triangular transparent regions on opposing corners. When the background moved, these sections weren't being changed - which meant the rendering engine, in an effort to be faster and more efficient, wasn't re-rendering the transparent sections properly with the new background position, creating the jitter effect.
The solution was to add a line to my parallax javascript to subtly modify the css of each of the transformed sections each time the background changed. I set the script to adjust the background of the div element on each loop of the parallax code, alternating between two colours - rgba(255, 0, 255, 0) and rgba(255, 255, 0, 0) - which were different in theory but in practice 100% transparent. This forced the browser to re-render properly and solved the jitter problem.
Related
I am trying to implement a sticky header row for table, but need to do it without position: sticky property.
The approach I am going for is translating Y coordinate of header row along with document vertical scroll by same amount of pixels.
As the document is scrolled down, we translate the header row downwards by same number of pixels giving the effect that header row is stuck at the top of table.
I have a working implementation at https://codepen.io/shubham_687/pen/porPwbP
PFA the gist of implementation below
canvas.onscroll = function (e) {
if (canvas.scrollTop >= rect.top) {
const numOfPixels = canvas.scrollTop - rect.top;
element.setAttribute(
"style",
"transition:0s;transform: translate3d(0px," + numOfPixels + "px, 0px);"
);
} else {
element.setAttribute(
"style",
"transition:0s;transform: translate3d(0px, 0px, 0px);"
);
}
};
The issue I am facing is that the implementation is jerky/sluggish/jumpy in some browsers and mobile devices while working perfectly fine in some (Google chrome).
As per my understanding, this behavior might be because of the following reason(s)
Large number of scroll events per second causing perf impact, might be possible to solve using debouncing, any suggestions?
Updating css dynamically leads to update layer tree which takes some time as well and the number of times this gets called is directly proportional to the times scroll event is registered.
Please share possible suggestions for perf improvement.
Im in the process of developing a 'flipbook-style' animation using Skrollr by triggering background image changes when the user scrolls to indicated positions on the page. The issue i'm having is that in browser the image changes are delayed, creating what can only be defined as a 'flicker' of white between the frames.
<div class="section" style="background: url('frame1.png')"
data-560-top="background-image:!url('frame1.png');"
data-440-top="background-image:!url('frame2.png');">
The HTML is simple; it basically states that at 560 pixels from the top of the div (in relation to the browser window), the background should be at frame 1, then as the user scrolls closer to the div (440 pixels from the top of the div) the background image changes to frame 2. I plan to use up to around 20 frames and the images are quite large.
I have created a JSBin here which includes a very simplified sample with images from placehold.it. This includes the Skrollr script and an example layout of a section of my project. The key difference being that the images in my project are of much larger scale.
(function($) {
var cache = [];
// Arguments are image paths relative to the current page.
$.preLoadImages = function() {
var args_len = arguments.length;
for (var i = args_len; i--;) {
var cacheImage = document.createElement('img');
cacheImage.src = arguments[i];
cache.push(cacheImage);
}
};
})(jQuery);
jQuery.preLoadImages(
'http://www.placehold.it/300x200.png',
'http://www.placehold.it/300x200.png'
);
The above snippet seems to be working on Chrome, however the flicker issue remains in Firefox. Based on research, firefox handles cached images differently from Chrome? (e.g Where an image is not considered needed by firefox at a given time, it is trashed?)
I would like to know how I could possibly force all browsers to preload the images efficiently, to potentially avoid the background image flicker upon change. I am still quite new to Javascript/JQuery.
I hope I have provided a clear explanation. All assistance appreciated.
Dan
You can preload images using CSS only, no need for JS. Check out this article for more info. Another interesting way to do it is in the comment section of the article. Basically you assign the background image to a pseudo-element so that is is cached and ready to be used whenever. See this code for an example:
#something:before {
content: url("./img.jpg");
width:0;
height:0;
visibility:hidden;
}
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 trying to re-create website with parallax effect using JavaScript. That means that I have two or more layers, that are moving different speeds while scrolling.
In my case I'm moving only one layer, the other one remains static:
layer 1 = website text;
layer 2 = element background;
for this I'm using simple source code (with jQuery 1.6.4):
var docwindow = $(window);
function newpos(pos, adjust, ratio){
return ((pos - adjust) * ratio) + "px";
}
function move(){
var pos = docwindow.scrollTop();
element.css({'top' : newpos(pos, 0, 0.5)});
}
$(window).scroll(function(){
move();
});
The Problem:
- All calculations are done right and the effect "works" as expected. But there is some performance glitch under some browsers (Chrome MAC/Windows, Opera MAC, IE, paradoxically not Safari).
What do I see during scrolling?
- While scrolling the background moves in one direction together with scroll, but it seems to occasionally jump few pixels back and then forth, which creates very disturbing effect (not fluid).
Solutions that I tried:
- adding a timer to limit scroll events
- using .animate() method with short duration instead of .css() method.
I've also observed, that the animation is smooth when using .scrollTo method (scrollTo jQuery plugin). So I suspect that there is something wrong with firing scroll events (too fast).
Have you observed the same behavior?
Do you know, how to fix it?
Can you post a better solution?
Thanks for all responses
EDIT #1:
Here you can find jsfiddle demonstration (with timer): http://jsfiddle.net/4h9Ye/1/
I think you should be using scrollTop() instead and change the background position to fixed. The problem is that setting it to absolute will make it move by default when you scroll up or down.
The flicker occurs because the position is updated as part of the default browser scroll and updated again as part of your script. This will render both positions instead of just the one you want. With fixed, the background will never move unless you tell it so.
I've created a demo for you at http://jsfiddle.net/4h9Ye/2/ .
I have this: http://jsfiddle.net/5r3Eh/10/show/ and than http://jsfiddle.net/5r3Eh/18/ but they are quite slow (on chrome 12, a bit better on 13).
Is there any way to do drag-drop of window with out jquery.event.drag-1.5.min.js and if possible without jquery at all? And how to implement it into my simple code http://jsfiddle.net/5r3Eh/10/:
$('#demo4_box').bind('dragstart', function(event) {
return $(event.target).is('.handle');
}).bind('drag', function(event) {
$(this).css({
top: event.offsetY,
left: event.offsetX
});
});
$(".resize").bind('dragstart', function(event) {
var $box = $(this).closest(".box");
$box.data("width", $box.width());
$box.data("height", $box.height());
$box.data("x", event.offsetX);
$box.data("y", event.offsetY);
}).bind("drag", function(event) {
var $box = $(this).closest(".box");
$box.width(Math.max($box.data("width") - $box.data("x") + event.offsetX, $box.data("minwidth")));
$box.height(Math.max($box.data("height") - $box.data("y") + event.offsetY, $box.data("minheight")));
});
The browser is re-drawing the entire background layer every time you drag by a pixel. You can confirm this by using the Chrome developer tools (Timeline): http://i.imgur.com/bzXj5.png
If you disable the box-shadow and re-profile, none of those rendering events fire. So, your problem is that you are causing the entire window to redraw. If you can cut down on that in some way, then your problem is solved.
However, doing this while achieving the look you want may be hard. The only thing I can think of that might work would be drawing a div behind your dialog for the box-shadow to be cast on. This div would be just large enough to receive the shadow, and would need an opaque background. In this case, the redraw may only affect the smaller dive instead of the entire background layer.
The bottom line is that box-shadow is expensive, try to avoid redraws like this.