pelago/emogrifier

将CSS样式转换为HTML代码中的内联样式属性

v7.2.0 2023-12-06 02:00 UTC

README

Build Status Latest Stable Version Total Downloads License Coverage Status

n. e•mog•ri•fi•er [\ē-'mä-grƏ-,fī-Ər] - 一种用于完全改变HTML电子邮件性质或外观的实用工具,尤其是在特别奇妙或奇异的方式

Emogrifier将CSS样式转换为HTML代码中的内联样式属性。这确保了在不支持样式表的电子邮件和移动设备阅读器上正确显示。

此实用工具是作为Intervals的一部分开发的,用于解决某些电子邮件客户端(尤其是Outlook 2007和GoogleMail)处理HTML电子邮件中包含的样式时引起的问题。正如许多网页开发人员和设计师所知,某些电子邮件客户端因其缺乏CSS支持而臭名昭著。尽管正在努力制定电子邮件标准,但实施仍有一段距离。

不合作的电子邮件客户端的主要问题是,大多数只关注内联CSS,丢弃所有<style>元素和<link>元素中的样式表链接。Emogrifier通过将CSS样式转换为HTML代码中的内联样式属性来解决此问题。

工作原理

Emogrifier通过解析CSS并在HTML标签中根据CSS选择器插入CSS定义,自动将HTML转换。

安装

要安装emogrifier,请将pelago/emogrifier添加到项目composer.json中的require部分,或者您可以使用以下方式使用composer

composer require pelago/emogrifier

有关更多信息和方法,请参阅https://getcomposer.org.cn/

使用方法

内联CSS

使用CssInliner类的最基本方法是创建一个实例,将原始HTML内联外部CSS,然后获取结果HTML

use Pelago\Emogrifier\CssInliner;

…

$visualHtml = CssInliner::fromHtml($html)->inlineCss($css)->render();

如果没有外部CSS文件,所有CSS都位于HTML中的<style>元素内,则可以省略$css参数

$visualHtml = CssInliner::fromHtml($html)->inlineCss()->render();

如果您想只获取<body>元素的内容而不是整个HTML文档,请使用renderBodyContent方法

$bodyContent = $visualHtml = CssInliner::fromHtml($html)->inlineCss()
  ->renderBodyContent();

如果您想使用任何可用的选项修改内联过程,您需要在内联CSS之前调用相应的方法。代码如下所示

$visualHtml = CssInliner::fromHtml($html)->disableStyleBlocksParsing()
  ->inlineCss($css)->render();

还有一些其他HTML处理类可供使用(所有这些都是AbstractHtmlProcessor的子类),您可以使用这些类在CSS内联后进一步更改HTML。有关类别的更多详细信息,请参阅下面的部分。)CssInliner和所有HTML处理类可以共享相同的DOMDocument实例来工作。

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();

标准化和清理HTML

HtmlNormalizer类按以下方式标准化给定的HTML:

  • 如果缺失,添加文档类型(HTML5)
  • 解开错误嵌套的标签
  • 添加HEAD和BODY元素(如果缺失)

类可以这样使用:

use Pelago\Emogrifier\HtmlProcessor\HtmlNormalizer;

…

$cleanHtml = HtmlNormalizer::fromHtml($rawHtml)->render();

将CSS样式转换为视觉HTML属性

CssToAttributeConverter将一些样式属性值转换为视觉HTML属性。这使得在不支持CSS的电子邮件客户端中至少能够获得一些视觉样式。例如,style="width: 100px"将转换为width="100"

类可以这样使用:

use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;

…

$visualHtml = CssToAttributeConverter::fromHtml($rawHtml)
  ->convertCssToVisualAttributes()->render();

您还可以让CssToAttributeConverterDOMDocument上工作

$visualHtml = CssToAttributeConverter::fromDomDocument($domDocument)
  ->convertCssToVisualAttributes()->render();

从HTML中移除冗余内容和属性

HtmlPruner类可以通过移除具有display: none样式声明的元素来减小HTML的大小,以及从class属性中移除不必要的类。

用法如下

use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;

…

$prunedHtml = HtmlPruner::fromHtml($html)->removeElementsWithDisplayNone()
  ->removeRedundantClasses($classesToKeep)->render();

