2

Here's a simple grid: https://codepen.io/ChucKN0risK/pen/zaWQOm

HTML:

<div class="card-wrapper">
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="space-filler">Space filler</div>
</div>

CSS:

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

.card-wrapper {
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  width: 100%;
  height: 100%;
}

.card {
  background-color: royalblue;
}

.space-filler {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: tomato;
}

I want my "space filler" item to fill the grid remaining space but only when the last row has more than one grid item.

Is this even possible?

Thanks in advance and have a nice day

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
ChucKN0risK
  • 395
  • 2
  • 7
  • 18
  • 2
    The container has no concept of when its children wrap. So it won't know when the last row has more than one item. You'll need to use media queries or JS. https://stackoverflow.com/q/37406353/3597276 – Michael Benjamin Jun 22 '18 at 21:18

1 Answers1

0

I did manage to create my own solution using JS

Here's the code: https://codepen.io/ChucKN0risK/pen/zaWQOm

HTML:

<div class="card-wrapper js-grid-wrapper">
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="card js-grid-el"></div>
  <div class="space-filler is-hidden js-space-filler">Space filler</div>
</div>

<div class="element-adder">
  <button class="js-add-el">+ Add element</button>
  <button class="js-remove-el">- Remove element</button>
</div>

SCSS:

.card-wrapper {
  position: relative;
  display: grid;
  grid-gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  width: 100%;
  height: 100%;
}

.card {
  background-color: royalblue;
}

.space-filler {
  position: absolute;
  bottom: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: tomato;
  opacity: 0.8;

  &.is-hidden {
    display: none;
  }
}

// -------------------------
// DEMO SPECIFIC STYLE
// -------------------------

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

.element-adder {
  display: flex;
  flex-direction: column;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 2rem;

  button {
    flex: 1;
    padding: 0.5rem;
    border: none;
    background-color: white;
    cursor: pointer;
  }

  button + button {
    margin-top: 0.5rem;
  }
}

JS:

class SpaceFillerElement {
  constructor() {
    this._gridWrapper = document.querySelector('.js-grid-wrapper');
    this.gridElClassname = 'js-grid-el';
    this._el = this._gridWrapper.querySelector('.js-space-filler');
    this._gridEl = document.querySelector(`.${this.gridElClassname}`);
    this.gridElCount = this._gridWrapper.querySelectorAll(`.${this.gridElClassname}`).length;
    this.gridColumnGap = getComputedStyle(this._gridWrapper).gridColumnGap;
    this.events();
  }

  events() {
    this.elementAdder();
    this.observeGridWrapper();
    this.displayEl();
    this.setElDimensions();
    // Since the user can resize
    // the viewport we need to listen to the resize event so that
    // the space filler element can adjust its dimensions.
    window.addEventListener('resize', (e) => {
      this.getColumnsCount();
      this.setElDimensions();
      this.displayEl();
    });  
  }

  getColumnsCount() {
    const gridWrapperWidth = this._gridWrapper.clientWidth;
    const gridElWidth = document.querySelector(`.${this.gridElClassname}`).clientWidth;
    return Math.floor(gridWrapperWidth / gridElWidth);
  }

  displayEl() {
    this.gridElCount = this._gridWrapper.querySelectorAll(`.${this.gridElClassname}`).length;
    // If Number of elements / number of columns doesn't
    // return an integer, it means that elements will be
    // in the last row. And our space filler only displays
    // itself during that condition.
    if (this.gridElCount % this.getColumnsCount() !== 0) {
      this._el.classList.remove('is-hidden');
    } else {
      this._el.classList.add('is-hidden');
    }
  }

  setElDimensions() {
    // 1) this._gridEl is the first child of our grid container. However,
    // everytime we remove an item, we remove the first child which makes
    // this._gridEl.clientHeight returns 0. That's why we must reassign
    // its value to the new first child of our grid container.
    this._gridEl = document.querySelector(`.${this.gridElClassname}`);
    // 2) We must know the number of grid item to determine the
    // number of child present in the last row.
    this.gridElCount = this._gridWrapper.querySelectorAll(`.${this.gridElClassname}`).length;
    const ElOnLastRow = this.gridElCount % this.getColumnsCount();
    // 3) We set our space filler element's dimensions according to the
    // space available in the last row.
    const ElWidth = `calc(${this._gridEl.clientWidth}px * (${this.getColumnsCount()} - ${ElOnLastRow}) + (${this.gridColumnGap} * (${this.getColumnsCount()} - ${ElOnLastRow})))`;
    const ElHeight = `calc(${this._gridEl.clientHeight}px + ${this.gridColumnGap})`;
    this._el.style.height = ElHeight;
    this._el.style.width = ElWidth;
  }

  // In my case, my grid item are dynamic and I need to adjust
  // the space filler dimensions according to the number of
  // grid items in the grid. I use the MutationObserver
  // to call the this.displayEl() and this.setElDimensions() methods
  // when child elements are added or removed from the grid.
  observeGridWrapper() {
    // Options for the observer (which mutations to observe)
    const config = {
      attributes: false,
      childList: true,
      subtree: true,
    };

    // Callback function to execute when mutations are observed
    const callback = (mutationsList) => {
      for(const mutation of mutationsList) {
        if (mutation.type == 'childList') {
          console.log('A child node has been added or removed.');
          this.displayEl();
          this.setElDimensions();
        }
      }
    };

    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(callback);

    // Start observing the target node for configured mutations
    observer.observe(this._gridWrapper, config);
  }

  // I use this to simulate the adding or the removal of grid items.
  elementAdder() {
    document.querySelector('.js-add-el').addEventListener('click', () => {
      // Create a new element
      const newNode = document.createElement('div');
      newNode.classList.add('card');
      newNode.classList.add(this.gridElClassname);
      // Insert the new node after the last element in the parent node
      this._gridWrapper.append(newNode);
    });
    document.querySelector('.js-remove-el').addEventListener('click', (e) => {
      this._gridWrapper.querySelector(`.${this.gridElClassname}`).remove();
    });
  }
}

new SpaceFillerElement();
ChucKN0risK
  • 395
  • 2
  • 7
  • 18