色を選択するカラーレンジスライダーを作成した

# はじめに

こんにちは、エンジニアのkeitaMaxです。

先日「春和の候！若手エンジニアふんわりLT Night！」に参加をしまして、そこで @mi111025 さんの「友達にコード送ったら1行にされた」という発表をきいて,僕もHSLを使用したカラーのレンジスライダーを作ってみたくて作成してみました。

とても良い発表だったのでぜひみてみてください！

https://speakerdeck.com/mi111025/0425

# HSLとは
>HLS色空間（エイチエルエスいろくうかん）とは、色相 (Hue)、彩度 (Saturation)、輝度（Lightness / Luminance または Intensity）の3つの成分からなる色空間。HSV色空間によく似ている。 HSL、HSIと呼ばれることもある。
(引用：https://ja.wikipedia.org/wiki/HLS%E8%89%B2%E7%A9%BA%E9%96%93)

色相、彩度、輝度の３要素の色空間ということでした。

今回は彩度、輝度は固定でそれぞれ100%,50%にして、色相を０から３６０の範囲で使用していきたいと思います。


# 内容

今回はNext.jsで作成しました。

まずは作成したソースコードからです。

```react:index.tsx
import React, { useState } from "react"
import { hslToRgb16 } from './hooks'

type Props = {
  defaultColorHSL: number
  // eslint-disable-next-line no-unused-vars
  handleColorSelect: (colorCode: string) => void,
}

export const ColorSelectView = React.memo<Props>(function ColorSelectView({
  defaultColorHSL = 180,
  handleColorSelect,
}) {
  const [color, setColor] = useState(defaultColorHSL)
  const doChangeColorSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const colorHTL = Number(e.target.value)
    setColor(colorHTL)
    handleColorSelect('#' + hslToRgb16(colorHTL))
  };
  return (
    <>
      <input type="range" value={color} min="0" max="360"
        data-testid="color"
        onChange={doChangeColorSelect}
        className="w-full appearance-none rounded-lg [&::-moz-range-thumb]:size-2.5
        [&::-moz-range-thumb]:appearance-none
        [&::-moz-range-thumb]:rounded-full
        [&::-moz-range-thumb]:border-4
        [&::-moz-range-thumb]:border-blue-600
        [&::-moz-range-thumb]:bg-white
        [&::-moz-range-thumb]:transition-all
        [&::-moz-range-thumb]:duration-150
        [&::-moz-range-thumb]:ease-in-out
        [&::-webkit-slider-thumb]:-mt-0.5
        [&::-webkit-slider-thumb]:size-2.5
        [&::-webkit-slider-thumb]:appearance-none
        [&::-webkit-slider-thumb]:rounded-full
        [&::-webkit-slider-thumb]:bg-white
        [&::-webkit-slider-thumb]:shadow-[0_0_0_4px_rgba(101,101,255,1)]
        [&::-webkit-slider-thumb]:transition-all
        [&::-webkit-slider-thumb]:duration-150
        [&::-webkit-slider-thumb]:ease-in-out
        [&::-webkit-slider-thumb]:dark:bg-white"
        style={{ backgroundImage: "linear-gradient(90deg, rgba(255, 0, 0, 1), rgba(204, 255, 0, 1) 20%, rgba(0, 255, 102, 1) 40%, rgba(0, 102, 255, 1) 60%, rgba(204, 0, 255, 1) 80%, rgba(255, 0, 0, 1)" }} />

    </>
  )
})
```

こんな感じで実装いたしました。

input type="range" でレンジスライダーを作成して、範囲を０から３６０にしました。

classNameでデザインを整え、styleでカラーのレンジの背景を作成しました。


![スクリーンショット 2024-04-27 10.16.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3715123/d6022806-21e6-ae70-d7db-5a8f426f790f.png)




HSLからカラーコードに直して親のコンポーネントに渡したかったので、以下で公開されていたコードで実装いたしました。

https://shanabrian.com/web/javascript/color-code-convert-hsl-to-16rgb.php

```ts:hooks.ts
export const hslToRgb16 = function (hue: number, saturation: number = 100, lightness: number = 50): string {
	var red = 0,
		green = 0,
		blue = 0,
		q = 0,
		p = 0,
		hueToRgb;

	hue = Number(hue) / 360;
	saturation = Number(saturation) / 100;
	lightness = Number(lightness) / 100;

	if (saturation === 0) {
		red = lightness;
		green = lightness;
		blue = lightness;
	} else {
		hueToRgb = function (p: number, q: number, t: number) {
			if (t < 0) t += 1;
			if (t > 1) t -= 1;

			if (t < 1 / 6) {
				p += (q - p) * 6 * t;
			} else if (t < 1 / 2) {
				p = q;
			} else if (t < 2 / 3) {
				p += (q - p) * (2 / 3 - t) * 6;
			}

			return p;
		};

		if (lightness < 0.5) {
			q = lightness * (1 + saturation);
		} else {
			q = lightness + saturation - lightness * saturation;
		}
		p = 2 * lightness - q;

		red = hueToRgb(p, q, hue + 1 / 3);
		green = hueToRgb(p, q, hue);
		blue = hueToRgb(p, q, hue - 1 / 3);
	}
	return colorNumberToString(red) + colorNumberToString(green) + colorNumberToString(blue)
}

function colorNumberToString(num: number): string {
	return Math.round(num * 255).toString(16).padStart(2, '0')
}
```

ちなみにストーリーブックのほうは以下のように書いています。


```react:index.stories.tsx

import { Meta, StoryObj } from '@storybook/react';
import { fireEvent, within } from '@storybook/testing-library';
import { ColorSelectView } from '.';
import { useState } from 'react';

const meta: Meta<typeof ColorSelectView> = {
  title: 'views/ColorSelectView',
  component: ColorSelectView,
  tags: ['autodocs'],
}
export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {
  render: () => {
    const defaultColor = 180
    const [color, setColor] = useState('')
    const handleColorSelect = (colorCode: string) => {
      setColor(colorCode)
    }
    return (
      <>
        {color}
        <svg width="20" viewBox="0 0 2 2" xmlns="http://www.w3.org/2000/svg" version="1.1">
          <circle fill={color} cx="1" cy="1" r="1"></circle>
        </svg>
        < ColorSelectView handleColorSelect={handleColorSelect} defaultColorHSL={defaultColor} />
      </>
    )
  },
  play: async ({ canvasElement }) => {
    const canvas = await within(canvasElement)
    await fireEvent.click(canvas.getByTestId('color'))
  },
}
```

# おわりに

LT会とてもたのしかったのでまたいきたいと思っています。次は登壇もしてみたいなと思っています。

この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。

最後まで読んでいただきありがとうございました！



# 参考

https://shanabrian.com/web/javascript/color-code-convert-hsl-to-16rgb.php