I want to make several arguments against the drawElementImage() API design, which expects the author to capture the returned transformation matrix and pipe it into the element's transform property via the style attribute in order to transform the element's input coordinates.
const transform = ctx.drawElementImage(drawableElement, 100, 0);
drawableElement.style.transform = transform.toString();
- Using the
style attribute and transform property as a core part of this drawElementImage() feature, which has nothing to do with CSS or its transforms feature, is awkward and weird and hacky.
- Asking authors to reliably follow this non-obvious pattern of capturing and setting the transform manually, which they can reasonably forget, skip, or get wrong, is not great API design. The
drawElementImage() API should pipe the transform to the element automatically, unless instructed not to. The "right thing to do" should happen by default.
- Piping a transform into the element by stringifying it and then parsing it back in and running style recalculation is not performant. That is so much wasted computation.
- The
transform property can only handle linear transforms, but this is not enough to address many of the use cases for canvas-drawn elements, which will often need non-linear transformations. Consider, for example, wrapping text around a sphere. The characters at the edges are compressed closer together while the characters in the middle are stretched apart. If you try to insert a cursor 25% of the way into the text visually, that will be more than 25% of the way into the text in terms of caret positioning. Many of the visual distortions proposed as use cases for this feature are even more complex.
To address these problems I propose the following (as a starting point for discussion):
- Introduce a dedicated
Element.inputTransform property, where the InputTransform object represents the transformation of the element for the purpose of capturing its input on the <canvas>. This avoids messing with Element.style.
- Give the
InputTransform object the following mutable properties:
.transform - accepts a DOMMatrix (the drawElementImage() return value) directly, avoiding the stringification issue.
.matchDrawElementImage - boolean that says whether drawElementImage() calls should update .transform automatically. Defaults to true (common case), but allows the author to easily and persistently opt out for certain elements.
.map - accepts a function that maps input coordinates received by the element's <canvas> rectangle (after undoing .transform) to the input that the element should receive in its own, untransformed coordinate space. To re-use the sphere example, it might map y=50% x=25% (clicked position relative to the left edge of the image of the sphere) to y=50% x=38% (clicked position relative to the left edge of the element as originally laid out for drawing).
- and the following methods:
.set(DOMMatrix) - sets the transformation explicitly, unsetting .map and setting .matchDrawElementImage to false.
.set(string) - like .set(DOMMatrix), but using CSS transform syntax.
.set(function) - sets .map, setting .transform to identity and .matchDrawElementImage to false.
- Give
drawElementImage() an updateTransform argument, defaulting to auto (check matchDrawElementImage), and allowing true and false. Or maybe a skipTransformUpdate argument, defaulting to false (check matchDrawElementImage) and allowing true (allowing to draw e.g. a reflection without updating the element's position).
This gives authors a much more flexible and powerful API, one that can address common cases easily while also handling complex cases straightforwardly. It also avoids unnecessary stringification and style calculation work, and by internalizing the most common case (keeping things in sync), it allows the UA more opportunities for optimization.
I want to make several arguments against the
drawElementImage()API design, which expects the author to capture the returned transformation matrix and pipe it into the element'stransformproperty via thestyleattribute in order to transform the element's input coordinates.styleattribute andtransformproperty as a core part of thisdrawElementImage()feature, which has nothing to do with CSS or its transforms feature, is awkward and weird and hacky.drawElementImage()API should pipe the transform to the element automatically, unless instructed not to. The "right thing to do" should happen by default.transformproperty can only handle linear transforms, but this is not enough to address many of the use cases for canvas-drawn elements, which will often need non-linear transformations. Consider, for example, wrapping text around a sphere. The characters at the edges are compressed closer together while the characters in the middle are stretched apart. If you try to insert a cursor 25% of the way into the text visually, that will be more than 25% of the way into the text in terms of caret positioning. Many of the visual distortions proposed as use cases for this feature are even more complex.To address these problems I propose the following (as a starting point for discussion):
Element.inputTransformproperty, where theInputTransformobject represents the transformation of the element for the purpose of capturing its input on the<canvas>. This avoids messing with Element.style.InputTransformobject the following mutable properties:.transform- accepts a DOMMatrix (thedrawElementImage()return value) directly, avoiding the stringification issue..matchDrawElementImage- boolean that says whetherdrawElementImage()calls should update.transformautomatically. Defaults totrue(common case), but allows the author to easily and persistently opt out for certain elements..map- accepts a function that maps input coordinates received by the element's<canvas>rectangle (after undoing.transform) to the input that the element should receive in its own, untransformed coordinate space. To re-use the sphere example, it might map y=50% x=25% (clicked position relative to the left edge of the image of the sphere) to y=50% x=38% (clicked position relative to the left edge of the element as originally laid out for drawing)..set(DOMMatrix)- sets the transformation explicitly, unsetting.mapand setting.matchDrawElementImageto false..set(string)- like.set(DOMMatrix), but using CSStransformsyntax..set(function)- sets.map, setting.transformto identity and.matchDrawElementImageto false.drawElementImage()anupdateTransformargument, defaulting toauto(checkmatchDrawElementImage), and allowingtrueandfalse. Or maybe askipTransformUpdateargument, defaulting tofalse(checkmatchDrawElementImage) and allowingtrue(allowing to draw e.g. a reflection without updating the element's position).This gives authors a much more flexible and powerful API, one that can address common cases easily while also handling complex cases straightforwardly. It also avoids unnecessary stringification and style calculation work, and by internalizing the most common case (keeping things in sync), it allows the UA more opportunities for optimization.