Having an issue, when I create multiple instances of this slideshow only one instance seems to work. Not sure how to reformat it so it works for multiple instances.
JAVASCRIPT
//SLIDESHOW
const slideshow = document.querySelector("section.slideshow")
const images = slideshow.querySelectorAll("img")
slideshow.addEventListener("mousemove", function (event) {
const x = event.offsetX
const width = this.offsetWidth
const percentage = x / width
const imageNumber = Math.floor(percentage * images.length)
images.forEach(image => {
image.style.zIndex = 0
})
images[imageNumber].style.zIndex = 1
})
HTML
<section class ="slideshow">
<img src="assets/images/1.png">
<img src="assets/images/3.png">
<img src="assets/images/2.png">
</section>
document.querySelector("section.slideshow") will always select the first <section class="slideshow"> element it encounters.
If you want the same code to be able to handle multiple slideshow elements, you would need to select all of them into a NodeList and run the initialisation code for each instance.
Should go something like this: (not tested)
const slideshow_instances = document.querySelectorAll("section.slideshow")
for (var i = 0; i < slideshow_instances.length; i++) {
const slideshow = slideshow_instances[i];
const images = slideshow.querySelectorAll("img")
slideshow.addEventListener("mousemove", function (event) {
const x = event.offsetX
const width = this.offsetWidth
const percentage = x / width
const imageNumber = Math.floor(percentage * images.length)
images.forEach(image => {
image.style.zIndex = 0
})
images[imageNumber].style.zIndex = 1
})
}
Related
I'm creating a grid of client logos using Tailwind, react, and GSAP. Client logo paths are loaded in via a json file. The client logo list is pretty long, so I thought it would be interesting to have the images in each grid col-spans ('cells') fade to a different image every few seconds.
My solution thus far is to map through all the logos and stack a certain number of them on top of each other as absolute before moving onto the next col span and then animate them in and out using the ids of the col-spans. I'm having trouble wrapping my mind around the approach. Especially with responsibly changing the grid-cols.
The approach so far (some pseudo-code some irl code):
const maxCols = 4
const maxRows = 3
const itemsPerRow = Math.floor( logosList.length()/maxCols/maxRows)
const isExtra = () =>{
if(logosList.length() % itemsPerRow >0) return true
else return false
}
const numRows = Math.floor( logosList.length()/maxCols )
export default function ClientImages(){
useEffect(() => {
for(i = 0; i <= i/maxCols/maxRows; i++ )
// to be figured out gsap method
gsap.to(`img-${i}`, {animate stuff})
},);
function setLogos(){
let subset
for ( index = 0; index == maxCols * maxRows; index++ ){
if(isExtra){
subset = logosList.slice(index, itemsPerRow + 1)
}
else subset = logosList.slice(index, itemsPerRow)
return(
<div className="col-span-1 relative" id={`clientColSpan-${index}`}>
{subset.map((logo) => {
<Image className='absolute' src={logo.src} yada yada yada />
})}
</div>
)
}
}
return(
<div className="grid grid-cols-2 md:grid-cols-4 2xl:grid-cols-6">
{setLogos}
</div>
)
}
Here's a visual representation of my thought process
Here's my solution based on your visual representation.
Create the grid
As we need two different grids for desktop and mobile, it's better to have a function that does this job specifically.
// spread images to grid
const createGrid = (images, col, row) => {
const totalItems = images.length;
const totalCells = col * row;
const itemsInCell = Math.floor(totalItems / totalCells);
let moduloItems = totalItems % totalCells;
// create grid
const grid = [];
for (let i = 0; i < totalCells; i++) {
let cell = [];
for (let j = 0; j < itemsInCell; j++) {
const index = i * itemsInCell + j;
cell.push(images[index]);
}
grid.push(cell);
}
// handle modulo items
while (moduloItems > 0) {
grid[totalCells - 1].push(images[totalItems - moduloItems]);
moduloItems--;
}
return grid;
};
const images = [1,2,3,4,...,37];
cosnt grid = createGrid(images, 2, 3); // [ [1,2,3,4,5,6], [7,8,9,10,11,12], ... [] ]
The createGrid() will return an array of grid cells, each cell will contain a number of items that meet your expectation. With this grid cells array, you have enough data to create your HTML.
Handle the responsive grid
With the provided grid array we can create responsive HTML grid layouts based on the window's width.
const createHTMLFromGrid = gridArray =>{// your function for HTML};
let html =
window.innerWidth > 1024
? createHTMLFromGrid(createGrid(images, 2, 3))
: createHTMLFromGrid(createGrid(images, 4, 3));
// append the HTML to your DOM
$(html).appendTo("body");
You can also change the grid based on the window resize event. Once you've got the HTML ready, you can play with the GSAP animation.
See CodePen
I can't find out what is going wrong in my code. Thank you.
I have attached the link to code via codepen.
https://codepen.io/tenzin12/pen/rNmmPbv
`const confirmBtn = document.querySelector(".confirm");
const tipField = document.querySelector(".p1");
const totalField = document.querySelector(".p2");
const tipPercentage = document.querySelector("#tip").children;
const customTip = document.querySelector(".custom").value;
const inputAmt = document.querySelector("#amount").value;
const totalPerson = document.querySelector(".number_of_people").value;
const calcFunction = (bill, percent, diners) => {
const percentage = percent / 100;
const tipPerPerson = (bill * percentage) / diners;
const finalBillPerPerson = bill / diners;
const finalWithTip = finalBillPerPerson + tipPerPerson;
tipField.textContent = tipPerPerson;
totalField.textContent = finalWithTip;
};
for (let i = 0; i < tipPercentage.length; i++) {
tipPercentage[i].addEventListener("click", () => {
if (parseInt(totalPerson) > 0) {
if (tipPercentage[i].value.toUpperCase() === "CUSTOM") {
calcFunction(parseFloat(inputAmt), parseInt(customTip), parseInt(totalPerson));
}
}
calcFunction(parseFloat(inputAmt), parseInt(tipPercentage[i].value), parseInt(totalPerson));
});
}
`
When you need to run calculations on element values, you need to collect those values at the time of calculation. You were collecting them up front - but then when you were calculating the function, it was using those old values. I moved those into your function. Note how I got rid of most of the parseInt and parseFloat functions in favor of the minimal + operator which does the same thing.
Additionally, I simplified the code a little and put in a validation to prevent totals being run on 0 people or 0 amounts. Finally, I changed your for loop into an HTMLCollection forEach loop. I find it is easier to read and maintain
const confirmBtn = document.querySelector(".confirm");
const tipField = document.querySelector(".p1");
const totalField = document.querySelector(".p2");
const tipPercButtons = document.querySelectorAll("#tip input.percentage");
const calcFunction = (bill, percent, diners) => {
const percentage = percent / 100;
const tipPerPerson = (bill * percentage) / diners;
const finalBillPerPerson = bill / diners;
const finalWithTip = finalBillPerPerson + tipPerPerson;
tipField.textContent = tipPerPerson;
totalField.textContent = finalWithTip;
};
tipPercButtons.forEach((el) =>
el.addEventListener("click", (e) => {
const customTip = +document.querySelector(".custom").value;
const inputAmt = +document.querySelector("#amount").value;
const totalPerson = +document.querySelector(".number_of_people").value;
if (isNaN(totalPerson) || isNaN(inputAmt)) {
alert("Please designate the number of people and the amount of the bill")
return;
}
if (totalPerson === 0) return;
let val
if (e.target.value.toUpperCase() === "CUSTOM") val = customTip;
else val = parseInt(e.target.value);
calcFunction(inputAmt, val, totalPerson);
})
);
Updated pen: https://codepen.io/john-tyner/pen/MWmmLMQ?editors=1111
i analysed your code there is some error in fetching the input value in the code.
below is the correct code. Hope this might work
make the following little changes in your code:
const inputAmt = document.querySelector("#amount");
const totalPerson = document.querySelector(".number_of_people");
and this at the bottom outside the if block
calcFunction(
parseFloat(inputAmt.value),
parseInt(tipPercentage[i].value),
parseInt(totalPerson.value)
);
overall your calculator is So interesting.
I'm sorry if there has already been such question, I haven't found.
I wanna make a progress (loading) animation with JS:
<div class = "load">
<div id = "load_fill"></div>
<p id = "percent">0%</p>
</div>
<script>
let percent = document.getElementById("percent");
let load_fill = document.getElementById("load_fill")
let j = 0;
let fill = () => {
j++;
percent.textContent = `${j}%`;
load_fill.style.width = `${j}%`
}
for (let i = 0; i < 100; i++){
setTimeout(fill, 200);
}
</script>
The problem is that the first iteration works with delay but others no;
Is there any way to make delay between each iteration?
Will be thankful for any answer.
new Array(100).map((el, ind) => ind).forEach((cur) => setTimeout(fill, (cur + 1) * 200))
Here, we space out each setTimeout by first creating a range array (the first part) before setting a time out 200 ms between each one.
I'm coding a sorting visualizer in ReactJS, and I use a state to hold the delay between each render.
When I change the slider of the delay, the sorting does not update.
I made it log the updated value, and in each loop I made it log the value it reads.
for some reason, when I read the getDelay inside the loop, and outside of it, they are different.
Here is the code:
import React, { useState, useEffect } from "react";
import "./SortingVisualizer.css";
class Bar {
constructor(value, className) {
this.value = value;
this.className = className;
}
}
const SortingVisualizer = () => {
const [getArray, setArray] = useState([Bar]); //array to hold the bars
const [getSlider, setSlider] = useState(50);
const [getDelay, setDelay] = useState(2);
//reset the array at the start
useEffect(() => {
resetArray(10);
}, []);
//function to reset the array
const resetArray = () => {
const array = [];
for (let i = 0; i < getSlider; i++) {
array.push(new Bar(randomInt(20, 800), "array-bar"));
}
setArray(array);
};
//a delay function. use like this: `await timer(time to wait)`
const timer = delay => {
return new Promise(resolve => setTimeout(resolve, delay));
};
//function to do buuble sort with given delay between each comparison
const bubbleSort = async () => {
let temp,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i--) {
//looping from the start of the section from the first loop to the end of it.
for (let j = 0; j < i - 1; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + 1].className = "array-bar compared-bar";
if (getDelay > 0) await timer(getDelay / 2);
setArray([...array]);
//comparing and switching if needed
if (array[j].value > array[j + 1].value) {
temp = array[j].value;
array[j].value = array[j + 1].value;
array[j + 1].value = temp;
setArray([...array]);
}
//updating the array and moving to the next pair
if (getDelay > 0) await timer(getDelay / 2);
array[j].className = "array-bar";
array[j + 1].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
}
array[i - 1].className = "array-bar completed-bar";
}
setArray([...array]);
console.log("done.");
};
const combSort = async () => {
let temp,
swapped,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i = Math.floor(i / 1.3)) {
//looping from the start of the section from the first loop to the end of it.
swapped = false;
for (let j = 0; j < array.length - i; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + i].className = "array-bar compared-bar";
setArray([...array]);
await timer(getDelay / 2);
//comparing and switching if needed
if (array[j].value > array[j + i].value) {
temp = array[j].value;
array[j].value = array[j + i].value;
array[j + i].value = temp;
setArray([...array]);
swapped = true;
await timer(getDelay / 2);
}
//updating the array and moving to the next pair
array[j].className = "array-bar";
array[j + i].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
console.log(getDelay);
}
//array[i - 1].className = "array-bar completed-bar";
if (i === 1 && swapped) i = 2;
}
setArray([...array]);
};
const sliderUpdate = e => {
setSlider(e.target.value);
resetArray(getSlider);
};
const delayUpdate = e => {
setDelay(e.target.value * 1);
console.log(getDelay);
};
return (
<>
<div className="menu">
<button onClick={() => resetArray()}>Geneate new array</button>
<button onClick={() => bubbleSort()}>Do bubble sort</button>
<button onClick={() => combSort()}>Do comb sort</button>
</div>
<div class="slide-container">
<input
type="range"
min="3"
max="250"
value={getSlider}
class="slider"
id="sizeSlider"
onChange={sliderUpdate}
/>
<input
type="range"
min="0"
max="1000"
value={getDelay}
class="slider"
id="delaySlider"
onChange={delayUpdate}
/>
</div>
<div className="array-container">
{getArray.map((bar, i) => (
<div
className={getArray[i].className}
key={i}
style={{ height: `${bar.value * 0.1}vh` }}
></div>
))}
</div>
</>
);
};
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default SortingVisualizer;
I don't know what the best solution is, but a solution would be to use useRef.
The problem is related to Why am I seeing stale props or state inside my function? : On each render you are creating new functions for bubbleSort and combSort. Those functions use the value of getDelay that existed at the moment those functions have been created. When one of the buttons is clicked the "version" of the function of the last render will be executed, so the value of getDelay that existed then and there will be used.
Now, changing the slider will cause a rerender, and thus new versions of bubbleSort and combSort are created ... but those are not the versions that are currently running!
useRef solves that problem because instead of directly referring to the delay, we are referring to an object whose current property stores the delay. The object doesn't change, but the current property does and every time it's accessed we get the current value. I highly encourage you to read the documentation.
After your state variables, add
const delayRef = useRef(getDelay);
delayRef.current = getDelay
The second line keeps the ref in sync with the state.
Everywhere else where you reference getDelay, except value of the slider itself, use delayRef.current instead. For example:
if (delayRef.current > 0) await timer(delayRef.current / 2);
Demo (couldn't get it to work on SO): https://jsfiddle.net/wuf496on/
I have written JavaScript function, to shuffle an array of divs onClick.
// Function to shuffle 3 divs
shuffle = () => {
const shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
// Button as an FYI
<button onClick={this.shuffle} className="has-text-black">shuffle hats</button>
This works absolutely fine, and randomises every time I click the button.
However, I want the divs to sort/shuffle 5 times automatically, onClick.
(IE = I don't want to have to click 5 times, to shuffle 5 times).
What's the best approach to do this?
(I've searched but haven't found anything to repeat shuffling on elements).
I thought about using async await/settimeout, to repeat this.state.divs.sort(() => Math.random() - .50) 5 times?
UPDATE:
To add context, here is a codesandbox...
https://codesandbox.io/s/distracted-shape-ifsiv
When I click the shuffle button, you can see the hats only swap positions once. Not 5 times.
Here is a posible way to do it, with javascript.
hats object get shuffled 5 times, with 200ms second transition.
Of course it's very simple, the object is meant to be extended!
let hats = [
{content: `<h6>1🎩</h6>`},
{content: `<h3>2🎩</h3>`},
{content: `<h1>3🎩</h1>`},
{content: `<h2>4🎩</h2>`},
{content: `<h4>5🎩</h4>`}
];
let timer;
function loop5(){
let i = 0
clearInterval(timer)
timer = setInterval(function(){
shuffle()
if (i >= 5){
clearInterval(timer)
}
i++
}, 200)
}
function shuffle(){
hats.sort(() => Math.random() - 0.5)
out.innerHTML = hats.map(e => e.content).join("")
}
div {display: flex; font-size: xx-large }
<button onclick="loop5()">shuffle hats</button>
<div id="out"></div>
I don't see why shuffling 5 times is better or any different than shuffling 5 times. Anyway you can do it in a naive way like this:
// Function to shuffle 3 divs
shuffle = () => {
const shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
shuffleTimesFive = () => {
for(var i = 0; i < 5; i++)
this.shuffle();
}
// Button as an FYI
<button onClick={this.shuffleTimesFive} className="has-text-black">shuffle hats</button>
Or maybe a smarter way is to have a shuffleNTimes function that takes a parameter, like so:
// Function to shuffle 3 divs
shuffle = () => {
const shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
shuffleNTimes = (n) => {
for(var i = 0; i < n; i++)
this.shuffle();
}
// Button as an FYI
<button onClick={this.shuffleNTimes.bind(this, 5)} className="has-text-black">shuffle hats</button>
I think shuffling one time and five times has the same effect, and Fisher-Yates is more efficient but keeping your way:
shuffle = () => {
let shuffled = [];
for(let i=0; i<5; i++)
shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
If you decide to use "Fisher-Yates" algorithm, you can implement it like:
const shuffle = () => {
let array = this.state.divs;
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
this.setState([...array]);
}
As I understood from your comment, you want to do animation, in this case you don't need a loop rather you can use setInterval() and reset it ones executed five times. I have written a demo on both shuffling ways, as you can see the method that uses sort() sometimes returns the same result while the "Fisher–Yates" always reshuffled.
<button onclick="shuffle()">Click To Shuffle</button>
<div id="1">div1</div>
<div id="2">div2</div>
<div id="3">div3</div>
<div id="4">div4</div>
<script>
//This one uses Fisher–Yates shuffle algorithm:
const divs = [...document.querySelectorAll('div')];
const shuffle = () => {
let count = 0;
const intervalId = setInterval( function() {
for (let i = divs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[divs[i], divs[j]] = [divs[j], divs[i]];
}
divs.forEach( div => document.body.appendChild(div) );
count++;
if(count === 5)
clearInterval(intervalId);
} ,1000)
}
</script>
<button onclick="shuffle()">Click To Shuffle</button>
<div id="1">div1</div>
<div id="2">div2</div>
<div id="3">div3</div>
<div id="4">div4</div>
<script>
const divs = [...document.querySelectorAll('div')];
shuffle = () => {
let shuffled = [];
let count = 0;
const intervalId = setInterval( function() {
shuffled = divs.sort(() => Math.random() - .50);
shuffled.forEach( div => document.body.appendChild(div) );
count++;
if(count === 5)
clearInterval(intervalId);
}, 1000 )
};
</script>
For your case, it would be like:
let divs = this.state.divs;
const shuffle = () => {
let count = 0;
const intervalId = setInterval( function() {
for (let i = divs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[divs[i], divs[j]] = [divs[j], divs[i]];
}
this.setState([...divs]);
count++;
if(count === 5)
clearInterval(intervalId);
} ,1000)
}