jQuery: Why does .width() sometimes return 0 after inserting elements with .html()?
I am getting some html and inserting it with .html()
, after which I am trying to get the width of one of newly inserted elements with .width()
(which has a CSS rule). However sometimes - the width is not picked up, and is returned as 0.
Is it because JS runs "ahead" of newly created elements and their css? I have tried chaining with .html('...').find('...').width()
but it still sometimes does not work. What's the reason and is there a work around?
Edit: By popular demand, EXAMPLE:
/* ajax.response is:
<div class="boxes">
<div class="box width-A">test 1</div>
<div class="box width-B">test 2</div>
<div class="box width-C">test 3</div>
</div> */
var boxOutput = $('.boxOutput'); /* .boxOutput is already on the page */
boxOutput.html(ajax.response);
// make width of ".boxes" equal to the sum of all ".box"
var boxWidth = 0;
$('.box', boxOutput).each(function(i) {
boxWidth += $(this).width(); /* this is where sometimes width returns 0 */
});
$('.boxes', boxOutput).css('width', boxWidth);
My original code is too long/ugly to copy/开发者_如何学JAVApaste, but this is a simplified exact thing I am doing (Sorry if there's a silly mistake somewhere, it's too late where I am :P). After getting html back from ajax, putting it into a div, I want to get width of each box (because it might be different), and then use that width. Again, 90% of the time, the width is returned correctly. It's those 10% when sometimes width is 0 :(
Depends on what browser. Occasionally I find something will break with the synchronous rendering expectations of the javascript runtime and I'll need to get the result on the next tick.
I'd try:
$(elem).html(data);
setTimeout(function(){
// Get the width here
},0);
jQuery will return a zero width if the element is not visible. For example, I was having the same issue with an item being inserted onto a hidden form tab. Temporarily assigning it to the body, where it was visible, I was able to determine the width:
$("body").append(el); // First assign it to the body so that it's visible
var width = el.width(); // The width will now be correct
wrapper.append(el); // Now place the element where you want it.
One caveat is that if the element inherits different styling when assigned to the body then the width may not be correct.
We found out that document.ready was not good enough so 300ms worked well:
setTimeout(function() {
resizeHeightOfMyDiv();
$(window).on("resize", function() {
resizeHeightOfMyDiv();
});
}, 300);
Not sure 100% but I think that your problem is that the code called outside the function where you attach the elements doesn't recognize the newly created elements. A quick example:
<div id="container">
</div>
<script>
$(document).ready(function() {
$('#container).html('<p style="width:200px">Lorem ipsum</p>');
alert($('#container p').width()); // alerts 0
$('#container p').html('Lorem ipsum dolor sit amet'); // if content doesn't change, it's clearly a problem of jquery not being able to bind the functions to your new elements
});
</script>
If you can tell us what you're doing exactly we'll surely find a solution.
LATER EDIT
Now that you posted code, I can actually help. You need to run this code:
$('.box', boxOutput).each(function(i) {
boxWidth += $(this).width(); /* this is where sometimes width returns 0 */
});
in the function in your ajax response function. Example:
$.get('script.php', function(data) {
$('#elem').html(data);
// your code for width()
});
Hope this helps.
// ...
$('.box', boxOutput).each(function(i) {
//boxWidth += $(this).width(); /* this is where sometimes width returns 0 */
boxWidth += this..getBoundingClientRect().width;
});
//...
If the element is an image, then the
width()
andheight()
methods should be accessed on the.load()
jQuery handler;If the element is a DOM subtree, then they should be accessed on the
.ready()
handler.
The reason is that you can't rely on what jQuery reports to be the size of the element until the element has actually been loaded.
I've also experienced the issue and solved it using a ResizeObserver. According to MDN:
Implementations should, if they follow the specification, invoke resize events before paint and after layout.
So basically the observer always fires after layout, thus we should be able to get the correct dimensions of the observed element.
As a bonus the observer already returns the dimensions of the element. Therefore we don't even need to call something like offsetWidth
(even though it should work too)
const myElement = $("<div>", {"text": "text string"});
const resizeObserver = new ResizeObserver(entries => {
const lastEntry = entries.pop();
// alternatively use contentBoxSize here
const width = lastEntry.borderBoxSize.inlineSize;
const height = lastEntry.borderBoxSize.blockSize;
resizeObserver.disconnect();
console.log("width:", width, "height:", height);
});
resizeObserver.observe(myElement[0]);
$("body").append(myElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
This can also we wrapped in a handy async function like this:
function appendAwaitLayout(parent, element) {
return new Promise((resolve, reject) => {
const resizeObserver = new ResizeObserver((entries) => {
resizeObserver.disconnect();
resolve(entries);
});
resizeObserver.observe(element);
parent.append(element);
});
}
// call it like this
appendAwaitLayout(document.body, document.createElement("div")).then((entries) => {
console.log(entries)
// do stuff here ...
});
精彩评论