<main>
<div class="element">
<div id="contrast-ratio">
<h3>Contrast Ratio</h3>
<div id="contrast-ratio-value" class="val"></div>
</div>
<div id="wcag-grading">
<h3>WCAG Grading</h3>
<div id="wcag-grading-value" class="val"></div>
</div>
</div>
</main>
<aside>
<div class="form">
<div class="control">
<label for="color1">Color1:</label>
<input type="color" value="#000000" name="color1" id="color1" />
</div>
<div class="control">
<label for="color2">Color2:</label>
<input type="color" value="#ffffff" name="color2" id="color2" />
</div>
<div class="control">
<label for="size">Text Size:</label>
<input type="number" min="0" max="60" value="14" step="1" name="size" id="size" />
</div>
<div class="control">
<span>Bold:</span>
<span class="checkbox">
<input type="checkbox" id="bold" name="style" />
<label for="bold"></label>
</span>
</div>
</div>
<div class="content">
<div class="overflow-scroll">
<div id="innerContent"></div>
</div>
</div>
</aside>
@import url("https://fonts.googleapis.com/css?family=Gochi+Hand");
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bgColor: #291642;
--color: rgb(120, 144, 250);
}
body {
margin: 0;
padding: 0;
background-color: var(--bgColor);
font-family: "Gochi Hand", sans-serif;
color: #fff;
font-size: 130%;
letter-spacing: 0.1rem;
display: grid;
grid-template-columns: 1fr auto;
grid-row-gap: 2vh;
width: 100vw;
height: 100vh;
padding: 10px;
overflow: hidden;
}
main {
margin: auto;
}
aside {
padding: 2vh;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background-image: radial-gradient(circle at center, var(--color), #291642),
linear-gradient(to bottom, #291642, #291642);
background-repeat: no-repeat;
background-position: center;
background-clip: padding-box;
border-radius: 8px;
background-blend-mode: multiply, screen;
}
.content {
flex: 1;
min-height: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.overflow-scroll {
padding-top: 30px;
overflow-y: auto;
display: flex;
flex-direction: column;
width: 100%;
text-align: left;
}
.form {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2vh 4vh;
}
.control {
display: grid;
grid-template-columns: 100px 1fr;
gap: 2vh;
}
span,
label {
display: block;
white-space: nowrap;
align-self: center;
text-align: right;
}
input:not([type="checkbox"]) {
padding: 3px;
border: 1px solid #ccc;
border-radius: 3px;
width: 100%;
min-height: 40px;
}
input[type="color" i]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color" i]::-webkit-color-swatch {
min-width: 0px;
border: none;
border-radius: 3px;
}
.checkbox input[type=checkbox]{
height: 0;
width: 0;
visibility: hidden;
clip-path: inset(1px 1px);
position: absolute;
}
.checkbox label {
cursor: pointer;
text-indent: -9999px;
width: 80px;
height: 40px;
background: grey;
display: block;
border-radius: 100px;
position: relative;
}
.checkbox label:after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 36px;
height: 36px;
background: #fff;
border-radius: 10rem;
transition: 0.3s;
}
.checkbox input:checked + label {
background: #bada55;
}
.checkbox input:checked + label:after {
left: calc(100% - 5px);
transform: translateX(-100%);
}
.checkbox label:active:after {
width: 130px;
}
.element {
border: 1px dashed #fff;
border-radius: 8px;
padding: 4vh;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2vh;
text-shadow: 1px 1px rgba(255, 255, 255, .5), -1px -1px rgba(255, 255, 255, .5);
}
.element > div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 20vw;
}
h3 {
margin-bottom: 4vh;
font-size: 2rem;
color: var(--color);
}
.val {
font-size: 3rem;
padding: 5vh;
white-space: nowrap;
}
.val span {
display: block;
text-align: center;
}
dl {
padding: 2vh 0;
border-bottom: 1px dashed #fff;
line-height: 1.5;
}
dt {
font-weight: 700;
margin-bottom: 2vh;
}
dd {
display: grid;
grid-template-columns: 80px 1fr;
gap: 2vh;
margin: 2vh 0;
}
dl:first-child dd{
grid-template-columns: 80px 1fr 1fr;
}
@media (max-width: 1200px) {
.element {
grid-template-columns: none;
}
}
@media (max-width: 860px) {
body {
grid-template-rows: auto 1fr;
grid-template-columns: auto;
}
.element {
grid-template-columns: repeat(2, 1fr);
}
}
View Compiled
const color1Element = document.getElementById("color1");
const color2Element = document.getElementById("color2");
const sizeElement = document.getElementById("size");
const checkboxElement = document.getElementById("bold");
const rootElement = document.documentElement;
const ratioValueElement = document.getElementById("contrast-ratio-value");
const wcagValueElement = document.getElementById("wcag-grading-value");
const inputElemes = document.querySelectorAll("input");
const innerContentElement = document.getElementById("innerContent");
/**
* Hex Converting RGB Function
* @param {string} hex - <hex-color> eg. #rgb | #rgba | #rrggbbaa | #rgba
*/
function HexToRGB(hex) {
let r = 0,
g = 0,
b = 0;
if (hex.length == 4) {
r = `0x${hex[1]}${hex[1]}`;
g = `0x${hex[2]}${hex[2]}`;
b = `0x${hex[3]}${hex[3]}`;
} else if (hex.length == 7) {
r = `0x${hex[1]}${hex[2]}`;
g = `0x${hex[3]}${hex[4]}`;
b = `0x${hex[5]}${hex[6]}`;
}
return `rgb(${+r} ${+g} ${+b})`;
}
/**
* 所有数字保持8位小数点,值在0~1
* @param {number} num
*/
function formatNumbers(num) {
if (num != 0 && num != 1) {
num = num.toFixed(8);
}
return num;
}
/**
* 计算颜色相对亮度
* @param {string} color - <hex-color>
*/
function luminance(color) {
// 将<hex-color>转换成<rgb-color>
color = HexToRGB(color);
let sep = color.indexOf(",") > -1 ? "," : " ";
color = color.substr(4).split(")")[0].split(sep);
let rgbText = ["R", "G", "B"];
// 计算R,G,B颜色通道的线性值 r/255, g/255, b/255
for (let i = 0; i < 3; i++) {
var rgb = color[i];
rgb /= 255;
console.log(`${rgbText[i]}: ${color[i]} / 255 = ${rgb}`);
// 如果r <= 0.03928 那么 r/12.92; 否则 ((r + 0.055) / 1.055)^2.4
// 如果g <= 0.03928 那么 g/12.92; 否则 ((g + 0.055) / 1.055)^2.4
// 如果b <= 0.03928 那么 b/12.92; 否则 ((b + 0.055) / 1.055)^2.4
rgb = rgb <= 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4);
color[i] = rgb;
}
let R = color[0];
let G = color[1];
let B = color[2];
// 计算颜色相对亮度
let Luma = 0.2126 * R + 0.7152 * G + 0.0722 * B;
return Luma;
}
function displayRGB(color) {
color = HexToRGB(color);
let sep = color.indexOf(",") > -1 ? "," : " ";
color = color.substr(4).split(")")[0].split(sep);
let r = color[0];
let g = color[1];
let b = color[2];
return `R: ${r}, G: ${g}, B:${b}`;
}
function displayLinerRGB(color) {
color = HexToRGB(color);
let sep = color.indexOf(",") > -1 ? "," : " ";
color = color.substr(4).split(")")[0].split(sep);
let r = (color[0] / 255).toFixed(3);
let g = (color[1] / 255).toFixed(3);
let b = (color[2] / 255).toFixed(3);
console.log(r);
return `L_r: ${r}, L_g: ${g}, L_b:${b}`;
}
/**
* 获取颜色对比度
* @param {number} Luma1 - 颜色1的相对亮度值
* @param {number} Luma2 - 颜色2的相对亮度值
*/
function getContrastRatio(Luma1, Luma2) {
// 颜色的相对亮度的值是0~1,越大的值越亮
// 确保较大的值为Luma1, 亮色颜色的相对亮度
// 确保较小的值为Luma2, 暗色颜色的相对亮度
// R = (Luma1 + 0.05) / (Luma2 + 0.05)
if (Luma1 > Luma2) {
console.log(`Contrast Ratio: ${(Luma1 + 0.05) / (Luma2 + 0.05)}`);
return (Luma1 + 0.05) / (Luma2 + 0.05);
} else {
console.log(`Contrast Ratio: ${(Luma2 + 0.05) / (Luma1 + 0.05)}`);
return (Luma2 + 0.05) / (Luma1 + 0.05);
}
}
function compareInit() {
// 获取颜色值
let color1Value = color1Element.value;
let color2Value = color2Element.value;
// 将颜色重置到自定义属性
rootElement.style.setProperty("--bgColor", color1Value);
rootElement.style.setProperty("--color", color2Value);
// 获取颜色相对亮度值
let Luma1 = luminance(color1Value);
let Luma2 = luminance(color2Value);
// 获取颜色对比度
let ratio = getContrastRatio(Luma1, Luma2).toFixed(2);
let fontSize = sizeElement.value;
let isBold = checkboxElement.checked;
if (Number(fontSize) <= 24 || (Number(fontSize) <= 19 && isBold)) {
if (Number(ratio) >= 4.5 && Number(ratio) < 7) {
wcagValueElement.innerHTML = `<span>4.5:1</span><span>AA</span>`;
ratioValueElement.innerHTML = `<span>${ratio}:1</span><span>AA</span>`;
ratioValueElement.style.color = "var(--color)";
wcagValueElement.style.color = "var(--color)";
} else if (Number(ratio) >= 7) {
wcagValueElement.innerHTML = `<span>7:1</span><span>AAA<span>`;
ratioValueElement.innerHTML = `<span>${ratio}:1</span><span>AAA</span>`;
ratioValueElement.style.color = "var(--color)";
wcagValueElement.style.color = "var(--color)";
} else if (Number(ratio) < 3) {
// wcagValueElement.innerHTML = `<span><3.0</span><span>FAIL</span>`;
ratioValueElement.innerHTML = `<span>${ratio}:1</span><span>FAIL</span>`;
ratioValueElement.style.color = "red";
wcagValueElement.style.color = "red";
}
} else if (Number(fontSize) > 24 || (Number(fontSize) >= 19 && isBold)) {
if (Number(ratio) >= 3 && Number(ratio) < 4.5) {
wcagValueElement.innerHTML = `<span>3:1</span><span>AA</span>`;
ratioValueElement.innerHTML = `<span>${ratio}:1</span><span>AA</span>`;
ratioValueElement.style.color = "var(--color)";
wcagValueElement.style.color = "var(--color)";
} else if (Number(ratio) >= 4.5) {
wcagValueElement.innerHTML = `<span>4.5:1 </span><span>(AAA)</span>`;
ratioValueElement.innerHTML = `<span>${ratio}:1</span><span>AAA</span>`;
ratioValueElement.style.color = "var(--color)";
wcagValueElement.style.color = "var(--color)";
} else if (Number(ratio) < 3) {
// wcagValueElement.innerHTML = `<span><3.0</span><span>(FAIL)</span>`;
ratioValueElement.innerHTML = `<span>${ratio}:1</span><span>FAIL</span>`;
ratioValueElement.style.color = "red";
wcagValueElement.style.color = "red";
}
}
innerContentElement.innerHTML = `
<dl>
<dt>Step01: 获取颜色</dt>
<dd>
<span>C1: </span>
<span>${color1Value}</span>
<span>${HexToRGB(color1Value)}</span>
</dd>
<dd>
<span>C2: </span>
<span>${color2Value}</span>
<span>${HexToRGB(color2Value)}</span>
</dd>
</dl>
<dl>
<dt>Step02: 获取RGB颜色</dt>
<dd>
<span>C1:</span>
<span>${displayRGB(color1Value)}</span>
</dd>
<dd>
<span>C2:</span>
<span>${displayRGB(color2Value)}</span>
</dd>
</dl>
<dl>
<dt>Step03: 获取线性RGB的值</dt>
<dd>
<span>C1 Linear:</span>
<span>${displayLinerRGB(color1Value)}</span>
</dd>
<dd>
<span>C2 Linear:</span>
<span>${displayLinerRGB(color2Value)}</span>
</dd>
</dl>
<dl>
<dt>Step04: 获取颜色相对亮度值</dt>
<dd>
<span>Luma1;</span>
<span>${luminance(color1Value).toFixed(3)}</span>
</dd>
<dd>
<span>Luma2;</span>
<span>${luminance(color2Value).toFixed(3)}</span>
</dd>
</dl>
<dl>
<dt>Step05: 计算颜色对比度</dt>
<dd>
<span>Contrast Ratio;</span>
<span>${getContrastRatio(
luminance(color1Value),
luminance(color2Value)
)} : 1</span>
</dd>
</dl>
`;
}
compareInit();
// color1Element.addEventListener("change", function () {
// compareInit();
// });
// color2Element.addEventListener("change", function () {
// compareInit();
// });
// checkboxElement.addEventListener("change", function () {
// compareInit();
// });
inputElemes.forEach((input) => {
input.addEventListener("change", () => {
compareInit();
});
});
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.