<div id="app"></div>
*, *:before, *:after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-size: 16px;
  line-height: 1.4;
  font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
}

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  
  &--dark {
    color: white;
  }
}

.color-range {
  input {
    display: block;
  }
}
.color-input {
  display: flex;
  width: 130px;
  margin-bottom: .5rem;
  
  &--value {
    margin-left: auto;
  }
}

.hex {
  margin-top: 1rem;
}

.wrap {
  display: flex;
  justify-content: center;
}

.box {
  margin: 10px;
}
View Compiled
const { Fragment, useState } = React;

const rgbToLightness = (r,g,b) => {
	const max = Math.max(r,g,b);
	const min = Math.min(r,g,b);
	return 1/2 * (max + min);
}

const rgbToSaturation = (r,g,b) => {
	const max = Math.max(r,g,b);
	const min = Math.min(r,g,b);
	const l = rgbToLightness(r,g,b);

	return (l === 0 || l === 1) ? 0 : (max - min)/(1 - Math.abs(2 * l - 1));	
}

const rgbToHue = (r,g,b) => {
  let hue = Math.round(
    Math.atan2(
      Math.sqrt(3) * (g - b),
      2 * r - g - b,
    ) * 180 / Math.PI
  );
  
  while (hue < 0) {
    hue = hue + 360;
  }
  
  return hue;
}

const rgbToHsl = (r,g,b) => {
  const hue = rgbToHue(r,g,b);
  const saturation = rgbToSaturation(r,g,b);
  const lightness = rgbToLightness(r,g,b);
  
  return [hue, saturation, lightness];
}

const hslToRgb = (h,s,l) => {
	const C = (1 - Math.abs(2 * l - 1)) * s;
	const hPrime = h / 60;
	const X = C * (1 - Math.abs(hPrime % 2 - 1));
	const m = l - C/2;
	const withLight = (r,g,b) => [r+m, g+m, b+m];

	if (hPrime <= 1) { return withLight(C,X,0); } else
	if (hPrime <= 2) { return withLight(X,C,0); } else
	if (hPrime <= 3) { return withLight(0,C,X); } else
	if (hPrime <= 4) { return withLight(0,X,C); } else
	if (hPrime <= 5) { return withLight(X,0,C); } else
	if (hPrime <= 6) { return withLight(C,0,X); }
}

const ValueRange = ({setColor, setOpposing, currentValue, name, label, min=0, max=100, ...rest}) => {
  const onChange = e => {
    setColor(e.target.value);
    setOpposing({...rest, ...{[name]: e.target.value}});
  }
  
  return (
    <Fragment>
      <label className="color-range">
        <input 
          type="range" 
          value={currentValue}
          min={min}
          max={max}
          onChange={onChange}
        />
      </label>
      <label className="color-input">
        {label}: 
          <input 
            className="color-input--value"
            type="number"
            min="0" max={max}
            value={currentValue} 
            onChange={onChange}
          />
      </label>
    </Fragment>
  )
}
  
const App = () => {
  const [hue, setHue] = useState(0);
  const [saturation, setSaturation] = useState(100);
  const [lightness, setLightness] = useState(50);
  
  const [red, setRed] = useState(255);
  const [green, setGreen] = useState(0);
  const [blue, setBlue] = useState(0);
  
  const setRgb = (attributes) => {
    const [r,g,b] = hslToRgb(attributes.hue, attributes.saturation / 100, attributes.lightness / 100);
    setRed(Math.round(r * 255));
    setGreen(Math.round(g * 255));
    setBlue(Math.round(b * 255));
  }
  
  const setHsl = (attributes) => {
    const [h,s,l] = rgbToHsl(attributes.red / 255,attributes.green / 255,attributes.blue / 255);
    setHue(Math.round(h || 0));
    setSaturation(Math.round(s * 100));
    setLightness(Math.round(l * 100));
  }
  
  const hsl = {hue, saturation, lightness};
  const rgb = {red, green, blue};
  const attributes = {...hsl,...rgb};
  const color = `hsl(${hue}deg,${saturation}%,${lightness}%)`;
  
  return (
    <div className={`app app--${lightness < 40 ? 'dark' : 'light'}`} style={{backgroundColor: color}}>
      <h1>颜色模式之间转换</h1>
      <div class="wrap">
        <div class="box">
      <h2>HSL</h2>
      <ValueRange {...attributes} setColor={setHue} currentValue={hue} label='Hue' max={360} setOpposing={setRgb} name='hue' />
      <ValueRange {...attributes} setColor={setSaturation} currentValue={saturation} label='Saturation' setOpposing={setRgb} name='saturation' />
      <ValueRange {...attributes} setColor={setLightness} currentValue={lightness} label='Lightness' setOpposing={setRgb} name='lightness' />
        </div>
        <div class="box">
      <h2>RGB</h2>
      <ValueRange {...attributes} setColor={setRed} currentValue={red} label='Red' setOpposing={setHsl} max={255} name='red' />
      <ValueRange {...attributes} setColor={setGreen} currentValue={green} label='Green' setOpposing={setHsl} max={255} name='green' />
      <ValueRange {...attributes} setColor={setBlue} currentValue={blue} label='Blue' setOpposing={setHsl} max={255} name='blue' />
        </div>
      </div>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('app'));
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js