No way to set canvas width/height without causing reflow




13 years ago
7 years ago


(Reporter: andrew, Unassigned)



Firefox Tracking Flags

(Not tracked)



(2 attachments)

1.05 KB, application/vnd.mozilla.xul+xml
1.04 KB, application/vnd.mozilla.xul+xml


13 years ago
User-Agent:       Mozilla/5.0 (X11; U; Linux i686; en-US; rv: Gecko/20060508 Firefox/
Build Identifier: Latest CVS trunk

The canvas width and height attributes have two distinct functions:
1) It controls the size of the canvas element on the page, in pixels, unless overriden by CSS width and style attributes.
2) It controls the size of the canvas coordinate space.

When using XUL flex attributes on a canvas, you can end up with different aspect ratios between the canvas bitmap and the area the canvas bitmap is drawn in. To correct this, it makes sense to set the width and height attributes of the canvas (and re-draw on the canvas), to set the coordinate space. Unfortunately, this then ends up causing a reflow, which changes the size of the canvas element again.

I think some better logic is required to determine when we are setting 1 & 2, and when we are just setting 2. I think the best way to do this would be to not cause a reflow when the existing width/height attributes are set, but do not match the current computed width and height.

Reproducible: Always

Steps to Reproduce:
Create a html:canvas inside a <xul:box>, such that the box is resizable due to a splitter. Setup a recurring timer to set the canvas width to the box's computed width from script.
Actual Results:  
The canvas keeps getting reflowed every time the computed width is set.

Expected Results:  
The canvas coordinate space, but not the canvas itself, should change.

Comment 1

13 years ago
After looking at the code more, it looks like the cleanest way to fix this would actually be to provide access to nsICanvasRenderingContextInternal::SetDimensions from another scriptable interface. It doesn't make sense for WHATWG to standardise this, because (AFAIK) neither WHATWG nor W3C define any specifications that allow elements to be resized based on user actions (i.e. they don't provide XUL splitters).

Comment 2

13 years ago
The height and width content attributes (as opposed to the CSS properties) of the <canvas> element must cause the canvas to be reset per the specification.

# If the width and height attributes are dynamically modified, the bitmap and
# any associated contexts must be cleared back to their initial state and
# reinitialised with the newly specified coordinate space dimensions.

(CSS3 UI has a 'resize' property fwiw, but I don't think that's related to this bug.)

Comment 3

13 years ago
Have you tried controlling the size using CSS (i.e. style="height: 300px; width:300px"), and setting the width and height attributes of the canvas to the coordinate space you need?

Comment 4

13 years ago
Re: Anne van Kesteren
The problem is not that you have to redraw the canvas (that makes sense) but rather, that there is no way to use it together with flexibility while keeping the aspect ratio correct (a repaint and a reflow are quite different, since a reflow moves the position of things on the screen). The WHATWG specification cannot provide any solution to this, because flexibility is a XUL specific feature, and the Web Apps specification does not include XUL.

Re: Eli Friedman
Setting the size via CSS is supported by the canvas, but that still doesn't allow you to use flexibility. Also, with the current code, even if you set the CSS width/height (which correctly triggers a reflow), a reflow is also triggered by changing the width/height attributes, which is inefficient.

My particular use-case showing why this is a problem follows:
I want to have a flexible canvas in a XUL layout which can be resized by moving splitters, and for which coordinate space width / coordinate space height = width on screen / height on screen, and so that the width of lines remains constant as the splitters are moved. When I detect that the user has resized the canvas (either through periodic checks on a timer, or perhaps by setting DOM listeners for height/width attributes being touched by splitter frames) I want to change the co-ordinate space size. However, if I do this by setting the width and height attributes on the canvas, this causes a reflow. During the reflow, the preferred size of the canvas is taken from the width and height attributes, but since it is flexible, the preferred size is not honoured and instead the size is computed. However, because preferred sizes have changed since the last reflow, the allocated sizes change too, and the new aspect ratio of the canvas has changed. Hence, by setting the co-ordinate width/height of the canvas to match the true width/height, we have changed the true width/height again.

Comment 5

13 years ago
Created attachment 228784 [details]
Example of flexible canvas

flex is really annoying for this kind of thing.  The way it works is to split the available width among elements that already have enough width.

There are a few things to note about this example.  One is the style attribute of the canvas tag; that forces it to have a zero width for the flex algorithm, which then adjusts the width of the canvas.  Another thing to note is the use of getComputedStyle to set the width and height properties of the canvas.

The way I did the scripting is not very efficient, you might want to do the update on command instead of mousemove or something like that if the canvas is expensive to repaint.  However, it demonstrates changing the coordinate space while maintaining a stable box model.

I believe it still reflows when the width is set, which probably isn't efficient, but nothing changes because the CSS width is set to 0.

The only thing I haven't figured out is why I need to wrap the canvas in a box.

Yes, this is tricky, and somewhat fragile; it took me quite a while to get it all correct.
So the "Example of flexible canvas" is an example of how it should work?
Maybe you could add a testcase that exposes the bug?

Comment 7

13 years ago
Created attachment 229921 [details]
Version that shows problems

This is an equivalent version, except the canvas isn't wrapped by a box.  I could be misunderstanding the XUL box model (I haven't coded much XUL), but I think this should be working.
Priority: -- → P4
I think this might more properly be a layout issue, or even XUL.
Component: Canvas: 2D → Layout
QA Contact: canvas.2d → layout
You need to log in before you can comment on or make changes to this bug.