Search code examples
javascripthtmljquerycsscss-grid

Get corner items in current CSS grid layout, expanding on hover


I'm using grid-template-columns: repeat(auto-fit, 250px) to keep grid items 250px wide but automatically adjust number of row-items according to screen-width. Now I want that when I hover over an item, it expands taking a bit of its sibling's space, with its sibling getting shrunk. By console.log(), I found nextElementSibling that can be used. So I'm thinking of something like-

function expand(card){
    card.setAttribute("style","width: 300px;");
    card.nextElementSibling.setAttribute("style","width: 200px");
}
function restore(card){
    card.setAttribute("style","width: 250px;");
    card.nextElementSibling.setAttribute("style","width: 250px");
}

This seems pretty bad (I guess!) because I need to manage corner items separately and hence need to know the current number of items in a row. Also, when I hover over a card, the card does expand but because of repeat(auto-fit,250px), the card intersects its sibling because the individual cells remain fixed (250px). How can I solve the issues? Or any better suggested solutions? Here is my git repository.

Edit 1- As to clarify my issues:

  1. How to manage the corner items, i.e., for an item in the right corner I want its previous sibling shrink; not the next.
  2. The cells remain fixed, i.e., the expanding item intercepts the shrinking item's border-

enter image description here

becomes

enter image description here

So how to deal with this behavior caused by auto-fit,250px?


Solution

  • I honestly tried to solve it using grid-template-columns: repeat(auto-fit, minmax(250px,auto)); and a different size of child elements, but in's too unstable and not working that way. But I don't think the grid is require for this task. The easiest way I see how this should be solved - using good old JavaScript (+jQuery) and flex. Although the right way will be resize all elements in a single row, not only nearest siblings of a hovered element. Try it on any screen size.

    $(document).ready(function() {
      /* defines main digits */
      let boxWidth = $('#box').innerWidth();
      let itemOuterWidth = $('.item').outerWidth(true);
      let itemInnerWidth = $('.item').innerWidth();
      SetSeparators(boxWidth, itemOuterWidth);
    
      /* refresh main digits ater page resize */
      $(window).resize(function() {
        $('.item').css({
          "flex": ""
        });
        boxWidth = $('#box').innerWidth();
        itemOuterWidth = $('.item').outerWidth(true);
        itemInnerWidth = $('.item').innerWidth();
        SetSeparators(boxWidth, itemOuterWidth);
      });
    
      $('#box').on('mouseover', '.item', function(e) {
        GetElementsPosition($(this).index('.item'), $(this), boxWidth, itemOuterWidth, itemInnerWidth);
      });
    
      $('#box').on('mouseleave', '.item', function(e) {
        $('.item').css({
          "flex": ""
        });
      });
    
    });
    
    /* set separator elemet to avoid blocks to jump from row to row while resizing */
    function SetSeparators(boxWidth, itemOuterWidth) {
      $('.separator').remove();
      let countRowItems = Math.floor(boxWidth / itemOuterWidth);
      $('<div class="separator"></div>').insertBefore('.item:nth-child(' + countRowItems + 'n+1)');
    }
    
    function GetElementsPosition(index, element, boxWidth, itemOuterWidth, itemInnerWidth) {
    
      /* calculating row items, column position and row position of a current elemet */
      let countRowItems = Math.floor(boxWidth / itemOuterWidth);
      let colPosition = index % countRowItems;
      let rowPosition = Math.floor(index / countRowItems);
      /* exmanpd size of a hovered element in pixels*/
      let expandSize = 50;
    
      /* counting number of items in a hovered row */
      let currentRowCounter = 0;
      $('.item').each(function(e) {
        let thisIndex = $(this).index('.item');
        let thisRowPosition = Math.floor(thisIndex / countRowItems);
        if (rowPosition == thisRowPosition) {
          currentRowCounter++;
        }
      });
    
      /* settting each element widht according to it's position in a list and row */
      $('.item').each(function(e) {
        $(this).css({
          "flex": "0 1 " + itemInnerWidth + "px"
        });
        let thisIndex = $(this).index('.item');
        let thisColPosition = thisIndex % countRowItems;
        let thisRowPosition = Math.floor(thisIndex / countRowItems);
        if ((rowPosition == thisRowPosition) && (colPosition == thisColPosition)) {
          $(this).css({
            "flex": "0 1 " + (itemInnerWidth + expandSize) + "px"
          });
        } else if (rowPosition == thisRowPosition) {
          $(this).css({
            "flex": "0 1 " + (itemInnerWidth - (expandSize / (currentRowCounter - 1))) + "px"
          });
        } else {
          $(this).css({
            "flex": ""
          });
        }
      });
    
    }
    * {
      box-sizing: border-box;
    }
    
    html {
      height: 100%;
    }
    
    body {
      min-height: 100%;
      margin: 0;
      padding: 0;
    }
    
    #box {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
      justify-content: flex-start;
      align-items: flex-start;
    }
    
    .item {
      background: gray;
      flex: 0 1 250px;
      height: 50px;
      transition: all .5s ease;
      margin: 0 15px 15px 15px;
    }
    
    .separator {
      flex: 0 1 100%;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id='box'>
      <div class='item'></div>
      <div class='item'></div>
      <div class='item'></div>
      <div class='item'></div>
      <div class='item'></div>
      <div class='item'></div>
      <div class='item'></div>
      <div class='item'></div>
    </div>