使用 jumpToBottom 实现自动滚动

Implementing autoscroll with jumpToBottom

setViewportOf 的文档包含函数 jumpToBottom

下面是一个使用它的小示例程序:

module Main exposing (..)

import Browser
import Html             exposing (..)
import Html.Events      exposing (..)
import Html.Attributes  exposing (..)
import Browser.Dom
import Task

type Msg = AddText | NoOp

type alias Model = { messages : List String }

jumpToBottom : String -> Cmd Msg
jumpToBottom id =
    Browser.Dom.getViewportOf id
        |> Task.andThen (\info -> Browser.Dom.setViewportOf id 0 info.scene.height)
        |> Task.attempt (\_ -> NoOp)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 
    case msg of
        AddText -> ({ model | messages = model.messages ++ [ "abc" ]}, jumpToBottom "messages")
        NoOp -> (model, Cmd.none)

view : Model -> Html Msg
view model =
    div [ id "outer" ]
    [
        div [ id "messages" ]
        [
            ul [] (List.map (\message -> li [] [ text message ]) model.messages)
        ],
        button [ onClick AddText ] [ text "Add" ]
    ]

init : () -> (Model, Cmd Msg)
init _ = ({ messages = [] }, Cmd.none)

main = Browser.element { init = init, view = view, update = update, subscriptions = (\_ -> Sub.none) }

可以将此程序粘贴到 Try Elm 中进行测试。

每次按下该按钮,文本都会添加到页面中。最终,将添加超出屏幕大小的文本,并出现滚动条。我希望页面自动滚动,但这似乎没有发生。

如果我改变

jumpToBottom "messages"

jumpToBottom "outer"

这似乎没有效果。

欢迎提出任何建议! :-)

这是一个 CSS 布局问题。为了更好地理解,将此添加到您的页面 CSS(您可以在 https://ellie-app.com/ 上测试)。

    #messages { 
      max-height: 200px;
      overflow-y:auto;
    }

以上内容使 messages 成为可以设置其视口的可滚动元素。在没有某种高度限制的情况下,messages div 将根据需要展开,视口将始终与场景相同,或者换句话说,它永远不会滚动。

以下是基于@pdamoc 建议的版本。

一些差异:

  • css 样式是内联的,因此可以将其粘贴到 https://elm-lang.org/try
  • max-height不是固定的,所以它会扩展以填充window。
module Main exposing (..)

import Browser
import Html             exposing (..)
import Html.Events      exposing (..)
import Html.Attributes  exposing (..)
import Browser.Dom
import Task

type Msg = AddText | NoOp

type alias Model = { messages : List String }

jumpToBottom : String -> Cmd Msg
jumpToBottom id =
    Browser.Dom.getViewportOf id
        |> Task.andThen (\info -> Browser.Dom.setViewportOf id 0 info.scene.height)
        |> Task.attempt (\_ -> NoOp)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 
    case msg of
        AddText -> ({ model | messages = model.messages ++ [ "abc" ]}, jumpToBottom "messages")
        NoOp -> (model, Cmd.none)

view : Model -> Html Msg
view model =
    div 
    [ 
      class "main", 
      style "flex-grow" "1", 
      style "display" "flex", 
      style "flex-direction" "column", 
      style "max-height" "100vh" 
    ]
    [
        div 
        [ 
          id "messages", 
          class "messages", 
          style "flex-grow" "1",
          style "padding" "24px 24px 0 24px",
          style "overflow-y" "scroll" 
        ]
        (List.map (\message -> p [] [ text message ]) model.messages),
        
        button [ onClick AddText ] [ text "Add" ]
    ]

init : () -> (Model, Cmd Msg)
init _ = ({ messages = [] }, Cmd.none)

main = Browser.element { init = init, view = view, update = update, subscriptions = (\_ -> Sub.none) }