w3c / csswg-drafts

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

[svg] Support for passing CSS styles to an SVG used via img[src] #8634

Open brandonmcconnell opened 1 year ago

brandonmcconnell commented 1 year ago

Table of Contents

Description

Currently, it is not possible to apply CSS styles to an SVG if it is used via img[src].

This proposal adds a new CSS syntax that would enable developers to target the root SVG element in such cases and apply CSS styles to it and its descendants.

Proposal

To achieve this, a pseudo-element can be added to the img src link that acts as the root for the SVG, allowing styles to be targeted for both the SVG and its child elements. The syntax for this can use ::src as it is tied to the src for the image.

Syntax

To target the root SVG element, the syntax can be as follows:

img::src {
  /* CSS styles to be applied to the root SVG element */
}

To target child elements within the SVG, the syntax can follow standard CSS selector patterns:

img::src path {
  /* CSS styles to be applied to a descendant element under the root SVG */
}

All usual CSS selector patterns, properties, rules, and other conventions would work here as if the SVG had been embedded directly on the page, under the img element.

Architecture

Any styles applied to img::src would not conflict with styles set on the img itself. Rather, think of it almost as if the svg element is a direct child of the img element, if img supported such a thing. This is very similar to how the ShadowDOM and shadow roots already work. In fact, the ShadowDOM may be the ideal implementation/solution for this proposal.

Consider this example:

img      { border: 10px solid red;  }
img::src { border: 10px solid blue; }

This above code would be treated similarly to a ShadowDOM tree where img::src exposes the svg element as a shadow-root under the image itself. So in this example, the two border styles would not conflict but rather, the img element would receive its assigned border styles, and then the svg would receive its border styles applied via img::src as if it had been assigned to the svg element itself.

Examples

SVG source for below examples (expand/collapse)

For completeness, here is example HTML/SVG source that can be used with the below examples: ```html example image ``` ```html hello world ``` This looks like this: image --- ### …and now for the actual examples:

To demonstrate the proposed syntax, here are two examples targeting the root SVG and a child element within it:

1️⃣  Example 1: Targeting the root SVG

In this example, the `fill` property is applied to the root `svg` element: ```css img::src { fill: red; border: 10px solid blue; } ```

2️⃣  Example 2: Targeting a child element within the SVG

In this example, `fill` and `stroke`-related properties are applied to the `rect` element, and `font`-related properties are applied to the `text` element within the SVG: ```css img::src g rect { fill: violet; stroke: red; stroke-width: 3%; stroke-dasharray: 2px; } img::src g text { font-family: cursive; font-size: 32px; } ```

3️⃣  Example 3: Applying a rotation animation to the rect element

In this example, an animation is applied to the `rect` element within the `g` group: ```css img::src g rect { animation: rotate 3s linear infinite; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ```

4️⃣  Example 4: Applying a transition to the text element on :hover

In this example, a transition is applied to the `fill` property of the `text` element within the `g` group. When the `text` element is hovered, its `fill` property is changed: ```css img::src g text { transition: fill 0.5s ease-in-out; } img::src g text:hover { fill: green; } ```

5️⃣  Example 5: Styling all icons from a specific directory to be a default color and then change colors on `:hover` and `:active` states, respectively

In this more practical example, any `svg` icons pulled from a specific directory (e.g. `/my-icons/`) will receive a default color and then a different color and transition on hover, only when displayed under the `.gallery` section: ```css :root { --default-gallery-icon-color: cyan; --hovered-gallery-icon-color: magenta; } .gallery img[src^="/my-icons/"]::src { fill: var(--default-gallery-icon-color); transition: all 0.5s ease-in-out; } .gallery img[src^="/my-icons/"]::src:hover { fill: var(--hovered-gallery-icon-color); transform: scale(1.25); } ``` I'm not sure how feasible this example is, depending on if we can granularly pass user events like a hover state into the image's source. If so, this would be great.

Other thoughts & gotchas

In terms of security, I don't foresee too much if any risk in this feature. That said, I can imagine situations where someone may not want their SVG meddled with and would want a way to prevent style injections like this. With that in mind, it could be helpful to add support for an attribute to `svg` to enable disallowing style injection, like `disallow-external-styles`: ```html ... ``` Notably, while we could add an attribute like `allow-external-styles` to instead allow such styling on a per-case basis, I think the better default would be to allow styling unless specifically disallowed.
brandonmcconnell commented 1 year ago

~Also posted this same proposal to https://github.com/whatwg/html/issues/9064. It's a clear duplicate, so whichever place is better suited for this proposal, let's keep it there and close the other.~


UPDATE: I closed the other duplicate issue in favor of this one, as it feels more in line with the realm of the CSSWG.

argyleink commented 1 year ago

