円形スライダーをCSSの三角関数を使ってつくってみた

![画面収録-2023-01-16-5.51.22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56184/4ff1f7f7-663e-2e4e-024d-d68d6490b4b7.gif)

## CSSで三角関数がサポートされたらしい

いつもお世話になっているColissさんより
[2023年はCSSで三角関数「sin(), cos(), tan()」が主要ブラウザのすべてで使用できるようになるぞ！](https://coliss.com/articles/build-websites/operation/css/css-trigonometric-functions.html)

という記事を見て**円形スライダーができる**んじゃないかなで試してみました。

## 前提

- いまのところ三角関数はSafariとFirefoxのみサポート
- 試しにSafariで動くようにする
- `input type="range"`で`value`に応じて円回転するようにしてみる
- ついでに`value`に応じて色を変えてみる

## HTML(React)

React(tsx)使っていますが`value`を変更するのみです。

```
const [count, setCount] = useState(0);
return (
  <input 
    className="circular-slider"
    type="range"
    min={0}
    max={100}
    step={1}
    value={count}
    onChange={e => setCount(parseInt(e.target.value))}
  />
)
```

## 円形スライダーのベース

SASSです。

- inputのベース部分
- 数値表示
- つまみとスライダー円形トラック部

の定義です。

### すこし特殊なところとしては

- カーソルを縦に動かすと変更

円形スライダーは縦動作の方が自然に思えたため、
カーソルや指を縦に動かすと変更されるようにしています。

- Webkitにしか対応させていない

SafariなどWebkitの`-webkit-slider-runnable-track`と`-webkit-slider-thumb`擬似要素を利用しています。
非標準です。

- 数値表示を`::after`擬似要素に`attr(value)`使って表示

```
$circle-size: 96px; // スライダーサイズ
$circle-border-width: 8px; // スライダーボーダー幅
$thumb-size: 16px; // つまみのサイズ

.circular-slider,
.circular-slider[type="range"]::-webkit-slider-runnable-track,
.circular-slider[type="range"]::-webkit-slider-thumb {
  appearance: none; // 既存の表示をリセット
}

.circular-slider {
  position: relative;
  transform: rotate(-90deg); // 縦にする
  height: $circle-size;
  width: $circle-size;

  // 数値部分
  &::after {
    display: block;
    content: attr(value); // attrでvalue値を表示
    font-size: 1.5rem;
    font-weight: 900;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(90deg);
  }

  // スライダーの円形トラック部
  &::-webkit-slider-runnable-track {
    border: $circle-border-width solid;
    border-radius: 50%;
    height: $circle-size;
    width: $circle-size;
  }

  // ノブ部分
  &::-webkit-slider-thumb {
    background: white;
    border: 2px solid;
    border-radius: 50%;
    position: absolute;
    bottom: calc($circle-size / 2 - $thumb-size / 2); // 初期位置
    left: calc($circle-size / 2 - $thumb-size / 2); // 初期位置
    height: $thumb-size;
    width: $thumb-size;
  }
}
```

## 三角関数でノブの位置指定

今回やりたかったことです。

- `translate3d`でノブ位置指定
- SASSの`for`で`step`ごとに指定

100個できてしまいますが`calc`で動的な値は使えないっぽいのでやむをえない(解決策あれば知りたいです)

```
$pi: 3.14;
$c: 360; // 円周角
$r: calc(-1 * ($circle-size / 2 - $circle-border-width / 2)); // 半径
$min: 0;
$max: 100;

@for $value from $min through $max {
  $theta: calc($c / $max * $value);
  $x: calc($r * cos($theta * $pi / 180)); // x位置計算
  $y: calc($r * sin($theta * $pi / 180)); // y位置計算

  // ノブの位置と色
  .circular-slider::-webkit-slider-thumb {
    transform: translate3d($x, $y, 0); // 位置
    border-color: hsl(calc(0 + $value * 2),90,45); // 色
  }

  // 円形スライダーの色
  .circular-slider::-webkit-slider-runnable-track {
    border-color: hsl(calc(0 + $value * 2),90,45);
  }
}
```

## 全体

```
$circle-size: 96px;
$circle-border-width: 8px;
$thumb-size: 16px;

.circular-slider,
.circular-slider::-webkit-slider-runnable-track,
.circular-slider::-webkit-slider-thumb {
  appearance: none;
}

.circular-slider {
  position: relative;
  transform: rotate(-90deg);
  height: $circle-size;
  width: $circle-size;

  &::after {
    display: block;
    content: attr(value);
    font-size: 1.5rem;
    font-weight: 900;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) rotate(90deg);
  }

  &::-webkit-slider-runnable-track {
    border: $circle-border-width solid;
    border-radius: 50%;
    height: $circle-size;
    width: $circle-size;
  }

  &::-webkit-slider-thumb {
    background: white;
    border: 2px solid;
    border-radius: 50%;
    position: absolute;
    bottom: calc($circle-size / 2 - $thumb-size / 2);
    left: calc($circle-size / 2 - $thumb-size / 2);
    height: $thumb-size;
    width: $thumb-size;
  }
}

$pi: 3.14;
$c: 360;
$r: calc(-1 * ($circle-size / 2 - $circle-border-width / 2));
$min: 0;
$max: 100;

@for $value from $min through $max {
  $theta: calc($c / $max * $value);
  $x: calc($r * cos($theta * $pi / 180));
  $y: calc($r * sin($theta * $pi / 180));

  .circular-slider[value="#{$value}"]::-webkit-slider-thumb {
    transform: translate3d($x, $y, 0);
    border-color: hsl(calc(0 + $value * 2),90,45);
  }

  .circular-slider[value="#{$value}"]::-webkit-slider-runnable-track {
    border-color: hsl(calc(0 + $value * 2),90,45);
  }
}

// 0の場合はOFFっぽく見せる
.circular-slider[value="0"] {
  &::-webkit-slider-runnable-track {
    border-color: #ccc;
  }

  &::-webkit-slider-thumb {
    border-color: #ccc;
  }

  &::after {
    color: #ccc;
  }
}
```

## 正直なところ

円形スライダー目的なら
**JavaScriptで同様のことをしたほうがいい**

三角関数はアニメーションや特殊配置に役立ちそうで期待しています。

なにかのお役に立てば幸いです。