removeRedundantClasses方法接受应保留的类名的白名单。如果在内联CSS后的后处理步骤中,您可以改用removeRedundantClassesAfterCssInlined,传递内联CSS的CssInliner实例(让HtmlPrunerDOMDocument上工作)。这将使用CssInliner的信息来确定哪些类仍然是必需的(即那些用于已复制到<style>元素的不可内联规则)

$prunedHtml = HtmlPruner::fromDomDocument($cssInliner->getDomDocument())
  ->removeElementsWithDisplayNone()
  ->removeRedundantClassesAfterCssInlined($cssInliner)->render();

removeElementsWithDisplayNone方法将不会移除具有类-emogrifier-keep的任何元素。因此,例如,如果某些元素默认具有display: none但通过@media规则被显示出来,或者作为预头信息(preheader),您可以添加这个类到这些元素上。即使这个HTML片段中的段落有display: none(可能是由CssInliner::inlineCss()从CSS规则.preheader { display: none; }应用),它也不会被移除

<p class="preheader -emogrifier-keep" style="display: none;">
    Hello World!
</p>

如果在removeElementsWithDisplayNone之后调用,removeRedundantClassesAfterCssInlined(或removeRedundantClasses)方法将移除-emogrifier-keep类。

选项

在调用inlineCss方法之前,您可以在CssInliner实例上设置几个选项

  • ->disableStyleBlocksParsing() - 默认情况下,CssInliner将抓取HTML中的所有<style>块并将CSS样式作为内联的“style”属性应用到HTML上。然后从HTML中移除这些<style>块。如果您想禁用此功能,以便CssInliner在HTML中保留这些<style>块并解析它们,则应使用此选项。如果使用此选项,则<style>块的内容将不会作为内联样式应用,并且您想让CssInliner使用的任何CSS都必须按上面用法部分中所述传递。
  • ->disableInlineStyleAttributesParsing() - 默认情况下,CssInliner会保留传递给它的HTML标签上所有的“style”属性。但是,如果您想在应用CSS之前丢弃HTML中现有的所有内联样式,则应使用此选项。
  • ->addAllowedMediaType(string $mediaName) - 默认情况下,CssInliner将仅保留媒体类型allscreenprint。如果您想保留其他媒体类型,可以使用此方法定义它们。
  • ->removeAllowedMediaType(string $mediaName) - 您可以使用此方法移除Emogrifier保留的媒体类型。
  • ->addExcludedSelector(string $selector) - 保持元素不受CSS内联的影响。请注意,只有匹配提供的选择器的元素才会从CSS内联中排除,而不一定是它们的后代。如果您希望排除整个子树,应提供将匹配子树中所有元素的选择器,例如使用通用选择器
    $cssInliner->addExcludedSelector('.message-preview');
    $cssInliner->addExcludedSelector('.message-preview *');
  • ->addExcludedCssSelector(string $selector) - 与 addExcludedSelector 相反,后者用于排除HTML节点,此方法用于排除内联的CSS选择器。例如,如果您不希望CSS重置规则在每个HTML节点上内联(例如,* { margin: 0; padding: 0; font-size: 100% }),这非常有用。请注意,这些选择器必须与您希望排除的选择器完全匹配。这意味着排除 .example 不会排除 p .example
    $cssInliner->addExcludedCssSelector('*');
    $cssInliner->addExcludedCssSelector('form');
  • ->removeExcludedCssSelector(string $selector) - 删除之前添加的排除选择器(如果有)。
    $cssInliner->removeExcludedCssSelector('form');

从已删除的 Emogrifier 类迁移到 CssInliner

最小示例

使用 Emogrifier 的旧代码

$emogrifier = new Emogrifier($html);
$html = $emogrifier->emogrify();

使用 CssInliner 的新代码

$html = CssInliner::fromHtml($html)->inlineCss()->render();

注意:在此示例中,旧代码会删除具有 display: none; 的元素,而新代码不会删除,因为旧类和新类在此方面的默认行为不同。

更复杂的示例

使用 Emogrifier 的旧代码

$emogrifier = new Emogrifier($html, $css);
$emogrifier->enableCssToHtmlMapping();

$html = $emogrifier->emogrify();

使用 CssInliner 和家族的新代码

