<div class="wrapper">
<header>
<h1>Compound grid generator</h1>
<div>
<p>A little tool to generate compound grids. Enter the number of columns for each of your grids, and they’ll be magically merged into a compound grid. Use the output in your <code>grid-template-columns</code> property when using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout">CSS Grid</a> for layout.</p>
<a href="https://www.smashingmagazine.com/2019/07/inspired-design-decisions-pressing-matters/">What is a compound grid?</a>
</div>
</header>
<form class="form">
<div class="form__group">
<label for="grid1">Grid 1
<input type="number" id="grid1" value="2" min="2" max="18" data-input />
</label>
<div class="sm-grid" data-sm-grid>
<div class="sm-grid__item"></div>
<div class="sm-grid__item"></div>
</div>
</div>
<div class="form__group">
<label for="grid2">Grid 2
<input type="number" id="grid2" value="4" min="2" max="18" data-input />
</label>
<div class="sm-grid sm-grid--second" data-sm-grid>
<div class="sm-grid__item"></div>
<div class="sm-grid__item"></div>
<div class="sm-grid__item"></div>
<div class="sm-grid__item"></div>
</div>
</div>
<div class="form__info" data-info>
<p>One grid is a multiple of the other</p></div>
<button class="form__button" type="button" data-button>Create grid</button>
</form>
<code class="result" data-result>
1fr 1fr 1fr 1fr
</code>
<div class="grid" data-grid>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
</div>
@import url('https://fonts.googleapis.com/css?family=Ubuntu&display=swap');
$bg: #171926;
* {
box-sizing: border-box;
}
p {
max-width: 70ch;
line-height: 1.5;
color: lighten($bg, 80%);
}
a {
color: lighten($bg, 50%);
&:hover,
&:focus {
color: orchid;
}
}
body {
font-family: 'Ubuntu', sans-serif;
background-color: $bg;
color: white;
font-size: 1.2rem;
padding: 1rem 1rem 3rem 1rem;
min-height: 100vh;
display: flex;
align-items: center;
@media (min-width: 50rem) {
padding: 3rem 3rem 5rem 3rem;
}
}
.wrapper {
width: 100%;
max-width: 75rem;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
@media (min-width: 50rem) {
grid-template-columns: 1fr 2fr;
gap: 3rem;
}
}
header {
grid-column: 1 / -1;
}
.form {
@media (min-width: 50rem) {
grid-column: 1;
grid-row: 2;
}
}
label {
display: flex;
align-items: baseline;
margin-bottom: 1rem;
}
input {
font-size: 1.4rem;
max-width: 6rem;
text-align: center;
margin-left: 1rem;
border: none;
padding: 0.5rem;
background: darken($bg, 10%);
color: white;
}
.result {
@media (min-width: 50rem) {
grid-column: 1;
grid-row: 3;
}
}
.form__button {
font-size: inherit;
font-family: inherit;
padding: 0.8rem 1.9rem;
border: none;
border-radius: 0.5rem;
margin-top: 2rem;
font-weight: 700;
background-color: turquoise;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
min-height: 25rem;
gap: 1.5%;
@media (min-width: 50rem) {
grid-column: 2;
grid-row: 2 / span 2;
}
}
.item {
background-color: orchid;
opacity: 0;
transform: translate3d(0, 0.5rem, 0);
animation: fadeIn 750ms ease-in-out var(--delay, 0ms) forwards;
box-shadow: 0.25rem 0.25rem 1rem darken($bg, 5%);
}
@keyframes fadeIn {
100% { opacity: 1; transform: translate3d(0, 0, 0); };
}
.sm-grid {
width: 9rem;
display: grid;
grid-template-columns: repeat(var(--cols, 2), 1fr);
grid-auto-rows: 5rem;
gap: 0.3rem;
margin-left: 2rem;
}
.sm-grid--second {
grid-template-columns: repeat(var(--cols, 4), 1fr);
}
.sm-grid__item {
background-color: rgba(turquoise, 0.3);
}
.form__group {
margin-bottom: 2rem;
display: flex;
}
.form__info {
--primary: #{lighten($bg, 40%)};
background-color: darken($bg, 3%);
padding: 0.25rem 1.5rem;
font-size: 1rem;
opacity: 0;
border-radius: 0.3rem;
position: relative;
border: 1px solid var(--primary);
&::before {
content: '\2139';
position: absolute;
top: -0.6rem;
font-size: 1.1rem;
color: var(--primary);
border-radius: 50%;
width: 1.45rem;
height: 1.45rem;
border: 2px solid var(--primary);
text-align: center;
background-color: $bg;
}
}
View Compiled
const code = document.querySelector('[data-result]')
const grid = document.querySelector('[data-grid]')
const button = document.querySelector('[data-button]')
const inputs = [...document.querySelectorAll('[data-input]')]
const smallGrids = [...document.querySelectorAll('[data-sm-grid]')]
const infoBox = document.querySelector('[data-info]')
const real_sort = function(a, b) {return a - b}
const getInputValues = () => {
return inputs.map(input => parseInt(input.value))
}
let grids = getInputValues()
const createArray = (n) => {
const newArray = new Array(n).fill(1)
return newArray.map((i, index) => 1 / n * index).slice(1)
}
const initialValues = () => {
const grid1 = createArray(grids[0])
const grid2 = createArray(grids[1])
return [...grid1, ...grid2, 1].sort()
}
const render = (columns) => {
const gridColumns = columns.map((column) => {
return `${column}fr`
}).join(' ')
code.innerHTML = gridColumns
grid.style.gridTemplateColumns = gridColumns
renderGridElements(columns)
}
const gridIsMultiple = () => {
const largestGrid = grids.sort(real_sort)[1]
const smallestGrid = grids.sort(real_sort)[0]
return largestGrid % smallestGrid === 0
}
const renderGridElements = (columns) => {
const gridColumnElements = columns.map((el, index) => {
return `<div class="item" style="--delay: ${index * 70}ms"></div>`
}).join('')
grid.innerHTML = gridColumnElements
}
const calculateGridColumns = () => {
const smallestSegment = initialValues().reduce((acc, curr) => {
let x = 1
x = curr - acc < x ? curr - acc : x
return x
})
let y = 0
const tracks = []
initialValues().map((column, index) => {
const n = column / smallestSegment
const roundedTrackValue = parseFloat(n.toFixed(2))
/* Don’t include duplicate values */
if (!tracks.includes(roundedTrackValue)) {
return tracks.push(roundedTrackValue)
}
})
const columns = []
/* Probably refactor this */
tracks.map((i, index) => {
/* Multiply each column so it’s at least 1fr */
const column = (i - y) * 10
columns.push(column.toFixed(1))
y = i
})
/* Get the smallest column, that will be 1fr, others will be n * 1fr */
const sm = columns.reduce((acc, curr) => acc > curr ? curr : acc)
const colsFrs = columns.map((col) => Math.round(col / sm))
/* Filter out any that are 0 */
return colsFrs.filter((i) => i > 0)
}
const buildGrid = (e) => {
e.preventDefault()
/* If one grid is multiple of the other, find the largest and build that grid */
if (gridIsMultiple()) {
const columns = new Array(grids.sort()[1]).fill(1)
return render(columns)
}
return render(calculateGridColumns())
}
button.addEventListener('click', (e) => buildGrid(e))
const renderSmallGridItems = (quantity) => {
const colsArray = new Array(quantity).fill(1)
return colsArray.map((el) => {
return `<div class="sm-grid__item"></div>`
}).join('')
}
const renderSmallGrid = (index) => {
const qty = getInputValues()[index]
smallGrids[index].style.setProperty('--cols', qty)
smallGrids[index].innerHTML = renderSmallGridItems(qty)
}
const renderInfo = () => {
if (gridIsMultiple()) {
infoBox.style.opacity = 1
infoBox.setAttribute('aria-hidden', false)
} else {
infoBox.style.opacity = 0
infoBox.setAttribute('aria-hidden', true)
}
}
inputs.forEach((input, index) => {
input.addEventListener('change', () => {
grids = getInputValues()
renderSmallGrid(index)
renderInfo()
})
})
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.