Fetch loading in data for HTML - javascript

So I'm making a simple pokemon app where the front-end makes fetch calls to grab the Pokemon from PokeAPI and displays it, but when loading a pokemon you can see that the fetch info gets loaded at different rates.
For example the pokemon loads then the name loads then the background loads then the type loads.
Is there anyway to have it so the HTML fills all at one time?
This is what my Javascript fetch function looks like, additionally here is the app so you can see how the information loads slowly / not all at once : https://bui-pokemon.herokuapp.com/
pokeButton.addEventListener('click', (e)=> {
e.preventDefault();
const pokeNameSearch = pokeSearch.value.toLowerCase();
fetch(`https://pokeapi.co/api/v2/pokemon/${pokeNameSearch}`)
.then((response) => response.json())
.then(data => {
if(pokeCard.classList.value === 'poke_card'){
pokeCard.classList.add('border');
};
//If a pokemon was normal / flying type then flying should be shown in the background isntead of normal
//because flying type is more of a defining characteristic of that pokemon rather than normal
if(data.types.length > 1 && data.types[0].type.name === "normal" && data.types[1].type.name === "flying"){
pokeCard.className = `poke_card border ${data.types[1].type.name}_background`
} else {
pokeCard.className = `poke_card border ${data.types[0].type.name}_background`;
}
pokeImg.src = data.sprites.front_default;
pokeName.innerHTML = data.name;
// Fill in Pokemon Type
pokeTypeIcon1.src = "";
pokeTypeIcon2.src = "";
pokeTypeIcon1.className = '';
pokeTypeIcon2.className = '';
pokeTypeIcon2.style.display = "none";
pokeType.innerHTML = `${data.types[0].type.name}`;
pokeNumDisplay.innerHTML = `#${fillInZero(data.id.toString())}`
pokeTypeIcon1.src = `img/icons/${data.types[0].type.name}.svg`
pokeTypeIcon1.className = `type_icon_1 ${data.types[0].type.name}`
if(data.types.length > 1){
pokeType.innerHTML += `/${data.types[1].type.name}`
pokeTypeIcon2.src = `img/icons/${data.types[1].type.name}.svg`
pokeTypeIcon2.style.display = "inline-block";
pokeTypeIcon2.className = `type_icon_2 ${data.types[1].type.name}`
} else {
pokeType.innerHTML = `${data.types[0].type.name}`
}
})

Seems the font and images loading causes the issue.
What you could do, is listen for the load event on each image and transition the opacity. Then, use document.fonts.ready and transition the color:
const container = document.querySelector(".container");
const background = document.getElementById("background")
const imgOne = document.getElementById("img-one")
const imgTwo = document.getElementById("img-two")
const fadeIn = (el) => {
el.addEventListener("load", () => el.style.opacity = "1")
}
document.fonts.ready.then(() => container.style.color = "#fff")
fadeIn(background)
fadeIn(imgOne)
fadeIn(imgTwo)
background.src = "https://ak.picdn.net/shutterstock/videos/9589454/thumb/2.jpg"
imgOne.src = "https://toppng.com/public/uploads/thumbnail/ikachu-8-bits-8-bit-pokemon-grid-11563233054e4sqfqyl2l.png"
imgTwo.src = "https://icons.iconarchive.com/icons/paomedia/small-n-flat/1024/lightning-icon.png"
#import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
background: #43464b;
font-family: "Press Start 2P", Arial, Helvetica, sans-serif;
}
.container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
margin: 0 auto;
width: 200px;
height: 300px;
color: transparent;
overflow: hidden;
transition: color 0.25s;
}
.container>*:not(#background) {
z-index: 1;
}
img {
opacity: 0;
transition: opacity 0.25s;
}
#background {
position: absolute;
top: 0;
left: 0;
}
#img-one,
#img-two {
height: 80px;
}
<div class="container">
<img id="background" />
<img id="img-one" />
<p>#25 Pikachu</p>
<img id="img-two" />
<p>Electric</p>
</div>
However, it could still be uneven this way if one thing takes longer to load than the other.
Another solution is to push values to an array once loaded, and check the length of the array before setting the opacity of the container:
const container = document.querySelector(".container");
const background = document.getElementById("background")
const imgOne = document.getElementById("img-one")
const imgTwo = document.getElementById("img-two")
const loaded = []
const setLoadState = (el) => {
loaded.push(el)
if (loaded.length === 4)
container.style.opacity = "1"
}
const imgLoad = (img) => {
img.addEventListener("load", () => setLoadState(img.id))
}
document.fonts.ready.then(() => setLoadState('font'))
imgLoad(background)
imgLoad(imgOne)
imgLoad(imgTwo)
background.src = "https://ak.picdn.net/shutterstock/videos/9589454/thumb/2.jpg"
imgOne.src = "https://toppng.com/public/uploads/thumbnail/ikachu-8-bits-8-bit-pokemon-grid-11563233054e4sqfqyl2l.png"
imgTwo.src = "https://icons.iconarchive.com/icons/paomedia/small-n-flat/1024/lightning-icon.png"
#import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
background: #43464b;
font-family: "Press Start 2P", Arial, Helvetica, sans-serif;
}
.container {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
margin: 0 auto;
width: 200px;
height: 300px;
color: #fff;
overflow: hidden;
opacity: 0;
transition: opacity 0.25s;
}
.container>*:not(#background) {
z-index: 1;
}
#background {
position: absolute;
top: 0;
left: 0;
}
#img-one,
#img-two {
height: 80px;
}
<div class="container">
<img id="background" />
<img id="img-one" />
<p>#25 Pikachu</p>
<img id="img-two" />
<p>Electric</p>
</div>

