Can't measure width and height in an img - javascript

I have a component that renders Posts into list items. One of the list items is
<li className="content media">
{content.isMedia === "image" && (
<img src={content.img} alt={content.title} ref={measuredRef}></img>
)}
</li>
I want to apply a specific class to the images that have a specific ratio (height / width > 3.5) of .streched (images that are way too tall for their width). The problem is I can't seem to get the measuredRef correctly, currently I'm just logging the values returned by the Ref with useCallback
const measuredRef = useCallback((node) => {
if (node !== null) {
console.log(node.clientHeight);
}
}, []);
but the clientHeights and clientWidths are always 0. This is so strange, because if I console.log(node) and inspect the object the clientHeight and clientWidth is the actual image sizes and correct.
It's like the ref is not returning the updated values, when I thought (according to the Hooks FAQ documentation) that the callback would run once the node is created. It seems to be called before the element is actually assigned a width and a size, but logging the node actually gives the correct height and width.
I tried getBoundingClientRect() but the object also has 0 values for width and height, but actually populates X and Y properties, but these don't accomplish what I need.
EDIT: For clarification: the image is not stored locally, it's fetched by a url. This probably is relevant.
EDIT2: I found that using the img onLoad listener gives me the correct information and is viable. Please let me know if y'all can think of a better solution, for now I'm sticking with
<img
src={content.img}
alt={content.title}
onLoad={(e) => console.log(e.target.clientHeight)}></img>

Related

Inconsistent useRef().clientHeight

I have a parent div card which can display one of three child divs. I want to adjust the height of the parent div to fit the exact contents of the currently-visible child div.
To do this, I create a updateDonationCardSize function which reads the refs of all three views, and depending on which one should be currently visible, sets the parent div height to match the appropriate child div height.
function updateDonationCardSize() { // Sets the size of the card to fit contents
const height = stepOneContainerRef.current.clientHeight
setDonationCardSize(prevState => ({
...prevState,
['width']: paymentFooterRef.current.clientWidth,
['height']: height
}))
}
I call this function whenever any of the refs pointing to one of the subviews updates.
useEffect(() => {
updateDonationCardSize()
setTimeout(() => updateDonationCardSize(), 700)
}, [stepOneContainerRef, stepTwoContainerRef, stepThreeContainerRef])
Notice the silly approach above where the function is called twice, once immediately and once after a brief delay. For some reason, the first time it runs, it returns the wrong height of 830 whereas the second time it runs, it returns the correct height of 700.
What's even stranger is that if I print out height variable as well as the stepOneContainerRef object into the console next to each other, the height variable is returned as 830 while stepOneContainerRef object actually displays its current.clientWidth property as 700...
I am completely clueless and have spent hours over this already.
Edit - Providing additional information
The parent div contains the following structure:
<div id='parent div' className='overflow-hidden'>
<div id='parent of dynamic content'>
<div id='containerOne' className='relative' style={stepOneContainerStyle} />
<div id='containerTwo' className='relative' style={stepTwoContainerStyle} />
<div id='containerThree' className='relative' style={stepThreeContainerStyle} />
</div>
<div id='footer' />
</div>
and the stepXContainerStyle is a dictionary which sets position: top and x to 0 if it the particular div should be currently visible, otherwise it moves it outside of the overflow region to hide it.
The different heights are result of unfinished DOM mutations during the rendering of the components. To fix that change useEffect hook to useLayoutEffect which fires synchronously after all DOM mutations. It will allow you to call updateDonationCardSize only once.
In addition, you should wrap updateDonationCardSize with useCallback for correct hook dependency specification.
const updateDonationCardSize = useCallback(() => { // Sets the size of the card to fit contents
setDonationCardSize(prevState => ({
...prevState,
['width']: paymentFooterRef.current.clientWidth,
['height']: stepOneContainerRef.current.clientHeight
}))
}, [paymentFooterRef, stepOneContainerRef]);
useLayoutEffect(() => {
updateDonationCardSize()
}, [updateDonationCardSize])
I'd also recommand to try solve this problem with CSS rules - it looks like a CSS problem more than component rendering problem.

Slides App Scripts - How to duplicate an image by keeping the scale

