Elmでわかる HTML5ドラッグ&ドロップAPI入門
HTML5のドラッグ&ドロップAPIですが使い方をすぐ忘れてしまうので備忘録的に記事を書いておきます。参考として、MDN ドラッグ&ドロップ APIとdrag イベントのサンプルを利用しました。
今回サンプルとして作るアプリケーションは以下の仕様を満たします。
- ドラッグを開始すると、ボタンが半透明になりラベルが「ふにゃ〜!!」に変わり、色が赤くなる
- 「にゃ〜」が猫檻ゾーンに到達すると檻が灰色になる
- 再び猫檻ゾーンから外れると檻が白色に戻る
- 猫檻に「にゃ〜」をドロップすると「にゃーん。。。」になる
- 猫檻ゾーンの外にドロップすると「にゃ〜」に変わり、色が黒くなる
こちらが動くコードです。
ドラッグを開始すると、ボタンのラベルが「ふにゃ〜!!」に変わり、色が赤くなる(DragStart, DragEnd)
ボタンの透明度とラベルと色が変わるときはドラッグ中かそうでないかで変わります。ドラッグ中かどうかを判別するフラグはModel
にisDragged
(初期値False)として定義しています。
type alias Model =
{ isDragged : Bool, isDragEnter : Bool, isPrisonEnter : Bool }
initialModel : Model
initialModel =
{ isDragged = False, isDragEnter = False, isPrisonEnter = False }
view
関数では、button
のstyle
でmodel.isDragged
を使用して値の出し分けをします。また、buttonをドラッグ可能な要素と見せたい場合は、Html.draggable
関数に"true"を渡します。ドラッグ開始とドラッグ終了のイベントはそれぞれ、dragstart
, dragend
で表現されます。それらのイベントはElmの標準関数では用意されていないため、Html.Events.onを利用して、onDragStart
とonDragEnd
を定義しています。
view model =
...
button
[ style "color" <|
if model.isDragged then
"red"
else
"black"
, style "opacity"
(if model.isDragged then
"50%"
else
"100%"
)
, draggable "true"
, onDragStart DragStart
, onDragEnd DragEnd
]
[ text <|
if model.isDragged then
"ふにゃ〜!!"
else
"にゃ〜"
]
onDragStart : msg -> Attribute msg
onDragStart msg =
on "dragstart" <| JD.succeed msg
onDragEnd : msg -> Attribute msg
onDragEnd msg =
on "dragend" <| JD.succeed msg
ドラッグ開始と終了のイベントはDragStart
とDragEnd
のMsgを発行します。ここでの変更は単純で、isDragged
の値を切り替えるだけです。
update : Msg -> Model -> Model
update msg model =
case msg of
NoOp ->
model
DragStart ->
{ model | isDragged = True }
DragEnd ->
{ model | isDragged = False }
...
「にゃ〜」が猫檻ゾーンに到達すると檻が灰色になる・再び猫檻ゾーンから外れると檻が白色に戻る(DragEnter・DragLeave)
猫檻の色が変わるときはドラッグ領域に入ったかそうでないかで変わります。判別するフラグはModel
にisDragEnter
(初期値False)として定義しています。
type alias Model =
{ isDragged : Bool, isDragEnter : Bool, isPrisonEnter : Bool }
initialModel : Model
initialModel =
{ isDragged = False, isDragEnter = False, isPrisonEnter = False }
猫檻を表すdiv
では、model.isDragEnter
を利用してbackground
スタイルを変えています。ドラッグ領域に入ったかどうかを判定するイベントは、dragenter
で onDragEnter
関数で定義しています。今回のサンプルでは必要ないのですが、ドラッグ領域の値をイベントをフックして取得することができます。event.target.className
の値を取得するJsonデコーダtargetClassName
を定義しています。あとはイベント定義時に利用しています。ドラッグ領域を離れたときのイベントはdragleave
をonDragLeave
関数で定義しています。
, div
[ class "prison"
, onDragEnter DragEnter
, onDragOver NoOp
, onDragLeave DragLeave
, onDrop Drop
, style "background" <|
if model.isDragEnter then
"gray"
else
"white"
]
targetClassName : JD.Decoder String
targetClassName =
JD.at [ "target", "className" ] JD.string
onDragEnter : (String -> msg) -> Attribute msg
onDragEnter tagger =
on "dragenter" <| JD.map tagger targetClassName
onDragLeave : msg -> Attribute msg
onDragLeave msg =
on "dragleave" <| JD.succeed msg
update
関数では、isDragEnter
を切り替えています。 せっかくなので、DragEnterの場合は、className
で猫檻かどうかを判別をしています。
update msg model =
case msg of
...
DragEnter className ->
if className == "prison" then
{ model | isDragEnter = True }
else
model
DragLeave ->
{ model | isDragEnter = False }
...
猫檻に「にゃ〜」をドロップすると「にゃーん。。。」になる(Drop, DragOver)
猫檻にドロップされたかどうかは、model.isPrisonEnter
フラグで判定します。
type alias Model =
{ isDragged : Bool, isDragEnter : Bool, isPrisonEnter : Bool }
initialModel : Model
initialModel =
{ isDragged = False, isDragEnter = False, isPrisonEnter = False }
「にゃ〜」がドロップされかどうかによって猫檻の中に「にゃーん。。。」を入れるかどうかをmodel.isPrisonEnter
で判定しています。ドロップされたかどうかをdrop
イベントで検知するために、onDrop
関数を定義しています。しかし、DropOverイベントをpreventDefault
しておかなければdrop
イベントに到達しません。ElmでpreventDefault
を使いたい場合にはonの代わりに、preventDefaultOnを利用します。preventDefaultにtrueを渡すためにタプル(msg, Bool)
にする必要があるため補助関数alwaysStop
を定義して利用します。dragover
のときはmodelは変化させる必要がないためNoOp
のMsgを定義して発行をします。
, div
[ class "prison"
, onDragEnter DragEnter
, onDragOver NoOp
, onDragLeave DragLeave
, onDrop Drop
, style "background" <|
if model.isDragEnter then
"gray"
else
"white"
]
[ text "猫檻"
, if model.isPrisonEnter then
button []
[ text "にゃーん。。。"
]
else
emptyElement
]
onDrop : msg -> Attribute msg
onDrop msg =
on "drop" <| JD.succeed msg
onDragOver : msg -> Attribute msg
onDragOver msg =
preventDefaultOn "dragover" <| JD.map alwaysStop <| JD.succeed msg
alwaysStop : a -> ( a, Bool )
alwaysStop x =
( x, True )
以上でドラッグ&ドロップAPIをElmから利用することができました。
Discussion