Contents

Use Case Descriptions

Use case #1: a Web application needs to render content to a canvas (using 2D or WebGL contexts) being displayed on a high-DPI screen. For best quality output, the pixels of the canvas backing store need to match 1:1 with screen pixels.

Use case #2: As #1, and the user moves the page from screen to screen resulting in dynamic DPI changes.

Use case #3: As #1, and the canvas is inside a CSS scale transform which may change dynamically.

Use case #4: As #1, and the canvas's CSS content box has arbitrary fractional CSS pixel size and offset from the viewport.

Use case #5: As #3, but the canvas is inside an <iframe> in a page with a different origin which contains the CSS transform.

Current Usage and Workarounds

Safari shipped a canvas implementation that automatically resized the backing store but this caused various problems, such as incompatibility with some existing content and a need for new "HD" APIs that authors were not motivated to use. This approach does not work for WebGL in any case.

The current best workaround is to set the canvas backing store size by computing the canvas content box in CSS pixels relative to the viewport, as accurately as possible (e.g. using getBoxQuads), multiplying by window.devicePixelRatio, and using that to set the canvas width and height. This approach does not easily adapt to dynamic changes and does not handle use cases #4 or #5. In use-case #4 the computed device pixel size may be non-integer, in which case the UA is likely to snap the content-box to device pixel edges and the Web developer cannot predict how that snapping will be done. In use-case #5 the developer cannot know about the CSS transform in the foreign-origin content.

Goals

Leave the application in control of setting the pixel size of the backing store.

Make it easy for applications to set the pixel size of the backing store so that backing store pixels are 1:1 with device pixels, when that's possible.

Non Goals

Enable high-resolution backing store for existing content.

Proposed Solutions

Hixie proposed a canvas attribute to opt into automatic sizing of the backing store, but this violates goals #1 and #2. Violating goal #1 is a problem because applications often use temporary canvases and other assets which are dependent on the pixel size of the backing store. Goal #1 is also essential for WebGL.

Suggested Solution

Expose a preferred size for the canvas backing store directly to Web authors. Web authors who want to match screen resolution set the canvas width and height to that size. Expose an event on the canvas that fires when the preferred size changes.

Suggested IDL

The values of these attributes are not normatively defined. For example, a UA might reduce these values to conserve system resources.
However, under normal steady-state conditions a UA is expected to choose values such that, if the canvas element has a CSS background color exactly filling its content-box, and the UA renders that CSS background color as a rectangle whose edges are aligned to device pixel edges, then renderedPixelWidth and renderedPixelHeight are the device pixel size of that rectangle.

Add a new event renderedsizechange to HTMLCanvasElement. This event does not bubble and is not cancelable. Whenever the value that would be returned by renderedPixelWidth or renderedPixelHeight changes, queue a task to fire renderedsizechange at the HTMLCanvasElement if there is not already a task pending to fire such an event at that element.

Rationale:

This API appears to be the minimal API needed to achieve the goals, is opt-in, and can be used in several different ways depending on how ambitious the app developer is:

Simplest possible usage: set the canvas size once when loading and don't handle dynamic changes.

Issues:

Hard to determine a rendered size when the canvas is not attached to the DOM. Perhaps in that case the current intrinsic size should be returned? (junov@chromium.org)

After a layout change that affects rendered pixel size, there is no guarantee that the size change event will be handled before the layout change is propagated to screen, so the content may be temporarily displayed in an inconsistent state. Note: the same issue exists with existing methods that may be based on mutation observers or window.onresize, for example. Though it is not the stated objective of this proposal to solve this problem, there may be an opportunity to do so. (junov)

Accessing rendered pixel size is layout-inducing. To avoid layout thrashing, we should consider making this an asynchronous getter (e.g. asyncGetBoundignClientRect). This would also prevent renderedsizechanged events from firing from within the evaluation of rendered pixel size, which is weird. (junov, credit: nduca)