2 571 Codepen

Бесконечный CSS grid

Бесконечно перетаскиваемые изображения по всем направлении на гридах и с применением библиотеки anime.js


HTML

<div class="container" id="container"><div id="grid" class="grid"></div></div>

CSS

* {
  box-sizing: border-box;
}
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
}
span {
  position: fixed;
  z-index: 5000;
  top: 0;
  left: 0;
  width: 50px;
  height: 50px;
  background: white;
}
.container {
  top: 0;
  left: 0;
  position: fixed;
  display: block;
  overflow:hidden;
  width: 100%;
  height: 100vh;
}
.grid {
  display: grid;
  width: 100%;
  height: 100%;
  overflow: hidden;
  curser: grab;
}
.grid:active{
  cursor: grabbing;
}

.grid-item {
  background: #fffff05;
  background-size: cover;
  user-select: none;
  pointer-events: none;
  overflow: hidden;
  background-position: 50%;
}
.grid-item img {
  width: 100%;
  height: 100%;
}

JS

Библиотека анимаций:
https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.min.js
и скрипт
// Uses css grid for the items for no apparent reason
// Original idea, with less issues from https://codepen.io/radixzz/pen/eRJKXy

console.clear();

let photos = Array.from({length: 26}, (v,i)=> i).map( i => `https://anemolo-codepen.s3.amazonaws.com/thumb_${i}.jpg` ) 
// The infinite grid changes the img.src for all images on a jump.
// And downloading that new images shows a flash. Even if it's a blob, or saved on storage
// So, I just convert the images > blobs > dataURI to make the flash go away.
// The drawback is that it doesn't work with large images
photos = photos.map( (src,i) => {
  return fetch(src).then(r => r.blob()).then((blob)=>{
    // Yolo swag blobs
    let reader  =  new FileReader();
    reader.readAsDataURL(blob);
    
    return new Promise( (resolve, reject) =>{
      reader.onload = function(){
        resolve(reader.result);
        //Hopefully this does not cause a memory leak :b
        photos[i] = reader.result;
      }
      reader.onerror = function(){
        reject('Error on reader');
        //Hopefully this does not cause a memory leak :b
        photos[i] = '';
      }
    });
  })
})


class GridItem  {
  constructor(index = 0){
    this.item = document.createElement('div');
    // this.item.append(document)
    this.img = document.createElement('img');
    this.index = index;
    this.setImage();
    // console.log(index)
    
    
    // this.item.append(this.img);
    this.item.classList.add('grid-item');
  }
  setIndex(index){
    this.index = index;
    this.setImage()
  }
  setImage(){
    let image = photos[(this.index % photos.length)];
    if(Object.prototype.toString.call(image) === "[object String]" ){
      this.item.style.backgroundImage = `url(${image})`;
    } else {
      image.then((src)=>{
        this.item.style.backgroundImage = `url(${src})`;
      })
    }
  }
}
class Drag {
  constructor(ele, handleDrag){
     this.dragging = false;
    this.lastX = null;
    this.lastY = null;
    this.handleDrag = handleDrag;
    
    ele.addEventListener('touchstart', this.onStart.bind(this), false);
    ele.addEventListener('touchmove', this.onMove.bind(this), false);
    ele.addEventListener('touchend', this.onEnd.bind(this), false);
    
    ele.addEventListener('mousedown', this.onStart.bind(this))
    ele.addEventListener('mousemove', this.onMove.bind(this));
    ele.addEventListener('mouseup', this.onEnd.bind(this));
    ele.addEventListener('mouseuot', this.onEnd.bind(this));
  }
  onStart(ev){
    ev = ev.type == 'touchstart' ? ev.touches[0] : ev;
    
    this.dragging = true;
    this.lastX = ev.clientX;
    this.lastY = ev.clientY;
    
  }
  onMove(ev){
    
    if(!this.dragging) return; 
    
    
    ev = ev.type == "touchmove" ? ev.touches[ 0 ] : ev;
    let xDelta = ev.clientX - this.lastX;
    let yDelta = ev.clientY - this.lastY;
    let vel = Math.abs(xDelta * yDelta);
    if(vel > 50){
      let v = {x: xDelta * 0.5, y:yDelta * 0.5};
      if(this.anime) this.anime.pause();
      this.anime = anime(
      {targets: v,
        x: 0,
        y: 0,
       update: (anim)=>{
         this.handleDrag(v.x,v.y)
       }
      }
      )
    }
    this.handleDrag(xDelta, yDelta);
    this.lastX = ev.clientX;
    this.lastY = ev.clientY;
    
  }
  onEnd(ev){
    this.dragging = false;
    
  }
}
class InfiniteGrid {
  constructor(nCol = 2, nRow = 2){
    this.grid = document.getElementById('grid');
    this.container = document.getElementById('container');
    this.Drag = new Drag(this.container, this.ondrag.bind(this));
    this.offsetX = 0;
    this.offsetY = 0;
    // Overshoot items
    this.items = [];
    this.setGrid(nCol,nRow);
    
    
    
  }
  ondrag(xDelta, yDelta){
    this.offsetX += xDelta;
    this.offsetY += yDelta;
    
    // Move the grid back by 1 item whenever it goes over 1/2 of an item
    // Making the movement invisible
    const itemWidth = 100./this.cols;
    const itemHeight = 100./this.rows;
    const pixelWidth = (itemWidth * window.innerWidth) / 100;
    const pixelHeight = (itemHeight * window.innerHeight) / 100;
    let jumpX = null;
    let jumpY = null;
    if(Math.abs(this.offsetX) > pixelWidth/2.){
      this.offsetX -= pixelWidth* Math.sign(this.offsetX);
      jumpX = Math.sign(this.offsetX);
    }
    if(Math.abs(this.offsetY) > pixelHeight/2.){
      this.offsetY -= pixelHeight * Math.sign(this.offsetY);
      jumpY = Math.sign(this.offsetY);
    }
    // console.log(this.rows, this.cols, jumpY)
    if(jumpX || jumpY){
      this.items.forEach(item => {
        if(jumpX) item.setIndex(this.shiftIndex(item.index + jumpX));
        if(jumpY) item.setIndex(this.shiftIndex(item.index + jumpY * (this.cols +2 )));
      })
    }
    
    this.grid.style.transform = `translate(${this.offsetX}px,${this.offsetY}px)`;
  }
  shiftIndex(index){
    if(index < 0 ){
      index = this.items.length + index;
    }
    index = index % this.items.length;
    
    return index;
  }
  
