Map an array, but place X amount of items each pass - javascript

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

Related

How to make an algorithm for complex pagination in JS?

I'm trying to make pagination work but it's a bit complex for me so I need help. If someone has an idea how to make it, I would be thankful.
I need to show items that the user owns. One page can have maximum of 12 items displayed.
I have a pageData array, that returns the number of items for a category. Category "shirts" has 2 items.
pageData = [ {id:'pants',totalItems: 15},
{id:'shirts',totalItems: 2}, {id:'dresses',totalItems: 13}]
On first load I need to show 12 items (lets say 12 pants), on second load (click on load more button) I need to show 3 more pants, 2 shirts and 7 dresses.
Metadata for an item has to be fetched via api (this is just an example how to get id of specific item, I'm trying to put it somehow into code but don't have clear idea how):
for (let i = 0; i < pageData['pants'].totalItems; i++) {
const metadata = await api.getMetadata({
userId: 'userId',
id: i,
});
}
This is some code that I have but like I said, I don't really have an idea how to make it:
const getItems = async (pageData, currentPage) => {
const itemsPerPage = 12;
let fetchedData = [];
let total = 0;
let start = currentPage * itemsPerPage;
for (let i = 0; i < pageData.length; i++) {
const minRange = total;
const maxRange = minRange + itemsPerPage;
if (start >= minRange && start <= maxRange) {
const minId = minRange - total;
const maxId = maxRange - total;
for (let j = minId; j < maxId; j++) {
const metadata = await api.getMetadata({
userId: 'userId',
id: j,
});
fetchedData.push(metadata);
}
}
total += itemsPerPage;
}
};

Three.js BufferGeometry Vertices Not Updating

I am looking to connect plane buffer geometry grid tiles which have real elevation data from IndexedDB. My issue is the data resolution on the STRM elevation is not perfect so the edges between the tiles are not the same. I need to essentially average out all the grid edges between the touching vertices to create a seamless terrain.
When I copy paste the code into the console in the scene it works. However just in the code it doesn't. The sceneRef that is passed is valid and the rest of the codebase using the sceneRef correctly.
The tiles are a 3 x 3 with the current grid tile being the center at 1,1 from range 0,0 - 2,2.
function connectTiles(currGridKey, sceneRef){
console.log("connectTiles");
console.log("currGridKey");
// Current Tile Connection
for (var lat = 0; lat < currGridKey[0]+2; lat++) {
for (var long = 0; long < currGridKey[1]+2; long++) {
const currentTile = sceneRef.getObjectByName(`${lat}-${long}`);
// Current Grid Tile Per Loop
if (currentTile) {
const currentTileVerts = currentTile.geometry.attributes.position.array,
latPlusTile = sceneRef.getObjectByName(`${lat}-${long+1}`),
longPlusTile = sceneRef.getObjectByName(`${lat+1}-${long}`);
// Connect Latitudinally
if (latPlusTile) {
const latPlusTileVerts = latPlusTile.geometry.attributes.position.array;
for (var z = 0; z < currentTileVerts.length; z+=27) {
const newVertHeight = (currentTileVerts[z] + latPlusTileVerts[z]) / 2;
latPlusTileVerts[z] = newVertHeight;
currentTileVerts[z] = newVertHeight;
}
latPlusTile.geometry.attributes.position.needsUpdate = true;
currentTile.geometry.attributes.position.needsUpdate = true;
}
// Connection Longitudinally
if (longPlusTile) {
const longPlusTileVerts = longPlusTile.geometry.attributes.position.array;
for (var x = 0; x < currentTileVerts.length; x+=3) {
const newVertHeight = (currentTileVerts[x] + longPlusTileVerts[x]) / 2;
longPlusTileVerts[x] = newVertHeight;
currentTileVerts[x] = newVertHeight;
}
longPlusTile.geometry.attributes.position.needsUpdate = true;
currentTile.geometry.attributes.position.needsUpdate = true;
}
}
}
}
If all values inside the array are in fact being updated, maybe they're just not getting uploaded to the GPU. Instead of changing the value inside geometry.attributes.position directly, try using the .setAttribute() method. The docs state that using .setAttribute() and .getAttribute() is preferrable than accessing it directly because it has its own internal storage mechanism.
const latPlusTileVerts = latPlusTile.geometry.getAttribute("position").array;
// ... Loops
latPlusTile.geometry.getAttribute("position").needsUpdate = true;
// Or an alternative is to generate a new attribute...
// in case updating the old one fails
const posAttrib = new THREE.BufferAttribute(latPlusTileVerts, 3);
latPlusTile.geometry.setAttribute("position", posAttrib);

React For Loop - Uncaught (in promise) RangeError: Maximum call stack size exceeded

I have this for loop in React, that loops through limited number of items, and creates JSX based on that, my render function looks like this:
public render(): React.ReactElement<ITilesProps> {
let numOfColumns = 2;
let items = this.state.items;
let rowsJsx = []
let initCounter = items.length - numOfColumns;
let jumper = initCounter > 0 ? numOfColumns : items.length;
for (let i = 0; i < items.length; i += jumper) {
let rowJsx = []
let limit = (i + jumper - 1) >= items.length ? items.length - 1 : (i + jumper - 1)
for (let j = i; j <= limit; j++) {
let props = {
previewImages: [
{
url: items[j].EncodedAbsUrl,
previewImageSrc: items[j].EncodedAbsUrl,
imageFit: ImageFit.cover,
width: 318,
height: 196
}]
}
rowJsx.push(<SingleCardContainer><DocCard previewImageProps={props} ></DocCard></SingleCardContainer>)
}
rowsJsx.push(<TileRow>{rowsJsx}</TileRow>)
}
return (
<div className="ms-Grid" dir="ltr">
{rowsJsx}
</div>
)
}
What I am creating, is a row of tiles, the first loop is looping through items, and skipping the # of tiles in each row, the second loop is looping through tiles in each row, creating the JSX for it, and adding it to the array of tiles in each row, which in turn is added to the all rows JSX array.
Any idea why this might give an error?
Thanks.
It appears it was just simple typo.. Easy done.
rowsJsx.push(<TileRow>{rowsJsx}</TileRow>)
In the above you have accidentally added rowsJsx instead of rowJsx , so your accumulated rows are getting added each time.
rowsJsx.push(<TileRow>{rowJsx}</TileRow>)

Multiples instances of Slideshow breaks code

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
})
}