$domDocument = CssInliner::fromHtml($html)->inlineCss($css)->getDomDocument();

HtmlPruner::fromDomDocument($domDocument)->removeElementsWithDisplayNone();
$html = CssToAttributeConverter::fromDomDocument($domDocument)
  ->convertCssToVisualAttributes()->render();

支持的CSS选择器

Emogrifier 当前支持以下 CSS选择器

以下选择器尚未实现

  • 不区分大小写的属性值
  • 以下未列出的受支持的静态 伪类 - 涉及它们的规则将仍然保留并复制到HTML中的 <style> 元素中 - 包括但不限于以下内容
    • 任何链接
    • :first-of-type(没有类型)
    • :last-of-type(没有类型)
    • :nth-last-of-type(没有类型)
    • :nth-of-type(没有类型)
    • :only-of-type(没有类型)
    • 可选
    • 必需

涉及以下选择器的规则无法作为内联样式应用。然而,它们将被保留并复制到HTML中的 <style> 元素中

注意事项

  • Emogrifier 需要 HTML 和 CSS 使用 UTF-8 编码。ISO8859-1 或 ISO8859-15 等编码不受支持。
  • Emogrifier 保留所有有价值的 @media 规则。媒体查询在响应式邮件设计中非常有用。请参阅 媒体查询支持。然而,为了使其有效,您可能需要在其中的一些声明中添加 !important,以便它们可以覆盖内联的 CSS 样式。例如,使用以下 CSS,@media 规则中的 font-size 声明在 HTML 中内联为 <p style="font-size: 16px;"> 后,将不会覆盖 p 元素的字体大小,除非有 !important 指令(即使如果没有内联 CSS,也不需要 !important
    p {
      font-size: 16px;
    }
    @media (max-width: 640px) {
      p {
        font-size: 14px !important;
      }
    }
  • Emogrifier 无法内联涉及伪元素(如 ::after)或动态伪类(如 :hover)的选择器的 CSS 规则 - 这是不可能的。然而,这些规则将被保留并复制到 <style> 元素中,就像 @media 规则一样。关于可能需要 !important 指令的相同警告也适用于伪类。
  • Emogrifier 将捕获现有的内联样式属性 以及 从您的 HTML 中捕获 <style> 块,但它不会捕获在 <link> 元素或 @import 规则中引用的 CSS 文件(尽管它将保留它们以供支持它们的邮件客户端使用)。
  • 即使在样式内联的情况下,某些 CSS 属性也会被某些邮件客户端忽略。有关更多信息,请参阅以下资源
  • 适用于元素的所有 CSS 属性都将应用,即使它们是冗余的。例如,如果您定义了字体属性 以及 字体大小属性,这两个属性都将应用于该元素(换句话说,更具体的属性不会合并到更通用的属性中)。
  • 如果您发现您的 HTML 不好或无效(DOMDocument 可能会报错),那么您可能会遇到问题。如果您遇到此类问题,请在将 HTML 传递给 Emogrifier 之前,考虑运行您的 HTML 通过 Tidy
  • Emogrifier 自动将提供的(X)HTML 转换为 HTML5,即,自闭合标签将丢失其斜杠。为了保持您的 HTML 有效性,建议使用 HTML5 而不是 XHTML 的一种变体。

API 和弃用策略

请参阅我们的 API 和弃用策略

贡献

以错误报告、功能请求或拉取请求的形式的贡献是非常受欢迎的。🙏 请参阅我们的 贡献指南 以了解更多有关如何为 Emogrifier 贡献的信息。

发布新版本步骤

  1. composer.json 中,更新 branch-alias 条目以指向即将发布的版本的发布 之后 的版本。
  2. CHANGELOG.md 中,创建一个新节,用于更改 之后 即将发布的子标题,设置即将发布的版本号,并删除任何空节。
  3. 更新 Dependabot 配置中的目标里程碑。
  4. 创建一个“准备版本 x.y.z 的发布”的拉取请求,包含这些更改。
  5. 对拉取请求进行审查和合并。
  6. 标记新版本。
  7. 发布选项卡 中创建一个新版本,并将更改日志条目复制到新版本中。
  8. 在社交媒体上发布有关新版本的消息。

维护者