榆树:向SVG元素添加点击事件不起作用 - 这可能吗?

r-g*_*-gr 4 svg mouseevent elm

我一直在尝试向on "click"Elm中的SVG元素添加一个事件,以确定鼠标在该元素中的相对位置.

下面给出了一个代码示例,您可以尝试在http://elm-lang.org/try上运行,以显示HTML元素上的点击事件如何按预期工作,但不能在SVG元素上工作.

在样本中,Html.on "click"使用而不是Html.onClick允许从事件中解码位置数据,如本讨论中所述.

在阅读文档和源代码之后,我希望当on "click"事件添加到SVG元素时,它将以与将事件添加到HTML元素相同的方式工作.但是,完成此操作后,单击SVG元素不会触发事件,也不会向更新功能发送任何消息.

在此示例中,单击黑色SVG rect应触发更新功能并更改白色的位置,rect但忽略点击.这可以通过打开控制台并注意Debug.log未调用来确认.div下面放置一个HTML ,其中包含相同的点击事件,当在其中注册点击时div,白色会rect更改位置.

这是Elm中的预期行为,是否有任何变通方法?

这里有一个关于stackoverflow的类似问题,但这是指画布形状,据我所知,这是一个完全独立的问题(虽然我可能错了).

代码示例:

import Html exposing (Html, div)
import Html.App as App
import Html.Attributes
import Html.Events exposing (on)
import Json.Decode as Json exposing (object2, int, at)
import Mouse exposing (Position)
import Svg exposing (svg, rect)
import Svg.Attributes exposing (..)

main =
  App.beginnerProgram
    { model = model
    , view = view
    , update = update
    }

type alias Model =
  Position

type Msg
  = ChangePosition Position

model : Model
model =
  Position 0 0

update : Msg -> Model -> Model
update msg _ =
  case Debug.log "msg" msg of
    ChangePosition position ->
      position

view : Model -> Html Msg
view model =
  div []
    [ svg
        [ width "400"
        , height "100"
        , viewBox "0 0 400 100"
        ]
        [ rect
            [ onClickLocation -- this should work but does nothing
            , width "400"
            , height "100"
            , x "0"
            , y "0"
            , fill "#000"
            , cursor "pointer"
            ]
            []
        , rect
            [ width "50"
            , height "50"
            , x (toString model.x)
            , y "20"
            , fill "#fff"
            ]
            []
        ]
    , div
        [ onClickLocation -- this works
        , Html.Attributes.style
            [ ( "background-color", "white" )
            , ( "border", "2px solid black" )
            , ( "width", "400px" )
            , ( "height", "100px" )
            , ( "position", "absolute" )
            , ( "left", "0px" )
            , ( "top", "150px" )
            , ( "color", "black" )
            , ( "cursor", "pointer" )
            ]
        ]
        [ div [] [ Html.text "Click in here to move x position of white svg square. Relative click coordinates shown below (y coordinate ignored)." ]
        , div [] [ Html.text (toString model) ]
        ]
    ]

onClickLocation : Html.Attribute Msg
onClickLocation =
  on "click"
    (Json.map
      ChangePosition
      (object2
        Position
        (object2 (-)
          (at [ "pageX" ] int)
          (at [ "target", "offsetLeft" ] int)
        )
        (object2 (-)
          (at [ "pageY" ] int)
          (at [ "target", "offsetTop" ] int)
        )
      )
    )
Run Code Online (Sandbox Code Playgroud)

Tos*_*osh 6

Json解码器不起作用的原因很明显,因为事件对象中offsetLeft也不offsetTop存在.

这有点令人困惑,因为这些属性可用于Html DOM的单击事件,但不适用于SVG DOM.(我在Elm中实现事件解码器的建议是在浏览器的调试器控制台中附加临时事件处理程序并研究实际的事件对象.Elm的解码器默默地失败,很难知道为什么解码器不起作用.)

在这里,我实现了一种替代方法,您可以使用portjavascript(不使用任何社区库)来获取父位置.

port module Main exposing (main)

import Html exposing (Html, div)
import Html.App as App
import Html.Attributes
import Html.Events exposing (on)
import Json.Decode as Json exposing (object2, object1, int, at)
import Mouse exposing (Position)
import Svg exposing (svg, rect)
import Svg.Attributes exposing (..)

main : Program Never
main =
  App.program
    { init = (initmodel, getParentPos ())
    , view = view
    , update = update
    , subscriptions = subscriptions
    }

type alias Model =
  { position : Position
  , parentPosition : Position
  }

type Msg
  = ChangePosition Position
  | UpdateParentPosition { top : Int, left : Int }

initmodel : Model
initmodel =
  { position = Position 0 0
  , parentPosition = Position 0 0
  }

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case Debug.log "msg" msg of
    ChangePosition position ->
      let
        relativepos = Position
          ( position.x - model.parentPosition.x )
          ( position.y - model.parentPosition.y )
      in ({ model | position = relativepos } , Cmd.none)
    UpdateParentPosition {top, left} ->
      ({ model | parentPosition = Position top left }, Cmd.none)

port getParentPos : () -> Cmd msg

subscriptions : Model -> Sub Msg
subscriptions model =
  parentPos UpdateParentPosition

port parentPos : ({ top : Int, left : Int } -> msg) -> Sub msg

view : Model -> Html Msg
view model =
  div []
    [ svg
        [ width "400"
        , height "100"
        , viewBox "0 0 400 100"
        , id "parent"
        ]
        [ rect
            [ onClickLocation -- this should work but does nothing
            , width "400"
            , height "100"
            , x "0"
            , y "0"
            , fill "#000"
            , cursor "pointer"
            ]
            []
        , rect
            [ width "50"
            , height "50"
            , x (toString model.position.x)
            , y (toString model.position.y)
            , fill "#fff"
            ]
            []
        ]
    , div
        [ onClickLocation -- this works
        , Html.Attributes.style
            [ ( "background-color", "white" )
            , ( "border", "2px solid black" )
            , ( "width", "400px" )
            , ( "height", "100px" )
            , ( "position", "absolute" )
            , ( "left", "0px" )
            , ( "top", "150px" )
            , ( "color", "black" )
            , ( "cursor", "pointer" )
            ]
        ]
        [ div [] [ Html.text "Click in here to move x position of white svg square. Relative click coordinates shown below (y coordinate ignored)." ]
        , div [] [ Html.text (toString model) ]
        ]
    ]

onClickLocation : Html.Attribute Msg
onClickLocation =
  on "click"
    (Json.map
      ChangePosition
      (object2
        Position
          (at [ "pageX" ] int)
          (at [ "pageY" ] int)
      )
    )
Run Code Online (Sandbox Code Playgroud)

JavaScript的:

const app = Elm.Main.fullscreen();

app.ports.getParentPos.subscribe(() => {
  const e = document.querySelector('#parent');
  const rect = e.getBoundingClientRect();
  app.ports.parentPos.send({
    top: Math.round(rect.top),
    left: Math.round(rect.left)
  });
});
Run Code Online (Sandbox Code Playgroud)