avoid same value to appear again using math.random()

animations = ['fadeIn','fadeInDown','slideInUp','flipInY','bounceInLeft'];
Imagine I generate random effect whenever user click something, so to achieve best experience, I would want the user to have same effect. But with
animations[ Math.floor(Math.random() * animations.length) -1];
that would happens.
How to avoid same value to appear again?
Two ways that i can suggest.
First shuffle the array and go one by one from index 0 to 5 and then loop as much as you like.
Pick a random element and slice it out up until the array is empty and then refresh your array from a back up. (be careful not to back up with a reference or your backup array gets deleted along with the one gets spliced. so use .slice())
Array.prototype.shuffle = function(){
var a = this.slice(), // don't morph the original
i = a.length,
j;
while (i > 1) {
j = ~~(Math.random()*i--);
a[i] = [a[j],a[j]=a[i]][0];
}
return a;
};
var album = ["photo1","photo2","photo3","photo4","photo5"];
photos = album.shuffle();
photos.forEach(p => console.log(p));
console.log("another way") // the splice way
photos = album.slice();
while (photos.length) console.log(photos.splice(Math.floor(Math.random() * photos.length),1)[0]);
!photos.length && (photos = album.slice()); // restore photos album and continue
while (photos.length) console.log(photos.splice(Math.floor(Math.random() * photos.length),1)[0]);
!photos.length && (photos = album.slice()); // restore photos album and continue
Following #Redu and my comments, take it out after you use it, but work on a copy.
var animations = ['fadeIn', 'fadeInDown', 'slideInUp', 'flipInY', 'bounceInLeft'];
var j;
var tmp = animations.slice(); //copy
var removed = 0;
for (var i = 1; i < 20; i++) {
j = Math.floor(Math.random() * tmp.length);
console.log(tmp[j]);
tmp.splice(j, 1);
removed++;
if (animations.length == removed) {
tmp = animations.slice();
removed = 0
}
}
I suggest to use a different method, by storing the last two selected elements and choose a different from the last selected items.
That prevent slicing and manipulation of original array.
function Random(array) {
var last = [];
this.next = function () {
var r;
do {
r = Math.floor(Math.random() * array.length);
} while (~last.indexOf(r))
last.length === 2 && last.shift();
last.push(r);
return array[r];
}
}
var animations = ['fadeIn', 'fadeInDown', 'slideInUp', 'flipInY', 'bounceInLeft'],
random = new Random(animations),
i;
for (i = 0; i < 15; i++) {
console.log(random.next());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories