🔊

input type="range" でチョットかっこいい Volume Fader を作る

2024/12/15に公開

概要

<input type="range"> というスライダーを表現できるタグがあります。
今回はこの <input type="range"> でボリュームフェーダーを作成したので公開しようと思います。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/input/range

出来上がったもの

全て HTML(React)、CSS(tailwind含む)オンリーです。
SVGなどは利用しないで、ネイティブで表現できる範囲で作成しています。

動かしてみよう

確認は右上のopen preview new tab をクリックしてプレビュー専用ページを推奨してます

実際にファイルを選択していただければ、ボリュームフェーダーを上下に動かしてボリュームの調節をお試しできます。
サイズが大きくないため拡大してご覧いただくと立体感などが分かりやすいかもしれません。

またデフォルトからの変化がわかるように通常のスライダーも並べております。

DJコントローラーやミキサー、DJソフトのフェーダーに近づけるべく、カタログなどを確認しながら近い雰囲気が出るように作成しました。

例えばPioneer社で販売されているDJコントローラーのサイトを確認すると四角いつまみと目盛りとして白線があるのが分かります。

https://alphatheta.com/ja/product/dj-controller/ddj-grv6/black/

環境

  • React - 18.3.1
  • TypeScript - 5.6.2
  • TailwindCSS - 3.4.16
  • Vite - 6.0.3

解説

以下それぞれ簡易ではありますが、どのようにして実現させたかをポイントで記載しております。
また VolumeFader.tsx にフェーダー自体の実装を全て入れ込んでいるので、コードだけ確認したい場合は添付した StackBlitz より、内容を確認可能です。

デザイン

フェーダーなのでなるべく立体感の出るようにニューモフィズムを適用したスタイルを当てています。
陰影の調整等はneumorphism.io というニューモフィズムのCSSを生成してくれるサイトで行い、 neumorphic.css ファイルに保存しています。
https://neumorphism.io/#e0e0e0

サンプルコード
#凸感のある陰影
.nph-convex {
  box-shadow: 5px 5px 10px #2d2d2d, -5px -5px 10px #494949;
  transition: box-shadow 0.2s ease, transform 0.2s ease;
}
#凹間のある陰影
.nph-concave {
  box-shadow: inset 5px 5px 10px #2d2d2d, inset -5px -5px 10px #494949;
}

#スライダーのつまみ
.nph-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: #494949;
  background-image: linear-gradient(
    to right,
    #494949 40%,
    #dadada 40% 60%,
    #494949 60% 100%
  );
}

#スライダーのtrack部分
#track部分は完全に打ち消して、#3b3b3bで上書き
#窪んでいるようにするために左上から右下にかけてbox-shadowで変化をつける
.nph-slider::-webkit-slider-runnable-track {
  -webkit-appearance: none;
  width: 4px;
  height: 100%;
  background-color: #3b3b3b;
  border-radius: 16px;
  box-shadow: inset 5px 5px 10px #2d2d2d, inset -5px -5px 10px #494949;
}

<input type="range">の既存のスタイルを書き換えるためには以下のプロパティを参照する必要があります。

前述の neumorphic.css でニューモフィズムを適用し、VolumeFader.tsx側で既存のプロパティを打ち消すような css を書いています。

   <input
    type="range"
    className={clsx(
      'slider',
      'nph-slider',
      '[&::-webkit-slider-thumb]:pointer-events-auto',
      //既存の⚫︎を消す
      '[&::-webkit-slider-thumb]:appearance-none',
      //サイズを再定義する
      '[&::-webkit-slider-thumb]:h-4',
      '[&::-webkit-slider-thumb]:w-4',
      //つまみのサイズを幅いっぱいにまで広がるようにする
      '[&::-webkit-slider-thumb]:scale-y-[400%]',
      '[&::-webkit-slider-thumb]:nph-convex',
      '[&::-webkit-slider-runnable-track]:scale-x-[97%]'
    )}
  />

ぱっと見以下の指定を見て、[&::-webkit-slider-thumb]:h-4[&::-webkit-slider-thumb]:w-4をもっと大きい数字にすればいいのではないかと思う人もいるかと思います。
しかし、実際はサイズを上げてしまうとつまみの位置が大きくずれてしまいます。

//サイズをあげてみる
'[&::-webkit-slider-thumb]:h-10',
'[&::-webkit-slider-thumb]:w-10',
//'[&::-webkit-slider-thumb]:scale-y-[400%]',


つまみがおかしなところに。。。

そのためscaleを上げることで対処しています。
scale-y であるのは rotate で回転させている都合上 x と y の関係が逆になっているから です。

目盛り

シンプルな白線でボリュームの目盛りを表現しています。
見た目上は最下部、中間地点、最上部が線の長さが2倍になるような作りをしています。
(この方が雰囲気が出るためです...笑)

これ自体は特に難しいところもなく、中心に配置しているスライダーに対して左右に divflex-col で20個並べているだけです。
ポイントとしては、スライダーのバー部分の近くに白線を配置したかったため、ボリュームフェーダーを構成する3つのパーツは全て absolute で配置して目盛り側がスライダーからの距離感を微調整するようにしています。

const SCALE = Array.from({ length: 20 }).map((_, index) => index / 10);

/** 省略 */

  <div
    className={clsx(
      'absolute',
      // 縦並びにする
      ['flex', 'flex-col', 'justify-between'],
      'h-64',
      // スライダーからの位置調整
      'py-1.5',
      'left-0.5'
    )}
  >
    {SCALE.map((v) => (
      <div
        key={v}
        className={clsx(
          'bg-text',
          //最初、最後、中間の目盛りだけ線の長さが2倍になるようにしています
          ['w-2', 'first:w-4', 'last:w-4', '[&:nth-child(10)]:w-4'],
          'ml-auto',
          'h-[2px]',
          'bg-[#DADADA]'
        )}
      ></div>
    ))}
  </div>

縦型スライダー

特に難しい部分といえばスライダーを縦にすることでした。


これが案外難しい。

標準外の属性としてスライダーを縦にするappearanceプロパティがありますが、今回はこれを使わず rotateでスライダーを回転させて縦向きにしています。
理由としては-webkit-appearance: vertical-sliderwriting-mode: bt-lrを当てた上で今共有しているこのデザインを目指して行く途中でスタイルがうまく当たりにくくなりました。
つまみの位置がおかしくなったり横向きに戻ったりなど、原因を探すのも手間がかかりそうだったため別の手段として rotate で要素自体を回転させてスライダーを縦にしています。

      <input
        type="range"
        className={clsx(
          /** 省略 */
          //闇その1
          'transform',
          'rotate-180',
        )}
        //闇その2
        style={{
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%) rotate(90deg)',
        }}
      />

感想

検証を重ねたままのを載せているので汚いコードではありますが、縦型のボリュームフェーダーを表現できたのでひとまず満足しています。
このVolumeFaderコンポーネントは個人で開発している中で作った1つでしたが、意外とスライダーを書き換えて公開している人が少ない印象だったので、<input type="range">の面白さや割と自由に拡張できることを少しでも知ってもらいたく記事にしました。

人材募集中

私が働くTuneCore Japanではエンジニア募集中です!
(当記事の冒頭から弊社のエンジニアが投稿している記事も是非ご覧ください!)

https://herp.careers/v1/wano/efvRXoCrwbyD

Discussion