Javascript too fast for DOM-manipulation? - javascript

In Javascript I load some elements via AJAX from a server, and for each data-element I create a <div> that contains another div and an <img>. The inner div contains between 2 and 5 lines of text. I arrange three of this items side by side in a row, then the next three in the next row and so on.
But since the texts have different lengths, I want to do some fine-adjustment of the elements within each row. I want the top-edges of all three images to be on the same height. To do this, I do this:
Insert the three <div><div>text</div><img></div>-blocks into their container.
Get the heights of the three <div>text</div>-elements,
calculate their maximum, and then
set their padding-top properties in a way that gives them all the same height.
In Safari and Chrome this works perfectly fine when I turn on the console and set breakpoints to watch what is going on in detail.
But when I turn off breakpoints in this two browsers, the text-diffs don't get their correct padding-values.
I am pretty sure, that - at breakpoints off - the browser is still working on inserting the three dom-elements and rendering the pictures, when javascript tries to measure the heights of the text-divs. So it measures them at a time when they don't have their final values. The script reads wrong values and so it puts wrong values to the padding-top-property.
This does not happen in all browsers:
When running normally (without console and breakpoints) it works always fine within:
Firefox
Opera
Internet Explorer (running in a virtual Machine on my iMac)
But I have those problems in:
Safari
Chrome
What can I do to ensure, that the measurement of an elements height happens when the rendering-machin has finished its manipulation?
EDIT:
I found out another very important detail:
The problem occurs, because of the height of the text-div above the image. Sometimes the text fits very tightly into two rows. One small letter more in any of the rows and it would be three rows.
In this case my script, that runs immediately after the div was created, measures a height of three lines (60 pixel), and everything would be absolutely correct, if this div did really contain 3 lines of text. My script manipulates the elements in a manner that would be perfect if this div really was 3 lines high.
But obviously, some milliseconds after my script was running, the browser (Safari and Chrome) performs an improvement of font-rendering. And then suddenly the text fits into 2 lines, which makes the text-div only 40 pixels high. And so the image moves up 20 pixels, and this destroys my just processed result (all images was at the same position)
So,does this give you any idea on how to solve the problem? Is there a way to let that part of my script run after all rendering-polishing has finished? Is there an event like onFinishingRenderingImprovementsDone?
(written on May the 4th be with you = Star Wars day)

How about having your script run with a short delay - setTimeout(function() {[your code here]}, 100) (or however long it needs...) - and see if you can simply avoid the problem altogether? Less than a second probably, and for an async action, adding a very short wait would likely not be noticeable.

You can monitor the height of the elements, and when an element height changes, you can recalculate the padding.
Example:
$.fn.changeHeight = function(callback){
return this.each(function(i, e){
var el = $(e), height = el.height();
window.setInterval(function(){
var h = el.height();
if (h != height) {
height = h;
callback.call(e);
}
}, 100);
});
};
function rndText() {
var txt = '';
for (var i = Math.floor(Math.random() * 20); i >= 0; i--) {
txt += 'asdf ';
}
return txt;
}
for (var i = 0; i < 10; i++) {
var d = $('<div>').append(
$('<div>').prop('contenteditable', true).text(rndText()).changeHeight(resize)
).appendTo('body');
}
resize();
function resize() {
console.log('resize');
var height = 0;
$('body > div').each(function(i, e) {
var d = $(e);
var h = d.find('div').height();
if (h > height) height = h;
});
$('body > div').each(function(i, e) {
var d = $(e);
d.css('padding-top', height - d.find('div').height());
});
}
Demo: http://jsfiddle.net/Guffa/Lunxbr6p/2/
The boxes in the demo are editable, so you can change the text and see how the boxes resize.

Related

How to obtain effective iframe height [duplicate]

