I'm using Elm 0.19 and the joakin/elm-canvas package.
All I'm trying to do is draw a canvas that spans the entire width and height of the screen and resizes dynamically as the window size changes. I've spent several hours debugging and researching but I'm just stuck. I've tried multiple implementations, and I can get it to work, but not on initial page load. Instead, it renders only after the update function gets called. I feel like I'm missing something obvious, as I'm still very new to Elm.
Here's a link to the working Ellie code/demo showing the problem: https://ellie-app.com/6JZDxnQWPLSa1 Notice how the screen is blank until after a keypress event fires the update, and then the canvas appears.
Edit: adding code from the Ellie demo.
module Main exposing (..)
import Browser
import Browser.Dom exposing (Viewport, getViewport)
import Browser.Events exposing (onKeyDown, onResize)
import Canvas exposing (..)
import Canvas.Settings exposing (..)
import Canvas.Settings.Advanced exposing (..)
import Color
import Html exposing (Html, div)
import Html.Attributes exposing (style)
import Json.Decode as Decode
import Task
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, subscriptions = subscriptions
, update = update
}
-- MODEL
type alias Model =
{ screen : { width : Int, height : Int }
}
init : () -> ( Model, Cmd Msg )
init _ =
( { screen = { width = 800, height = 600 }
}
, Task.perform (\{ viewport } -> ScreenSize (round viewport.width) (round viewport.height)) getViewport
)
-- UPDATE
type Msg
= TurnLeft
| TurnRight
| MoveForward
| Other
| ScreenSize Int Int
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
TurnLeft ->
( model, Cmd.none )
TurnRight ->
( model, Cmd.none )
MoveForward ->
( model, Cmd.none )
ScreenSize w h ->
( { model | screen = { width = w, height = h } }
, Cmd.none
)
Other ->
( model, Cmd.none )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ onKeyDown keyDecoder
, onResize ScreenSize
]
keyDecoder : Decode.Decoder Msg
keyDecoder =
Decode.map toDirection (Decode.field "key" Decode.string)
toDirection : String -> Msg
toDirection string =
case string of
"ArrowLeft" ->
TurnLeft
"ArrowRight" ->
TurnRight
"ArrowUp" ->
MoveForward
_ ->
Other
-- VIEW
clearScreen : Float -> Float -> Renderable
clearScreen width height =
shapes [ fill Color.black ] [ rect ( 0, 0 ) width height ]
view : Model -> Html Msg
view { screen } =
div
[ style "display" "flex"
, style "justify-content" "center"
, style "align-items" "center"
]
[ Canvas.toHtml
( screen.width, screen.height )
[]
[ clearScreen (toFloat screen.width) (toFloat screen.height)
, shapes [ fill Color.red ] [ rect ( 30, 30 ) 200 200 ]
]
]
Here a working example: https://ellie-app.com/6KrhJjGLwc5a1. It's blinking a bit, but the canvas is adjusted on resize. This post helped me a lot to find this workaround.
I added a busy
prop to the model:
type alias Model =
{ screen : { width : Int, height : Int }
, busy : Bool
}
and I added a PostScreenSize
to force the view to reload after ScreenSize
done:
ScreenSize w h ->
( { model | screen = { width = w, height = h }, busy = True }
, delay PostScreenSize
)
PostScreenSize ->
( { model | busy = False }
, Cmd.none
)