Create a gist now

Instantly share code, notes, and snippets.

CSS でドラッグをきめる

CSS でドラッグをきめる

これは CSS Programming Advent Calendar 2012 の 7 日目の記事です。

CSS プログラミングの基本は、「ユーザの操作をなんとかして受け取ってスタイルをあてる」ということの繰り返しです。 クリックの検出に :checked:active, mouseover なら :hover を使う……とやるわけですね。 ならば次は「ドラッグ」も検出したいところです。

それ CSS でできるよ!

ドラッグで操作できる HTML 要素というと、まず input[type=range] が思い浮かびます。 しかし残念ながら、input 要素の value の値は動的に変化しない(CSS で検知できない)うえ、一方向にしか動かせず自由度が低いです。 そこで、resize プロパティを使います。 textarea 要素のシステムスタイルに設定されているアレです。

resizer に注目

resize: both; /* または horizontal, vertical */
overflow: auto; /* visible 以外 */

上のようにした要素はリサイズが可能になりますが、この「取っ手(resizer)」の部分を利用するわけです(ただし IE や Opera では使えないようです)。 適当なスタイルをあてれば、リサイズしているのではなくあたかも「ドラッグして移動している」ように見せることができます。

初期位置を自由に決める

WebKit では、resize を指定した要素は「大きく」することはできますが、もともとの width, height より小さくリサイズすることはできません(Firefox なら可能)。 左上方向にはドラッグができないことになります。 これを解決するには、最初は marginleft, top などで初期位置を指定し、少し移動された時点で(style 属性がついた時点で)それらを解除する、ということが必要です。

resizer をデザインする

このままでは大きさやデザインが固定で扱いづらいですが、WebKit では ::-webkit-resizer, ::-webkit-scrollbar 擬似要素を利用してスタイルを指定することができます。 上の初期位置指定とまとめて、次のような SCSS mixin を用意すると便利です。

@mixin draggable-frame($width, $height, $left, $top, $style: both) {
  resize: $style;
  overflow: hidden;
  width: $left + $width;
  height: $top + $height;

  // 以下 WebKit の場合
  @media screen and (-webkit-min-device-pixel-ratio: 0) {
    overflow: scroll; // ::scrollbar を必ず存在させておく
    width: $width;
    height: $height;
    // 最初は margin を指定しておく
    margin-left: $left;
    margin-top: $top;

    &:active {
      // 移動されようとしている(:active)(かつ、まだ移動されていない)状態で
      // 少しだけ左上にずらすと、左上への移動もうまくいく(ことが多い)
      margin-left: $left - 1;
      margin-top: $top - 1;
    }
    &[style] {
      // 少しでも移動されたら margin をゼロに
      margin-left: 0;
      margin-top: 0;
    }
  }
  &::-webkit-scrollbar {
    // ::scrollbar のサイズを指定すると ::resizer のサイズが変わる
    // ::resizer に直接 width, height を指定しても効果なし
    width: $width;
    height: $height;
  }
  &::-webkit-resizer, &::-webkit-scrollbar-corner {
    visibility: hidden;
  }
}

スクロールバーまわりの擬似要素についてはこちらの記事が網羅的でわかりやすいです。

また、CSS Transforms を利用してスタイリングをすることもできますが、それでも Firefox では表示と resizer の実際の位置がずれてしまい、意図した通りに動かすことができませんでした。 Firefox で resizer の大きさを変える方法があれば教えてください。

ドラッグ位置を取得する

先に述べたように、input 要素の value は動的に変化しないのが難点でしたが、resize を指定された要素はインラインスタイルの形で width, height が変化してくれます。 属性セレクタで [style*="width: 300px"] などとルールを指定すれば、「このくらいドラッグされたらスタイルを当てる」というようなことができるのです。 次のデモでは [style*="width: 3"] などとして、最上位桁の値によって 100px ごとに分岐させています。

インラインフレームの大きさを変え、background-image や Media Queries を利用して描画に反映させる方法もあります。 別のドキュメントになってしまいますが、閾値を細かく指定しやすい(上の Demo 3 は 100px 固定でしたね)利点があります。

(ドラッグと関係ないですが) Media Queries で状態を保持するテクニック自体がかなり便利で、例えば次のものに使われていました。

CSS Custom Filters (CSS Shaders) を利用してドラッグを受け取る手もあります。 Custom Filters とは、CSS で OpenGL ES 2.0 / WebGL 互換のシェーダエフェクトをかけられるようにするものです。 WebKit NightlyChrome dev には実装が載っています(設定で有効にする必要があります)。 このシェーダに表示領域のサイズを u_textureSize として渡してやり、シェーダ側で処理を行なって最終的な表示に反映するわけです。 しかしこれを CSS プログラミングと呼んでいいのか疑問であります……。

というわけで、ただし WebKit に限る、という部分もありましたが、なんとかドラッグに反応することができました。 めでたし、めでたし……

8 日目は

明日は GeckoTang さん(7 日ぶり 2 度目)です!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment