Search code examples
javascripthtmlcssposition

Center multiple elements around parent midpoint


I have to float multiple elements around the midpoint of their element. This is currently solved in css by using display: table / display: table-cell.

The problem with this solution is that each group of blocks must wrapped in an extra element, what makes it hard to layout this responsive. Moreover, the visible order is not correct.

I would like to use javascript, to align the elements by using position: absolute, but I have just no clue how to calculate the offsets. Another option might be to create each group dynamically (depending on the window width / height), and to use the actual css (below) to align the elements.

html, body {
  height: 100%;
}

.blocks {
  display: table;
  margin-left: auto;
  margin-right: auto;
  max-width: 40em;
  width: 100%;
  height: 100%;
}

.group {
  display: table-cell;
  vertical-align: middle;
}

.block {
  background-color: rgb(50, 50, 50);
  color: rgb(255, 255, 255);
  height: 8em;
  line-height: 8em;
  margin: 1em;
  text-align: center;
  width: 8em;
}

/* Debug
------------------------------------------------ */
 .blocks {
  counter-reset: tile;
}
.block:before {
  counter-increment: tile;
  content: counter(tile);
}
<!-- small screens -->
<div class="blocks">
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
  </div>
</div>

<hr>

<!-- large screens -->
<div class="blocks">
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
  </div>
</div>

<!-- huge screens -->
<div class="blocks">
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
    <div class="block"></div>
  </div>
  <div class="group">
    <div class="block"></div>
    <div class="block"></div>
  </div>
</div>

...for completeness, position:absolute approach:

.blocks {
  margin-left: auto;
  margin-right: auto;
  max-width: 40em;
  position: relative;
}
.block {
  background-color: rgb(0, 225, 225);
  border: 1px solid white;
  box-sizing:border-box;
  height: 8em;
  line-height: 8em;
  position: absolute;
  text-align: center;
  width: 8em;
}
.block:nth-of-type(1) {
  top: 8em;
  left: 0;
}
.block:nth-of-type(2) {
  top: 4em;
  left: 8em;
}
.block:nth-of-type(3) {
  top: 0;
  left: 16em;
}
.block:nth-of-type(4) {
  top: 4em;
  left: 24em;
}
.block:nth-of-type(5) {
  top: 8em;
  left: 32em;
}
.block:nth-of-type(6) {
  top: 16em;
  left: 0;
}
.block:nth-of-type(7) {
  top: 12em;
  left: 8em;
}
.block:nth-of-type(8) {
  top: 8em;
  left: 16em;
}
.block:nth-of-type(9) {
  top: 12em;
  left: 24em;
}
.block:nth-of-type(10) {
  top: 16em;
  left: 32em;
}
.block:nth-of-type(11) {
  top: 20em;
  left: 8em;
}
.block:nth-of-type(12) {
  top: 16em;
  left: 16em;
}
.block:nth-of-type(13) {
  top: 20em;
  left: 24em;
}
.block:nth-of-type(14) {
  top: 24em;
  left: 16em;
}
/* Debug
------------------------------------------------ */
 .blocks {
  counter-reset: tile;
}
.block:before {
  counter-increment: tile;
  content: counter(tile);
}
<div class="blocks">
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
  <div class="block"></div>
</div>

Ideally the layout would respect both, the window width and height. So that the blocks always fit in the current screen size. Could be done with something like this:

var cols = Math.floor(innerWidth / children[0].clientWidth)
var rows = Math.round(innerHeight / children[0].clientHeight)

I have searched now for a while, but have not found any existing solutions. The closest are:

By the way, I'm inspired by https://typekit.com/ ;)


Solution

  • Got it... A percentage based solution, that creates an evenly spaced grid.

    It feels a bit hacky, but should work:

    function alignAround(elements, columns, gutter, weight) {
    
      var width = ((2 / 3) * 100) - (gutter / 2)
      var push = 100 - width
    
      var gutterCount = columns - 1
      var repeatAt = (columns * 2) - 1
    
      var firstShort = 1
      var lastShort = gutterCount
      var lastLong = repeatAt
    
      if (weight === 'odd') {
        firstShort = columns + 1
        lastShort = repeatAt
        lastLong = columns
      }
    
      if (columns > 1) {
        width = ((100 - (gutter * gutterCount)) / columns / 100) * 100
        push = (width + gutter) / 2
      }
    
      return Array.prototype.forEach.call(elements, function(element, index) {
    
        var styles = {
          width: width,
          height: width
        }
    
        if (columns > 1) {
          var i = index + 1
    
          styles.marginRight = gutter
    
          if ((i - firstShort) % repeatAt === 0) {
            styles.marginLeft = push
          }
    
          if ((i - lastShort) % repeatAt === 0) {
            styles.marginRight = push
          }
    
          if ((i - lastLong) % repeatAt === 0) {
            styles.marginRight = 0
          }
        } else if (columns === 1) {
    
          if (index & 1) {
            styles[weight === 'odd' ? 'margin-left' : 'margin-right'] = push
          } else {
            styles[weight === 'odd' ? 'margin-right' : 'margin-left'] = push
          }
    
        }
    
        for (var key in styles) {
          element.style[key] = styles[key] + '%'
        }
      })
    }
    
    alignAround(document.querySelectorAll('.block'), 4, 2)
    html, body, .blocks {
      height: 100%;
    }
    
    .block {
      background: #e44;
      float: left;
    }
    
    /* Debug
    ------------------------------------------------ */
    .blocks {
      counter-reset: tile;
    }
    .block:before {
      counter-increment: tile;
      content: counter(tile);
    }
    <div class="blocks">
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
      <div class="block"></div>
    </div>

    Sorry, code not commented...