simple function that generates color palettes based on color ramps used by pixelartists
A Pen by HARUN PEHLİVAN on CodePen.
simple function that generates color palettes based on color ramps used by pixelartists
A Pen by HARUN PEHLİVAN on CodePen.
<article> | |
<h2>HSL Slice Preview</h2> | |
<figure> | |
<svg data-figure viewbox="0 0 100 100"> | |
</svg> | |
</figure> | |
<h2>Full Palette</h2> | |
<div data-colors></div> | |
<h2> | |
Example Use: <strong>4 random colors form palette</strong> | |
(click to re-generate) | |
</h2> | |
<div data-palette> | |
<b> | |
<i></i> | |
<i></i> | |
</b> | |
</div> | |
<div data-ramp></div> | |
</article> |
// https://medium.com/@greggunn/how-to-make-your-own-color-palettes-712959fbf021 | |
const hsv2hsl = (h,s,v,l=v-v*s/2, m=Math.min(l,1-l)) => [h,m?(v-l)/m:0,l]; | |
const random = (min, max) => { | |
if (!max) { | |
max = min; | |
min = 0; | |
} | |
return Math.random() * (max - min) + min | |
}; | |
const shuffleArray = array => { | |
let arr = [...array]; | |
for (let i = arr.length - 1; i > 0; i--) { | |
const j = Math.floor(Math.random() * (i + 1)); | |
[arr[i], arr[j]] = [arr[j], arr[i]]; | |
} | |
return arr; | |
} | |
const generateRandomColorRamp = ( | |
total, | |
baseColorH = random(360), | |
hueCycle = .3, | |
offset = 0.05, | |
curveMod = 0, | |
hueShift = .1, | |
) => { | |
const baseColors = []; | |
const SL = []; | |
const part = (Math.PI/2) / (total + 1); | |
for (let i = 1; i < (total + 1); i++) { | |
/* | |
-(Math.sin(-Math.PI/2 + i * part - curveMod) + offset), | |
1 - Math.cos(-Math.PI/2 + i * part + curveMod) - offset | |
*/ | |
/* | |
let x = Math.cos(-Math.PI/2 + i * part + curveMod) - offset; | |
let y = Math.sin(-Math.PI/2 + i * part - curveMod) + offset; | |
let hsl = hsv2hsl(0,x,-y); | |
*/ | |
/* | |
let hsl = hsv2hsl( | |
(baseColorH + i * (360/total) * hueCycle) % 360, | |
(Math.cos(-Math.PI/2 + i * part + curveMod)), | |
(1 + Math.sin(-Math.PI/2 + i * part - curveMod)) | |
) | |
*/ | |
let hsl = hsv2hsl( | |
(baseColorH + i * (360/total) * hueCycle) % 360, | |
Math.cos(-Math.PI/2 + i * part + curveMod), | |
-Math.sin(-Math.PI/2 + i * part - curveMod) | |
) | |
baseColors.push( | |
[ | |
hsl[0], | |
hsl[1], | |
hsl[2] | |
] | |
) | |
/* | |
(offset + Math.cos(-Math.PI/2 + i * part + curveMod)), | |
(offset + 1 + Math.sin(-Math.PI/2 + i * part - curveMod)) | |
*/ | |
} | |
const lightColors = baseColors.map(c => [(c[0] + 360 * hueShift) % 360, c[1] - offset, c[2] + offset]) | |
const darkColors = baseColors.map(c => [(c[0] - 360 * hueShift) % 360, c[1] - offset, c[2] - offset]) | |
return [...lightColors,...baseColors,...darkColors]; | |
} | |
const pane = new Tweakpane.Pane(); | |
const PARAMS = { | |
colors: 9, | |
baseColorH: 280, | |
hueCycle: .4, | |
offset: 0.1, | |
curveMod: 0, | |
hueShift: .02, | |
}; | |
// `min` and `max`: slider | |
pane.addInput( | |
PARAMS, 'colors', | |
{min: 3, max: 15, step: 1} | |
); | |
pane.addInput( | |
PARAMS, 'baseColorH', | |
{min: 0, max: 360, step: 0.1} | |
); | |
pane.addInput( | |
PARAMS, 'hueCycle', | |
{min: 0, max: 1.5, step: 0.001} | |
); | |
pane.addInput( | |
PARAMS, 'offset', | |
{min: 0, max: 0.4, step: 0.001} | |
); | |
pane.addInput( | |
PARAMS, 'curveMod', | |
{min: 0, max: 1, step: 0.001} | |
); | |
pane.addInput( | |
PARAMS, 'hueShift', | |
{min: 0, max: 1, step: 0.001} | |
); | |
const $pal = document.querySelector('[data-palette]'); | |
const $picker = document.querySelector('[data-figure]'); | |
const $ramp = document.querySelector('[data-ramp]'); | |
let colors = []; | |
const palette = (colors, method) => { | |
let allColors = []; | |
const lightColors = shuffleArray( colors.slice(0, colors.length/3 - 1) ); | |
const mediumColors = shuffleArray( colors.slice(colors.length/3 - 1, (colors.length/3) * 2 - 1) ); | |
const darkColors = shuffleArray( colors.slice((colors.length/3) * 2 - 1, colors.length - 1) ); | |
switch (method) { | |
case 'random': | |
allColors = shuffleArray(colors); | |
break; | |
case 'l2md': | |
allColors = [lightColors[0], mediumColors[0], darkColors[0], lightColors[1]]; | |
break; | |
case 'lmd2': | |
allColors = [darkColors[0], mediumColors[0], lightColors[0], darkColors[1]]; | |
break; | |
case 'lm2d': | |
allColors = [mediumColors[1], mediumColors[0], lightColors[0], darkColors[1]]; | |
break; | |
} | |
for(let i = 0; i < 4; i++) { | |
$pal.style.setProperty(`--col-${i}`, `hsl(${allColors[i][0]},${allColors[i][1] * 100}%,${allColors[i][2] * 100}%)`); | |
} | |
$ramp.style.setProperty('background', `linear-gradient(90deg, ${allColors.slice(0,4).map(c => `hsl(${c[0]},${c[1] * 100}%,${c[2] * 100}%)`).join(',')})`) | |
} | |
function bam() { | |
colors = generateRandomColorRamp( | |
PARAMS.colors, | |
PARAMS.baseColorH, | |
PARAMS.hueCycle, | |
PARAMS.offset, | |
PARAMS.curveMod, | |
PARAMS.hueShift, | |
); | |
points(PARAMS.colors, PARAMS.offset, PARAMS.curveMod, colors.slice(colors.length/3, (colors.length/3) * 2)); | |
/* | |
let shuffledcolors = colors | |
.map((a) => ({sort: Math.random(), value: a})) | |
.sort((a, b) => a.sort - b.sort) | |
.map((a) => a.value)*/ | |
$picker.style.setProperty(`--deg`, `${colors[Math.floor(colors.length * .5)][0]}deg`); | |
document.querySelector('[data-colors]').innerHTML = colors.reduce((r,c) => `${r}<i style="--w: ${1/PARAMS.colors}; --h: ${c[0]}; --s: ${c[1] * 100}; --l: ${c[2] * 100}"></i>`,''); | |
palette(colors, 'random'); | |
} | |
function points (colorsInt, offset, curveMod, colorsArr) { | |
$picker.innerHTML = ''; | |
const part = (Math.PI/2) / (colorsInt + 1); | |
for (let i = 1; i < (colorsInt + 1); i++) { | |
let x = Math.cos(-Math.PI/2 + i * part + curveMod) - offset; | |
let y = Math.sin(-Math.PI/2 + i * part - curveMod) + offset; | |
let hsl = hsv2hsl(0,x,-y); | |
let newElement = document.createElementNS("http://www.w3.org/2000/svg", 'circle'); | |
newElement.setAttribute('cx', x * 100); | |
newElement.setAttribute('cy', 100 + y * 100); | |
newElement.setAttribute('r','3'); | |
newElement.style.fill = `hsl(${colorsArr[i-1][0]}deg, ${hsl[1] * 100}%,${hsl[2] * 100}%)`; //Set stroke colour | |
newElement.style.stroke = '#212121'; //Set stroke colour | |
newElement.style.strokeWidth = '.5px'; //Set stroke width | |
$picker.appendChild(newElement); | |
} | |
} | |
$pal.addEventListener('click', () => palette(colors, 'random')); | |
pane.on('change', bam); | |
bam(); | |
<script src="https://cdn.jsdelivr.net/npm/tweakpane@3.0.3/dist/tweakpane.min.js"></script> |
@import url('https://rsms.me/inter/inter.css'); | |
:root { | |
font-family: 'Inter', sans-serif; | |
background: #212121; | |
} | |
article { | |
width: 25rem; | |
padding: 2rem; | |
background: #212121; | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
overflow: auto; | |
//display: flex; | |
//flex-direction: column; | |
} | |
h2 { | |
margin: 2rem 0 1rem; | |
color: #fff; | |
&:first-child { | |
margin-top: 0; | |
} | |
} | |
[data-colors] { | |
display: flex; | |
flex-wrap: wrap; | |
width: 100%; | |
} | |
[data-colors] i { | |
flex: 1 0 calc(var(--w, 0.11) * 100%); | |
width: calc(var(--w, 0.11) * 100%); | |
padding-top: calc(var(--w, 0.11) * 100%); | |
background: hsl(var(--h), calc(var(--s) * 1%), calc(var(--l) * 1%)) | |
} | |
[data-palette] { | |
position: relative; | |
background: var(--col-0); | |
padding-top: 100%; | |
margin: 2rem 0; | |
b { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
width: 50%; | |
height: 50%; | |
transform: translate(-50%,-50%); | |
background: var(--col-1); | |
} | |
i { | |
position: absolute; | |
width: 50%; | |
height: 50%; | |
right: 0; | |
} | |
i:first-child { | |
background: var(--col-2); | |
} | |
i:last-child { | |
bottom: 0; | |
background: var(--col-3); | |
} | |
} | |
figure { | |
margin: 2rem 0; | |
padding: 0; | |
} | |
[data-figure] { | |
background-image: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0)), | |
linear-gradient(to left, hsl(var(--deg, 0deg), 100%, 50%), hsl(var(--deg, 0deg), 0%, 100%)); | |
} | |
[data-ramp] { | |
height: 4rem; | |
} |