// some simple utilities to reduce typing;
// caching a reference to the document:
let D = document,
// an alias for document.createElement(), with the option to provide
// properties for the created element:
create = (tag, props) => Object.assign(D.createElement(tag), props),
// an alias for both document.querySelector() - by default - and
// Element.querySelector(), if an Element node is passed as the
// context argument:
get = (selector, context = D) => context.querySelector(selector),
// caching a reference to the element in which the grid should
// be appended:
gridParent = get('.colored-grid[data-size]');
// defining a function to create the grid, taking one argument,
// the element in which the created elements are to be placed:
let createGrid = (source) => {
// if there is no HTMLElement passed to the function we
// return false and quit the function:
if (!source || 1 !== source.nodeType) {
return false;
}
// while the source element has any firstChild (this includes
// text-nodes, comment nodes, and elements:
while (source.firstChild) {
// we use parentNode.removeChild to remove that firstChild;
// had the childNodes been only elements, we could have
// used HTMLElement.remove(), but that would (probably) have
// left an accumulation of text-nodes behind in the DOM (which
// I personally find messy, as inconsequential as it may be):
source.removeChild(source.firstChild);
}
// using destructuring assignment to declare the variables
// 'sz' and 'c', as aliases for 'size' and 'color' from
// the source.dataset Object, as the variables need to
// be modified for use in the function and I prefer to
// use the 'meaningful' names once they're ready for use:
let {
size: sz,
colors: c
} = source.dataset,
// here we create the 'size' variable, after using
// parseInt() to convert the 'sz' string to a base-10
// integer:
size = parseInt(sz, 10),
// and declare the 'colors' variable after creating an
// array of colors from the supplied dataset-attribute;
// first splitting the string on commas:
colors = c.split(/,/)
// using Array.prototype.filter() to retain
// only those Array entries that:
.filter(
// both have an entry, have a non-zero length
// and which is not composed of just white-space:
(color) => color && color.trim().length > 0
)
// we then use Array.prototype.map() to create a
// new Array based on the filtered Array (which
// retains or discards Array-elements, but doesn't
// modify the Array elements):
.map(
// here we use String.prototype.trim() to
// remove leading and trailing white-space:
(color) => color.trim()
),
// creating a document-fragment in order that all
// created elements are appended to the document at
// once, minimising reflows and repaints:
fragment = D.createDocumentFragment();
// to enable the size to be used in CSS, we use
// CSSStyleDeclaration.setProperty() to set a '--size'
// property, initialised to the value of the size integer:
source.style.setProperty(`--size`, size);
// we use Array.from() to create an Array from the provided
// Object, which sets the length equal to the square of the
// size integer (using Math.pow(<integer>,<power>) to do
// so, but size * size would work equally well):
Array.from({
length: Math.pow(size, 2)
})
// iterating over the created Array, which passes in
// a reference to the current Array-element (which is
// undefined), and the index of the current Array-
// element in the created Array:
// within the Arrow function, we create a <div> element,
// set its classList property using a template-literal,
// to add the classes of 'grid-cell', 'row-n' (where n
// is the row-number), 'row-odd' or 'row-even',
// 'column-n' (where n is an integer, and reflects the
// column-number), and column-odd or column-even:
.forEach((_, i) => fragment.append(create('div', {
classList: `grid-cell row-${ Math.floor(i/size) + 1}
row-${ 0 === Math.floor(i/size)%2 ? 'even' : 'odd'}
column-${ Math.floor(i%size) + 1}
column-${ 0 === Math.floor(i%size) + 1 ? 'odd' : 'even' }`,
// we also set the --backgroundColor CSS custom property, which
// cycles through the Array of colors passed via the data-colors
// attribute:
style: `--backgroundColor: ${ colors[(i%size)%colors.length] }`
// the created element (on each iteration of the forEach()) is
// appended to the document fragment using the initial fragment.append()
})));
// appending the fragment to the source, using Element.append():
source.append(fragment);
};
// calling the function, passing in the gridParent element:
createGrid(gridParent);
// the below is largely irrelevant, it's just to enable some editing:
// here we use an Array literal along with spread syntax to create an
// Array of the child elements of the <fieldset> element; we then use
// Array.prototype.forEach() to iterate over those child-elements:
[...get('fieldset').children].forEach(
// passing a reference to the current child element to the function:
(child) => {
// caching a reference to the <input> element within the
// current child element:
let input = get('input', child);
// setting the value of the <input> to be equal to
// the data-[input.name] attribute-value of the
// gridParent element; so the value of the <input>
// is equal to the current attribute-values on page-
// load:
input.value = gridParent.dataset[input.name];
// binding the anonymous function as the event-handler
// for the 'input' event:
input.addEventListener('input', (evt) => {
// using destructuring assignment to set
// the variable 'target' to be equal to the
// 'target' property-value of the Event (evt)
// Object:
let {
target
} = evt, {
name,
value
} = target;
// setting the gridParent's dataset[input.name]
// attribute value to be equal to the value of the
// <input>:
gridParent.dataset[name] = value;
createGrid(gridParent);
});
});
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
margin: 0;
display: flex;
flex-flow: column nowrap;
gap: 1rem;
justify-content: center;
align-items: center;
min-height: 100vh;
/* using syntax from level 4 of the
CSS Colors Module: */
background-color: rgb(255 255 255 / 0.5);
font-family: Arial, sans-serif;
}
form {
backdrop-filter: blur(3px) brightness(300%) hue-rotate(25deg);
background-color: #fff7;
position: sticky;
top: 0;
}
fieldset {
display: flex;
gap: 1rem;
justify-content: space-between;
}
label {
display: grid;
gap: 0.5rem;
padding: 0.5rem;
}
.colored-grid {
counter-reset: cell;
display: grid;
gap: 5px;
height: auto;
grid-auto-rows: 50px;
grid-template-columns: repeat(var(--size), 50px);
}
.grid-cell {
background-color: var(--backgroundColor);
counter-increment: cell;
}
/*
.grid-cell:nth-child(odd) {
background-color: red;
}
.grid-cell:nth-child(even) {
background-color: green;
}
*/
.even {
background-color: red;
}
.odd {
background-color: green;
}
<form action="#" method="post">
<fieldset>
<label>
<span class="labelText">Number of grid-cells:</span>
<input type="number" min="1" max="20" step="1" name="size"></label>
<label>
<span class="labelText">grid-column colours:</span>
<input type="text" name="colors"></label>
</fieldset>
</form>
<!--
data-size: an integer, to define the number of cells on each axis,
data-colors: a comma-separated list of colors to cycle through:
-->
<div class="colored-grid" data-size="10" data-colors="red, green"></div>