2016-10-27 2 views
2

Я пытаюсь реализовать программу перетаскивания, используя boundingClientRect из пакета DOM, чтобы получить размеры перемещаемого элемента и position от мыши, чтобы отслеживать движение мыши при перетаскивании.boundingClientRect, чтобы получить положение элемента относительно документа

Программа отлично работает, прежде чем прокручивать, но когда я прокручиваю вниз, элемент перетаскивания появляется выше в представлении, чем до того, как я щелкнул его. Я подозреваю, что происходит: boundingClientRect получает позицию элемента относительно точки обзора, а затем я использую эти значения для установки значенийи 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 поля обновляются с положением мыши и размеры (Rectangle) в movableItem соответственно. Однако эти измерения относятся к точке зрения. movingItemDiv затем звонит getCurrentMovingRectangle, который устанавливает left и top стили movingItemDiv в соответствии с размерами movingItem и drag в модели. Поскольку размеры movingItem основаны на размерах movableItemDiv относительно точки обзора, а не относительно документа, тогда как значения, установленные для значенийи left, определяют положение элемента относительно документа (или родительский элемент, я не уверен, если честно), movingItemDiv не установлен правильно. Надеюсь, это ясно!

+1

Вы, вероятно, с помощью [Дебуа/вяз-РОМ] (http://package.elm-lang.org/packages/debois/elm-dom/1.2.2/DOM) пакета. Как объяснено [здесь] (http://package.elm-lang.org/packages/debois/elm-dom/1.2.2/DOM#boundingClientRect), 'boundingClientRect' является приблизительным и не работает должным образом при прокрутке. – wintvelt

+0

И что работает правильно при прокрутке: -D? –

+0

Альтернативный подход может заключаться в использовании абсолютного позиционирования для движущихся объектов. например, в примере [mouse drag] (http://elm-lang.org/examples/drag) и использовать [эту функцию из elm-lang/dom] (http://package.elm-lang.org/packages /elm-lang/dom/1.1.0/Dom-Scroll#y), чтобы компенсировать прокрутку, когда выясняется, находится ли ваш движущийся объект по намеченной цели. – wintvelt

ответ

2

Обновлен вяз-0,18

Ниже быстрый и грязный пример списка с перемещаемыми пунктов

(который вы можете скопировать в elm-lang.org/try, чтобы увидеть его в действии)

  • каждый элемент имеет относительное позиционирование
  • transform: translate() предназначено для размещения предмета, перетаскиваемого
  • Мы не знаем абсолютного положения предмета, но знаем, сколько он переместился относительно его (неизвестной) исходной позиции.

Следующим шагом будет определение, если мы закончили зону перетаскивания при завершении таскания. Для вычисления, вы должны знать:

  • относительное положение ваших зон падения по сравнению с верхнего левого угла список вашего контейнера
  • размер (ширина, высота) каждой зоны падения
  • исходное положение элемента тащат по отношению к верхней левой части списка контейнера
    • для этого, вам нужно будет знать фактическую высоту каждого элемента (я всегда использовал фиксированную высоту по каждому пункту)
  • количества свитка в контейнере списка (используя 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) 
Смежные вопросы