これは CSS Programming Advent Calendar 2012 の 7 日目の記事です。
CSS プログラミングの基本は、「ユーザの操作をなんとかして受け取ってスタイルをあてる」ということの繰り返しです。
クリックの検出に :checked
や :active
, mouseover なら :hover
を使う……とやるわけですね。
ならば次は「ドラッグ」も検出したいところです。
ドラッグで操作できる HTML 要素というと、まず input[type=range]
が思い浮かびます。
しかし残念ながら、input
要素の value
の値は動的に変化しない(CSS で検知できない)うえ、一方向にしか動かせず自由度が低いです。
そこで、resize
プロパティを使います。
textarea
要素のシステムスタイルに設定されているアレです。
resize: both; /* または horizontal, vertical */
overflow: auto; /* visible 以外 */
上のようにした要素はリサイズが可能になりますが、この「取っ手(resizer)」の部分を利用するわけです(ただし IE や Opera では使えないようです)。 適当なスタイルをあてれば、リサイズしているのではなくあたかも「ドラッグして移動している」ように見せることができます。
WebKit では、resize
を指定した要素は「大きく」することはできますが、もともとの width
, height
より小さくリサイズすることはできません(Firefox なら可能)。
左上方向にはドラッグができないことになります。
これを解決するには、最初は margin
や left
, top
などで初期位置を指定し、少し移動された時点で(style
属性がついた時点で)それらを解除する、ということが必要です。
このままでは大きさやデザインが固定で扱いづらいですが、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 Nightly や Chrome dev には実装が載っています(設定で有効にする必要があります)。
このシェーダに表示領域のサイズを u_textureSize
として渡してやり、シェーダ側で処理を行なって最終的な表示に反映するわけです。
しかしこれを CSS プログラミングと呼んでいいのか疑問であります……。
というわけで、ただし WebKit に限る、という部分もありましたが、なんとかドラッグに反応することができました。 めでたし、めでたし……
明日は GeckoTang さん(7 日ぶり 2 度目)です!