We are modifying an older pre-existing web app and as part of that have begun viewing it using IE10. This app has a third party menu control (menu9_com.js?) and among the numerous issues we are noticing, is the positioning of this menu on IE7+ in Standards mode. In FF, Chrome, or any version of IE with Quirks - it is positioned correctly. In Standards mode, however, it is shoved far off to the right.
I've identified the function below as a possible source for the issue. Running in any mode, the value of StartLeft begins about the same. In the working modes it finishes at a value which - by definition - works. In the broken modes, it is much much higher.
Though it's not fully clear, I believe the function is walking up the DOM from a given target location and adding values on to calculate a "total" offset for the menu element it is adding. And I think the issue comes down to the different ways that offsetLeft (and maybe offsetParent?) are handled. So I'm trying to find the best way to get consistent behavior from this function but just not familiar enough with the intention of the function, nor with the behavior of offsetLeft etc in the various modes.
Here's the function:
function ClcTrgt() {
var TLoc=Nav4?FLoc.document.layers[TargetLoc]:DomYesFLoc.document.getElementById(TargetLoc):FLoc.document.all[TargetLoc];
if (DomYes) {
while (TLoc) {
StartTop += TLoc.offsetTop;
StartLeft += TLoc.offsetLeft;
TLoc = TLoc.offsetParent;
}
}
else {
StartTop+=Nav4?TLoc.pageY:TLoc.offsetTop;
StartLeft+=Nav4?TLoc.pageX:TLoc.offsetLeft;
}
}
Any suggestions? For example, I'd convert this function to use jQuery, if I knew how.
UPDATE:
I've posted the script on pastebin.
My current direction, in the absence of an actual fix to the script (which may not be worth the work), is adding this function to run after the script itself. I added some markup to facilitate it, and it just takes the menu, and puts it where it should be (which it right-aligned with another element I've identified). This is for from optimal, but it works.
function fixMenu9() {
var pTD = $('#pgMenuDivTD');
var pMN = $('#pgMenuDiv');
var additionalOffset = ExpYes ? 3 : 0;
var leftVal = (parseInt(pTD.offset().left) + parseInt(pTD.css('width'))) - (parseInt(pMN.css('width')) + additionalOffset);
$('#pgMenuDiv').css('left', leftVal);
}
Related
I've been using getBoundingClientRect() in my application to get the dimensions of an element while the user is dragging an item around. I've been using this and it's been working well; however, I'm beginning to get major performance issues while this calculation is being performed during mouse move. It gets even worse the larger the node is that's being moved. I've used the profiling tools in Chrome and noticed that this function, being wrapped in the mouse move event that gets fired while the user is dragging, is taking a long time (31.4ms per call---scale that to being called every time the mouse moves.) While researching the issue, I came across others who've used it and noticed the performance issues as well (http://dcousineau.com/blog/2013/09/03/high-performance-js-tip/).
What are my alternatives to getBoundingClientRect() in vanilla Javascript (absolutely no jQuery)? My front-end framework is AngularJS 1.5.8, and I've built out the application using components (looking toward migrating our large app to NG2 in the future). Thanks!
Element.getBoundingClientRect() is "vanilla", although it's part of a working draft of the specification. To make your code more performant, limit the number of invocations. The link you referred to which mentions similiar performance says just that:
All calls to get any calculated dimension from the DOM should be cached or avoided.
!! Following solution doesn't work in combination with CSS3 scale transform but it's an alternative :
function getPosition(elm) {
var xPos = 0, yPos = 0;
while(elm) {
xPos += (elm.offsetLeft - elm.scrollLeft + elm.clientLeft);
yPos += (elm.offsetTop - elm.scrollTop + elm.clientTop);
elm = elm.offsetParent;
}
return { x: xPos, y: yPos };
}
Source
I am working on a terminal emulator for fun and have the basics of the backend up and running. However I keep running into performance problems on the frontend.
As you all probably know is that each character in a terminal window can have a different style. (color, backdrop, bold, underline etc). So my idea is was to use a <span> for each character in the view window and apply an inline style if necesary so I have the degree of control I need.
The problem is that the performance is horrendous on a refresh. Chrome can handle it on my pc with about 120 ops per second and firefox with 80. But internet explorer barely gets 6. So after my stint with html I tried to use canvas but the text on a canvas is ultra slow. Online I read caching helps so I implement a cache for each character and could apply colors to the then bitmapped font with some composite operation. However this is way way slower than DOM.
Then I went back to the dom and tried using document.createDocumentFragment but it performs a little bit worse then just using the standard.
I have no idea on where to begin optimization now. I could keep track on what character changes when but then I will still run into this slowness when the terminal gets a lot of input.
I am new to the DOM so I might do something completely wrong...
any help is appreciated!
Here is a jsperf with a few testcases:
http://jsperf.com/canvas-bitma32p-cache-test/6
Direct insertion of HTML as string text is surprisingly efficient when you use insertAdjacentHTML to append the HTML to an element.
var div = document.getElementById("output");
var charCount = 50;
var line, i, j;
for (i = 0; i < charCount; i++) {
line = "";
for (j = 0; j < charCount; j++) {
line += "<span style=\"background-color:rgb(0,0,255);color:rgb(255,127,0)\">o</span>";
}
div.insertAdjacentHTML("beforeend","<div>"+line+"</div>");
}
#output{font-family:courier; font-size:6pt;}
<div id="output"></div>
The downside of this approach is obvious: you never get the chance to treat each appended element as an object in JavaScript (they're just plain strings), so you can't, for example, directly attach an event listener to each of them. (You could do so after the fact by querying the resultant HTML for matching elements using document.querySelectorAll(".css selector).)
If you're truly just formatting output being printed to the screen, insertAdjacentHTML is perfect.
I'm trying to stop strings in a div expanding beyond the size of their variable-sized parent divs using the tactic of setting an overflow and fixing the width. I have about 4400 dom elements on the page (can't be decreased and typically can be more), but only about 100-300 need to be changed. Of course, not a problem in FF/Webkit which can do that in less than a second, but IE is extraordinarily slow at over 7 seconds.
I've already eliminated any dom traversing by using an array of pre-determined id elements to address the tags in question. Is there something I'm missing or some alternative trick to do this in a shorter time for IE?
for (id in ids) {
jq("#" + ids[id] + "_name").css({"overflow": "hidden",
'width': jq("#" + ids[id]).innerWidth() - 1
});
}
Well, at the point of being right down to the metal of the DOM and still not eliminating any speed, I've gone for the alternative which is to mitigate the problem so it's less of a problem for the user (maybe IE9 will save MS from this sort of embarrasment!). I looked at this blog entry by Nick Fitzgerald which showed a technique for overcoming the issue. So here, using Nick's pattern, is my solution in the end (just the part for handling IE, I left the non-IE version as is):
yieldingEach(ids, function(namebox) {
var elemName = document.getElementById(namebox + '_name');
if (elemName) {
var elem = document.getElementById(namebox);
elemName.style.width = (elem.scrollWidth - 4) + 'px';
}
});
This is a non-jQuery version...verify it works in IE, but it would be slightly faster since you're not having jQuery do it for you.
for (id in ids) {
var elemName = document.getElementById(ids[id] + '_name'),
elem = document.getElementById(ids[id]);
elemName.setAttribute('overflow', 'hidden');
elemName.setAttribute('width', elem.innerWidth - 1);
}
The while statement in this function runs too slow (prevents page load for 4-5 seconds) in IE/firefox, but fast in safari...
It's measuring pixel width of text on a page and truncating until text reaches ideal width:
function constrain(text, ideal_width){
$('.temp_item').html(text);
var item_width = $('span.temp_item').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
var original = text.length;
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
$('.temp_item').html(smaller_text);
item_width = $('span.temp_item').width();
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
Any way to improve performance? How would I convert this to a bubble-sort function?
Thanks!
move the calls to $() outside of the loop, and store its result in a temporary variable. Running that function is going to be the slowest thing in your code, aside from the call to .html().
They work very very hard on making the selector engines in libraries fast, but it's still dog slow compared to normal javascript operations (like looking up a variable in the local scope) because it has to interact with the dom. Especially if you're using a class selector like that, jquery has to loop through basically every element in the document looking at each class attribute and running a regex on it. Every go round the loop! Get as much of that stuff out of your tight loops as you can. Webkit runs it fast because it has .getElementsByClassName while the other browsers don't. (yet).
Instead of removing one character at time until you find the ideal width, you could use a binary search.
I see that the problem is that you are constantly modifying the DOM in the loop, by setting the html of the temp_item, and then re reading the width.
I don't know the context of your problem, but trying to adjust the layout by measuring the rendered elements is not a good practice from my point of view.
Maybe you could approach the problem from a different angle. Truncating to a fixed width is common.
Other possibility (hack?) if dont have choices, could be to use the overflow css property of the container element and put the … in other element next to the text. Though i recommend you to rethink the need of solving the problem the way you are intending.
Hugo
Other than the suggestion by Breton, another possibility to speed up your algorithm would be to use a binary search on the text length. Currently you are decrementing the length by one character at a time - this is O(N) in the length of the string. Instead, use a search which will be O(log(N)).
Roughly speaking, something like this:
function constrain(text, ideal_width){
...
var temp_item = $('.temp_item');
var span_temp_item = $('span.temp_item');
var text_len_lower = 0;
var text_len_higher = smaller_text.length;
while (true) {
if (item_width > ideal)
{
// make smaller to the mean of "lower" and this
text_len_higher = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_lower)/2));
}
else
{
if (smaller_text.length>=text_len_higher) break;
// make larger to the mean of "higher" and this
text_len_lower = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_higher)/2));
}
temp_item.html(smaller_text);
item_width = span_temp_item.width();
}
...
}
One thing to note is that each time you add something to the DOM, or change the html in a node, the page has to redraw itself, which is an expensive operation. Moving any HTML updates outside of a loop might help speed things up quite a bit.
As other have mentioned, you could move the calls to $() to outside the loop. You can create a reference to the element, then just call the methods on it within the loop as 1800 INFORMATION mentioned.
If you use Firefox with the Firebug plugin, there's a great way of profiling the code to see what's taking the longest time. Just click profile under the first tab, do your action, then click profile again. It'll show a table with the time it took for each part of your code. Chances are you'll see a lot of things in the list that are in your js framework library; but you can isolate that as well with a little trial and error.
I have some legacy javascript that freezes the tfoot/thead of a table and lets the body scroll, it works fine except in IE8 its very slow.
I traced the problem to reading the clientWidth property of a cell in the tfoot/thead... in ie6/7 and FireFox 1.5-3 it takes around 3ms to read the clientWidth property... in IE8 it takes over 200ms and longer when the number of cells in the table is increased.
Is this a known bug ? is there any work around or solution ?
I've solved this problem if you are still interested. The solution is quite complex. Basically, you need to attach a simple HTC to the element and cache its clientWidth/Height.
The simple HTC looks like this:
<component lightweight="true">
<script>
window.clientWidth2[uniqueID]=clientWidth;
window.clientHeight2[uniqueID]=clientHeight;
</script>
</component>
You need to attach the HTC using CSS:
.my-table td {behavior: url(simple.htc);}
Remember that you only need to attach the behavior for IE8!
You then use some JavaScript to create getters for the cached values:
var WIDTH = "clientWidth",
HEIGHT = "clientHeight";
if (8 == document.documentMode) {
window.clientWidth2 = {};
Object.defineProperty(Element.prototype, "clientWidth2", {
get: function() {
return window.clientWidth2[this.uniqueID] || this.clientWidth;
}
});
window.clientHeight2 = {};
Object.defineProperty(Element.prototype, "clientHeight2", {
get: function() {
return window.clientHeight2[this.uniqueID] || this.clientHeight;
}
});
WIDTH = "clientWidth2";
HEIGHT = "clientHeight2";
}
Notice that I created the constants WIDTH/HEIGHT. You should use these to get the width/height of your elements:
var width = element[WIDTH];
It's complicated but it works. I had the same problem as you, accessing clientWidth was incredibly slow. This solves the problem very well. It is still not as fast IE7 but it is back to being usable again.
I was unable to find any documentation that this is a known bug. To improve performance, why not cache the clientWidth property and update the cache periodically? I.E if you code was:
var someValue = someElement.clientWidth + somethingElse;
Change that to:
// Note the following 3 lines use prototype
// To do this without prototype, create the function,
// create a closure out of it, and have the function
// repeatedly call itself using setTimeout() with a timeout of 1000
// milliseconds (or more/less depending on performance you need)
var updateCache = function() {
this. clientWidthCache = $('someElement').clientWidth;
};
new PeriodicalExecuter(updateCache.bind(this),1);
var someValue = this.clientWidthCache + somethingElse
Your problem may be related to something else (and not only the clientwidth call): are your updating/resizing anyhting in your DOM while calling this function?
Your browser could be busy doing reflow on IE8, thus making clientwidth slower?
IE 8 has the ability to switch between IE versions and also there is a compatibility mode.
Have you tried switching to Compatibility Mode? Does that make any difference?
I though I had noticed a slow performance also when reading the width properties. And there may very well be.
However, I discovered that the main impact to performance in our app was that the function which was attached to the window's on resize event was itself somehow causing another resize which caused a cascading effect, though not an infinite loop. I realized this when i saw the call count for the function was orders of magnitude larger in IE8 than in IE7 (love the IE Developer Tool). I think the reason is that some activities on elements, like setting element widths perhaps, now cause a reflow in IE8 that did not do so in IE7.
I fixed it by setting the window's resize event to: resize="return myfunction();" instead of just resize="myfunction();" and making sure myfunction returned false;
I realize the original question is several months old but I figured I'd post my findings in case someone else can benefit.