Stars: 885
Forks: 154
Pull Requests: 855
Issues: 377
Watchers: 35
Last Updated: 2023-09-12 17:09:46
Converts CSS styles into inline style attributes in your HTML code.
License: MIT License
Languages: PHP
n. e•mog•ri•fi•er [\ē-'mä-grƏ-,fī-Ər] - a utility for changing completely the nature or appearance of HTML email, esp. in a particularly fantastic or bizarre manner
Emogrifier converts CSS styles into inline style attributes in your HTML code. This ensures proper display on email and mobile device readers that lack stylesheet support.
This utility was developed as part of Intervals to deal with the problems posed by certain email clients (namely Outlook 2007 and GoogleMail) when it comes to the way they handle styling contained in HTML emails. As many web developers and designers already know, certain email clients are notorious for their lack of CSS support. While attempts are being made to develop common email standards, implementation is still a ways off.
The primary problem with uncooperative email clients is that most tend to only
regard inline CSS, discarding all <style>
elements and links to stylesheets
in <link>
elements. Emogrifier solves this problem by converting CSS styles
into inline style attributes in your HTML code.
Emogrifier automagically transmogrifies your HTML by parsing your CSS and inserting your CSS definitions into tags within your HTML based on your CSS selectors.
For installing emogrifier, either add pelago/emogrifier
to the require
section in your project's composer.json
, or you can use composer as below:
composer require pelago/emogrifier
See https://getcomposer.org/ for more information and documentation.
The most basic way to use the CssInliner
class is to create an instance with
the original HTML, inline the external CSS, and then get back the resulting
HTML:
use Pelago\Emogrifier\CssInliner;
…
$visualHtml = CssInliner::fromHtml($html)->inlineCss($css)->render();
If there is no external CSS file and all CSS is located within <style>
elements in the HTML, you can omit the $css
parameter:
$visualHtml = CssInliner::fromHtml($html)->inlineCss()->render();
If you would like to get back only the content of the <body>
element instead
of the complete HTML document, you can use the renderBodyContent
method
instead:
$bodyContent = $visualHtml = CssInliner::fromHtml($html)->inlineCss()
->renderBodyContent();
If you would like to modify the inlining process with any of the available options, you will need to call the corresponding methods before inlining the CSS. The code then would look like this:
$visualHtml = CssInliner::fromHtml($html)->disableStyleBlocksParsing()
->inlineCss($css)->render();
There are also some other HTML-processing classes available
(all of which are subclasses of AbstractHtmlProcessor
) which you can use
to further change the HTML after inlining the CSS.
(For more details on the classes, please have a look at the sections below.)
CssInliner
and all HTML-processing classes can share the same DOMDocument
instance to work on:
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
…
$cssInliner = CssInliner::fromHtml($html)->inlineCss($css);
$domDocument = $cssInliner->getDomDocument();
HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner);
$finalHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
The HtmlNormalizer
class normalizes the given HTML in the following ways:
The class can be used like this:
use Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer;
…
$cleanHtml = HtmlNormalizer::fromHtml($rawHtml)->render();
The CssToAttributeConverter
converts a few style attributes values to visual
HTML attributes. This allows to get at least a bit of visual styling for email
clients that do not support CSS well. For example, style="width: 100px"
will be converted to width="100"
.
The class can be used like this:
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
…
$visualHtml = CssToAttributeConverter::fromHtml($rawHtml)
->convertCssToVisualAttributes()->render();
You can also have the CssToAttributeConverter
work on a DOMDocument
:
$visualHtml = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
The HtmlPruner
class can reduce the size of the HTML by removing elements with
a display: none
style declaration, and/or removing classes from class
attributes that are not required.
It can be used like this:
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
…
$prunedHtml = HtmlPruner::fromHtml($html)->removeElementsWithDisplayNone()
->removeRedundantClasses($classesToKeep)->render();
The removeRedundantClasses
method accepts a whitelist of names of classes that
should be retained. If this is a post-processing step after inlining CSS, you
can alternatively use removeRedundantClassesAfterCssInlined
, passing it the
CssInliner
instance that has inlined the CSS (and having the HtmlPruner
work
on the DOMDocument
). This will use information from the CssInliner
to
determine which classes are still required (namely, those used in uninlinable
rules that have been copied to a <style>
element):
$prunedHtml = HtmlPruner::fromDomDocument($cssInliner->getDomDocument())
->removeElementsWithDisplayNone()
->removeRedundantClassesAfterCssInlined($cssInliner)->render();
The removeElementsWithDisplayNone
method will not remove any elements which
have the class -emogrifier-keep
. So if, for example, there are elements which
by default have display: none
but are revealed by an @media
rule, or which
are intended as a preheader, you can add that class to those elements. The
paragraph in this HTML snippet will not be removed even though it has
display: none
(which has presumably been applied by CssInliner::inlineCss()
from a CSS rule .preheader { display: none; }
):
<p class="preheader -emogrifier-keep" style="display: none;">
Hello World!
</p>
The removeRedundantClassesAfterCssInlined
(or removeRedundantClasses
)
method, if invoked after removeElementsWithDisplayNone
, will remove the
-emogrifier-keep
class.
There are several options that you can set on the CssInliner
instance before
calling the inlineCss
method:
->disableStyleBlocksParsing()
- By default, CssInliner
will grab
all <style>
blocks in the HTML and will apply the CSS styles as inline
"style" attributes to the HTML. The <style>
blocks will then be removed
from the HTML. If you want to disable this functionality so that CssInliner
leaves these <style>
blocks in the HTML and does not parse them, you should
use this option. If you use this option, the contents of the <style>
blocks
will not be applied as inline styles and any CSS you want CssInliner
to
use must be passed in as described in the Usage section above.->disableInlineStyleAttributesParsing()
- By default, CssInliner
preserves all of the "style" attributes on tags in the HTML you pass to it.
However if you want to discard all existing inline styles in the HTML before
the CSS is applied, you should use this option.->addAllowedMediaType(string $mediaName)
- By default, CssInliner
will keep only media types all
, screen
and print
. If you want to keep
some others, you can use this method to define them.->removeAllowedMediaType(string $mediaName)
- You can use this
method to remove media types that Emogrifier keeps.->addExcludedSelector(string $selector)
- Keeps elements from
being affected by CSS inlining. Note that only elements matching the supplied
selector(s) will be excluded from CSS inlining, not necessarily their
descendants. If you wish to exclude an entire subtree, you should provide
selector(s) which will match all elements in the subtree, for example by using
the universal selector:
$cssInliner->addExcludedSelector('.message-preview');
$cssInliner->addExcludedSelector('.message-preview *');
Emogrifier
class to the CssInliner
classOld code using Emogrifier
:
$emogrifier = new Emogrifier($html);
$html = $emogrifier->emogrify();
New code using CssInliner
:
$html = CssInliner::fromHtml($html)->inlineCss()->render();
NB: In this example, the old code removes elements with display: none;
while the new code does not, as the default behaviors of the old and
the new class differ in this regard.
Old code using Emogrifier
:
$emogrifier = new Emogrifier($html, $css);
$emogrifier->enableCssToHtmlMapping();
$html = $emogrifier->emogrify();
New code using CssInliner
and family:
$domDocument = CssInliner::fromHtml($html)->inlineCss($css)->getDomDocument();
HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone();
$html = CssToAttributeConverter::fromDomDocument($domDocument)
->convertCssToVisualAttributes()->render();
Emogrifier currently supports the following CSS selectors:
~
(one word within a whitespace-separated list of words)|
(either exact value match or prefix followed by a hyphen)^
(prefix match)$
(suffix match)*
(substring match)p:first-of-type
but not *:first-of-type
)The following selectors are not implemented yet:
<style>
element in the HTML – including (but not
necessarily limited to) the following:
Rules involving the following selectors cannot be applied as inline styles.
They will, however, be preserved and copied to a <style>
element in the HTML:
:hover
)::after
)@media
rules. Media queries can be very
useful in responsive email design. See
media query support.
However, in order for them to be effective, you may need to add !important
to some of the declarations within them so that they will override CSS styles
that have been inlined. For example, with the following CSS, the font-size
declaration in the @media
rule would not override the font size for p
elements from the preceding rule after that has been inlined as
<p style="font-size: 16px;">
in the HTML, without the !important
directive
(even though !important
would not be necessary if the CSS were not inlined):
p {
font-size: 16px;
}
@media (max-width: 640px) {
p {
font-size: 14px !important;
}
}
::after
) or dynamic pseudo-classes (such as :hover
) – it is
impossible. However, such rules will be preserved and copied to a <style>
element, as for @media
rules. The same caveat about the possible need for
the !important
directive also applies with pseudo-classes.<style>
blocks from your HTML, but it will not grab CSS files
referenced in <link>
elements or @import
rules (though it will leave them
intact for email clients that support them).branch-alias
entry to
point to the release after the upcoming release.