Light spectrum to RGB tool
by PhilQ
HTML
<div id="container">
<label>Color method:</label>
<div id="rainbows"></div>
<label for="wavelength">Wavelength:</label>
<input type="number" id="wavelength" />
<input type="range" id="wavelength-slider" />
</div>
<div id="card-wrapper">
<div class="card">
<div></div>
<div></div>
</div>
</div>
<svg id="pattern" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 2560 1600" enable-background="new 0 0 2560 1600" xml:space="preserve">
<pattern width="200" height="200" patternUnits="userSpaceOnUse" id="New_Pattern" viewBox="0 -200 200 200" overflow="visible">
<g>
<rect y="-200" fill="#ffffff" width="200" height="200"/>
<path d="M103.371-82.318c10.899,7.046,22.825,14.239,35.833,21.522c-7.283-13.008-14.476-24.933-21.522-35.833 c-2.724,1.951-5.528,3.917-8.414,5.897C107.288-87.846,105.322-85.041,103.371-82.318z"/>
<path d="M82.318-103.371c-1.576,1.129-3.126,2.253-4.648,3.371c1.522,1.118,3.072,2.242,4.648,3.371 c0.734-1.135,1.466-2.258,2.196-3.371C83.784-101.113,83.052-102.236,82.318-103.371z"/>
<path d="M82.318-96.629C75.272-85.73,68.079-73.804,60.796-60.796c13.008-7.283,24.933-14.476,35.833-21.522 c-1.951-2.724-3.917-5.528-5.897-8.414C87.846-92.712,85.041-94.678,82.318-96.629z"/>
<path d="M96.629-82.318c1.129,1.576,2.253,3.126,3.371,4.648c1.118-1.522,2.242-3.072,3.371-4.648 c-1.135-0.734-2.258-1.466-3.371-2.196C98.887-83.784,97.764-83.052,96.629-82.318z"/>
<path d="M117.682-103.371c7.046-10.899,14.239-22.825,21.522-35.833c-13.008,7.283-24.933,14.476-35.833,21.522 c1.951,2.724,3.917,5.528,5.897,8.414C112.154-107.288,114.958-105.322,117.682-103.371z"/>
<path d="M100-115.486c-3.179,2.087-6.268,4.16-9.268,6.218c-2.058,3-4.131,6.089-6.218,9.268c2.087,3.179,4.16,6.268,6.218,9.268 c3,2.058,6.089,4.131,9.268,6.218c3.179-2.087,6.268-4.16,9.268-6.218c2.058-3,4.131-6.089,6.218-9.268 ...
SCSS
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#container {
display: flex;
width: 1000px;
margin: 0 auto;
padding: 20px;
flex-direction: column;
justify-content: center;
gap: 1rem;
background: #fff;
box-shadow:
0 10px 25px -5px rgba(0, 0, 0, 0.5),
0 5px 10px -5px rgba(0, 0, 0, 0.5);
border-radius: 6px;
font-size: 25px;
}
label {
text-transform: uppercase;
font-size: 0.8rem;
color: rgba(0, 0, 0, 0.3);
margin-bottom: -0.5rem;
}
select, input {
font-size: 1.4rem;
width: 100%;
&[type="radio"] {
width: auto;
margin-right: 0.5rem;
}
}
.rainbow {
width: 100%;
height: 2rem;
background: #eee;
}
@property --mx {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
@property --my {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
#pattern, #pattern2 {
position: absolute;
z-index: -1;
opacity: 0;
width: 0;
height: 0;
}
#pattern2 path {
fill: #fff;
}
#card-wrapper {
position: relative;
width: 100%;
height: 500px;
perspective: 1500px;
perspective-origin: 50% 50%;
&:not(:hover) .card {
animation-name: card-neutral;
animation-duration: 0.35s;
animation-timing-function: ease;
animation-fill-mode: forwards;
animation-iteration-count: 1;
}
}
@keyframes card-neutral {
100% {
--mx: 0.5;
--my: 0.5;
}
}
.card {
--mx: 0;
--my: 0;
position: absolute;
left: 50%;
top: 0;
translate: -50% 0;
width: 1000px;
height: 400px;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 20px 50px -20px rgba(0, 0, 0, 1);
background: repeating-linear-gradient(-35deg, #ccc 0%, #eee 20%, #ccc 40%);
margin: 3rem auto 0;
transform:
rotateX(calc((2 * var(--my) - 1) * -20deg))
rotateY(calc((2 * var(--mx) - 1) *...
JavaScript
/**
* (c) All rights reserved - PvH
*
* Based on article at:
* https://www.baeldung.com/cs/rgb-color-light-frequency
*
* CIE 1931 XYZ color-matching multi-lobe, piecewise Gaussian fit:
* https://jcgt.org/published/0002/02/01/paper.pdf
*/
const lambda_min = 380;
const lambda_max = 750;
let rainbows = null,
inputNumber = null,
inputRange = null,
fakeChangeEvent = new Event("change")
lambda = 405, // 0.5 * (lambda_min + lambda_max),
lambdaToRgb = null,
dummy = null;
const makeElement = (tag, props = {}, events = {}) => {
let element = document.createElement(tag);
for (const [k, v] of Object.entries(props)) {
element.setAttribute(k, v);
}
for (const [k, v] of Object.entries(events)) {
element.addEventListener(k, v);
}
return element;
};
const clamp = (v, min = 0, max = 1) => Math.min(max, Math.max(min, v));
const map = (v, from_min, from_max, to_min, to_max) => {
return to_min + (to_max - to_min) * (v - from_min) / (from_max - from_min);
};
const rgbToHex = (rgb) => `#${rgb.map(c => c.toString(16).padStart(2, '0')).join('')}`;
const colorMappings = {
glynn: [
"Earl F. Glynn mapping",
[380, 420, 440, 490, 510, 580, 645, 700, 780],
(lambda) => {
const r = lambda >= 580
? 1
: (
lambda >= 510
? ((lambda - 510) / (580 - 510))
: (
lambda >= 440
? 0
: ((440 - lambda) / (440 - 380))
)
);
const g = lambda >= 645 || lambda < 440
? 0
: (
lambda >= 580
? ((645 - lambda) / (645 - 580))
: (
lambda >= 490
? 1
: ((lambda - 440) / (490 - 440))
)
);
const b = lambda >= 510
? 0
: (
lambda >= 490
? ((510 - lambda) / (510 - 490))
: 1
);
const...