w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.37k stars 642 forks source link

[selectors] Styling Content in <img> Elements #3730

Open tabatkins opened 5 years ago

tabatkins commented 5 years ago

(Originally sent to the mailing list) Adam Sobieski said:

CSS Working Group, MathML Refresh Community Group,

In a recent MathML Refresh Community Group teleconference call, we briefly discussed styling content (e.g. SVG) loaded via <img> elements.

If there isn’t already a means of utilizing CSS to style content loaded via <img> elements, I would like to propose a new combinator – perhaps “>>”.

img >> svg { background-color: blue }

What do you think?

henke37 commented 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.

upsuper commented 5 years ago

<img> is a document boundary (at least in Gecko), so this is probably similar to something like applying rule across <iframe>.

Marat-Tanalin commented 5 years ago

@upsuper Ability to style IFRAME contents via styles in the document the IFRAME is embedded to would be nice too.

fantasai commented 5 years ago

@upsuper Yeah, from CSS perspective, they should be functionally equivalent.

emilio commented 5 years ago

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?

AmeliaBR commented 5 years ago

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.

SVG Parameters

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&param2=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.

SVG Parameters + CSS Variables

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.

Context value keywords

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).

"Deep" selectors (as in the original post for this issue discussion)

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.

AmeliaBR commented 5 years ago

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:

What types of situations should this styling approach cover?

Which document should control the properties that are passed through?

Other design issues

henke37 commented 5 years ago

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.

TUSF commented 5 years ago

A couple considerations:

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 applied to it. An example:

@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.

Crissov commented 5 years ago

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&amp;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);} 
SebastianZ commented 5 years ago
  • 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

fvsch commented 4 years ago

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:

  1. It works for <img> and background-image, which is great.
  2. UI work rarely needs more than setting the main fill color for an icon.
  3. The main limit found was that you can only pass one 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:

but for <img> and background-image.

chrishtr commented 4 years ago

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?

AdamSobieski commented 4 years ago

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" />

tabatkins commented 4 years ago

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.