Closed njt1982 closed 1 month ago
Hi! No, at the moment, this library does not convert CSS variables into inline values (yet). I’m also not sure this should happen at the parser level, though, or whether this should be the responsibility of the code taking the syntax tree (the result from parsing the CSS).
@sabberworm @JakeQZ What are your thoughts on this?
The evaluation of CSS variables is based on the DOM tree. The value is taken from self-or-closest-ancestor where the variable is effectively a property defined according to normal CSS precedence rules.
So I think to support this, we need
:root
(commonly used for variable definitions, though is just another way of matching the html
element).Then there's non-inlinable CSS (like @media
and :hover
). The best we can do for that is to include all the variable definitions that might apply (to begin with we can include all, then, as an enhancement, filter out those that aren't needed).
Some refactoring might not go amiss first, though I'm not sure where or how. Alternatively, we go ahead, and review later where there seems to be common duplicated functionality.
There will be caveats if we do this. E.g.
:root {
--font-size: 16px;
}
@media (max-width: 640px) {
:root {
--font-size: 14px;
}
}
body {
font-size: var(--font-size);
}
If we copy through the actual value of the --font-size
variable to the inline style, the @media
rule will not be able to override it.
Maybe to begin with, we should just ensure that all variable definitions are included in the non-inlined CSS. That would at least ensure that this CSS feature works on clients that support it (probably not many).
I’m also not sure this should happen at the parser level, though, or whether this should be the responsibility of the code taking the syntax tree (the result from parsing the CSS).
I just realized that I had read this ticket in the context of our sister project PHP-CSS-Parser, not in the context of the Emogrifier project. 🤦 Yes, of course Emogrifier would be the right library to inline CSS variables (even if we might need to add support for parsing those to PHP-CSS-Parser).
You’ve raised some really interesting points I’d not even considered when asking the question! Especially the part about css variables in the DOM. This is very non-trivial!
If we copy through the actual value of the
--font-size
variable to the inline style, the@media
rule will not be able to override it.
I think (in the example given), we could set the inline style to font-size: 16px; font-size: var(--font-size);
so that if the renderer picks up the variable definition (from the @media
rule) it will be used, and otherwise the default (in this case for :root
) would apply.
An initial Emogrification pass to determine the variables specifically assigned at each element node, and some way of storing the results so they can be looked up by element
I've not tested this specifically for the case in point, but it's usually possible to simply assign custom properties to objects. E.g. $domElement->emogrifierCssVariables['--font-size'] = '16px';
should work. I've used similar tactics working with other third-party libraries without problem.
I don't think a separate Emogrification pass to determine the variable assignments is actually needed, since in the main pass, all ancestor elements will have already been processed. We just need to
Support for
:root
I'm hoping this already works in the Symfony CssSelector component, and that all we need to do is confirm this (by adding a test - if we haven't already).
I don't think a separate Emogrification pass to determine the variable assignments is actually needed, since in the main pass, all ancestor elements will have already been processed. We just need to
1. record all variable definitions encountered; 2. look up the record from [1] by traversing the DOM tree whenever a property value uses a variable.
I'm thinking of a slightly alternative approach now.
After emogrification with some CSS using custom properties (as they are officially known), we would end up with some HTML like this:
<html style="--text-color: blue;">
<body>
<p style="color: var(--text-color);">
Hello universe
</p>
</body>
</html>
So the reconcilliation of custom properties could be done as an entirely separate HTML processing step, with a separate HtmlProcessor
-derived class (akin to CssPruner
and CssToAttributeConverter
).
Options might include whether to retain a declaration using the variable (color: blue; color: var(--text-color);
) in case some non-inlinable (e.g. @media
) rules might override the variable definition, and/or to strip the variable definitions from the inline style
once they have been applied (since they would then be redundant - I can't think of a case when they would not become redundant).
This approach would help avoid overburdening the CSSInliner
class.
Options might include whether to retain a declaration using the variable (
color: blue; color: var(--text-color);
)
I tested this in both Firefox and Chrome (using the inspector/style-editor). An undefined variable (custom property) seems to result in the property not being applied at all, even if specified by some other CSS. It possibly defaults to the value inherit
, which is undesired. So that's not an 'option' that could sensibly be provided.
It makes the solution easier. We just simply make the replacements with the values that can be found in the DOM tree after Emogrification.
Hi,
Is it already possible to convert CSS variables into inline values?
For example...
bg-brand
class on it.(this is a simplified example!).
For now, I'm going to change my code to replace
var(--brand)
with something like[BRAND-COLOR]
and then string replace that with the hex code...I'm only asking as I already have CSS elsewhere that defines these brand colour variables and it would be nice to reuse that rather than repeat the same variables in different languages in different places :)
(PS Thank for all the work on this package!)