我尝试实施拖放程序,使用DOM包中的boundingClientRect
来获取要移动的元素的尺寸,并使用鼠标中的position
来跟踪拖动时鼠标的移动。
程序在我滚动之前工作正常,但是当我向下滚动时,拖动元素在视图中显得比我点击之前更高。我怀疑发生的是,boundingClientRect
获取元素相对于视点的位置,然后我使用这些值来设置top
和left
值。但是,top
和left
相对于文档或父元素。但是,我不知道除了boundingClientRect
之外我可以使用什么来获取相对于文档或父元素的left
和top
值。
这里是代码,它可能比我的漫无边际更清晰。
type alias Model =
{ movableItemsList : List Item
, originalMovableItems : List Item
, movingItem : Maybe ( Item, Rectangle )
, receivingItemsList : List Item
, updatedItemsList : List ( Item, Rectangle )
, drag : Maybe Drag
, scrollTop : Float
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
DragAndDelete deleteMsg xy movingItem movingRectangle ->
model
! [ command (DragStart xy movingItem movingRectangle)
, command (deleteMsg movingItem)
]
DragStart xy selectedItem movingRectangle ->
let
movingItem =
List.head (List.filter (\i -> i.id == selectedItem.id) model.originalMovableItems)
|> Maybe.withDefault (Item "" "" 0 "")
in
{ model
| drag = Just (Drag xy xy)
, movingItem = Just ( movingItem, movingRectangle )
}
! []
DragAt xy ->
{ model
| drag =
(Maybe.map (\{ start } -> Drag start xy) model.drag)
}
! []
DragEnd _ ->
{ model
| movingItem = Nothing
, drag = Nothing
}
! []
DeleteFromUpdatedList movingItem ->
let
isKeepable iteratingItem =
iteratingItem.id /= movingItem.id
updatedItemsData =
List.filter (\( i, _ ) -> isKeepable i) model.updatedItemsList
in
{ model
| updatedItemsList = updatedItemsData
}
! []
DeleteFromMovableList movingItem ->
let
isKeepable iteratingItem =
iteratingItem.id /= movingItem.id
movableItemsData =
List.filter isKeepable model.movableItemsList
in
{ model
| movableItemsList = movableItemsData
}
! []
UpdateReceivingItemsOnOverlap receivingRectangle receivingItem ->
let
receivingItemsData =
if (checkOverlap (getCurrentMovingRectangle model) receivingRectangle) then
List.map (\i -> updateItemColor i receivingItem) model.receivingItemsList
else
model.receivingItemsList
in
{ model | receivingItemsList = receivingItemsData } ! []
RestoreReceivingItemsListColor _ ->
let
receivingItemsData =
List.map restoreReceivingItemColor model.receivingItemsList
in
{ model | receivingItemsList = receivingItemsData } ! []
AddValues receivingRectangle receivingItem ->
let
movingItem =
movingItemMaybe model.movingItem
updatedItemsData =
if (checkOverlap (getCurrentMovingRectangle model) receivingRectangle) then
( { movingItem
| value = receivingItem.value + movingItem.value
, color = "#1A6B0D"
}
, receivingRectangle
)
:: model.updatedItemsList
else
model.updatedItemsList
in
{ model
| updatedItemsList = updatedItemsData
}
! [ command (DeleteFromMovableList movingItem)
]
RestoreListContent ->
let
movingItem =
movingItemMaybe model.movingItem
listItems =
movingItem :: model.movableItemsList
in
{ model | movableItemsList = listItems } ! []
getCurrentMovingRectangle : Model -> Rectangle
getCurrentMovingRectangle model =
let
movingItemTuple =
Maybe.withDefault ( Item "" "" 0 "0", Rectangle 0 0 0 0 ) model.movingItem
( _, movingRect ) =
movingItemTuple
in
case model.drag of
Nothing ->
movingRect
Just { start, current } ->
Rectangle
(movingRect.top + toFloat (current.y - start.y))
(movingRect.left + toFloat (current.x - start.x))
(movingRect.width)
(movingRect.height)
-- VIEW
view : Model -> Html Msg
view model =
div
[]
[ receivingAndUpdatedItemsLayersDiv model
, movableItemsListDiv model
, if model.movingItem /= Nothing then
movingItemDiv model
else
div [] []
]
receivingAndUpdatedItemsLayersDiv : Model -> Html Msg
receivingAndUpdatedItemsLayersDiv model =
div
[ style [ ( "position", "relative" ) ] ]
[ div
[ style
[ ( "position", "relative" )
, ( "top", "10px" )
, ( "left", "80px" )
]
]
[ div
[ style
[ ( "z-index", "3" )
, ( "position", "absolute" )
]
, attribute "class" "drag-here-overlay"
]
(List.map receivingItemOverlay model.receivingItemsList)
, div
[ style
[ ( "z-index", "0" )
, ( "position", "absolute" )
]
, attribute "class" "drag-here-underlay"
]
(List.map receivingItemUnderlay model.receivingItemsList)
]
, div
[]
[ div
[ style
[ ( "position", "absolute" )
, ( "z-index", "1" )
]
, attribute "class" "drag-here-updated"
]
(List.map updatedItemUnderlay model.updatedItemsList)
, div
[ style
[ ( "position", "absolute" )
, ( "z-index", "4" )
]
]
(List.map updatedItemOverlay model.updatedItemsList)
]
]
movableItemsListDiv : Model -> Html Msg
movableItemsListDiv model =
div
[ style
[ ( "position", "relative" )
, ( "top", "10px" )
, ( "left", "800px" )
]
]
(List.map movableItemDiv model.movableItemsList)
updatedItemUnderlay : ( Item, Rectangle ) -> Html Msg
updatedItemUnderlay ( item, rectangle ) =
div
[ attribute "class" "drag-here-updated-underlay-item"
, sharedStyles
, style
[ ( "background-color", item.color )
, ( "border", "1px solid #000" )
, ( "position", "absolute" )
, ( "left", px rectangle.left )
, ( "top", px rectangle.top )
]
]
[ text item.text
, br [] []
, text (toString item.value)
]
updatedItemOverlay : ( Item, Rectangle ) -> Html Msg
updatedItemOverlay ( item, rectangle ) =
div
[ onDragStart DeleteFromUpdatedList item
, attribute "class" "drag-here-updated-overlay-item"
, sharedStyles
, style
[ ( "background-color", "transparent" )
, ( "position", "absolute" )
, ( "left", px rectangle.left )
, ( "top", px rectangle.top )
]
]
[]
receivingItemUnderlay : Item -> Html Msg
receivingItemUnderlay item =
div
[ attribute "class" "drag-here-underlay-item"
, sharedStyles
, style
[ ( "background-color", item.color )
-- , ( "border", "1px solid #1A6B0D" )
]
]
[ text item.text
, br [] []
, text (toString item.value)
]
receivingItemOverlay : Item -> Html Msg
receivingItemOverlay item =
div
[ on "mouseenter" (Decode.map (\d -> UpdateReceivingItemsOnOverlap d item) (DOM.target DOM.boundingClientRect))
, on "mouseleave" (Decode.map (\d -> RestoreReceivingItemsListColor d) (DOM.target DOM.boundingClientRect))
, on "mouseup" (Decode.map (\d -> AddValues d item) (DOM.target DOM.boundingClientRect))
, attribute "class" "drag-here-overlay-item"
, sharedStyles
, style
[ ( "background-color", "transparent" ) ]
]
[]
movableItemDiv : Item -> Html Msg
movableItemDiv item =
div
[ onDragStart DeleteFromMovableList item
, attribute "id" ("drag-me " ++ toString item.value)
, sharedStyles
, style
[ ( "background-color", item.color )
, ( "border", "1px solid #DD0848" )
, ( "position", "relative" )
]
]
[ text "Drag Me!"
, br [] []
, text (toString item.value)
]
movingItemDiv : Model -> Html Msg
movingItemDiv model =
let
movingItem =
movingItemMaybe model.movingItem
realRectangle =
getCurrentMovingRectangle model
in
div
[ onMouseUp RestoreListContent
, sharedStyles
, style
[ ( "background-color", "#FF3C8C" )
, ( "border", "1px solid #DD0848" )
, ( "position", "absolute" )
, ( "top", px (realRectangle.top) )
, ( "left", px (realRectangle.left) )
, ( "z-index", "2" )
]
]
[ text movingItem.text
, br [] []
, text (toString movingItem.value)
]
sharedStyles : Attribute a
sharedStyles =
style
[ ( "width", "100px" )
, ( "height", "100px" )
, ( "border-radius", "4px" )
, ( "color", "white" )
, ( "justify-content", "center" )
, ( "align-items", "center" )
, ( "display", "flex" )
]
onDragStart : (Item -> Msg) -> Item -> Attribute Msg
onDragStart deleteMsg item =
on "mousedown"
(Mouse.position
`Decode.andThen`
(\posit ->
DOM.target DOM.boundingClientRect
`Decode.andThen`
(\rect ->
Decode.succeed (DragAndDelete deleteMsg posit item rect)
)
)
)
px : countable -> String
px number =
toString number ++ "px"
因此,正如您所看到的,当您点击movableItemDiv
时,模型的drag
和movingItem
字段会根据鼠标的位置和尺寸进行更新( ({1}}的矩形)。但是,这些尺寸与观点有关。 movableItem
然后调用movingItemDiv
,根据getCurrentMovingRectangle
和{{1}的维度设置left
和top
样式movingItemDiv
在模型中。因为movingItem
的维度基于drag
相对于视点的维度,而不是相对于文档的维度,而为movingItem
和movableItemDiv
值设置的值top
建立元素相对于文档的位置(或父元素,我不确定是诚实的),left
未正确定位。我希望这很清楚!
答案 0 :(得分:2)
更新为elm-0.18
以下是带有可拖动项目的列表的快速而肮脏的示例
(您可以将其复制到elm-lang.org/try以查看其中的操作)
transform: translate()
用于定位被拖动的项目下一步是确定拖动结束时我们是否超过了一个放置区域。 要计算,您需要知道:
希望这会帮助你朝着正确的方向前进!
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (on)
import Json.Decode as Json
import Mouse exposing (Position)
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- MODEL
type alias Model =
{ position : Position
, items : List String
, drag : Maybe Drag
}
type alias Drag =
{ id : Int
, start : Position
, current : Position
}
init : ( Model, Cmd Msg )
init =
Model
(Position 200 200)
[ "Apples", "Bananas", "Cherries", "Dades" ]
Nothing
! []
-- UPDATE
type Msg
= DragStart Int Position
| DragAt Position
| DragEnd Position
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
( updateHelp msg model, Cmd.none )
updateHelp : Msg -> Model -> Model
updateHelp msg ({position, items, drag} as model) =
case msg of
DragStart id xy ->
Model position items (Just (Drag id xy xy))
DragAt xy ->
Model position items (Maybe.map (\{id, start} -> Drag id start xy) drag)
DragEnd _ ->
Model position items Nothing
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
case model.drag of
Nothing ->
Sub.none
Just _ ->
Sub.batch [ Mouse.moves DragAt, Mouse.ups DragEnd ]
-- VIEW
(=>) = (,)
view : Model -> Html Msg
view model =
div []
<| List.indexedMap (itemView model) model.items
itemView : Model -> Int -> String -> Html Msg
itemView model index item =
let
zIndex =
case model.drag of
Just {id} ->
if index == id then
"99"
else
"0"
Nothing ->
"0"
in
div
[ onMouseDown index
, style
[ "background-color" => "#3C8D2F"
, "border" => "2px solid orange"
, "cursor" => "move"
, "position"=> "relative"
, "transform" => (getOffset model index)
, "z-index" => zIndex
, "width" => "100px"
, "height" => "100px"
, "border-radius" => "4px"
, "color" => "white"
, "display" => "flex"
, "align-items" => "center"
, "justify-content" => "center"
, "user-select" => "none"
]
]
[ text item
]
px : Int -> String
px number =
toString number ++ "px"
getOffset : Model -> Int -> String
getOffset {position, items, drag} index =
case drag of
Nothing ->
translate 0 0
Just {id, start,current} ->
if index == id then
translate (current.x - start.x) (current.y - start.y)
else
translate 0 0
translate : Int -> Int -> String
translate x y =
"translate(" ++ toString x ++ "px , " ++ toString y ++ "px)"
onMouseDown : Int -> Attribute Msg
onMouseDown id =
on "mousedown" (Json.map (DragStart id) Mouse.position)