Why is clientHeight of images 0 after creating array from HTMLCollection? - javascript

So im performing a GET request to unsplash to load images on a page. I want to get the clientHeight of the loaded images so I can manipulate CSS grid span properties on each one. I'm able to get the images and their clientHeights, but when I use Array.from to make an array from the HTMLCollection, the items in the new array have a clientHeight of 0 and im not sure why. I appreciate any help!
Here's my full JS code:
axios
.get('https://api.unsplash.com/photos/random', {
params: { count: 5 },
headers: {
Authorization: `Client-ID ${ID}`
}
})
.then(response => {
let html = '';
response.data.forEach(image => {
html += `
<img src=${image.urls.regular} alt=${
image.alt_description
} />
`;
});
document.getElementById('posts').innerHTML = html;
updateHTML();
});
const updateHTML = () => {
let postsHTML = document.getElementsByTagName('img');
console.log(postsHTML);
let postsArray = Array.from(postsHTML);
console.log(postsArray);
let html = '';
postsArray.map(post => {
console.log(post.clientHeight);
const span = setSpans(post.clientHeight);
html += `
<img src=${post.src} alt=${
post.alt
} style="grid-row-end: span ${span}"/>
`;
});
console.log(html);
document.getElementById('posts').innerHTML = html;
};
const setSpans = height => {
return Math.ceil(height / 10 + 1);
};

Your images are still in loading state, you can't get the right clientHeight. You need to handle the onload event of images and then query the clientHeight.

Related

Clean way to show data from an API grouped?

I am using an API which gives me 70-ish projects. From these projects I need to show the title, image and tagline on a website. I managed to get the data on the website, but right now it shows the data as follows:
Titles
Images
Taglines
Which means that right now I'm seeing 70 titles, then 70 images, then 70 taglines. I, of course, want this to be shown per project, so one title, one image, one tagline. This then should repeat for every project.
My code:
function fetchProjects(url) {
fetch(url)
.then(response => {
return response.json();
})
.then(data => {
appendData(data.data);
})
.catch(error => {
console.log(error);
});
}
function appendData(data) {
for (let i = 0; i < data.length; i++) {
let h3 = document.createElement('h3');
h3.innerHTML = data[i].project.title;
document.getElementById('project__header').appendChild(h3);
let img = new Image();
img.src = data[i].project.header_image;
document.getElementById('project__image').appendChild(img);
let p = document.createElement('p');
p.innerHTML = data[i].project.tagline;
document.getElementById('project__tagline').appendChild(p);
}
}
fetchProjects('api_url');
I tried reformatting my code but that didn't work. I also tried Googling this but I didn't get any results that could help me, so I probably didn't use the right keywords. My apologies.
Can anyone point me to a clean way I can achieve this in plain Javascript and HTML? Thanks!
EDIT: Of course I don't want to have to make 70 unique ID's and solving it like that, I know it's possible but it's definitely not the clean solution I'd presume
Inside your for loop, you're appending the items to the same element, so if you have:
<div id="project__header"></div>
<div id="project__image"></div>
<div id="project__tagline"></div>
after two cycles you'll have
<div id="project__header">
<h3>...</h3>
<h3>...</h3>
</div>
<div id="project__image">
<img ... />
<img ... />
</div>
<div id="project__tagline">
<p>...</p>
<p>...</p>
</div>
You should append them to the same element, or create a div and then append the entire div, something like:
const div = document.createElement("div");
const h3 = document.createElement('h3');
h3.innerHTML = data[i].project.title;
div.appendChild(h3);
const img = new Image();
img.src = data[i].project.header_image;
div.appendChild(img);
const p = document.createElement('p');
p.innerHTML = data[i].project.tagline;
div.appendChild(p);
document.getElementById("...").appendChild(div);
you append all the titles to the 'project__header' div, images to the 'project__image' div, and tag-lines to the 'project__tagline' div. If I do this, it could be
for (let i = 0; i < data.length; i++) {
// create a div container for each project
const dataDiv = document.createElement('div');
const projectTitle = document.createElement('h3');
projectTitle.innerHTML = data[i].project.title;
dataDiv.appendChild(projectTitle);
const img = new Image();
img.src = data[i].project.header_image;
dataDiv.appendChild(img);
const projectTag = document.createElement('p');
projectTag.innerHTML = data[i].project.tagline;
dataDiv.appendChild(projectTag);
// append the dataDiv to the parent container (use your own DOM element) of the projects
parentDiv.append(dataDiv);
}

html element caputre using html2canvas error

Hi I'm making a code using mediapipe javascript and I want to capture the output canvas
which has the landmarks and line, so I'm trying to use html2canvas but I'm having some errors could you guys help me or give me other solutions? As a result I want the capture the image and store it in sessionStorage or firestore and show it on the next html page.
This is the part where the html elements are.
the output_canvas is where the mediapipe landmarks are shown.
When I run this code I get the error of typeerror: cannot read properties of undefined (reading 'toDataURL')
<body>
<div class="container">
<div class = "ui-element" id="angle21"></div>
<div class="ui-element" id = "clock"></div>
<div class="ui-element" id = "clock2"></div>
<video class="input_video"></video>
<canvas class="output_canvas" width="1280px" height="720px"></canvas>
<div class="landmark-grid-container"></div>
</div>
</body>
And this is the script part where I use the html2canvas
const takeScreenShot = async () => {
//const screenshotTarget = document.body;
var screenshotTarget = document.querySelector('.output_canvas');
// document.querySelectorAll('.ui-element').forEach(element => {
// element.style.display = 'none'
// })
//var screenshotTarget=document.querySelector('.output_canvas');
const canvas = html2canvas(screenshotTarget, {
allowTaint:true,
foreignObjectRendering: true
});
console.log(canvas)
const base64image = canvas[1].toDataURL();
window.location.href = base64image;
// document.querySelectorAll('.ui-element').forEach(element => {
// element.style.display = 'block'
// })
}
setTimeout(async () => {
await takeScreenShot()
}, 1000 * 8)

