Open tabatkins opened 5 years ago
w3c/html/issues/322 seems a bit related.
If nothing else, this seems related to the ability for javascript to access the dom of a svg image.
<img>
is a document boundary (at least in Gecko), so this is probably similar to something like applying rule across <iframe>
.
@upsuper Ability to style IFRAME
contents via styles in the document the IFRAME
is embedded to would be nice too.
@upsuper Yeah, from CSS perspective, they should be functionally equivalent.
This would not be able to happen cross-origin at all, fwiw. Also generally cross-document selector-matching seems like a huge can of worms...
Gecko does have some limited support for styling embedded SVGs using -moz-context-properties: https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties
But I'm not sure of the history of those and how they work perf-wise and such. Maybe @jwatt can comment?
Some background for everyone:
Styling/modifying embedded SVG has been discussed in many contexts. Here's an overview of some of the main approaches that have been proposed. I'll add my thoughts/suggested next steps in a separate post.
The SVG Parameters spec, edited by @shepazu, was published by the SVG WG as an official WD in 2009. Parameters, in this sense, are aspects of an SVG file that are designed to be set by the external web page — it could be something like a fill color, or text content (for a fancy button/icon label), or an aspect of geometry (for a dashboard/data viz widget).
For SVG embedded as an <object>
, the parameters would be set using the <param>
element. For other types of embedded SVG, the proposal suggested using URL query strings:
<object type="image/svg+xml" data="icon.svg">
<param name="param1" value="value1" />
<param name="param2" value="value2" />
</object>
<img src="icon.svg?param1=value1¶m2=value2" />
A new <ref>
element in SVG would have allowed SVG authors to declare the available parameters and their default values. Those values would then be used throughout the SVG file using cross references to the ref element id.
In 2014, @TabAtkins worked with Doug to come up with a new spec that integrated with SVG parameters CSS custom properties: the parameters would be custom props. You could pass values to an image used in CSS with a URL modifier:
.foo {
background-image: url("http://example.com/image.svg" param(--color var(--primary-color)));
/* pass the value of the --primary-color variable from the outer document
in as the initial value of the --color value inside the SVG file */
}
I believe the plan was that <object>
and <param>
could still work as in the original proposal, but the URL notation for <img>
elements was switched to use hash fragments (similar to SVG view fragments), to avoid the caching issues from using query strings.
<img src="icon.svg#param(--text-color%20blue)param(--bg-color%20white)” />
Inside the SVG, the passed-in values would be accessed using var()
notation, assuming increased conversion of SVG geometric attributes to CSS properties and eventual support for CSS generated content in SVG text. Default values for the custom properties could be set using standard CSS mechanisms, although Tab also suggested in the initial email discussion that SVG could still have a dedicated element for declaring available variables and setting their defaults.
An alternative approach is the idea of context
values: Special keywords that could be used in most SVG properties to refer to values from the outer "context".
Context values were initially proposed for SVG markers (e.g., arrowheads), so that they could match with the color of the fill/stroke of the line/shape they are marking. Because you often want the fill of the marker to match the stroke of a line, context-fill
and context-stroke
would be dedicated keywords that could be used in either property. A more generic context-value
keyword was proposed for use in any SVG property (similar to initial
or revert
, but it would grab the value from the outer context).
Context keywords were included in the initial OpenType SVG specification. The idea was that the SVG glyphs could correctly map distinct fill and stroke values set on the text in the document using the font. Length-based values (like stroke-width) were supposed to be automatically scaled to the changed coordinate system of the font.
I'm not sure how many SVG-OpenType implementations supported them, and to what degree. The context keywords have been dropped from OpenType:
Previous versions of this specification allowed use of context-fill and other context-* property values, which are defined in the draft SVG 2 specification. Use of these properties is deprecated: conforming implementations may support these properties, but support is not required or recommended, and use of these properties in fonts is strongly discouraged.
We will probably be deferring the context keywords from SVG 2 because of a lack of implementations, (and quite frankly, a lack of clarity about implementation details), but the underlying use cases still need to be solved.
-moz-context-properties
And finally, we have the -moz-context-properties
approach, that @Emilio noted. This property is supported in the Firefox UI (but not in web content, as far as I know), to allow an SVG embedded as an image to be styled from the outer document. It applies not only to <img>
images, but also background images and so on.
It works a little differently than the context keywords: you set -moz-context-properties
on the element (e.g., img
) in the outer document, to a list of certain CSS property names (fill
, stroke
, fill-opacity
, or stroke-opacity
). The value of those properties on that element are then passed through as the default/root values for the embedded SVG.
-moz-context-properties: fill, stroke;
Correction: I originally wrote that any property, including custom properties, could be specified. That isn't supported as the property is currently defined, although logically it could be extended. But that introduces other complications for other properties, like scaling of stroke widths and dealing with properties that would have an effect on the element with the image itself.
So, this approach is different from the others in that the embedded SVG never explicitly "asks" for a value from the outside; it just receives it through inheritance. Which is how it works with inline SVG icons created with <use>
, and (as I understand it), the main reason behind this property was to have use-element-like style inheritance without having to inline all the SVG icons.
But it doesn't work well for style properties other than those that normally inherit, and doesn't work for cases like having the fill of a shape in the icon match the stroke color set on the external document. And it gets a little confusing in that the same properties can apply to many images (content images, background images, etc.) without a way to specify which ones you're trying to modify. And the properties you're passing through also apply to the element you're setting them on, so e.g., text on that element would also get the same fill/stroke values (if and when fill and stroke for CSS text is implemented).
I've never seen this discussed before for styling embedded SVG, but the idea of using selectors to cross a document tree boundary was originally proposed for shadow tree styling. Using a dedicated selector, the outer-document stylesheet could identify individual elements inside the nested document and apply arbitrary styles to them.
This again would mean that the customization is entirely in control of the outer document, but even more so: the SVG file would not need to be designed to work with parameters/variables, and it would not need to rely on inheritance.
However, the downsides are the same as for the shadow-tree deep
selector (which have been discussed at length elsewhere): it breaks the normal encapsulation model of the content, creating a dependency between the outer stylesheets and the DOM structure of the embedded content.
So, after that history lesson, what's next?
First thing: it is clear that modifying styles on embedded SVG images is a frequent desire. I didn't even touch on the ways people work around the limitation, e.g., using filters to modify the color of a single-color icon.
I am very much hoping that this will be one of the projects that gets picked up as an incubated spec discussion in the SVG Community Group.
Wherever the discussion happens, here are some thoughts to get started:
SVG loaded as <img>
and CSS background-image
(or other CSS image properties) both seem to get mentioned frequently.
Ideally, a method for styling embedded SVG images would also work for styling image-like SVG effects (markers and patterns), which are rendered as image layers outside of the CSS inheritance tree of the document where they are used.
It would be nice if the method could be harmonized with strategies for styling shadow DOM components (e.g., ::theme()
), although shadow DOM is slightly different in that it does inherit styles by default. For SVG in particular, it would be nice to have more nuanced ways to customize <use>
cloned content, especially when you're re-using assets defined in a separate file.
This is the first time I've seen someone suggest that the same styling approaches could be used for other types of embedded documents — like HTML embedded in <iframe>
— but I immediately like that idea. I've had projects where I've injected piles of styles into iFrames created for shopping cart widgets, when all I really want is "please inherit the font from the webpage".
Both the "parameter" model and the "context value" model were based on the assumption that the SVG would be intentionally designed to use values from the outside scope.
But for a web dev using an SVG created by someone else (or dynamically generated iFrame content), I can see how it would be nice to want to override the styles on use without editing the inner document at all.
Having the outside document set the default/inherited value for CSS properties used by the inner document is a bit of a compromise. For simple SVG icons, you might not need any special code in the SVG file, just use default inherited styles. For more complicated parameterization, the SVG file could use custom properties and var()
to assign their values as needed.
Either way, there could be cross-origin concerns that need to be considered. You wouldn't want the server that provided the nested document to be able to access values from the outer scope that weren't intentionally passed to it, and you wouldn't want the outer document to be able to detect if the embedded content used passed-in style parameters, unless the embedded document expressly granted crossorigin permissions.
Should any values get transformed as they pass the document boundary? E.g., should lengths be adjusted to a different coordinate system?
Is simple inheritance of values set on the embedding element enough, or should values be explicitly assigned to the embedded document? If values are assigned to the document, what's the best way to do so: as part of the URL, as a separate attribute / parameter object, by using a pseudoelement, … ?
Personally I want the option to traverse the document boundary in scripting. The HTMLIFrameElement
interface already has a contentDocument
attribute, so this isn't totally unheard of. A copy and paste job could be done for the HTMLImageElement
and HTMLObjectElement
interfaces.
More troublesome would be the way of exposing the Document interface for images used by CSS, such as backgrounds. And yes, I have some shenanigans in mind if this was available.
A couple considerations:
>>
) would make it easy on Authors to apply styling to content defined in the document tree (an <img>
or <iframe>
), but any such image defined within CSS would require a different approach.element()
?Instead of a new selector, it may be more orderly to have all selectors aimed at images within an @embed
at-rule (or @image
?). Something like:
@embed <selector> {
<rule-list>
}
Where any <img>
or <iframe>
matching the selector has the
@embed #mysvg {
g.bg path {
stroke: white;
}
}
Here, any rules defined within the at-rule are applied to the image context matching the #mysvg
selector. This can be extended by giving @embed
properties, such as a name (if the selector is excluded), that can be applied to url/image functions by passing the at-rule's name.
For the URL approach, regardless whether using a #
fragment identifier or ?
query string (which are client and server side, respectively, in my book), it makes sense to investigate whether there should be a common solution for SVG and CSS, and perhaps a parallel one for (structural) SVG only.
The common solution would probably make parameter values available within var()
or env()
or a new sibling function like param()
. The nice thing about this is that it works out of the box with url()
and with slight modification also with :target
(perhaps :target()
then).
foo.svg#bar&baz=quuz
<svg>
<style>
rect {fill: red}
:target {fill: magenta}
#bar:target {fill: green}
#baz:target {fill: orange}
#baz:target(quuz) {fill: lime}
</style>
<rect id="bar" width="300" height="200"/>
<!-- red traditionally, now green -->
<rect id="baz" width="200" height="300"/>
<!-- red traditionally, orange without :target() support, green with it -->
<rect id="bar&baz=quuz" width="300" height="300"/>
<!-- magenta traditionally(?), now red -->
</svg>
:root {background: url(foo.svg#bar=lime&baz=quuz);}
img[href$=".svg"],
img[href*=".svg#"] {parameter: "bar", "baz" "quuz"}
rect {fill: param(bar, color);}
rect {fill: map(param(baz), "quuz" lime, magenta);}
- Thoughts on this working on images created by
element()
?
Note the hint in the specification of element()
:
The
element()
function only reproduces the appearance of the referenced element, not the actual content and its structure.
So they are basically bitmaps without structure and therefore not the same as SVGs or documents within an iframe.
Sebastian
I worry that trying to find an all-powerful solution with arbitrary Author styling and/or script access would make that solution too hard to specify and/or undesirable for implementers, leading to a "we can't have good things" situation.
My experience from using -moz-context-properties
in Firefox DevTools and a couple other places:
<img>
and background-image
, which is great.context-fill
color, so people abuse context-stroke
as a secondary fill:/* the-stylesheet.css */
.some-image {
-moz-context-properties: fill, stroke;
fill: blue;
stroke: red;
}
<!-- the-icon.svg -->
<path d="..." fill="context-fill" />
<path d="..." fill="context-stroke" />
-moz-context-properties
also supports fill-opacity
and stroke-opacity
and we use that sometimes, though it's maybe less needed because you can always pass fill: rgba(...)
to set the opacity.
Things like deep selection and access to the SVG document for scripting could be nice, but those needs don't come up in my work on DevTools, websites and web apps. When you want to manage styles and scripting deeply for a SVG document, it's often straightforward to embed that SVG in the HTML page.
Personally I'd be very happy with something which mimicks what you can do with:
<use href="..."></use>
andfill: blue
)--secondary-fill: red
),but for <img>
and background-image
.
I see a lot of overlap between this bug and styling of custom element shadow dom. Is there a strong argument for SVG images being special enough to special-case? (SVG images being the only existing image format that could potentially be styled via parameters.)
Also, it's sounds like the use case which motivated this issue is MathML, which sounds like using SVG as a fancy font?
The original impetus for this issue, in the MathML Refresh Community Group teleconference call, was that, in theory, MathML markup could someday, too, be included in documents via <img>
elements.
<img src="equation.mml" />
I see a lot of overlap between this bug and styling of custom element shadow dom.
Only in vague theoretical terms. Custom element shadow DOM can be trivially styled with custom properties; our efforts beyond that (::part) are just to make it easier to handle some more complex cases.
You can't pass custom properties across the img/iframe boundary, tho. That's the point of this issue.
(Originally sent to the mailing list) Adam Sobieski said: