3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

こんにちは、エンジニアの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で作成しました。

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

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" でレンジスライダーを作成して、範囲を0から360にしました。

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

スクリーンショット 2024-04-27 10.16.08.png

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

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')
}

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

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会とてもたのしかったのでまたいきたいと思っています。次は登壇もしてみたいなと思っています。

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

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

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?