How to get the window size with Browser.Dom?

Hi,

My application is a Browser.element, and gets embedded into the Html body like this:

const {Elm} = require('../elm/Main.elm');
const app = Elm.Main.init({node: document.getElementById('diagram')});

...

<body id="diagram" style="overflow: hidden;">
</body>

When I try to get the window size using Browser.Dom.getViewport, I get the following runtime error:

Main.elm:4749 Uncaught TypeError: Cannot read property 'scrollWidth' of null
    at _Browser_getScene (Main.elm:4749)
    at _Browser_getViewport (Main.elm:4734)
    at eval (Main.elm:4709)

Is it possible to get the window size when using a Browser.element? Or do I need to switch to a Browser.document or Browser.application?

1 Like

I just changed to use a Browser.document instead, and now it works. I guess that makes sense - given that getViewport is a Task and can therefore handle errors might it be better if the signature were something like:

getViewport : Task Error Viewport

with the Error type extended to:

type Error 
   = NotFound String
   | NotSupported String

This creates another problem, I set up some attributes on my body:

<body id="diagram" style="overflow: hidden;">

but there is no way to construct a body like this with Browser.document. It might be better if body had its own type and constructor:

type Body msg = ...

body : List (Attribute msg) -> List (Html msg) -> Body msg

I fairly often want to create SVG applications that are full screen, but where the viewport is larger than the available screen size, so you can scroll around and zoom in and out and so on. To do this nicely you have to set overflow: hidden otherwise the scroll bars will flicker on and off.

Why is it required to set these attributes on the body rather than, say, a div immediately inside the body? And at least the styling can be done inside dedicated CSS, rather than as an Elm attribute, unless you want to toggle them.

Thanks, I tried putting it in a div, and happily that seems to work better when the screen is resized. :+1:

I set the SVG viewport to exactly match the pixel size it is rendered at, as this makes it easier to pixel align a drawing and render it nice and sharp looking. Directly in the body, when the screen is resized the drawing stretches before the new viewport size kicks in. Done in a div, this does not seem to happen, so the whole drawing does not jump around during a resize.

I think the reason for this is that I set the div to a fixed pixel size, so when the window is resized, it does not change the size of the div. The div is then set to a new size in the next view.

It would be helpful if there was a subscription like this:

module Browser.Events ...

onResizeOf : String -> (Int -> Int -> msg) -> Sub msg

Then I would just use getViewportOf and onResizeOf on the div containing the drawing. I think this would work equally with Browser.element as Browser.document.

For now, I just write my own onResizeOf.

1 Like

Its nice to be able to set overflow: hidden on the body, as sometimes the browser will show the scroll bar even when the content fits, and sometimes not. Its just a way of ensuring it is never shown, but yes, can put that in a global style.

What would onResizeOf do exactly? How would it work differently from the normal onResize event that is part of Browser.Events?

onResizeOf would take the id of some node, and notify every time that it changes size - a ResizeObserver basically. But I realize this is only wishful thinking with the current level of support for ResizeObserver: https://caniuse.com/#feat=resizeobserver

What I do currently is watch for mutations on some div that contains things I am interested in tracking size of, and get their size with getViewportOf. I also do this if the overall window changes size, and on any other actions that I think may change the size of something I am interested in - its a bit messy but seems to work ok.

When you talk about the “viewport” I guess you talk about the viewport property of the svg element, and not the visual viewport of the page? All this gets sometime confusing. Especially with svg sizing. Anyway, it’s not always necessary to use the “viewport” property to manage positionning and zooming in an SVG drawing. In fact I’ve had a lot of dealing with this for specific use cases where the SVG element is filling the space available in the page, (with flexbox or elm-ui) so it never displays scroll bars.

I have made a repo (not published package) with the code I use to manage this: GitHub - mpizenberg/elm-2d-viewer: A 2D (image) viewer with zoom and pan in mind. Don’t hesitate to have a look for inspiration.

The only requirement is that you know the size of the viewer. In my case I have a port sending the new page size on resize and I can deduce the viewer size.

Thanks, I’ll take a look at your 2d viewer repo.

Yes, I mean the SVG viewport. I generally set that so that in a normal (not-zoomed) drawing, its size is 1:1 with the pixel size it is being rendered. The drawing may extend over a much larger area of which only a smaller viewport is being shown. If the zoom changes, then obviously the size will no longer be 1:1 with the pixels.

There was a discussion about this on Slack today - a simple way to set overflow: hidden on the body, is simply to put it in an embedded style node on the body. Didn’t think of that. :grinning:

2 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.