I monitored the page load time for my site in chrome (using Timeline), and got hundreds of warnings that said "Forced synchronous layout is a possible performance bottleneck" for all the Raphael.js calls:
I don't know how to fix this :/
Any ideas on where to look?
Here's how I normally create text on the page:
currentPaper.text(width,height,"text")
.attr({opacity:.8, fill: "#333", 'font-size':10})
.id = "someId";
//Or, to be exact:
currentPaper.text(x_pos+(width/2),
y_pos-height/8,
lopaLayoutArray.x[s].y1[r].y2[x])
.attr({opacity:.8, fill: "#333", 'font-size':(10*size)})
.id = seatName+"-textLabel";
Thanks!
Problem:
The problem is called "Layout Thrashing" and is happening because the browser was trying to immediately refresh / redraw after every Raphael drawing.
For example, this would cause layout thrashing:
var a = document.getElementById('element-a');
var b = document.getElementById('element-b');
a.clientWidth = 100;
var aWidth = a.clientWidth;
b.clientWidth = 200;
var bWidth = b.clientWidth;
"In this simple example, the browser will update the whole layout twice. This is because after setting the first element's width, you are asking to retrieve an element's width. When retrieving the css property, the browser know's it needs updated data, so it then goes and updates the whole DOM tree in memory. Only then, it will continue to the next line, which will soon after cause another update because of the same reasons."
This can simply be fixed by changing around the order of the code, as so :
var a = document.getElementById('element-a');
var b = document.getElementById('element-b');
a.clientWidth = 100;
b.clientWidth = 200;
var aWidth = a.clientWidth;
var bWidth = b.clientWidth;
"Now, the browser will update both properties one after the other without updating the tree."
Sources:
https://blog.idrsolutions.com/2014/08/beware-javascript-layout-thrashing/
http://www.codeproject.com/Articles/758790/Debugging-and-solving-the-Forced-Synchronous-Layou
http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/
https://dev.opera.com/articles/efficient-javascript/?page=3#reflow
Soultion:
One way to fix this (a really quick and dirty way) is to hide() the div you are drawing to, then, when you are finished, show() it. I have had hit and miss results with this with Raphael, and it has been drawing strange this way. However it is faster...
"When an element has its display style set to none, it will not need to repaint, even if its contents are changed, since it is not being displayed. This can be used as an advantage. If several changes need to be made to an element or its contents, and it is not possible to combine these changes into a single repaint, the element can be set to display:none, the changes can be made, then the element can be set back to its normal display."
Here's the results:
This way worked for me!
Related
I am setting up a specialised Print button. The page is complicated and needs some pre-processing before being sent to window.print().
I have the Highcharts code working at this point. It correctly resizes the charts on the page, and then, post print, it sizes them back to their original size.
The problem is that from then on, the charts will not respond to Media Query changes. The site is Responsive, built on Bootstrap, so this is not a functional result.
How do I change the chart size, but leave it able to respond to Media Queries?
The code I am using is:
Setup Code
var saveChartContainerWidth = $('.highcharts-container').css('width');
var saveChartContainerWidthSVG = saveChartContainerWidth.slice(0, -2);
$('.highcharts-container').css('width', '690px');
$(Highcharts.charts).each(function(i,chart){
var height = chart.renderTo.clientHeight;
var width = 690; // chart.renderTo.clientWidth;
chart.setSize(width, height);
chart.reflow();
});
Post Print Teardown Code
$('.highcharts-container').css('width', saveChartContainerWidth);
$(Highcharts.charts).each(function(i,chart){
var height = chart.renderTo.clientHeight;
var width = saveChartContainerWidthSVG;
chart.setSize(width, height);
chart.reflow();
});
With the help of #Ryan's link, and comments from a co-worker, I tried a number of solutions which, while not working, got me closer.
Having then a better idea of what to look for, I finally found this answer Highcharts + Bootstrap .table-responsive issue which provided a key ingredient.
The two keys are:
Do not set the size on either the SVG itself, or the direct Highchart's container. (I set it on a containing Div one level higher.)
Trigger a $(window).trigger("resize"); event.
This worked in all browsers tested (Firefox, Chrome, IE 9 - 11).
----------------- The Final Code -----------------------
// Setup
$('.chart-container').css('width', '690px');
$(window).trigger("resize");
// Teardown
$('.chart-container').css('width', '');
$(window).trigger("resize");
Do this after setting the size will do the trick.
window.onresize = () => {
chart.setSize($('parentElement').offsetWidth, height)
chart.reflow()
}
Yet if you change the onresize function to something else, it might not work. So this is more like a quick solution, yet it works perfectly
I have a routine that sizes elements in a page to fit snugly within their parent. In most cases, it is working admirably, but in Firefox (JUST Firefox - Chrome, IE, etc are fine) it is fumbling on the first attempt in one particular instance - a div nested within a fieldset fails to resize on the first attempt, but succeeds on the second (and subsequent) attempts.
Each element is sized relative to its parent using the following:
function resizeChild(elem) {
// Get gutter based on margins, borders, padding, etc
var gutter = getGutter(elem); // returns obj with x and y properties
var parent = elem.parentElement;
var parentStyles = window.computedStyle(parent);
var targetWidth = (parseInt(parentStyles['width']) - gutter.x;
var widthPx = targetWidth + 'px';
// prototype.js setStyle shortcut
elem.setStyle({
width: widthPx,
maxWidth: widthPx,
minWidth: widthPx
});
}
I run this in a loop, iterating over every element with a particular CSS class.
According to the Firefox debugger, the outer element (the fieldset) is always being resized before the inner div. I can inspect the element, and see the style attributes being set appropriately. However, on the next iteration of the loop, when the parent is being evaluated (I can see in the javascript property inspector that the parent is indeed the fieldset), the value for width that is returned for the computed style is the previous, unmodified value, thus the inner div is resized incorrectly.
Can somebody shed some light on this please?
Edits after comments:
parent.clientWidth returns 0.
Not sure if this is relevant, but a parent div of the fieldset had display set to none shortly prior the resize operation being called. However, at the point at which the fieldset was resized, the display of the div was set to inline-block. I don't think this would make a difference, but then I'm not well educated on some of the particular behaviours of Firefox in this scenario.
I found a solution to this, although it's a little situational.
It seems that if the width of the parent element has been dynamically modified using prototype.js#Element.setStyle() (and, for all I know, other libraries that directly modify the style attribute), then the computedStyle() method won't reflect the change until all changes have completed.
The solution was to check to see if the parent element of the element being resized also had the CSS class that flagged the elements for resize, and if it did, get the size from the style attribute instead of using computedStyle(). Here's the full function, with modifications:
function resizeFullwidth() {
$$('*.fullWidth').each(function(elem, i) {
// Get gutter based on margins, borders, padding, etc
var gutter = getGutter(elem); // returns obj with x and y properties
var parent = elem.parentElement;
var parentStyles = (
parent.hasClassName('fullWidth')
? window.computedStyle(parent)
: parent.style);
var targetWidth = (parseInt(parentStyles['width']) - gutter.x;
var widthPx = targetWidth + 'px';
// prototype.js setStyle shortcut
elem.setStyle({
width: widthPx,
maxWidth: widthPx,
minWidth: widthPx
});
});
}
This now works correctly in all browsers :)
Thanks very much for your help, people :)
Have you tried var targetWidth = parent.clientWidth ?
See : MDN Element.clientWidth
I am working on web application which contains different number of nested frames and iframes.
Currently when web page renders browser renders scroll bars against them. Sometimes count is 4-5 scroll bar in a page which looks very ugly. So I want to remove these scroll-bars through Javascript.
To understand the problem, Please see below example of web page with frame/iframe hierarchy.
Frame1
--Frame2
--Frame3
--Frame4
--Frame5
My approach :
Start from the root document and search all frames/iframes from the page and put these frame node in Array with additional variable called level.
After completion of this I am sorting the array on level. So after this activity array will look like.Below
0> Frame5 node, level=3
1> Frame4 node, level=3
2> Frame3 node, level=2
3> Frame2 node, level=2
4> Frame1 node, level=1
After this I am considering two option to remove scroll-bar
Option #1 :
start from 0th element from array and get scrollheight of frame and set it to height property of Frame.
var frameScrollHeight=Frame5.contentWindow.document.body.scrollHeight;
Frame5.style.height =frameScrollHeight;
Do same procedure for every frame in array.
My assumption here is that If innermost frames height changes, Its parent nodes scroll Height must changes to true scrollheight.
e.g After setting the Frame5 and Frame4 height. when we fetch scrollheight of Frame3 it must be Frmae5 height +Frame4 height. But now its not reflecting the true scrollheight.
I am not sure what is wrong here. When we set Frame5 and Frame 4 height will Dom refreshes immediately? .
Am i using wrong property? because I verified all other proprieties also using script debugger like clientHeight,offsetHeight but no one equivalent to Frame5 height+ Frame4 height.
Option #2
As above approach is not working correctly I took another approach of adding previous level frames height manually and set it to parent node in next level.
e.g Get scrollheight of Frame5 and Frame4 and set it to Frame3 Also add height of Frame3 and Frame2 and set it to Frame1.
Option two is working now but still it has some drawback like all frames must be in hierarchical order and must not be in parallel to each other.
Also when I am setting scrollheight as height to Frame scroll-bar still appears as it is and I have to add some constant like 15 to scrollheight. So actual code is like
var frameScrollHeight=Frame5.contentWindow.document.body.scrollHeight+15;
Frame5.style.height =frameScrollHeight;
I don’t know what this 15 is. Is it a frameborder? Can I get this constant using any property of frame?
I will prefer option 1 but don’t know why this is not working.
If there is any other way to solve this problem please suggest.
Why not just use
html,body{overflow:hidden}
in the CSS for the iframes?
I had the same problem and the following function (using jquery) worked for me after I converted all frames to iframes. This function will resize every frame so that there is no need for scrollbars:
var resizeFramesSoAllContentVisible = function (win) {
for (var i = 0; i < win.frames.length; i++) {
var frame = win.frames[i];
var contentWindow = frame.contentWindow ? frame.contentWindow : frame.window;
resizeFramesSoAllContentVisible(contentWindow);
}
if (win.frameElement) {
var myFrameOuterHeight = $(win.frameElement).height();
var myFrameInnerHeight = $(win.document).height();
var myFrameOuterWidth = $(win.frameElement).width();
var myFrameInnerWidth = $(win.document).width();
if (myFrameOuterHeight != myFrameInnerHeight) {
$(win.frameElement).height(myFrameInnerHeight);
}
if (myFrameOuterWidth != myFrameInnerWidth) {
$(win.frameElement).width(myFrameInnerWidth);
}
}
}
I want to change the background color of in-viewport elements (using overflow: scroll)
So here was my first attempt:
http://jsfiddle.net/2YeZG/
As you see, there is a brief flicker of the previous color before the new color is painted. Others have had similar problems.
Following the HTML5 rocks instructions, I tried to introduce requestAnimationFrame to fix this problem to no avail:
http://jsfiddle.net/RETbF/
What am I doing wrong here?
Here is a simpler example showing the same problem: http://jsfiddle.net/HJ9ng/
Filed bug with Chromium here: http://code.google.com/p/chromium/issues/detail?id=151880
if it is only the background color, well why don't you just change the parent background color to red and once it scroll just change it to pink?
I change your CSS to that
#dad
{
overflow-y: scroll;
overflow-x: hidden;
width: 100px;
height: 600px;
background-color:red;
}
I remove some of you Jquery and change it to this
dad.bind('scroll', function() {
dad.css('background-color', 'pink');
});
And I remove this line
iChild.css('backgroundColor', 'red');
But is the Red color it is important that won't work for sure http://jsfiddle.net/2YeZG/5/
I like Manuel's Solution.
But even though I don't get what you're exactly trying to do, I want to point out a few things.
In your fiddle code, I saw that you included Paul Irish's Shim for requestAnimationFrame.
But you never use it.
(It's basically a reliable setTimeOut, nothing else) it's from frame based animations.)
So since you just want to change some CSS properties, I don't see why you would need it. Even if you want transitions, you should rely on CSS transitions.
Other than that your code could look something like
dad.bind('scroll', function() {
dad.css('background-color', 'pink');
eachElemNameHere.css('background-color','randomColor');
});
Also you should ideally not use something like that if you can help it. You should just add and remove class names and add all these properties in your CSS. Makes it work faster.
Also, again I don't quite get it, but you could use the jQuery function to find out each elements' position from the top to have better control.
Your problem seems to be that you only change the background color of the elements which have already been scrolled into view. Your code expects that the browser waits for your code to handle the scroll event before the browser redraws its view. This is most probably not a guarantee given by the HTML spec. That's why it flickers.
What you should do instead is to change the elements which are going to be scrolled into view. This is related to off screen rendering or double buffering as it is called in computer games programming. You build your scene off screen and copy the finished scene to the visible frame buffer.
I modified your first JSFiddle to include a multiplier for the height of the scroll area: http://jsfiddle.net/2YeZG/13/.
dad.bind('scroll', function() {
// new: query multiplier from input field (for demonstration only) and print message
var multiplier = +($("#multiplier")[0].value);
$("#message")[0].innerHTML=(multiplier*100)-100 + "% of screen rendering";
// your original code
var newScrollY = newScrollY = dad.scrollTop();
var isForward = newScrollY > oldScrollY;
var minVal = bSearch(bots, newScrollY, true);
// new: expand covered height by the given multiplier
// multiplier = 1 is similar to your code
// multiplier = 2 would be complete off screen rendering
var newScrollYHt = newScrollY + multiplier * dadHeight;
// your original code (continued)
var maxVal;
for (maxVal = minVal; maxVal < botsLen; maxVal++) {
var nxtTopSide = tops[maxVal];
if (nxtTopSide >= newScrollYHt) {
break;
}
}
maxVal = Math.min(maxVal, botsLen);
$(dadKids.slice(minVal, maxVal)).css('background', 'pink');
});
Your code had a multiplier of 1, meaning that you update the elements which are currently visible (100% of scroll area height). If you set the multiplier to 2, you get complete off screen updates for all your elements. The browser updates enough elements to the new background color so that even a 100% scroll would show updated elements. Since the browser seldom scrolls 100% of the area in one step (depends of the operating system and the scroll method!), it may be sufficient to reduce the multiplier to e.g. 1.5 (meaning 50% off screen rendering). On my machine (Google Chrome, Mac OS X with touch pad) I cannot produce any flicker if the multiplier is 1.7 or above.
BTW: If you do something more complicated than just changing the background color, you should not do it again and again. Instead you should check whether the element has already been updated and perform the change only afterwards.
When writing a Javascript a function that I had gotten help from earlier, which gets the height of an element that is hidden, someone reffered me to the Prototype function getDimensions(). In the example, they set "visibility: hidden; position: absolute; display: block;", which effectively lets us measure what the clientHeight would be if it were being displayed. Then they set it all back and you can go about your business. I haven't used prototype, but I would assume that works fine. However, when I tried to mimic the same function in my own code, the use of "position: absolute;" threw off the measurement. It works fine without it, but its use is what allows us to do this for a split second without skewing the design. My version is below, any idea why it isn't working?
var objStyle = obj[objName].style;
// Record original style values
var visibility = objStyle.visibility;
//var position = objStyle.position;
var display = objStyle.display;
// Modify object for measuring
objStyle.visibility = "hidden";
//objStyle.position = "absolute";
objStyle.display = "block";
// Measure height
height = obj[objName].clientHeight;
// Fix object
objStyle.visibility = visibility;
//objStyle.position = position;
objStyle.display = display;
// Return height
return parseInt(height);
Thanks in advance for your help.
I don't know if it works while invisible, but jQuery has some options here - in particular the height function; worth a look? Based on your example, something like:
height = $(obj[objName]).height();
Are you seeing this only on a cetain browser, or on all browsers? Prototype's getDimensions() does a check for Safari (and possibly other buggy browsers), you should try putting that in your code as well and see if it fixes the issue.
It could also be due to the fact that you're using obj[objName] as opposed to document.getElementById() - AFAIK these will return slightly different objects, which could cause the inconsistency you're seeing.
I usually measure my heights with .offsetHeight, something like:
var h = document.getElementById(divname).offsetHeight;
When I need to measure something, if it has position:absolute;
I usually run into this when I have two columns and one is absolute, and the parent needs to be pushed down by the one that's absolute if that's bigger than the other one. I'll use the offsetHeight to set the parent height if it's bigger that the height of the other column.