let hexResult, rgbResult;
function updateData() {
let background = document.getElementById('background');
let bgTextarea = document.getElementById('bg');
let foreground = document.getElementById('foreground');
let preview = document.getElementById('preview');
let result = document.getElementById('result');
let rgb = document.getElementById('rgb');
let hex = document.getElementById('hex');
let rgbaTextarea = document.getElementById('rgba');
let rgba = hexToRgb(rgbToHex(rgbaTextarea.value));
let bg = hexToRgb(rgbToHex(bgTextarea.value));
rgbResult = rgbaRgbToRGB(rgba, bg); // define locally if you don't need to copy to clipboard
hexResult = rgbToHex(rgbResult); // define locally if you don't need to copy to clipboard
let textColor = lumens(rgbResult);
background.style.backgroundColor = bgTextarea.value;
foreground.style.backgroundColor = rgbaTextarea.value;
foreground.style.color = textColor;
if (rgbaTextarea.value.indexOf('#') !== -1 && rgbaTextarea.value.indexOf(' ') !== -1) {
foreground.style.opacity = rgbaTextarea.value?.split(' ')[1];
}
result.style.backgroundColor = rgbResult;
result.style.color = textColor;
rgb.innerHTML = rgbResult;
hex.innerHTML = hexResult;
}
function hexToRgb(hex) {
if (hex.indexOf('rgb') !== -1) return hex;
if (hex.indexOf(',') !== -1) return hex;
var value = hex?.split(' ')?.[0]?.replace(/:;\s+/g, ''),
percentAlpha = hex?.split(' ')?.[1]?.replace(/%/g, ''),
rValue = parseInt(value.slice(1, 3), 16),
rColor = rValue > 255 ? '255' : rValue < 0 ? '0' : rValue?.toString(),
gValue = parseInt(value.slice(3, 5), 16),
gColor = gValue > 255 ? '255' : gValue < 0 ? '0' : gValue?.toString(),
bValue = parseInt(value.slice(5, 7), 16),
bColor = bValue > 255 ? '255' : bValue < 0 ? '0' : bValue?.toString(),
aValue =
percentAlpha === undefined
? parseInt(value.slice(7, 9), 16) !== NaN
? parseInt(value.slice(7, 9), 16)
: 255
: percentAlpha > 1
? percentAlpha / 100 * 255
: percentAlpha * 255,
alpha = aValue >= 255 ? '1' : aValue < 1 ? '0' : (aValue / 255)?.toString(),
final = `${
alpha === '1' || alpha === 'NaN'
? `rgb(${rColor},${gColor},${bColor})`
: `rgba(${rColor},${gColor},${bColor},${alpha})`
}`,
test =
/(^rgb\((\d+),\s*(\d+),\s*(\d+)\)$)|(^rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+(.\d+)?)*\)$)/i.exec(
final
);
return test ? final : 'rgb(255,255,255)';
}
function rgbToHex(rgb) {
if (rgb?.indexOf('#') !== -1) return rgb;
var value = rgb.replace(/([rgba()\s])/g, '')?.split(',')
rValue = parseInt(value[0], 10),
rString = rValue > 255 ? 'ff' : rValue < 0 ? '00' : rValue.toString(16),
rColor = rString ? (rString.length === 1 ? '0' + rString : rString) : 'ff',
gValue = parseInt(value[1], 10),
gString = gValue > 255 ? 'ff' : gValue < 0 ? '00' : gValue.toString(16),
gColor = gString ? (gString.length === 1 ? '0' + gString : gString) : 'ff',
bValue = parseInt(value[2], 10),
bString = bValue > 255 ? 'ff' : bValue < 0 ? '00' : bValue.toString(16),
bColor = bString ? (bString.length === 1 ? '0' + bString : bString) : 'ff',
aValue =
!value[3] || parseFloat(value[3]) === 1
? 255 // undefined alpha is opaque
: parseFloat(value[3]) === 0
? 0 // zero alpha is transparent
: value[3]?.indexOf('%') !== -1 // value is percent
? (parseFloat(value[3], 10) / 100) * 255 // divide by 100 multiply by 255
: value[3]?.indexOf('.') !== -1 // value is decimal
? parseFloat(value[3], 10) > 1
? parseInt(value[3], 10) // value out of 255
: parseInt(value[3] * 255, 10) // multiply by 255
: parseInt(value[3], 10) // value exists
? parseInt(value[3], 10) // value out of 255
: 255, // default to 255
aString =
aValue >= 255
? 'ff'
: aValue <= 0
? '00'
: parseInt(aValue)?.toString(16),
aColor = aString.length === 1 ? '0' + aString : aString,
final = `#${rColor}${gColor}${bColor}${aColor !== 'ff' ? aColor : ''}`.toLowerCase(),
test = /^#?([a-f0-9]{8}|[a-f0-9]{6}|[a-f0-9]{3})$/i.exec(final);
return test ? final : '#FFFFFF';
}
function rgbaRgbToRGB(rgba, bg) {
let rgbav = rgba.split(' ')[0].replace(/([(rgb)a])/g, '').split(','),
bgv = bg.replace(/([(rgb)a])/g, '').split(','),
alpha = parseFloat(rgbav[3], 10),
beta = 1 - alpha,
alphaR = parseInt(rgbav[0] || 0, 10),
alphaG = parseInt(rgbav[1] || 0, 10),
alphaB = parseInt(rgbav[2] || 0, 10),
betaR = parseInt(bgv[0] || 0, 10),
betaG = parseInt(bgv[1] || 0, 10),
betaB = parseInt(bgv[2] || 0, 10),
targetR = parseInt((beta * (betaR / 255) + alpha * (alphaR / 255)) * 255),
targetG = parseInt((beta * (betaG / 255) + alpha * (alphaG / 255)) * 255),
targetB = parseInt((beta * (betaB / 255) + alpha * (alphaB / 255)) * 255),
final = `rgb(${targetR},${targetG},${targetB})`;
return alpha?.toString() === 'NaN' || alpha >= 1 ? rgba : alpha <= 0 ? bg : final;
}
function lumens(color) {
var nums = hexToRgb(color).replace(/([rgba()\s])/g, '')?.split(','),
r = parseInt(nums[0], 10),
g = parseInt(nums[1], 10),
b = parseInt(nums[2], 10),
lum = (r * 299 + g * 587 + b * 114) / 1000;
if (lum >= 128) {
return 'rgb(255,255,255)';
}
return 'rgb(0,0,0)';
}
function copyRGB() {
navigator.clipboard.writeText(rgbResult);
}
function copyHex() {
navigator.clipboard.writeText(hexResult);
}
updateData();
body {
overflow: hidden;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
height: 100vh;
width: 100vw;
padding: 0;
margin: 0;
}
form {
display: flex;
flex-direction: column;
width: calc(100% - 6px);
}
textarea {
width: 100%;
height: 100%;
}
#preview {
position: relative;
}
#background {
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
top: 0;
left: 0;
}
#foreground {
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
top: 0;
left: 0;
display: flex;
flex-direction: column;
padding-top: 3px;
}
#foreground > div {
height: 50%;
}
#result {
display: flex;
flex-direction: column;
}
#hex, #rgb {
width: 100%;
height: 100%;
}
#hex {
padding-top: 3px;
}
#rgb {
padding-top: 3px;
}
<!--
This simple converter helps determine the solid rgb code for any translucent forground color on an opaque background color. For accessibility, the text color is changed for maximum contrast. Please note that maximum contrast might not be enough contrast in some scenarios and you should always follow the lastest WCAG.
To get started, simply copy/paste the rgb or hex values into the background container and the rgba or octal values into the rgba container. The other areas will automatically update to show the actual real world example in the middle and the calculated value on the right.
Opacity is great in a design to keep a similar color palette and maintian cohesion, but it is horrible for use in responsive and dynamic designs due overlapping elements or the same colors being applied to other elements. This tool allows you to keep pixel perfect designs while making them easier to work with.
-->
<form>
<textarea id="bg" oninput="updateData()" placeholder="background (rgb or hex only)">rgb(255,255,255)</textarea>
<textarea id="rgba" oninput="updateData()" placeholder="foreground (rgba, octal, or hex opacity)">rgba(0,0,255,0.625)</textarea>
</form>
<div id="preview">
<span id="background"></span>
<span id="foreground">
<div>background (rgb or hex only)</div>
<div>foreground (rgba, octal, or hex opacity)</div>
</span>
</div>
<div id="result">
<span id="hex" onclick="copyHex()"></span>
<span id="rgb" onclick="copyRGB()"></span>
</div>