How do you get the rendered height of an element?
Let's say you have a <div> element with some content inside. This content inside is going to stretch the height of the <div>. How do you get the "rendered" height when you haven't explicitly set the height. Obviously, I tried:
var h = document.getElementById('someDiv').style.height;
Is there a trick for doing this? I am using jQuery if that helps.
Try one of:
var h = document.getElementById('someDiv').clientHeight;
var h = document.getElementById('someDiv').offsetHeight;
var h = document.getElementById('someDiv').scrollHeight;
clientHeight includes the height and vertical padding.
offsetHeight includes the height, vertical padding, and vertical borders.
scrollHeight includes the height of the contained document (would be greater than just height in case of scrolling), vertical padding, and vertical borders.
It should just be
$('#someDiv').height();
with jQuery. This retrieves the height of the first item in the wrapped set as a number.
Trying to use
.style.height
only works if you have set the property in the first place. Not very useful!
NON JQUERY since there were a bunch of links using elem.style.height in the top of these answers...
INNER HEIGHT:
https://developer.mozilla.org/en-US/docs/Web/API/Element.clientHeight
document.getElementById(id_attribute_value).clientHeight;
OUTER HEIGHT:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.offsetHeight
document.getElementById(id_attribute_value).offsetHeight;
Or one of my favorite references: http://youmightnotneedjquery.com/
I use this to get the height of an element (returns float):
document.getElementById('someDiv').getBoundingClientRect().height
It also works when you use the virtual DOM. I use it in Vue like this:
this.$refs['some-ref'].getBoundingClientRect().height
For a Vue component:
this.$refs['some-ref'].$el.getBoundingClientRect().height
You can use .outerHeight() for this purpose.
It will give you full rendered height of the element. Also, you don't need to set any css-height of the element. For precaution you can keep its height auto so it can be rendered as per content's height.
//if you need height of div excluding margin/padding/border
$('#someDiv').height();
//if you need height of div with padding but without border + margin
$('#someDiv').innerHeight();
// if you need height of div including padding and border
$('#someDiv').outerHeight();
//and at last for including border + margin + padding, can use
$('#someDiv').outerHeight(true);
For a clear view of these function you can go for jQuery's site or a detailed post here.
it will clear the difference between .height() / innerHeight() / outerHeight()
style = window.getComputedStyle(your_element);
then simply: style.height
Definitely use
$('#someDiv').height() // to read it
or
$('#someDiv').height(newHeight) // to set it
I'm posting this as an additional answer because theres a couple important things I just learnt.
I almost fell into the trap just now of using offsetHeight. This is what happened :
I used the good old trick of using a debugger to 'watch' what properties my element has
I saw which one has a value around the value I was expecting
It was offsetHeight - so I used that.
Then i realized it didnt work with a hidden DIV
I tried hiding after calculating maxHeight but that looked clumsy - got in a mess.
I did a search - discovered jQuery.height() - and used it
found out height() works even on hidden elements
just for fun I checked the jQuery implementation of height/width
Here's just a portion of it :
Math.max(
Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
Math.max(document.body["offset" + name], document.documentElement["offset" + name])
)
Yup it looks at BOTH scroll and offset. If that fails it looks even further, taking into account browser and css compatibility issues. In other words STUFF I DONT CARE ABOUT - or want to.
But I dont have to. Thanks jQuery!
Moral of the story : if jQuery has a method for something its probably for a good reason, likely related to compatibilty.
If you haven't read through the jQuery list of methods recently I suggest you take a look.
I think the best way to do this in 2020 is to use vanilla js and getBoundingClientRect().height;
Here's an example
let div = document.querySelector('div');
let divHeight = div.getBoundingClientRect().height;
console.log(`Div Height: ${divHeight}`);
<div>
How high am I? 🥴
</div>
On top of getting height this way, we also have access to a bunch of other stuff about the div.
let div = document.querySelector('div');
let divInfo = div.getBoundingClientRect();
console.log(divInfo);
<div>What else am I? 🥴</div>
I made a simple code that doesn't even need JQuery and probably gonna help some people.
It gets the total height of 'ID1' after loaded and use it on 'ID2'
function anyName(){
var varname=document.getElementById('ID1').offsetHeight;
document.getElementById('ID2').style.height=varname+'px';
}
Then just set the body to load it
<body onload='anyName()'>
document.querySelector('.project_list_div').offsetHeight;
Hm we can get the Element geometry...
var geometry;
geometry={};
var element=document.getElementById(#ibims);
var rect = element.getBoundingClientRect();
this.geometry.top=rect.top;
this.geometry.right=rect.right;
this.geometry.bottom=rect.bottom;
this.geometry.left=rect.left;
this.geometry.height=this.geometry.bottom-this.geometry.top;
this.geometry.width=this.geometry.right-this.geometry.left;
console.log(this.geometry);
How about this plain JS ?
So is this the answer?
"If you need to calculate something but not show it, set the element to visibility:hidden and position:absolute, add it to the DOM tree, get the offsetHeight, and remove it. (That's what the prototype library does behind the lines last time I checked)."
I have the same problem on a number of elements. There is no jQuery or Prototype to be used on the site but I'm all in favor of borrowing the technique if it works. As an example of some things that failed to work, followed by what did, I have the following code:
// Layout Height Get
function fnElementHeightMaxGet(DoScroll, DoBase, elementPassed, elementHeightDefault)
{
var DoOffset = true;
if (!elementPassed) { return 0; }
if (!elementPassed.style) { return 0; }
var thisHeight = 0;
var heightBase = parseInt(elementPassed.style.height);
var heightOffset = parseInt(elementPassed.offsetHeight);
var heightScroll = parseInt(elementPassed.scrollHeight);
var heightClient = parseInt(elementPassed.clientHeight);
var heightNode = 0;
var heightRects = 0;
//
if (DoBase) {
if (heightBase > thisHeight) { thisHeight = heightBase; }
}
if (DoOffset) {
if (heightOffset > thisHeight) { thisHeight = heightOffset; }
}
if (DoScroll) {
if (heightScroll > thisHeight) { thisHeight = heightScroll; }
}
//
if (thisHeight == 0) { thisHeight = heightClient; }
//
if (thisHeight == 0) {
// Dom Add:
// all else failed so use the protype approach...
var elBodyTempContainer = document.getElementById('BodyTempContainer');
elBodyTempContainer.appendChild(elementPassed);
heightNode = elBodyTempContainer.childNodes[0].offsetHeight;
elBodyTempContainer.removeChild(elementPassed);
if (heightNode > thisHeight) { thisHeight = heightNode; }
//
// Bounding Rect:
// Or this approach...
var clientRects = elementPassed.getClientRects();
heightRects = clientRects.height;
if (heightRects > thisHeight) { thisHeight = heightRects; }
}
//
// Default height not appropriate here
// if (thisHeight == 0) { thisHeight = elementHeightDefault; }
if (thisHeight > 3000) {
// ERROR
thisHeight = 3000;
}
return thisHeight;
}
which basically tries anything and everything only to get a zero result. ClientHeight with no affect. With the problem elements I typically get NaN in the Base and zero in the Offset and Scroll heights. I then tried the Add DOM solution and clientRects to see if it works here.
29 Jun 2011,
I did indeed update the code to try both adding to DOM and clientHeight with better results than I expected.
1) clientHeight was also 0.
2) Dom actually gave me a height which was great.
3) ClientRects returns a result almost identical to the DOM technique.
Because the elements added are fluid in nature, when they are added to an otherwise empty DOM Temp element they are rendered according to the width of that container. This get weird, because that is 30px shorter than it eventually ends up.
I added a few snapshots to illustrate how the height is calculated differently.
The height differences are obvious. I could certainly add absolute positioning and hidden but I am sure that will have no effect. I continued to be convinced this would not work!
(I digress further) The height comes out (renders) lower than the true rendered height. This could be addressed by setting the width of the DOM Temp element to match the existing parent and could be done fairly accurately in theory. I also do not know what would result from removing them and adding them back into their existing location. As they arrived through an innerHTML technique I will be looking using this different approach.
* HOWEVER * None of that was necessary. In fact it worked as advertised and returned the correct height!!!
When I was able to get the menus visible again amazingly DOM had returned the correct height per the fluid layout at the top of the page (279px). The above code also uses getClientRects which return 280px.
This is illustrated in the following snapshot (taken from Chrome once working.)
Now I have noooooo idea why that prototype trick works, but it seems to. Alternatively, getClientRects also works.
I suspect the cause of all this trouble with these particular elements was the use of innerHTML instead of appendChild, but that is pure speculation at this point.
offsetHeight, usually.
If you need to calculate something but not show it, set the element to visibility:hidden and position:absolute, add it to the DOM tree, get the offsetHeight, and remove it. (That's what the prototype library does behind the scenes last time I checked).
Sometimes offsetHeight will return zero because the element you've created has not been rendered in the Dom yet. I wrote this function for such circumstances:
function getHeight(element)
{
var e = element.cloneNode(true);
e.style.visibility = "hidden";
document.body.appendChild(e);
var height = e.offsetHeight + 0;
document.body.removeChild(e);
e.style.visibility = "visible";
return height;
}
If you are using jQuery already, your best bet is .outerHeight() or .height(), as has been stated.
Without jQuery, you can check the box-sizing in use and add up various paddings + borders + clientHeight, or you can use getComputedStyle:
var h = getComputedStyle(document.getElementById('someDiv')).height;
h will now be a string like a "53.825px".
And I can't find the reference, but I think I heard getComputedStyle() can be expensive, so it's probably not something you want to call on each window.onscroll event (but then, neither is jQuery's height()).
With MooTools:
$('someDiv').getSize().y
If i understood your question correctly, then maybe something like this would help:
function testDistance(node1, node2) {
/* get top position of node 1 */
let n1Pos = node1.offsetTop;
/* get height of node 1 */
let n1Height = node1.clientHeight;
/* get top position of node 2 */
let n2Pos = node2.offsetTop;
/* get height of node 2 */
let n2Height = node2.clientHeight;
/* add height of both nodes */
let heightTogether = n1Height + n2Height;
/* calculate distance from top of node 1 to bottom of node 2 */
let actualDistance = (n2Pos + n2Height) - n1Pos;
/* if the distance between top of node 1 and bottom of node 2
is bigger than their heights combined, than there is something between them */
if (actualDistance > heightTogether) {
/* do something here if they are not together */
console.log('they are not together');
} else {
/* do something here if they are together */
console.log('together');
}
}
Have you set the height in the css specifically? If you haven't you need to use offsetHeight; rather than height
var h = document.getElementById('someDiv').style.offsetHeight;

Basic JS - Is this function okay?

I have a carousel (Owl Carousel) with vertically centered controls. Because of the structure, I have to absolutely position the previous and next arrow. Because the page is responsive, their position is dynamic. The size of the controls may also change.
I've written a function that runs on load and resize. It gets the height of the image and the height of the controls, subtracts the latter from the former, divides by two, and then uses that number as the controls' margin-top.
It works, but I'm questioning if I'm getting and using all the variables correctly. Does JavaScript read in order? Where it runs the first line, then the next, then the next... I'm strong in CSS but JS has always been a crutch.
Can I write this more efficiently?
function centerCarouselControls() {
var carouselImage = $('.carousel-card > img');
var carouselControls = $('.owl-nav > div');
var carouselHeight = carouselImage.outerHeight();
var controlHeight = carouselControls.outerHeight();
var controlMargin = (carouselHeight - controlHeight) / 2;
carouselControls.css('margin-top', controlMargin);
}
$('.carousel-card > img').load(centerCarouselControls);
$(window).on('resize', centerCarouselControls);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I feel like this might be the type of question that gets flagged on here for not being specific enough. If that's the case, could someone please point me to a community where this would be more appropriate? Thanks!
In some browsers your code works like firefox 51, but it is more complete code this:
carouselControls.css('margin-top', controlMargin + 'px');

Bootstrap 3 Equal Height Rows Not Working 100% On Our Site

We have almost completed the development of our Bootstrap 3 based web site, but where we need to display columns comprising of thumbnails and text they don't always wrap and align correctly. It works on Chrome and Safari, but doesn't work 100% of the time on Firefox, and we have pretty much run out of ideas.
We are using a script from CSS Tricks (http://css-tricks.com/equal-height-blocks-in-rows/) to set the rows to equal height.
The best way to describe what is happening is to let you take a look for yourself. A good example is on this page
Set the view to settings to Show All Products and the Display to Gallery View.
On page load, the script ("columnConform") should set the height of columns with id "page-wrap" to equal the height of the tallest in each row. It doesn't always do this 100%.
If you resize the window, the script will again be triggered, and generally works better this time, although not 100% in Firefox.
The script code for setting equal heights is below (on our site in file thumbnail-row-fix.js):
// these are (ruh-roh) globals. You could wrap in an
// immediately-Invoked Function Expression (IIFE) if you wanted to...
var currentTallest = 0,
currentRowStart = 0,
rowDivs = new Array();
function setConformingHeight(el, newHeight) {
// set the height to something new, but remember the original height in case things change
el.data("originalHeight", (el.data("originalHeight") == undefined) ? (el.height()) : (el.data("originalHeight")));
el.height(newHeight);
}
function getOriginalHeight(el) {
// if the height has changed, send the originalHeight
return (el.data("originalHeight") == undefined) ? (el.height()) : (el.data("originalHeight"));
}
function columnConform() {
// find the tallest DIV in the row, and set the heights of all of the DIVs to match it.
$('#page-wrap > div').each(function() {
// "caching"
var $el = $(this);
var topPosition = $el.position().top;
if (currentRowStart != topPosition) {
// we just came to a new row. Set all the heights on the completed row
for(currentDiv = 0 ; currentDiv < rowDivs.length ; currentDiv++) setConformingHeight(rowDivs[currentDiv], currentTallest);
// set the variables for the new row
rowDivs.length = 0; // empty the array
currentRowStart = topPosition;
currentTallest = getOriginalHeight($el);
rowDivs.push($el);
} else {
// another div on the current row. Add it to the list and check if it's taller
rowDivs.push($el);
currentTallest = (currentTallest < getOriginalHeight($el)) ? (getOriginalHeight($el)) : (currentTallest);
}
// do the last row
for (currentDiv = 0 ; currentDiv < rowDivs.length ; currentDiv++) setConformingHeight(rowDivs[currentDiv], currentTallest);
});
}
$(window).resize(function() {
columnConform();
});
// Dom Ready
// You might also want to wait until window.onload if images are the things that
// are unequalizing the blocks
window.onload = function() {
columnConform();
//$(window).load(function() {
//columnConform();
// setTimeout(function(){columnConform()},6000);
};
Supposedly window.onload or $(window).load are the same and are triggered when the DOM and ALL images have loaded. If so, I can't explain why the script works differently when the page is loaded to when the window is resized in Firefox.
Maybe it's a bug in the latest version of Firefox, but I can't see anything relevant on the forums.
Any thoughts, tips and ways to try and get this working would be much appreciated.
I hope I explained this adequately, displaying the page ought to trigger the problem.
I have (for now) had to give up on finding the ideal solution to this problem, but have managed to find a solution that at least works.
The "columnConform" script works by setting the height of all columns in a row (ie: having the same offset from the top of the page) to the height of the tallest column in the row.
Unfortunately where columns are of sufficient unequal height, they do not output to the page in the correct row, and therefore this script will not fix the problem.
A minor change makes the script think that ALL columns have an offset of 0 from the top of the page, therefore they are ALL in the same row and are all given the height of the tallest.
Provided that there isn't a single column that is a LOT taller than the rest, this is not a problem. On most of our pages fortunately doing this only results in a little more white space between the rows than we wanted to achieve.
The code change is as follows:
// var topPosition = $el.position().top;
var topPosition = 0;

Whether element is behind inner nested scroll

How to detect if an element inside a scrollable block is visible to user (i.e. is in the visible area of the scrollable parent)?
Is there a universal solution, not involving iterating over all parent nodes that have scroll?
P.S. One idea I had was getElementAtPoint, however it gives me headaches when I need to determine if at least 50% of the element is visible. So ideally the solution must involve collision detection between two rectangles: the element rectangle and the window.
P.P.S. Another idea I've come up with is to use scrollIntoView on the element in question, determine the difference in its position, and then scroll it back to original position. It appears scrollIntoView always does the right thing – scrolls both window and the inner scrollable blocks!
I'm afraid this can't be done without iterating, and even less cross-browser, with some simple code.
Here's an example, how this can be done in IE. Unfortenately other browsers seem to return different values from body/html.getBoundingClientRect(). Also margins are treated differently, (IE ignores, others take them account).
getVisibilityPercent = function () {
var target = document.getElementById('target'),
height = target.offsetHeight,
parent = target.parentElement,
targetRect = target.getBoundingClientRect(),
tLim, bLim,
percent = 1;
while (parent) {
parentRect = parent.getBoundingClientRect();
tLim = Math.max(targetRect.top, parentRect.top);
bLim = Math.min(targetRect.bottom, parentRect.bottom);
percent *= (bLim - tLim) / height;
percent = (percent < 0) ? 0 : percent;
parent = parent.parentElement;
}
return +((percent * 100).toFixed(2));
};

jquery: calculating 'margin-left' or 'left' relative to $(window).scrollLeft() is really jagged in Firefox — using .animate() or .css()

I have a horizontally scrolling website, and I have a block that I want to stay in frame at all times as the user scrolls right. It looks perfectly smooth in webkit browsers, but is crazy jagged in Firefox and I don't really care about IEs.
function fixMyId(){
$('#myId').css({'margin-left': 150 + $(window).scrollLeft()});
}
function fixMyIdAlt(){
$('#myId').stop().animate({'margin-left': 150 + $(window).scrollLeft()}, 300);
}
And then I have it triggered on window scroll.
What would be a best way to average out the scrolling, so that maybe every so many seconds or pixels of scrolling it fires the function, or upon stopping the scrolling it animates the block into place? I tried playing with delay() but that doesn't do anything. And this one just looks stupid (plus I have no idea what the overhead of this kind of crunching is):
function fixMyIdStupid(){
window.scrollCounter++;
if(window.scrollCounter % 20 == 0) $('#myId').stop().animate({'margin-left': 150 + $(window).scrollLeft()}, 300);
}
So what do I do? setTimeout and setInterval may be required, but those always make my head hurt.
EDIT: Here's a jsfiddle of it in action: http://jsfiddle.net/xsxSq/
The #f0f square is the #myId.
I tried to do such things as well, problem is that the scroll event isn't fired as much as you want. A nice workaround was subscribing the calculation function to the mousemove event, so it triggers A LOT. But on the other hand, I came up with another solution.
Why not turn things around and ask yourself:
Lets make it a position:fixed object and calculate what happens on resize. Because you actually are trying to create a position-x:fixed; and a position-y:absolute;
I actually did the following for the opposite kind of thing. A block that has to be exactly in the middle of the x-document, but in the y it was fixed.
$(document).ready(function ()
{
replaceFixed();
$(window).resize(replaceFixed);
$('#content').ajaxSuccess(replaceFixed);
$(window).scroll(replaceFixed);
function replaceFixed()
{
var jEl = $('#centeredFixedContainer');
var winW = $(window).width();
var docW = $(document).width();
var scrL = $(window).scrollLeft();
var divW = jEl.width();
var result = 0;
// window bigger than the element
if(winW > divW)
{
result = -scrL + ((docW-winW)/2);
}
else
{
result = $('#mainContainer').offset().left - scrL;
}
jEl.css('left',result);
}
});
Copying this code will not give you the solution, but will indicate another way to look at your problem.

Categories