  setGrid(nCol = 2, nRow = 2) {            
    if(nCol === this.cols && nRow === this.rows) return;
    
    // Overshoot items
    const cols = nCol + 2;
    const rows = nRow + 2;
    // Add space for 2 more rows and columns using the current col/row size
    this.container.style.width = `${100 + 100/nCol*2.}vw`;
    this.container.style.height = `${100 + 100/nRow*2.}vh`;
     // Move the grid back by 1 col and row
    this.container.style.transform = `translate(${-100/nCol}vw, ${-100/nRow}vh)`
    
    // Do everything else taking into account the overshoot items
    this.grid.style.gridTemplateColumns = Array.from({length: cols}, ()=>'1fr').join(' ');
    this.grid.style.gridTemplateRows =  Array.from({length: rows}, ()=>'1fr').join(' ');
    
    const nItems =  cols * rows;
    this.cols = nCol;
    this.rows = nRow; 
    while(nItems < this.items.length){
    this.grid.removeChild(this.grid.children[this.items.length -1]);
      this.items = this.items.slice(0,this.items.length - 1);
    }
    while(nItems > this.items.length) {
        const item = new GridItem(this.items.length);
        this.items = this.items.concat(item);
        this.grid.append(item.item) 
    }
    
    this.items.forEach((item,index)=>{
      item.setIndex(index);
    })
    
  }
}

const grid = getColsAndRows();
const Infinite = new InfiniteGrid(grid.cols,grid.rows);

window.addEventListener('resize', ()=>{
const aspectRatio = window.innerWidth / window.innerHeight;
  
  const grid = getColsAndRows();
  Infinite.setGrid(grid.cols, grid.rows);
})

function getColsAndRows(){
  let cols = 3;
  let rows = 3;
  if(window.innerWidth > 800){
    cols = 4;
    rows = 4;
  }
  if(window.innerWidth < 500){
    rows = 1;
  }
  
const aspectRatio = window.innerWidth / window.innerHeight;
  
  cols +=  Math.max(0,Math.floor(aspectRatio) - 1);
  rows += Math.max(0, Math.floor(1. / (aspectRatio - 0.4)) -1)
  
  rows = Math.min(5, rows);
  cols = Math.min(8, cols);
  
  return {cols, rows};
}

Комментарии

  • Facebook
  • Вконтакте

Похожие статьи