<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

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.