Related

How to add a smooth animation to the progress bar

When I click I want to smoothly add segments to the progress bar. They are added but instantly. What could be the problem?
I tried to implement a smooth animation with setInterval, but nothing comes out. Percentages are also added instantly.
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
let progressBarStartValue = 0;
let progressBarEndValue = 100;
let speed = 50;
body.addEventListener("click", function(e) {
if (progressBarStartValue === progressBarEndValue) {
alert("you have completed all the tasks");
} else {
let progress = setInterval(() => {
if (progressBarStartValue != 100) {
progressBarStartValue += 10;
clearInterval(progress);
}
progressBarValue.textContent = `${progressBarStartValue}%`;
progressBar.style.background = `conic-gradient(
#FFF ${progressBarStartValue * 3.6}deg,
#262623 ${progressBarStartValue * 3.6}deg
)`;
}, speed);
}
});
.progressbar {
position: relative;
height: 150px;
width: 150px;
background-color: #262623;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.progressbar::before {
content: "";
position: absolute;
height: 80%;
width: 80%;
background-color: #0f0f0f;
border-radius: 50%;
}
.progressbar__value {
color: #fff;
z-index: 9;
font-size: 25px;
font-weight: 600;
}
<main class="main">
<section class="statistic">
<div class="container">
<div class="statistic__inner">
<div class="statistic__text">
<h2 class="statistic__title">You're almost there!</h2>
<p class="statistic__subtitle">keep up the good work</p>
</div>
<div class="progressbar"><span class="progressbar__value">0%</span></div>
</div>
</div>
</section>
</main>
This may not be exactly what you're looking for, but with the conic-gradient() implementation you're using, I'd recommend checking out a library call anime.js.
Here's an example with your implementation (same html and css):
// your.js
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
// Switched to object for target in anime()
let progressBarObject = {
progressBarStartValue: 0,
progressBarEndValue: 100,
progressBarAnimationValue: 0 * 3.6 // New value needed for smoothing the progress bar, since the progress value needs to be multiplied by 3.6
}
// Not necessary, but I recommend changing the event listener to pointerup for better support
// Also not necessary, I changed function to arrow function for my own preference
body.addEventListener("pointerup", e => {
e.preventDefault()
if (progressBarObject.progressBarStartValue === progressBarObject.progressBarEndValue) {
alert("you have completed all the tasks");
} else {
let newValue = 0 // Needed so we can set the value, before it's applied in anime()
if (progressBarObject.progressBarStartValue != 100) {
// Math.ceil() allows us to round to the nearest 10 to guarantee the correct output
newValue = Math.ceil((progressBarObject.progressBarStartValue + 10) / 10) * 10;
}
// Optional: Prevents accidentally going over 100 somehow
if (newValue > 100) {
newValue = 100
}
anime({
targets: progressBarObject,
progressBarStartValue: newValue,
progressBarAnimationValue: newValue * 3.6,
easing: 'easeInOutExpo',
round: 1, // Rounds to nearest 1 so you don't have 0.3339...% displayed in progressBarValue
update: () => {
progressBar.style.backgroundImage = `conic-gradient(
#FFF ${progressBarObject.progressBarAnimationValue}deg,
#262623 ${progressBarObject.progressBarAnimationValue}deg)`;
progressBarValue.textContent = `${progressBarObject.progressBarStartValue}%`;
},
duration: 500
});
}
});
Here's a CodePen using the anime.js CDN: Circular Progress Bar Smoothing
If you don't want to use a javascript library, then I'd recommend switching from the conic-gradient() to something else. I hear using an .svg circle with stroke and stroke-dasharray can work great with CSS transition.
You shouldn't setInterval your progress variable like this. instead, put it as a global variable outside the function then use it to gradually add 1 as long as the start value is less than progress, and you still can control the speed with your speed variable.
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
let progressBarStartValue = 0;
let progressBarEndValue = 100;
let speed = 50;
let progress = 0;
body.addEventListener("click", function(e) {
if (progressBarStartValue === progressBarEndValue) {
alert("you have completed all the tasks");
} else {
progress += 10;
setInterval(() => {
if (progressBarStartValue < progress) {
progressBarStartValue += 1;
clearInterval();
}
progressBarValue.textContent = `${progressBarStartValue}%`;
progressBar.style.background = `conic-gradient(
#FFF ${progressBarStartValue * 3.6}deg,
#262623 ${progressBarStartValue * 3.6}deg
)`;
}, speed);
}
});
.progressbar {
position: relative;
height: 150px;
width: 150px;
background-color: #262623;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid red;
}
.progressbar::before {
content: "";
position: absolute;
height: 80%;
width: 80%;
background-color: #0f0f0f;
border-radius: 50%;
border: 3px solid blue;
}
.progressbar__value {
color: #fff;
z-index: 9;
font-size: 25px;
font-weight: 600;
}
<main class="main">
<section class="statistic">
<div class="container">
<div class="statistic__inner">
<div class="statistic__text">
<h2 class="statistic__title">You're almost there!</h2>
<p class="statistic__subtitle">keep up the good work</p>
</div>
<div class="progressbar"><span class="progressbar__value">0%</span></div>
</div>
</div>
</section>
</main>