I would love this, I'm tired of tricky <use> setups that provide the desirable cached asset strategy while also being stylable. With this proposal, I'd use the <img> tag and let it do all the tricky work, while i get to style shapes inside it 👍🏻

SebastianZ commented 1 year ago

With CSS Linked Parameters there is already an approach to pass parameters to an SVG or other external resources.

It provides three ways to style SVGs via specific parameters.

Sebastian

brandonmcconnell commented 1 year ago

@SebastianZ Thanks! I hadn't heard of that yet.

TL;DR — I think that CSS Linked Parameters draft looks really cool, though I don't think it removes the need/value in a proposal like ::src. I think they would both be valuable tools in the tool belt, and I can imagine scenarios where I would reach for either one.

Some key differences between the two proposals are…

More rambling thoughts…
That draft looks rad, and I definitely could have benefitted from it on a handful of projects of my own over the years, though, the real value in this `::src` proposal is the ability to directly manipulate any SVG you pull in, which will often be the case for more custom modifications. I certainly see icon libraries adopting the features outlined in the CSS Linked Parameters draft, though there are certainly bound to be instances where you want to style/manipulate an SVG in a way that the SVG author did not foresee. In cases like these, something like `::src` would be really useful for adding any desired custom styling to any SVG you have. I think both this proposal and the CSS Linked Parameters spec would be super useful to the community at large, and that they may often be used together. For common, native resources like SVGs housed in your codebase and even some 3rd-party libs, CSS Linked Parameters would be very useful and provide a more uniform and controlled method for styling SVGs dynamically. For other cases where you want to style an SVG but don't want to host it in your codebase and where the author isn't adding CSS Linked Parameters-support, `::src` provides an instant solution and alternative to downloading the resource, adding it to your codebase and adding your own CSS Linked Parameters support to it.
SebastianZ commented 1 year ago

@brandonmcconnell I actually also like your approach, just wanted to outline that there is already something targetting the SVG styling issues we currently have.

You outlined the differences quite well. And those differences indicate their advantages and disadvantages.

From a CSS author's perspective, the big advantage of this proposal is that they are in full control of styling the SVGs. Though this may be seen as a disadvantage, too, as its easier to violate any copyrights regarding the SVGs when allowing to manipulate them arbitrarily. But that's something that can (roughly) be targeted via the mentioned disallow-external-styles or allow-external-styles.

And the approach here is (at least currently) limited to SVGs referenced in <img> elements. The Linked Parameters approach, while mainly focusing on SVG as well, can also target other resources like documents in iframes. From this view, and especially regarding documents in iframes, it makes a lot of sense that both the author's CSS and the resource have to agree on specific parameters for manipulation. So that approach can be seen as an API.

Also, this approach is limited to resources referenced via HTML. And because it uses a pseudo-element, it is limited to styling one resource. Though in CSS you can reference different resources like in the following example:

.container-with-border {
  border-image: src('border.svg') 30;
  background: src('background.svg') center / contain no-repeat;
}

With Linked Parameters you can style both images individually. With the ::src approach, styling them is not possible at all.

For what it's worth, the approach might also target <source>s within <picture> elements and possibly other media referencing HTML elements.

Sebastian

brandonmcconnell commented 1 year ago

@SebastianZ Thanks! I really appreciate your insight, and that helps me to better understand the reaches of the Linked Parameters draft as well as better understand the differences between both approaches and their advantages/disadvantages. Super exciting stuff 👏🏼

I totally agree with you btw. I'm sure this approach will be more useful than just for SVGs on img, as will probably reach picture/source and maybe even video someday depending on what sources are supported down the line.

Crissov commented 1 year ago

I would call the proposed pseudo-element ::content because it forms the shadow root for the replaced content, i.e. if we assume img {content: attr(src)} still worked. That’s why there should also be likewise pseudo-elements to match all other properties that may embed external content (usually images):

brandonmcconnell commented 1 year ago

@Crissov I really like that idea. We can certainly bikeshed on the name a bit more and do something other than ::src.

I like the ::content name a lot too, as long as it won't confuse users between the content property and the ::content pseudo-element, but as you pointed out, I think that they could actually work well together. In that case, keeping the name the same would be ideal.

SebastianZ commented 1 year ago

@Crissov Note that background-image can take a list of images, so ::background would have to use a function notation.

Having said that, this would allow to "pierce into" the contents of individual images. Though I am not a big fan of introducing a new pseudo-element for each property that can take an <image> value.

Sebastian

brandonmcconnell commented 1 year ago

I think the immediate and most useful case would be for svg images. At least for this first spec, I think it would be best to continue either without support for background-image support, as that is a pretty unique use case, or do so with a separate pseudo, as you mentioned, like ::background though I hear you and also would like to avoid adding too many pseudos if we can help it.