I'd like to duplicate an image on a first slide to another slide using Google Slide App Scripts.
For this, I get the data inside this image as a blob before inserting the blob in the next slide. The issue is that even when I set top, left, width and height values, I don't understand how I can keep the same scale (in my case I've cropped the image).
This is my code:
function myFunction() {
var slides = SlidesApp.getActivePresentation().getSlides();
var blop, width, height;
slides[0].getImages().forEach(function(image){
if(image.getTitle() == "image") {
blop = image.getBlob()
left = image.getLeft()
top = image.getTop()
width = image.getWidth()
height = image.getHeight()
}
});
slides[1].insertImage(blop, left, top, width, height)
}
You can see how it looks here:
https://docs.google.com/presentation/d/1LlyHNbjcANWspPvJZedW9vgx9c9JHRD-kWOvXkwrDNo/edit?usp=sharing (viewer mode)
When I try to apply transformation the result is really different...
Modification points:
Unfortunately, in the current stage, it seems that at Slides Service and Slides API, the crop of image cannot be used. This has already been reported. Ref So, I thought that in your situation, it might be required to duplicate the image including the crop information.
In your script, insertImage(blobSource, left, top, width, height) is used. In this case, blob is the original image. By this, the crop information is not included. I thought that this might be the reason of your issue.
In this case, I would like to propose to use insertImage(image). The document of this method says as follows.
Inserts a copy of the provided Image on the page.
When above points are reflected to your script, it becomes as follows.
Modified script:
function myFunction() {
var slides = SlidesApp.getActivePresentation().getSlides();
slides[0].getImages().forEach(function(image){
if(image.getTitle() == "image") {
slides[1].refreshSlide();
slides[1].insertImage(image);
slides[1].refreshSlide();
}
});
}
Result:
When above modified script is used for your sample Google Slides, the following result is obtained.
When I tested this modified script, I noticed an important point. When this script is used, it seems that refreshSlide might be required to be used. Because I confirmed that when refreshSlide is not used, the image without including the crop and coordinate information is copied.
And also, In my environment, at most cases, the script worked fine. But, I noticed that when I tested several times, even when refreshSlide is used, there is the case that the image without including the crop and coordinate information is copied. So I thought that insertImage(image) might have a bug.
If in your environment, you have the same situation, how about reporting this to Google issue tracker? Ref
References:
insertImage(blobSource, left, top, width, height)
insertImage(image)
refreshSlide()

Element's style's top and left sometimes doesn't update in ReactJS app

I have this element that looks something like this:
render() {
// figure these values out based on props and state
let left = ...
let top = ...
// the values of these are floating point or integer
let src = '/image.png'
let style={top: top, left: left}
console.log("SETTING STYLE", style)
return <img src={src} style={style}/>
}
So far so good. A stylesheet sets these images to position:absolute and that works. I can control the position of the elements with props and state and business logic.
However, the element can be dragged and moved around (using Draggabilly). I have an event listener that listens to when the element stopped being dragged around. That gives me a position (x, y). I then pass that into a function which, based on some business logic, "corrects" the numbers and updates the props and state so that the element is re-rendered.
However, sometimes, the change doesn't take effect. Why?!
As you can see I console.log what the style should be and I can clearly see that it says something like Object {top: 123.5, left: 234.5} but when I inspect the element in my devtools and look at the Rules set, the numbers are different!
E.g.
Basically, those numbers sometimes don't add up with what I set when I rendered the element in the render() method.
The answer the Draggabilly plugin sets the top and left too after it has fired the event which my React app is relying on. I.e. It does something like this:
Draggabilly.prototype._stopDragging = function(event) {
var x = event.something.x; y = event.something.y;
this.triggerEvent('dragEnd', ...)
this.element.style.left = x
this.element.style.top = y
}
So two distinct systems are trying to set the style attribute.

Get height at which scroll bars will appear in javascript