transform: Scale , automatically reduces the size when using with javascript

eduReveal = 400;
var eduAni = false;
var education = document.getElementById('education');
var track = document.getElementById('track');
var plane = document.getElementById('plane');
window.addEventListener('scroll', function() {
var topMost = window.innerHeight;
var trackPos = education.getBoundingClientRect().top;
if (eduAni == false && (topMost - eduReveal) > trackPos) {
track.style.width = '100%';
plane.style.transform = 'translate(800px)';
plane.style.transitionDuration = '5s';
track.style.transitionDuration = '5s';
eduAni = true;
console.log("Track");
}
});
#trackBox {
width: 800px;
height: 2px;
transform: rotate(-20deg);
}
#track {
width: 0%;
height: 100%;
background-color: white;
}
#plane {
color: white;
position: relative;
transform: scale(2);
}
<div id="trackBox">
<div id="track"><i class="fa-solid fa-plane" id="plane"></i></div>
</div>
All the parts of the puzzle are given above. When I change the size of an element using transform: scale(), and then using javascript translate that element, its size turns down to normal again.
When you set the CSS transform property, you reset any transformations that were previously on it. So when you were setting your translate in JS, you were overwriting the scale that was set in your CSS. An easy solution is to store your transformations individually as custom properties like so:
eduReveal = 400;
var eduAni = false;
var education = document.getElementById('education');
var track = document.getElementById('track');
var plane = document.getElementById('plane');
window.addEventListener('scroll', function() {
var topMost = window.innerHeight;
var trackPos = education.getBoundingClientRect().top;
if (eduAni == false && (topMost - eduReveal) > trackPos) {
track.style.width = '100%';
plane.style.transform = 'scale(var(--scale, 2)) translate(800px)';
plane.style.transitionDuration = '5s';
track.style.transitionDuration = '5s';
eduAni = true;
console.log("Track");
}
});
body {
min-height: 101vh;
background-color: skyblue;
}
#trackBox {
width: 800px;
height: 2px;
transform: rotate(-20deg);
}
#track {
width: 0%;
height: 100%;
background-color: white;
}
#plane {
--scale: 2;
color: white;
position: relative;
}
<div id="education">
<div id="trackBox">
<div id="track"><i class="fa-solid fa-plane" id="plane">Plane Icon</i></div>
</div>
</div>

How to remove the overlay image on only the selected lazyloaded video?

