boundingClientRect获取元素相对于文档的位置

时间:2016-10-27 13:20:46

标签: elm

我尝试实施拖放程序,使用DOM包中的boundingClientRect来获取要移动的元素的尺寸,并使用鼠标中的position来跟踪拖动时鼠标的移动。

程序在我滚动之前工作正常,但是当我向下滚动时,拖动元素在视图中显得比我点击之前更高。我怀疑发生的是,boundingClientRect获取元素相对于视点的位置,然后我使用这些值来设置topleft值。但是,topleft相对于文档或父元素。但是,我不知道除了boundingClientRect之外我可以使用什么来获取相对于文档或父元素的lefttop值。

这里是代码,它可能比我的漫无边际更清晰。

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时,模型的dragmovingItem字段会根据鼠标的位置和尺寸进行更新( ({1}}的矩形)。但是,这些尺寸与观点有关。 movableItem然后调用movingItemDiv,根据getCurrentMovingRectangle和{{1}的维度设置lefttop样式movingItemDiv在模型中。因为movingItem的维度基于drag相对于视点的维度,而不是相对于文档的维度,而为movingItemmovableItemDiv值设置的值top建立元素相对于文档的位置(或父元素,我不确定是诚实的),left未正确定位。我希望这很清楚!

1 个答案:

答案 0 :(得分:2)

更新为elm-0.18

以下是带有可拖动项目的列表的快速而肮脏的示例

(您可以将其复制到elm-lang.org/try以查看其中的操作)

  • 每个项目都有相对定位
  • transform: translate()用于定位被拖动的项目
  • 我们不知道该项目的绝对位置,但我们确实知道它相对于其(未知)起始位置移动了多少。

下一步是确定拖动结束时我们是否超过了一个放置区域。 要计算,您需要知道:

  • 放置区域与列表容器左上角相对的位置
  • 每个放置区的大小(宽度,高度)
  • 相对于列表容器的左上角拖动的项目的原始位置
    • 为此,您需要知道每个项目的实际高度(我总是在每个项目上使用固定高度)
  • 列表容器中的滚动量(使用来自elm-lang / dom的Dom.y)

希望这会帮助你朝着正确的方向前进!

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)