Here's a couple of ways to ask this question:
How can I get the height (in pixels) at which the page will start having scroll bars? In other words, how do i get the window height at which a scroll bar will appear?
How can I get the maximum height of all elements on the page that don't have relative
heights (e.g. height: 100%)?
This question is related, but the answer doesn't do what I want in the case of relative heights: Finding the full height of the content of a page/document that can have absolutely positioned elements
I made a js fiddle of what I'm talking about: http://tinyurl.com/kgf8dae . Unfortunately, jsfiddle seems to break the relative height put on div e - run it as an html page in a normal browser to see the real behavior.
I might be misunderstand the question. In general, if the window height is less than the document height you will get a vertical scrollbar.
So in jQuery the check might look like this:
if( $(document).height() > $(window).height() ){ /* There will be a scrollbar */ }
You can perform this check within DOM changing and window resizing events to ascertain if a scrollbar has appeared. To preemptively determine if an event would cause a scrollbar to appear can be tricky and would likely require some understanding of the page and potential events to handle efficiently.
This is tagged through jQuery so I'm going to use jQuery; even though it's not mentioned in the question body.
a) It sounds like you want to get the height of the viewport (window); which can be retrieved like this:
var height = $(window).height();
If the height of the document (page) exceeds the height of the window, and there are no CSS properties blocking the display of scrollbars, then scrollbars will indeed by visible.
if( $(document).height() > $(window).height() )
b) This is going to be a bit trickier, in the sense the only way off the top of my head is to query every DOM element.. this is not a elegant solution; and in fact I'd ask you to reconsider your approach if you really you must do this. That said.. for curiosity...
If you're looking for the max height, in the sense of the largest element - then this would work:
// Get height of largest element.
var max_height = 0;
$('*').each( function(){
// skip <html> and <body>
if( ( $(this).get(0) == $('body').get(0) ) || ( $(this).get(0) == $('html').get(0) ) )
return;
var current_height = $(this).height();
if( current_height > max_height )
max_height = current_height;
});
For example, running that on this page...
> console.log( max_height );
570
However, I'm not sure if you want the maximum height of all combined elements.. In which case we obviously need to add all the elements up, but there's the obvious problem: elements are nested!
If this is what you want, then by using .children() we can just iterate through the lengths of the elements that are immediate children of your containing element/body.
// Get height of all combined elements
var combined_height = 0;
$('body').each( function(){ // replace with containing element?
combined_height = combined_height + jQuery(this).height();
});
For example, running that on this page:
> console.log(combined_height);
2176
Using the HTML/CSS from the example your provided via (jsfiddle.net/RMe3n/1). The answer is and always will be 242.
However, I assume you're looking for a more dynamic approach. Running the following after DOM ready will also produce 242:
var answer = 0;
$('#absolutes > div').each(function(){
var h = $(this).outerHeight(true);
if(answer < h) answer = h;
})
alert(answer);
While the above will solve for the particular HTML/CSS you provided it makes a lot of assumptions about the page's HTML structure and CSS.
Is it possible that the problem you are attempting to address with JS could be resolved in a "cleaner" way by adjusting the HTML/CSS of your page?
If you are looking for a fool proof JS method to account for ALL the multitude of unique layouts/styles that exist now and may exist as more CSS3 display types are adopted in the future I believe you're out of luck. There is no recommendable, consistent, efficient way to do so.
Note: If this is more than just a theoretical discussion, consider being more specific about the exact scenario you are faced with as there is likely a vastly different approach that may resolve the issue.

jQuery image resizing - over a variable and directly gives different results

I've got couple of lines of JavaScript using jQuery to resize images to thumbnails.
var thumb = $(this);
thumb.load(function() {
var ratio = thumb.height() / config.maxHeight;
var newWidth = Math.ceil(thumb.width() / ratio);
thumb.height(config.maxHeight);
// this line matters
thumb.width(newWidth);
});
Fotunately this works fine. But if I replace the last line with:
thumb.width(Math.ceil(thumb.width() / ratio));
It changes width of images that hasn't got explicitly defined dimensions badly (too narrow). To me, it seems like totally equivalent ways - via a variable or directly - but obviously they're not.
I tried casting the ceil() result to a Number or Integer and it behaved opposite way - images with undefined dimension were OK but the rest was too wide (width of original image).
Although I the first solution works I guess there's something fundamental I'm missing. So I want to avoid it in the future.
Thank you!
I would guess that the <img> element you are manipulating does not have declared height or width attributes. If that is the case, then the issue is how browsers intelligently resize images given only one constraint.
If you have an image that is 1000px wide, and 1000px tall, and you write an IMG tag like this:
<img src="big_image.gif" width="10" />
Modern browsers will render the huge image resized down to 10 by 10px.
So, on the line where you alter the height:
thumb.height(config.maxHeight);
the browser goes ahead an also alters the width. If you subsequently read the width (i.e. thumb.width(Math.ceil(thumb.width() / ratio))), you are going to be reading the new width, not the width it had before being given a new height.
var someImg = new Image();
someImg.src = <theURLofDesiredImage>
alert(someImg.width + " : " + someImg.height);
This is not Jquery but its vanilla JS and its a true way to determine "an unloaded" (not cached!) image. Add a query string to the URL url + "?asdasdasdadads" will allow you to circumvent the browser caching the image. This will result in a longer "image load time" but you will ALWAYS and more importantly, PREDICTABLY, resolve the dynamically loaded image.

Categories

Resources