In the script below, I'm lazyloading two videos. My script is designed to remove the overlay image from the selected video when clicked. However, it's also removing the overlay image from the second video and placing it above it. Another click removes the duplicate image, and a third click plays the video.
How do I remove only the image for the selected video in a way that doesn't affect a second video on the page?
const getVideoId = (wistia_vid) => {
const classes = Array.from(wistia_vid.querySelector(".wistia_embed").classList);
const idClass = classes.find((cls) => cls.startsWith("wistia_async_"));
const id = idClass.replace("wistia_async_", "");
return id;
};
const removeElems = (wistia_vid) => {
const toRemove = Array.from(
wistia_vid.querySelectorAll(".wistia__overlay, .embed-youtube__play, .embed-video__play")
);
toRemove.forEach((node) => node.remove());
};
Array.from(document.querySelectorAll(".wistia")).forEach((node) => {
node.addEventListener("click", () => {
const videoId = getVideoId(node);
let wistiaSupportScripts = [
//adds jsonp file to provide security over requests
`https://fast.wistia.com/embed/medias/${videoId}.jsonp`
];
removeElems(node);
//Checks if above scripts are already loaded, and if they are... they won't be loaded again
const id = 'script-ev1';
if (!document.getElementById(id)) {
// const id = 'script-ev1';
var script = document.createElement('script');
script.id = id;
script.onload = () => {
console.log('Ev-1.js loaded and ready to go!');
};
script.src = `https://fast.wistia.com/assets/external/E-v1.js` ;
document.getElementsByTagName('head')[0].appendChild(script);
} else {
console.log(`Ev-1.js script with id: ${videoId} already loaded.`);
}
//loads supporting scripts into head
for (var i = 0; i < wistiaSupportScripts.length; i++) {
let wistiaSupportScript = document.createElement("script");
wistiaSupportScript.src = wistiaSupportScripts[i];
let complete = false;
if (
!complete &&
(!this.readyState ||
this.readyState == "loaded" ||
this.readyState == "complete")
) {
complete = true;
console.log(`JSONP script was added.`);
}
let wistiaContainers = document.querySelector(".wistia");
wistiaContainers ? document.getElementsByTagName("head")[0].appendChild(wistiaSupportScript) : console.log("No Wistia videos here.");
}
window._wq = window._wq || [];
_wq.push({
//globally scoped
id: videoId,
options: {
autoPlay: true,
volume: 0.5
},
onReady: function (video) {
playedOnce = true;
video.popover.show();
video.play();
}
});
});
});
.wistia {
position: relative;
display: block;
width: 100%;
max-width: 500px;
padding: 0;
overflow: hidden;
cursor: pointer;
}
.wistia__overlay {
width: 100%;
height: auto;
}
.wistia::before {
display: block;
content: "";
}
.wistia button.embed-youtube__play {
background: url("https://nextiva.com/assets/svg/play-button.svg") no-repeat center center, rgba(33, 33, 33, 0.8);
background-size: 40%;
background-position: 55%;
border: 0;
border-radius: 50%;
position: absolute;
transition: all 0.2s ease;
-webkit-transition: background 0.2s;
width: 10%;
aspect-ratio: 1/1;
max-height: 15%;
cursor: pointer;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
.wistia:hover button.embed-youtube__play,
.wistia button.embed-youtube__play:focus-visible,
.wistia button.embed-youtube__play:focus {
background: url("https://nextiva.com/assets/svg/play-button.svg") no-repeat center center, #005fec;
background-size: 40%;
background-position: 55%;
}
.wistia_embed,
.wistia embed,
.wistia iframe {
width: 100%;
max-height: 100%;
}
<div class="wistia">
<picture>
<source srcset="https://embedwistia-a.akamaihd.net/deliveries/48f1d62d1ceddb4284ad9cf67c916235.jpg?auto=format&w=640" media="(min-width: 1200px)">
<source srcset="https://embedwistia-a.akamaihd.net/deliveries/48f1d62d1ceddb4284ad9cf67c916235.jpg?auto=format&w=310" media="(min-width: 768px)">
<img src="https://embedwistia-a.akamaihd.net/deliveries/48f1d62d1ceddb4284ad9cf67c916235.jpg?auto=format&w=310" alt="some text" class="wistia__overlay lazy" loading="lazy">
</picture>
<div class="wistia_embed wistia_async_vhkqhqhzyq videoFoam=true"></div>
<button class="embed-youtube__play"></button>
</div>
<div class="wistia">
<picture>
<source srcset="https://embed-fastly.wistia.com/deliveries/2eab84ad71cf5acd9c7572d36667d255.jpg?auto=format&w=640" media="(min-width: 1200px)">
<source srcset="https://embed-fastly.wistia.com/deliveries/2eab84ad71cf5acd9c7572d36667d255.jpg?auto=format&w=310" media="(min-width: 768px)">
<img src="https://embed-fastly.wistia.com/deliveries/2eab84ad71cf5acd9c7572d36667d255.jpg?auto=format&w=310" alt="Some text" class="wistia__overlay lazy" loading="lazy">
</picture>
<div class="wistia_embed wistia_async_8ei13wuby7 videoFoam=true"></div>
<button class="embed-youtube__play"></button>
</div>
Put this in your CSS:
.wistia_embed {
display: none;
}
.wistia.shown .wistia_embed {
display: block;
}
Then, put this in your JS:
if (!node.classList.contains("shown")) {
node.classList.add("shown");
} else {
return;
}
Right in the beginning of the event listener function.
Explanation
The E-v1.js script shows all the videos at once, when you load this script by the first click with this piece of code:
const id = 'script-ev1';
if (!document.getElementById(id)) {
// const id = 'script-ev1';
var script = document.createElement('script');
script.id = id;
script.onload = () => {
console.log('Ev-1.js loaded and ready to go!');
};
script.src = `https://fast.wistia.com/assets/external/E-v1.js`;
document.getElementsByTagName('head')[0].appendChild(script);
} else {
console.log(`Ev-1.js script with id: ${videoId} already loaded.`);
}
Before you load this script, there are no videos as is, just the <source> elements. You never indicate with you CSS that videos should be invisible; henceforth, they are visible by default, once the E-v1.js script loads them.
Now, when you add this CSS snippet above, you indicate that .wistia_embed elements, which are basically the loaded videos, have to be invisible from the beginning.
With this single line of JS code, only one video will be revealed on click (setting .shown class, which contains display: block; attribute for the .wistia_embed).
Undefined video.popover
I don't know Wistia API that much, but the browser tells that there is no video.popover.show() function. Remove this from your code as well, otherwise the second video won't auto-play by clicking on it.
onReady: function (video) {
playedOnce = true;
video.popover.show(); // remove
video.play();
}
Full working code
const getVideoId = (wistia_vid) => {
const classes = Array.from(wistia_vid.querySelector(".wistia_embed").classList);
const idClass = classes.find((cls) => cls.startsWith("wistia_async_"));
const id = idClass.replace("wistia_async_", "");
return id;
};
const removeElems = (wistia_vid) => {
const toRemove = Array.from(
wistia_vid.querySelectorAll(".wistia__overlay, .embed-youtube__play, .embed-video__play")
);
toRemove.forEach((node) => node.remove());
};
Array.from(document.querySelectorAll(".wistia")).forEach((node) => {
node.addEventListener("click", () => {
if (!node.classList.contains("shown")) {
node.classList.add("shown");
} else {
return;
}
const videoId = getVideoId(node);
let wistiaSupportScripts = [
//adds jsonp file to provide security over requests
`https://fast.wistia.com/embed/medias/${videoId}.jsonp`
];
removeElems(node);
//Checks if above scripts are already loaded, and if they are... they won't be loaded again
const id = 'script-ev1';
if (!document.getElementById(id)) {
// const id = 'script-ev1';
var script = document.createElement('script');
script.id = id;
script.onload = () => {
console.log('Ev-1.js loaded and ready to go!');
};
script.src = `https://fast.wistia.com/assets/external/E-v1.js`;
document.getElementsByTagName('head')[0].appendChild(script);
} else {
console.log(`Ev-1.js script with id: ${videoId} already loaded.`);
}
//loads supporting scripts into head
for (var i = 0; i < wistiaSupportScripts.length; i++) {
let wistiaSupportScript = document.createElement("script");
wistiaSupportScript.src = wistiaSupportScripts[i];
let complete = false;
if (
!complete &&
(!this.readyState ||
this.readyState == "loaded" ||
this.readyState == "complete")
) {
complete = true;
console.log(`JSONP script was added.`);
}
let wistiaContainers = document.querySelector(".wistia");
wistiaContainers ? document.getElementsByTagName("head")[0].appendChild(wistiaSupportScript) : console.log("No Wistia videos here.");
}
window._wq = window._wq || [];
_wq.push({
//globally scoped
id: videoId,
options: {
autoPlay: true,
volume: 0.5
},
onReady: function (video) {
playedOnce = true;
video.play();
}
});
});
});
.wistia {
position: relative;
display: block;
width: 100%;
max-width: 500px;
padding: 0;
overflow: hidden;
cursor: pointer;
}
.wistia__overlay {
width: 100%;
height: auto;
}
.wistia::before {
display: block;
content: "";
}
.wistia button.embed-youtube__play {
background: url("https://nextiva.com/assets/svg/play-button.svg") no-repeat center center, rgba(33, 33, 33, 0.8);
background-size: 40%;
background-position: 55%;
border: 0;
border-radius: 50%;
position: absolute;
transition: all 0.2s ease;
-webkit-transition: background 0.2s;
width: 10%;
aspect-ratio: 1/1;
max-height: 15%;
cursor: pointer;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
.wistia:hover button.embed-youtube__play,
.wistia button.embed-youtube__play:focus-visible,
.wistia button.embed-youtube__play:focus {
background: url("https://nextiva.com/assets/svg/play-button.svg") no-repeat center center, #005fec;
background-size: 40%;
background-position: 55%;
}
.wistia_embed,
.wistia embed,
.wistia iframe {
width: 100%;
max-height: 100%;
}
.wistia_embed {
display: none;
}
.wistia.shown .wistia_embed {
display: block;
}
<div class="wistia">
<picture>
<source
srcset="https://embedwistia-a.akamaihd.net/deliveries/48f1d62d1ceddb4284ad9cf67c916235.jpg?auto=format&w=640"
media="(min-width: 1200px)">
<source
srcset="https://embedwistia-a.akamaihd.net/deliveries/48f1d62d1ceddb4284ad9cf67c916235.jpg?auto=format&w=310"
media="(min-width: 768px)">
<img src="https://embedwistia-a.akamaihd.net/deliveries/48f1d62d1ceddb4284ad9cf67c916235.jpg?auto=format&w=310"
alt="some text" class="wistia__overlay lazy" loading="lazy">
</picture>
<div class="wistia_embed wistia_async_vhkqhqhzyq videoFoam=true"></div>
<button class="embed-youtube__play"></button>
</div>
<div class="wistia">
<picture>
<source
srcset="https://embed-fastly.wistia.com/deliveries/2eab84ad71cf5acd9c7572d36667d255.jpg?auto=format&w=640"
media="(min-width: 1200px)">
<source
srcset="https://embed-fastly.wistia.com/deliveries/2eab84ad71cf5acd9c7572d36667d255.jpg?auto=format&w=310"
media="(min-width: 768px)">
<img src="https://embed-fastly.wistia.com/deliveries/2eab84ad71cf5acd9c7572d36667d255.jpg?auto=format&w=310"
alt="Some text" class="wistia__overlay lazy" loading="lazy">
</picture>
<div class="wistia_embed wistia_async_8ei13wuby7 videoFoam=true"></div>
<button class="embed-youtube__play"></button>
</div>

CSS class add/remove no longer working || vanilla JS

So previously I asked your help to execute a behaviour for when clicking a container div, said div would change height, clicking it again would revert it back to normal, as would clicking a sibling container div.
I also added code for when clicking said div, the img inside would change data-src.
This img div (not the container) has a sibling that is just text. It would change state through css, when the container div is clicked, a bit like the container div itself.
When adding the code for the data-src change, I lost this ability and cant find out why.
Can you help me?
This is my code:
const allImages = document.querySelectorAll('.containertira .imgclasstosize');
const allContainers = document.querySelectorAll('.containertira');
allContainers.forEach(el => {
el.addEventListener('click', function(event) {
const thisImg = el.querySelector('.imgclasstosize');
const thisTxt = el.querySelector('.centered');
const sibling = thisImg.nextElementSibling; // Get the next sibiling
const bigSrc = thisImg.dataset.srcBig;
const allOtherImages = Array.from(allImages).filter(img => {
return img !== thisImg;
});
const isBig = thisImg.classList.contains('big');
1
if (isBig) {
thisImg.classList.remove('big');
thisTxt.classList.remove('left');
// reset to the small image URL
thisImg.src = thisImg.dataset.smallSrc;
} else {
// save the small image URL first:
if (!thisImg.dataset.smallSrc) {
thisImg.dataset.smallSrc = thisImg.src;
}
// change to the big image URL:
thisImg.src = bigSrc;
thisImg.classList.add('big');
thisTxt.classList.add('left');
sibling.classList.remove('hide');
}
allOtherImages.forEach(img => {
img.classList.remove('big');
// reset to the small image URL
if (img.dataset.smallSrc) {
img.src = img.dataset.smallSrc;
}
img.nextElementSibling.classList[isBig ? 'remove' : 'add']("hide");
});
});
}
);
.imgclasstosize{
width: 100%;
object-fit: cover;
position: relative;
}
img.imgclasstosize {
height: 80px;
border: 1px solid gray;
transition : 1.5s all ease;
}
img.imgclasstosize.big {
height: 100%;
transition: 1.5s all ease;
width: 70vw;
margin-left: auto;
}
.containertira {
position: relative;
text-align: center;
color: white;
display: flex;
justify-content: space-between;
}
.imgclasstosize {
transition: all ease 0.5s;
}
.imgclasstosize.big {
/* transform: scale(1.1); */
}
.centered {
opacity: 1;
transition: all ease 0.5s;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: 'blacker_pro_displayregular';
padding-top: 10px;
/* background: linear-gradient(90deg, rgba(33,37,41,1) 0%, rgba(255,255,255,0) 50%); */
color: white;
padding-left: 10px;
padding-bottom: 6.5px;
font-size: 5vh;
display:block;
pointer-events: none;
}
#media screen and (max-width: 960px) {.centered {font-size: 4.6vh;}}
#media screen and (max-width: 500px) {.centered {font-size: 3.5vh;}}
#media screen and (max-width: 375px) {.centered {font-size: 3vh;}}
.centered.left{
top: 0%;
left: 0%;
transform: translate(0%, 0%);
color: black;
padding-left: 10px;
padding-bottom: 6.5px;
transition: all ease 0.5s;
font-size: 7vh;
}
.imgclasstosize.big+.centered.hide {
opacity: 0;
}
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="//images.impresa.pt/expresso/2021-07-23-1---Minho.png-0c586cc8" data-src-big="//images.impresa.pt/expresso/2021-07-23-960px_minho.png-3938db2f">
<div class="centered">Minho</div>
</div>
<br>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="//images.impresa.pt/expresso/2021-07-23-1---Minho.png-0c586cc8" data-src-big="//images.impresa.pt/expresso/2021-07-23-960px_minho.png-3938db2f">
<div class="centered">Minho</div>
</div>
Best regards everyone.
I can't seem to reproduce your error. Tried copying your code and pasting it on a separated HTML file and everything worked.
If you execute this snippet and look at the console, you will see that the classes and properties are changing accordingly to your code.
Maybe your code is executing before some library causing it to not execute something?
const allImages = document.querySelectorAll('.containertira .imgclasstosize');
const allContainers = document.querySelectorAll('.containertira');
allContainers.forEach(el => {
el.addEventListener('click', function(event) {
const thisImg = el.querySelector('.imgclasstosize');
const thisTxt = el.querySelector('.centered');
const sibling = thisImg.nextElementSibling; // Get the next sibiling
const bigSrc = thisImg.dataset.srcBig;
const allOtherImages = Array.from(allImages).filter(img => {
return img !== thisImg;
});
const isBig = thisImg.classList.contains('big');
1
if (isBig) {
thisImg.classList.remove('big');
thisTxt.classList.remove('left');
// reset to the small image URL
thisImg.src = thisImg.dataset.smallSrc;
} else {
// save the small image URL first:
if (!thisImg.dataset.smallSrc) {
thisImg.dataset.smallSrc = thisImg.src;
}
// change to the big image URL:
thisImg.src = bigSrc;
thisImg.classList.add('big');
thisTxt.classList.add('left');
sibling.classList.remove('hide');
}
allOtherImages.forEach(img => {
img.classList.remove('big');
// reset to the small image URL
if (img.dataset.smallSrc) {
img.src = img.dataset.smallSrc;
}
img.nextElementSibling.classList[isBig ? 'remove' : 'add']("hide");
});
});
}
);
.containertira {border:1px black solid;}
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho</div>
</div>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho2</div>
</div>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho3</div>
</div>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho4</div>
</div>

How to Output chosen DIV elements that are stored in an array

I'm creating a card game where the user chooses 2 out of 3 cards. I then store those cards into an array and want to be able to print out the cards that were stored in the array with the actual image of the cards that the user chose.
I've tried looping through the array and then using innerHTML to push the results to a specific div but I keep getting "[object HTMLDivElement]". It also prints that out 3 times instead of 2 (since we are choosing 2 cards there should only be two elements to print out, I suspect the loop is running an extra time).
The below is the loop I have tried but I also am including a codepen for further clarity.
https://codepen.io/cramos2/pen/pMVjez
var holder = document.getElementById("cardResults");
for(var i=0; i < chosenCards.length; i++){
holder.innerHTML += "<p>" + chosenCards[i] + "</p><br>";
}
let chosenCards = new Array();
class tarot {
//constructor
constructor(cards) {
this.cardsArray = cards;
}
startReading() {
this.shuffleCards(this.cardsArray);
//call shuffle method
}
//Adds class "flipped" to the cards
flipCard(card, cards) {
if (this.canFlipCard(card)) {
if (chosenCards.length >= 2) {
console.log("removing1");
//from here
for (let card0 in cards) {
let list = card0.classList;
if (list) {
if (!list.contains('visible')) {
card0.removeEventListener('click', card0.fn);
}
}
}
} //to here
else if (!card.classList.contains('visible')) {
debugger;
card.classList.add('visible');
chosenCards.push(card);
console.log(chosenCards);
//this is where print out
var holder = document.getElementById("cardResults");
for (var i = 0; i < chosenCards.length; i++) {
holder.innerHTML += "<p>" + chosenCards[i] + "</p><br>";
}
card.removeEventListener('click', card.fn);
}
}
}
//Need a Shuffle method in here
shuffleCards(cardsArray) {
for (let i = cardsArray.length - 1; i > 0; i--) {
const randIndex = Math.floor(Math.random() * (i + 1));
[cardsArray[i], cardsArray[randIndex]] = [cardsArray[randIndex], cardsArray[i]];
}
cardsArray = cardsArray.map((card, index) => {
card.style.order = index;
});
}
//gets the card
getCardType(card) {
return card.getElementsByClassName('card-value')[0].src;
}
//returns card
canFlipCard(card) {
return card
}
}
//this will call the reading to start when page is loaded
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', ready)
} else {
ready()
}
function ready() {
//declares card's' & sets it to the card class in HTML
let cards = Array.from(document.getElementsByClassName('card'));
//creates new instance of tarot class
let tarotReading = new tarot(cards);
let over = Array.from(document.getElementsByClassName('over'));
over.forEach(overlay => {
overlay.addEventListener('click', () => {
overlay.classList.remove('visible');
tarotReading.startReading();
});
});
//flips the cards
cards.forEach(card => {
card.addEventListener('click', card.fn = function clicked() {
tarotReading.flipCard(card, cards);
//remove cards that dont have visible tag
});
})
console.log(chosenCards[0]);
}
h1 {
color: #7B68EE;
padding left: 50px;
padding right: 50px;
padding-top: 5px;
text-align: center;
}
.container {
display: grid;
grid-template-columns: repeat(6, auto);
grid-gap: 10px;
margin: 50px;
justify-content: center;
perspective: 500px;
}
.card {
position: relative;
height: 175px;
width: 125px;
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
backface-visibility: hidden;
border-radius: 12px;
border-width: 1px;
border-style: solid;
transition: transform 500ms ease-in-out;
}
.card.visible .card-back {
transform: rotateY(-180deg);
}
.card.visible .card-front {
transform: rotateY(0)
}
.card-back {
background-color: black;
border-color: white;
color: white;
}
.card-front {
background-color: black;
border-color: white;
color: white;
transform: rotateY(180deg);
}
<body>
<h1>Tarot</h1>
<div class="container">
<div class="card">
<div class="card-back card-face card1" id="card1">
<p> 1
<p>
</div>
<div class="card-front card-face">
<p> The Hermit
<p>
</div>
</div>
<div class="card">
<div class="card-back card-face card2">
2
</div>
<div class="card-front card-face">
The Fool
</div>
</div>
<div class="card">
<div class="card-back card-face card3">
3
</div>
<div class="card-front card-face">
The Empress
</div>
</div>
</div>
<button type="button" class="over container">Shuffle</button>
</div>
<hr>
<div id="cardResults">
</div>
</body>
The expected result would be the flipped over card with the text (not the number of the card) that the user has chosen.
You could try something like this:
for(var i=0; i < chosenCards.length; i++){
holder.appendChild(chosenCards[i].cloneNode(true));
}

Categories