Using CSS grid and a few lines of JavaScript, visualize a bubble sort algorithm with a bit of a rainbow.
A Pen by Gabriele Corti on CodePen.
Using CSS grid and a few lines of JavaScript, visualize a bubble sort algorithm with a bit of a rainbow.
A Pen by Gabriele Corti on CodePen.
<!-- include a single button, to be absolute positioned in the center of the viewport | |
the columns of different colors are added through the script | |
--> | |
<button> | |
🌈 Sort <span>🌈</span> | |
</button> |
/* | |
variables tweaking the design | |
- numberOfColumns: the number of columns to be included in the viewport | |
! the value is capped to 360 to have at most one color for each hue in the [0-360] color wheel | |
- duration: the number of seconds it takes to complete the bubble sort | |
! this does not account for the animation of the button, but the animation of the columns only | |
- lightness and saturation: default values for the hsl color | |
*/ | |
const numberOfColumns = 359; | |
const duration = 7.5; | |
const saturation = 70; | |
const lightness = 70; | |
// array describing the hues of the different columns, to avoid doubles | |
const hues = []; | |
// function returning a random hue, which is not already in the hues array | |
const uniqueHue = () => { | |
const hue = Math.floor(Math.random() * 360); | |
// if the hue is already present re-run the function | |
if (hues.includes(hue)) { | |
return uniqueHue(); | |
} | |
// else add the hue to the array and return its value | |
hues.push(hue); | |
return hue; | |
}; | |
// select the node in which to add the columns | |
const body = document.querySelector('body'); | |
// in the body include as many div elements as specified by the columns variable | |
for (let i = 0; i < Math.min(numberOfColumns, 359); i += 1) { | |
const columns = document.createElement('div'); | |
// with a unique hue | |
const hue = uniqueHue(); | |
columns.style.background = `hsl(${hue}, ${saturation}%, ${lightness}%)`; | |
// with a transition incrementally delayed for each successive column | |
// ! ultimately this has the effect of swapping the columns one after the other | |
columns.style.transition = `all 0.2s ${duration / numberOfColumns * i}s ease-out`; | |
// add a data attribute to rapidly order the columns according to the hue value | |
columns.setAttribute('data-hue', hue); | |
body.appendChild(columns); | |
} | |
// target the button | |
const button = document.querySelector('button'); | |
// function called to sort the items of an array according to data-hue attribute | |
function bubbleSort(arr) { | |
// bubble sort algorithm | |
// instead of swapping the items based on their values, swap them on the basis of the data-hue attribute | |
for (let n = arr.length; n >= 0; n -= 1) { | |
for (let i = 0; i < n - 1; i += 1) { | |
const currentPosition = i; | |
const nextPosition = i + 1; | |
const current = arr[currentPosition]; | |
const next = arr[nextPosition]; | |
const currentHue = Number.parseInt(current.getAttribute('data-hue'), 10); | |
const nextHue = Number.parseInt(next.getAttribute('data-hue'), 10); | |
if (currentHue > nextHue) { | |
[arr[currentPosition], arr[nextPosition]] = [arr[nextPosition], arr[currentPosition]]; | |
} | |
} | |
} | |
return arr; | |
} | |
// function called when the button is pressed | |
function handleClick() { | |
// add the class prompting the transition | |
button.classList.add('clicked'); | |
// once the button's animation is complete | |
button.addEventListener('animationend', () => { | |
// target all the columns | |
const columns = body.querySelectorAll('div'); | |
// sort an copy of the node list, converting it first to an array | |
const sortedColumns = bubbleSort([...columns]); | |
// loop through the sortedColumns array | |
for (let i = 0; i < sortedColumns.length; i += 1) { | |
// find the column in the unsorted data structure with the data-hue attribute matching the sorted value | |
const sortedColumn = sortedColumns[i]; | |
const matchingColumn = [...columns].find(column => column.getAttribute('data-hue') === sortedColumn.getAttribute('data-hue')); | |
// apply a property of order beginning with the smallest values, pushing the smaller hues to the beginning of the grid | |
matchingColumn.style.order = i - sortedColumns.length; | |
} | |
}); | |
} | |
// on click call the function prompting the sorting and the application of the order property according to the hue | |
button.addEventListener('click', handleClick); |
@import url("https://fonts.googleapis.com/css?family=Luckiest+Guy"); | |
* { | |
box-sizing: border-box; | |
padding: 0; | |
margin: 0; | |
} | |
/* specify the width and height of the body to encompass the viewport */ | |
body { | |
width: 100vw; | |
height: 100vh; | |
/* include a grid whose items are automatically added as column and occupy a fraction of the width */ | |
display: grid; | |
grid-auto-columns: 1fr; | |
grid-auto-flow: column; | |
} | |
/* absolute position the button in the center of teh viewport */ | |
button { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
padding: 0.6rem 1.25rem; | |
background: hsl(0, 0%, 100%); | |
border: none; | |
border-radius: 10px; | |
box-shadow: 0 2px 5px hsla(0, 0%, 0%, 0.3); | |
color: #1c2d4a; | |
text-transform: uppercase; | |
font-family: "Luckiest Guy", cursive; | |
font-size: 2rem; | |
letter-spacing: 0.3rem; | |
/* transition for the transform and box-shadow properties */ | |
transition: all 0.3s ease-out; | |
} | |
/* on hover slightly elevate the button */ | |
button:hover { | |
transform: translate(-50%, calc(-50% - 2px)); | |
box-shadow: 0 2px 7px hsla(0, 0%, 0%, 0.4); | |
} | |
/* when a class of .clicked is added (through the script) animate the button out of sight */ | |
button.clicked { | |
animation: clicked 0.8s cubic-bezier(0.13, 1.03, 0.72, 1.32) forwards; | |
} | |
/* animation to have the button pushed downward and then disappear as it returns to its original location */ | |
@keyframes clicked { | |
10%, | |
35% { | |
transform: translate(-50%, calc(-50% + 8px)) scaleY(0.95); | |
box-shadow: 0 1px 5px hsla(0, 0%, 0%, 0.2); | |
opacity: 1; | |
visibility: visible; | |
} | |
80%, | |
100% { | |
opacity: 0; | |
visibility: hidden; | |
} | |
} | |
button span { | |
display: inline-block; | |
transform: rotateY(180deg); | |
} |