Search code examples
javascriptcsslaraveltailwind-csslaravel-livewire

How to create a scrollable/touchable grid in CSS and JS?


I'm interested in how I can make a grid with an undetermined amount of columns and rows that I can put inside another div and have it not spill into others objects or mess with the parent size.

I want it to be square and I'm using Tailwind CSS but I can adapt to SCSS or vanilla CSS. Also I want it to be touchable/moveable with a mouse on desktop and touch capable devices.

How would I go about accomplishing this?


Solution

  • Assuming I've understood your question correctly, here is one way you could do it. I haven't tested it with a touch device but it shouldn't be hard to modify it to also respond to touch events.

    const items = [
      ['a0', 'a1', 'a2'],
      ['b0', 'b1', 'b2'],
      ['c0', 'c1', 'c2']
    ];
    
    let html = '';
    for (let rowItems of items) {
      html += '<div class="row">';
      for (let item of rowItems) {
        html += '<div class="item">';
        html +=   item;
        html += '</div>';
      }
      html += '</div>';
    }
    
    const viewElem = document.querySelector('#view');
    const outputElem = document.querySelector('#output');
    outputElem.innerHTML = html;
    
    
    
    let mouseStartPos = null;
    let startOffset = null;
    
    outputElem.addEventListener('mousedown', e => {
      outputElem.classList.remove('animate');
      mouseStartPos = {
        x: e.clientX,
        y: e.clientY
      };
      startOffset = {
        x: outputElem.offsetLeft - viewElem.offsetLeft,
        y: outputElem.offsetTop - viewElem.offsetTop
      };
    });
    
    window.addEventListener('mouseup', e => {
      mouseStartPos = null;
      startOffset = null;
    
      outputElem.classList.add('animate');
      const xGridOffset = -1 * Math.max(0, Math.min(Math.round((outputElem.offsetLeft - viewElem.offsetLeft) / -100), items.length - 1));
      const yGridOffset = -1 * Math.max(0, Math.min(Math.round((outputElem.offsetTop - viewElem.offsetTop) / -100), items[0].length - 1));
      outputElem.style.left = `${xGridOffset * 100}px`;
      outputElem.style.top = `${yGridOffset * 100}px`;  
    });
    
    window.addEventListener('mousemove', e => {
      if (mouseStartPos) {
        const xOffset = mouseStartPos.x - e.clientX;
        const yOffset = mouseStartPos.y - e.clientY;
        outputElem.style.left = `${-1 * xOffset + startOffset.x}px`;
        outputElem.style.top = `${-1 * yOffset + startOffset.y}px`;
      }
    });
    #view {
      width: 100px;
      height: 100px;
      overflow: hidden;
      
      border: 2px solid blue;
    }
    
    #output {
      position: relative;
    }
    
    .row {
      display: flex;  
    }
    
    .item {
      display: flex;
      min-width: 100px;
      width: 100px;
      height: 100px;
      box-sizing: border-box;
      
      justify-content: center;
      align-items: center;
      border: 1px solid red;
    }
    
    #output.animate {
      transition: left 1s ease 0s, top 1s ease 0s;
    }
    Drag it!<br/>
    <br/>
    <div id="view">
      <div id="output"></div>
    </div>