Render an element inside literal templates with a EventListener

I'm trying to insert an element with an EventListener at a certain point of an element. I can achieve this via appendChild but I want to insert it at a certain point. Like this:
const divT = () => {
const div = document.createElement("div");
const div_inside = document.createElement("div");
div_inside.addEventListener("click", () => {console.log("div_inside")});
div_inside.innerHTML = "INSIDE";
div.innerHTML = ` TEST ${div_inside.outerHTML} TEST `;
return div;
};
document.body.appendChild(divT());
The main problem is the outerHTML does not contain the information of the listener. There is a method to render the HTML with the listener still active?
Thanks in advance
Find a workaround using insertAdjacentElement and attaching an empty div:
const divT = () => {
const div = document.createElement("div");
const div_inside = document.createElement("div");
div_inside.innerHTML = "Hello World";
div_inside.addEventListener("click", () => {console.log("div_inside")});
div.innerHTML = ` TEST <div id="inside"></div> TEST `;
const div_s = div.getElementsByTagName('div')
div_s[0].insertAdjacentElement('beforebegin', div_inside)
return div;
};

Check if html tag contains text node

I have a popup modal in Shopify, I'm using text node instead of innerHtml for security concerns. However, everytime I open the popup modal, the text node keeps getting appended to my h1 tag. Is there any way to check if the node already has been appended? (I don't want to use a boolean value to check if text node has been appended)
html:
<h1 id="ProductHeading" class="product__title product__title--template"></h1>
<h2 id="ProductHeadingModal" class="product__title product__title--template product__title--modal"></h2>
javascript:
var title = document.createTextNode(product.title);
// Product heading is an element with h1 tag
var productHeading = document.getElementById("ProductHeading");
if(// how to check if element has no node?) {
productHeading.appendChild(title);
}
the entire javascript block:
window.onload = () => {
if (window.__shgProductInits.length) {
window.__shgProductInits.forEach((ele) => {
let proId = document.getElementById(ele.uuid);
proId.setAttribute('url', ele.productHandle);
proId.style.cursor='pointer';
proId.addEventListener('click', (e) => {
let productHandle = e.target.parentElement.parentElement.parentElement.getAttribute('url');
fetch('/products/'+productHandle+'.js')
.then((res) =>{return res.json()})
.then((product) => {
console.log(product)
var product = product;
document.getElementsByClassName("product-modal")[0].style.display = "block";
var title = document.createTextNode(product.title);
var productHeading = document.getElementById("ProductHeading");
var productHeadingModal = document.getElementById("ProductHeadingModal");
if(!(productHeading.hasChildNodes())) {
productHeading.appendChild(title);
productHeadingModal.appendChild(title);
var price = document.createTextNode("$" + parseInt(product.price).toFixed(2));
document.getElementById("product-price").appendChild(price);
}
document.getElementById("product-image").src = product.images[0];
});
});
});
}
ProductHeading itself is not a node (I think). And checking innerHtml for length doesn't work as it is always 0
Update:
I've added the conditional check, it still returns false everytime I open the modal.
My code:
My browser console:
My website displays:
Inspect element in browser:
A couple of ways:
if (element.firstChild) {
// It has at least one
}
or the hasChildNodes() function:
if (element.hasChildNodes()) {
// It has at least one
}
or the length property of childNodes:
if (element.childNodes.length > 0) { // Or just `if (element.childNodes.length)`
// It has at least one
}
So you can just write this
var title = document.createTextNode(product.title);
// Product heading is an element with h1 tag
var productHeading = document.getElementById("ProductHeading");
if(!(productHeading.hasChildNodes())) {
productHeading.appendChild(title);
}
Referring this answer
if (productHeading.hasChildNodes()) {
}

Prerender images from URL in reactjs

Is it possible to prerender a list of images based on URL to prevent first loading on display with react or JS?
Thanks in advance.
EDIT: I have a carousel of images and all images are display one by one. When an image are are display for the first time this image take some time to be render because she need to be get from the url. And if I display this same image a second time I will not wait. Then I want to know if there is a solution to preload all images directly after get the url in a ComponentDidMount even the loading become longer.
An other example: I have a list of image and I want display them all at the same time. How can I preload them all before end the loading and start the render.
Do you simply want to preload images in advance?
The article of the Preloading Content might help.
componentDidMount() {
const imageUrlList = ['a.jpg', 'b.jpg', 'c.jpg'];
this.preloadImages(imageUrlList);
}
preloadImages(urlList) {
let fragment = document.createDocumentFragment();
for (let i = 0; i < urlList.length; i++) {
const imgUrl = urlList[i];
const linkEl = document.createElement('link');
linkEl.setAttribute('rel', 'preload');
linkEl.setAttribute('href', imgUrl);
linkEl.setAttribute('as', 'image');
fragment.appendChild(linkEl);
}
document.head.appendChild(fragment);
}
Not so clear from what you mean by prerender.
By preloading, do you mean that you want that the component renders only when the image is ready?
If so, you could do something like this:
componentDidMount() {
const img = new Image();
img.src = Newman; // by setting an src, you trigger browser download
img.onload = () => {
// when it finishes loading, update the component state
this.setState({ imageIsReady: true });
}
}
render() {
const { imageIsReady } = this.state;
if (!imageIsReady) {
return <div>Loading image...</div>; // or just return null if you want nothing to be rendered.
} else {
return <img src={Newman} /> // along with your error message here
}
}

Categories

Resources