はじめに
こんにちは、エンジニアのkeitaMaxです。
先日「春和の候!若手エンジニアふんわりLT Night!」に参加をしまして、そこで @mi111025 さんの「友達にコード送ったら1行にされた」という発表をきいて,僕もHSLを使用したカラーのレンジスライダーを作ってみたくて作成してみました。
とても良い発表だったのでぜひみてみてください!
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)
色相、彩度、輝度の3要素の色空間ということでした。
今回は彩度、輝度は固定でそれぞれ100%,50%にして、色相を0から360の範囲で使用していきたいと思います。
内容
今回はNext.jsで作成しました。
まずは作成したソースコードからです。
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" でレンジスライダーを作成して、範囲を0から360にしました。
classNameでデザインを整え、styleでカラーのレンジの背景を作成しました。
HSLからカラーコードに直して親のコンポーネントに渡したかったので、以下で公開されていたコードで実装いたしました。
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')
}
ちなみにストーリーブックのほうは以下のように書いています。
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会とてもたのしかったのでまたいきたいと思っています。次は登壇もしてみたいなと思っています。
この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。
最後まで読んでいただきありがとうございました!
参考