Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
brandonmcconnell opened this issue Mar 22, 2023 · 10 comments
Open

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

brandonmcconnell opened this issue Mar 22, 2023 · 10 comments

Comments

@brandonmcconnell
Copy link

brandonmcconnell commented Mar 22, 2023

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:

<!-- index.html -->
<img src="example.svg" alt="example image">
<!-- example.svg -->
<?xml version="1.0"?>
<svg width="250" height="150" style="border: 1px solid black;" xmlns="http://www.w3.org/2000/svg">
  <g>
    <rect width="36.416" height="36.416" x="30" y="30" fill="red" />
    <text x="80" y="80" font-family="Verdana" font-size="24" fill="blue">hello world</text>
  </g>
</svg>

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:

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:

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:

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:

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:

: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:

<svg disallow-external-styles>...</svg>

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
Copy link
Author

brandonmcconnell commented Mar 22, 2023

Also posted this same proposal to whatwg/html#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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Author

brandonmcconnell commented Mar 22, 2023

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

  • This proposal allows you to directly style such image sources (e.g. svg) in your usual CSS stylesheets rather than needing to declare such styles on the image source itself whether in img[src] or in the CSS url() function.
  • This proposal allows you to work with the cascade and add any styles you want dynamically based on both the context the img element is in as well as the structure of the svg source itself (see example 5 above).
  • (possibly most vital of all) The CSS Linked Parameters draft requires the SVG itself to accept and utilize these parameters directly, meaning that the SVG and the CSS using it are somewhat coupled.
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
Copy link
Contributor

@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
Copy link
Author

brandonmcconnell commented Mar 23, 2023

@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
Copy link
Contributor

Crissov commented Mar 26, 2023

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

  • ::marker could be extended to become the same for list-style-image.
  • ::background or an extended ::backdrop or maybe ::canvas for background-image
  • ::border for border-image (and probably variants for all edges)

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Mar 26, 2023

@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
Copy link
Contributor

@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
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants