如何强制Elm重用DOM元素?

问题描述

我想用Elm制作一个Web应用程序,其中将包含一些视频元素,其动态布局可以根据浏览器的宽度和高度而变化。当我尝试这样做时,很明显Elm正在为视频生成新元素,但这是行不通的,因为视频元素的状态需要保留。

为简单起见,我用计数器而不是视频来演示该问题。我试图用Html.lazyKeyed.node解决问题,但问题仍然存在。

这里的代码也可以从https://github.com/ijt/elm-dom-elt-reuse克隆。

src/Main.elm

port module Main exposing (..)

import browser
import Html exposing (..)
import Html.Attributes as Attribute exposing (id,style)
import Html.Events exposing (onClick)


main =
    browser.element
        { init = init,view = view,update = update,subscriptions = subscriptions
        }


type alias Model =
    { layout : Layout }


type Layout
    = Row
    | Column


init : () -> ( Model,Cmd Msg )
init _ =
    ( { layout = Row },startCounters () )


type Msg
    = ToggleLayout


update : Msg -> Model -> ( Model,Cmd Msg )
update msg model =
    case msg of
        ToggleLayout ->
            let
                l2 =
                    case model.layout of
                        Row ->
                            Column

                        Column ->
                            Row
            in
            ( { model | layout = l2 },Cmd.none )


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick ToggleLayout ] [ text "Toggle Layout" ],counters model
        ]


counters : Model -> Html Msg
counters model =
    case model.layout of
        Column ->
            div []
                [ div [] [ counter1 ],div [] [ counter2 ]
                ]

        Row ->
            div [] [ counter1,spacer,counter2 ]


spacer : Html Msg
spacer =
    text " "


counter1 : Html Msg
counter1 =
    span [ id "counter1" ]
        [ text "0" ]


counter2 : Html Msg
counter2 =
    span [ id "counter2" ]
        [ text "0" ]


port startCounters : () -> Cmd msg

static/index.html

<!DOCTYPE HTML>
<html>
<head>
  <Meta charset="UTF-8">
  <title>Main</title>
  <style>body { padding: 0; margin: 0; }</style>
</head>

<body>

<pre id="elm"></pre>

<script src="Main.js"></script>
<script>
  window.app = Elm.Main.init( { node: document.getElementById("elm") } );

  window.app.ports.startCounters.subscribe(function() {
    let c1 = document.getElementById("counter1");
    let c2 = document.getElementById("counter2");
    function increment(e) {
      let n = parseInt(e.innerText);
      e.innerText = n + 1;
    }
    requestAnimationFrame(function() {
      setInterval(function() {
        increment(c1);
        increment(c2);
      },100)
    })
  });
</script>

</body>
</html>

Makefile

static/Main.js: src/Main.elm
    elm make src/Main.elm --output=static/Main.js

解决方法

将元素始终保持在DOM树中相同的深度即可。

这是新代码:

counters : Model -> Html Msg
counters model =
    let
        d =
            case model.layout of
                Column ->
                    "column"

                Row ->
                    "row"
    in
    div
        [ style "flex-direction" d,style "display" "flex"
        ]
        [ counter1,counter2
        ]


counter1 : Html Msg
counter1 =
    span [ id "counter1",style "padding" "8px" ]
        [ text "0" ]


counter2 : Html Msg
counter2 =
    span [ id "counter2",style "padding" "8px" ]
        [ text "0" ]


port startCounters : () -> Cmd msg

感谢Elm Slack频道上的jessta。