mdgriffith / elm-ui

What if you never had to write CSS again?
https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/
BSD 3-Clause "New" or "Revised" License
1.35k stars 112 forks source link

Unexpected scrolling/layout behavior #70

Open gampleman opened 5 years ago

gampleman commented 5 years ago

https://ellie-app.com/3TtR8yh7Rw5a1

Expected behavior

column [ width fill, height fill, spacing 30 ]
        [ myElement
        , column [ width fill, height fill ] [
            column [ width (px 350), height fill, Element.scrollbarY, Border.width 1 ] -- here
                [ column [ width fill ] <|
                    el [] (text "this should scroll indepedendently")
                        :: List.map (always myElement) (List.range 0 100)
                ]
            , el [] (text "this should be at the bottom of the screen")
            ]
        ]

The column above is marked height fill, so one would expect it to fill the container. It has children that overflow that height, but Element.scrollbarY has been set there.

So my expectation would be that the marked column would take the remaining space of the container but would allow all its children to scroll.

Versions

Motivation

This came up when building a sidebar for our application where in the main area the user can make a selection and related data to the selected items appears in the sidebar. We wanted the navigation and other items to remain visible in the sidebar, but have just the related data part scroll.

ghost commented 5 years ago

Yup. I ran into this exact behavior when I was learning Elm by building a browser for my photos. I wanted a top bar that allowed me to change the viewing mode and only the view would scroll.

        [ (Element.layout [] <|
            column [ width fill, height fill ]
            [ row
                [ paddingXY 20 0
                , spacing 10
                , verticalGrayGradient
                , width fill
                , height (px 60)
                ]

                [ modeButton "Grid" Grid
                , modeButton "Timeline" Timeline
                , modeButton "Slideshow" Slideshow
                , Input.button
                    [ alignRight ]
                    { onPress = Just TodoInput
                    , label = Element.el
                        [ padding 8
                        , verticalBlueGradient
                        , Border.rounded 3
                        , Font.color offWhite
                        ]
                        (text "Autoplay")
                    }
                ]
            , el
                [width fill, height fill, scrollbarY] -- !!! PROBLEM RIGHT HERE !!!
                (case model.appMode of
                    Grid ->
                        (viewGrid model)
                    Timeline ->
                        (viewTimeline model)
                    Slideshow ->
                        none  -- TODO. Not implemented
                )
            ]
        )]

The viewGrid and viewTimeline functions actually handle the rendering of the view but the idea is that the containing el will be constrained to the screen size so whatever DOM elements those functions produce will scroll inside of the el rather than causing the el to keep growing. Correct me if I am wrong about this expectation as I still an Elm n3wb.

Anyways, I worked around this by keeping the Viewport information from Browser.Dom around in my model and forcing the maximum size of my container el to be whatever was left after subtracting out the top bar. Note that this only works however if you know the size of all of your elements.

-- Problematic line changes to this
[width fill, height (fill |> maximum (Basics.round (model.viewport.viewport.height - 60.0))), scrollbarY]

Because AFAIK, we don't know IDs of anything elm-ui produces so Browser.Dom.getViewportFor is of no use.

jhbrown94 commented 5 years ago

I discovered by accident that a workaround is to add scrollbarY to the parent of the intended container, see https://ellie-app.com/538HjqscB6ka1

jhbrown94 commented 5 years ago

I've done some digging. I think the root cause is that flex-shrink is set to 0 except in scrolling elements. So if a would-be scrolling element is contained in something which doesn't have a fixed size, that container will grow indefinitely rather than triggering the scrollbars. Liberal application of htmlAttribute <| HtmlAttr.style "flex-shrink" "1" to containers resolves the issue for me.

@mdgriffith Would something break if you just set flex-shrink to 1 for everything?

jhbrown94 commented 5 years ago

Wait, is this just #12?

jhbrown94 commented 5 years ago

The more I read, the more complicated this sounds. flex-shrink might not be sufficient? I'm unclear. https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size

opsb commented 5 years ago

In the past I've found wrapping children in a div with position absolute can help resolve flexbox issues. If you're stuck you can use this helper for now

scrollbarYEl : List (Attribute msg) -> Element msg -> Element msg
scrollbarYEl attrs body =
    el [ height fill, width fill ] <|
        el
            ([ htmlAttribute <| Html.Attributes.style "position" "absolute"
             , htmlAttribute <| Html.Attributes.style "top" "0"
             , htmlAttribute <| Html.Attributes.style "right" "0"
             , htmlAttribute <| Html.Attributes.style "bottom" "0"
             , htmlAttribute <| Html.Attributes.style "left" "0"
             , Element.scrollbarY
             ]
                ++ attrs
            )
            body
rex891 commented 5 years ago

Anyone figure a good solution to this?

cmditch commented 5 years ago

Indeed, experiencing this issue as well.

bburdette commented 4 years ago

@kmBlaine your post got me thinking - I was able to do some measuring by putting in an HTML element of zero size that has an ID, and then doing a getViewportFor on it. You have to add in the padding afterwards.

topBar : Model a -> Element Msg
topBar model =
    E.row
        [ E.width E.fill]
        [ EI.button buttonStyle { label = E.text "select pdf", onPress = Just SelectClick }
        <... more stuff ... >
        , E.html <|  Html.div [ HA.id "topbarheight", HA.style "height" "100%" ] [ Html.text "" ]
        ]
    , Task.attempt TopViewport <| Browser.Dom.getViewportOf "topbarheight"
Mox93 commented 4 years ago

the way I worked around it was by adding htmlAttribute <| Html.Attributes.style "height" "100vh" to the outer row and adding scrollbarY to the columns, and it worked.

alexkorban commented 4 years ago

Also happens with elm-ui 1.1.5. #bug #has-ellie

dullbananas commented 4 years ago

I want this fixed. It's the only thing that has ever frustrated me a lot when using Elm.

tankorsmash commented 2 years ago

Maybe having an easy workaround listed somewhere would help, since this is a hard-to-fix issue. There's a few listed in this thread, but maybe there could be one canonical solution.

Maldus512 commented 2 years ago

@opsb 's solution was the most straightforward to apply for me.