精通-PostCSS-Web-设计-全-

精通 PostCSS Web 设计(全)

原文:zh.annas-archive.org/md5/5118bf765617fb08db8d26340ef42ee2

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

作为一名开发者,我敢打赌你有一个完美的流程——你要么使用纯 CSS 编写样式,要么使用当前处理器,如 SASS 或 Less,来创建它们。你将使用 Autoprefixer 之类的工具添加供应商前缀——手动或使用工具,如 Grunt 或 Gulp。

听起来熟悉吗?如果你觉得它对你有用,为什么还要去打扰它呢,对吧?

问题在于,一个朋友或同事开始谈论一个名为 PostCSS 的新处理器——他们已经足够吸引你的兴趣,让你想要了解更多关于它是什么以及它是如何工作的信息。

好吧,欢迎来到快速发展的 PostCSS 生态系统!单从工具本身来看,它并不做任何事情,但当你搭配正确的插件(并且有数百个可供使用)时,它有可能成为你真正强大的处理器。我们不再需要依赖像 SASS 或 less 这样的单体库。相反,我们可以根据项目需求挑选和选择使用哪些插件。PostCSS 是一个极快的处理器,问题是,你准备好加入这场旅程了吗?

希望答案是肯定的;如果是这样,让我们开始吧。

本书涵盖的内容

第一章,介绍 PostCSS,以介绍 PostCSS 的世界开始我们的旅程,探索其特性和我们如何使用这个生态系统将基本代码转换为可以在项目中使用的有效 CSS 样式。你将发现使用这个生态系统的优势,以及它的架构和模块化方法如何使我们能够构建一个专门针对我们需求的处理器。

第二章,创建变量和混入,探讨了现有处理器用户熟悉的一些基本概念,例如变量和混入。你将学习如何将它们过渡到 PostCSS,并发现使用这些技术的优势如何过渡到使用 PostCSS。

第三章,嵌套规则,探讨了现有处理器,如 SASS 或 less,如何利用嵌套等概念来减少我们需要编写的代码量,以及我们如何在 PostCSS 处理器中复制相同的功能。

第四章,构建媒体查询,通过 PostCSS 和媒体查询的使用,向我们介绍了添加响应式支持到网站的基本知识。你将学习如何为旧网站和浏览器添加支持,并探讨随着 CSS4 媒体查询的出现,我们如何进一步发展,并在 PostCSS 中提供当前的支持。

第五章, 管理颜色、图像和字体,检查了用于在 PostCSS 中处理和操作图像、颜色和字体的插件。我们将通过一些示例来说明如何在 PostCSS 中操作图像和颜色,例如创建图像精灵或使用系统中的调色板更改颜色。

第六章, 创建网格,带我们通过使用网格构建网站骨架的旅程——我们将探索使用网格的基本概念,并发现一些在 PostCSS 中创建网格的插件选项。我们将使用 Bourbon Neat 网格系统进行一些示例,然后在 PostCSS 等效插件中复制这些示例,并添加响应式功能到生成的代码中。

第七章, 动画元素,首先快速回顾了使用 JavaScript 进行内容动画的方法,然后转向使用 CSS 进行动画,以及如何过渡到使用 PostCSS。我们将探索一些更知名的库,例如 Animate.css,然后使用 PostCSS 创建一个快速演示,并学习如何使用 PostCSS 优化我们的动画。

第八章, 创建 PostCSS 插件,教我们如何使用插件来扩展 PostCSS,并带我们了解这样一个插件典型的架构。然后,你将查看一些示例插件,在利用可用的模板代码创建你自己的插件并测试之前,以及在使用 PostCSS 将插件提供给用户下载之前。

第九章, 使用快捷方式、回退和打包,首先检查了一些可用的快捷方式插件和打包工具,然后探索我们如何通过创建自己的快捷方式插件来补充它们。你还将发现如何使用 PostCSS 可用的插件包来 lint 和优化你的代码,以及如何为 PostCSS 代码提供回退,以帮助维护对旧浏览器的支持。

第十章, 构建自定义处理器,将本书中介绍的一些技术结合起来,以生成一个自定义处理器,我们可以将其用作在项目中转换代码的基础。你将探索如何优化输出,在添加源映射和供应商前缀支持之前,然后在网站上测试它。然后,你将本章以扩展处理器来使用 CSStyle 框架结束,这将允许你编写适用于 SASS 或 PostCSS 的代码。

第十一章,操作自定义语法,介绍了使用 API 编写自定义语法,并探讨了使用 SASS 或 less 等语法编写的代码的可用解析选项。在将输出转换为可在屏幕上显示或保存到文件的内容之前,我们将通过一些使用 PostCSS 解析代码的示例来操作。我们还将添加使用 midas 库突出显示代码的支持。

第十二章,混合预处理程序,展示了我们如何开始混合处理器作为辅助工具,以帮助过渡到使用 PostCSS。在安装并使用其一些功能之前,我们将查看 Pleeease 库。然后,在使用它来修改标准 WordPress 主题之前,我们将设置一个编译过程。

第十三章,解决 PostCSS 问题,探讨了在使用 PostCSS 时我们可能会遇到的一些更常见的问题,例如“taskname 不在我们的 gulpfile 中”错误。如果所有其他方法都失败了,我们还将探讨接下来应该做什么。我们将介绍在 PostCSS 的核心系统或其插件中获取帮助或记录错误详情的方法。

第十四章,为未来做准备,涵盖了从人们所知的 CSS4 支持的某些可能选项。你还将探索其中涉及的一些风险以及如何使用现有的插件来复制支持或扩展它们以增加对新 CSS4 选择器的支持。

你需要这本书的内容

为了完成这本书中的大多数示例,你需要一个简单的文本或代码编辑器、NodeJS(适用于您的平台)的副本、互联网接入和浏览器。我推荐安装 Sublime Text 3;它与 Node 和 Gulp 配合得很好,我们将在整本书中使用它们。

一些示例使用了额外的插件;大多数(如果不是全部)都可以直接从 NodeJS 内部安装。详细信息包含在相应的章节中,并附有查看插件源代码和文档的链接。

这本书面向的对象

这本书是为熟悉 HTML5 和 CSS3 的前端开发者编写的,但希望掌握 PostCSS 作为简化其开发工作流程的一部分或移除对现有处理器(如 SASS 或 Stylus)的依赖。为了最大限度地利用这本书,你应该对 HTML、CSS3 和 JavaScript 有良好的了解,并且最好有一些使用预处理器的经验,例如 SASS、Less 或 Stylus。

惯例

在这本书中,你会发现许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称应如下所示:“我们将首先安装此演示所需的相关插件:我们需要 postcss-nested、autocomplete 和 postcss-scss 插件。”

代码块设置如下:

gulp.task('rename', ['styles'], function () {
  return gulp.src('dest/example.css')
    .pipe(postcss([ cssnano ]))
    .pipe(rename('example.min.css'))
    .pipe(gulp.dest("dest/"));
});

当我们希望引起你对代码块中特定部分的注意时,相关的行或项目将以粗体显示:

var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
var cssnano = require('cssnano')

任何命令行输入或输出都应如下所示:

npm install -–save-dev cssnano
npm install -–save-dev gulp-rename

新术语重要词汇以粗体显示。屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“当我们查看页面并选择图像选项卡后,经过短暂的延迟,我们应该看到六张新图像。”

注意

警告或重要提示以如下方式显示在框中。

小贴士

小技巧和窍门如下所示。

读者反馈

我们始终欢迎读者的反馈。请告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者的反馈对我们来说很重要,因为它帮助我们开发出你真正能从中获得最大收益的标题。

要发送给我们一般性的反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件的主题中提及书籍的标题。

如果你在某个领域有专业知识,并且对撰写或参与一本书籍感兴趣,请参阅我们的作者指南,链接为www.packtpub.com/authors

客户支持

现在你已经是 Packt 书籍的骄傲拥有者,我们有许多事情可以帮助你从购买中获得最大收益。

下载示例代码

你可以从www.packtpub.com的账户下载这本书的示例代码文件。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给你。

你可以通过以下步骤下载代码文件:

  1. 使用你的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的支持选项卡上。

  3. 点击代码下载与勘误

  4. 搜索框中输入书籍的名称。

  5. 选择你想要下载代码文件的书籍。

  6. 从下拉菜单中选择你购买这本书的地方。

  7. 点击代码下载

你也可以通过点击 Packt Publishing 网站书籍网页上的代码文件按钮下载代码文件。可以通过在搜索框中输入书籍的名称来访问此页面。请注意,你需要登录到你的 Packt 账户。

一旦文件下载完成,请确保使用最新版本的以下软件解压或提取文件夹:

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Mastering-PostCSS-for-Web-Design。我们还有来自我们丰富的书籍和视频目录的其他代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载本书的彩色图像

我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出变化。您可以从www.packtpub.com/sites/default/files/downloads/MasteringPostCSSForWebDesign_ColorImages.pdf下载此文件。

错误清单

尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误提交表单链接,并输入您的错误详细信息来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站或添加到该标题的错误清单部分。

要查看之前提交的错误清单,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在错误清单部分。

盗版

在互联网上对版权材料的盗版是所有媒体中持续存在的问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过发送链接到疑似盗版材料至<copyright@packtpub.com>与我们联系。

我们感谢您在保护我们作者和我们提供有价值内容的能力方面的帮助。

问题

如果您对本书的任何方面有问题,请通过发送邮件至<questions@packtpub.com>与我们联系,我们将尽力解决问题。

第一章. 介绍 PostCSS

网站的关键部分是样式——无论是简单的元素标签还是复杂的动画,网站没有颜色和动作就不是网站。为任何在线存在构建样式需要时间和精力——我们可以通过使用预处理器来自动创建样式,自动应用供应商前缀等来减少开发时间,但额外的库依赖可能就像用大锤砸核桃一样!

进入 PostCSS——它独特的模块化风格允许我们创建一个更精简、更快的 CSS 处理器,没有外部依赖。在本章中,我们将探讨安装 PostCSS、了解其架构,并学习如何利用其速度和力量将代码编译成有效的 CSS。在本章中,我们将涵盖多个主题,包括以下内容:

  • 考虑创建我们自己的预处理器的好处

  • 介绍 PostCSS 并探索其功能

  • 使用 PostCSS 设置开发环境

  • 使用 PostCSS 创建一个简单的示例

  • 探索 PostCSS 的工作原理及其架构

让我们开始吧…!

注意

本书中的所有练习都是为 Windows 平台编写的;如果您使用的是其他操作系统,请相应地进行调整。

发现处理艺术的技巧

一个问题:SASS、Stylus、Haml 和 Less 都有什么共同之处?

答案是,它们都是编译器,源到源编译,或者称为转换器(以它们的官方名称),自 20 世纪 80 年代以来一直存在。它们以许多不同的格式出现,Digital Research 的 XLT86 是其中最早的版本之一,始于 1981 年。

最近,知名的 SASS 处理器于 2006 年出现;随后是 2009 年由 Alexis Sellier 创建的 Less。它们以类似的方式工作:它们接受一组规则并将其编译成有效的 CSS。我们可以通过所有种类的功能扩展 CSS,如变量、混入、函数等。尽管处理器可能无法减少我们必须编写的物理行数,但它们帮助我们重新组织代码成更易于管理的块,我们可以在未来的项目中重用,这有助于使 CSS 更易于维护。

但是,几乎总是如此,使用处理器的缺点也是有的:

  • 几乎总是涉及到某种形式的依赖——对于 SASS 来说,是 Ruby;如果您使用 Less,则是一个库,尽管它是用 JavaScript 编写的

  • 我们的项目可能只使用一小部分预处理的代码,但我们被迫依赖于可能是一个大型库,如 SASS

  • 使用预处理器处理样式表很慢;可能只有几秒钟,但随着时间的推移,这会积累成大量的等待过程完成的时间

嗯,这并没有让处理变得那么吸引人!但如果有一种方法可以缓解所有这些问题,同时去除对依赖的需求呢?

好吧,原因如下:让我们自己构建一个处理器!好吧,这听起来可能有点疯狂,但正如有人曾经说过,疯狂之中自有方法,所以请耐心听我解释为什么这可能是一个更好的选择。

介绍 PostCSS

在本章的开头,我提到我们会专注于创建自己的预处理器,对吧?好吧,我有一个小小的坦白要讲:我们并没有这么做。等等,这是怎么回事?

好吧,我们将创建一个预处理器……但我们也会创建一个后处理器。让我解释一下原因——我们的替代“选项”允许我们同时创建这两个处理器。我们的替代选项是 PostCSS,可以从 github.com/postcss/postcss 下载。PostCSS 被一些主要公司使用,例如 Twitter、Google、Bootstrap 和 CodePen,甚至 WordPress(在有限的范围内)。

PostCSS 是作为一个 Node.js 模块构建的,因此将与 Node.js 已经提供的任何数量的现有插件一起工作——我们将在整本书中使用这些插件中的许多。让我们花点时间来探索这个工具的一些好处。

探索使用 PostCSS 的好处

我们所说的 PostCSS 是什么意思?简单来说,它可以指代两件事之一——PostCSS 核心工具或由该工具驱动的插件生态系统。单独来看,它实际上并没有做什么;一旦我们开始添加插件,我们就能实现很多功能。让我们来看看在实践中这意味着什么:

  • 它的模块化架构意味着我们可以挑选和选择我们需要的;这允许我们保持库的大小非常小且响应迅速。

  • 现有的处理器往往分为两个阵营——预处理或后处理——这在使用选择时是一个限制因素。PostCSS 允许我们在同一个过程中执行这两个操作,这意味着我们得到了两个处理世界的好处!

  • PostCSS 集成了对 Gulp、Grunt 或 Broccoli 等常见任务运行器的无缝支持;我们可以将其与许多其他可自动化的任务结合起来。

  • 编译时没有依赖项;PostCSS 完全用 JavaScript 编写,因此不需要 Ruby 或 libsass 等库来编译代码。唯一的依赖项(如果可以这样称呼的话)是 Node.js——许多开发者可能已经安装了它。

  • 没有必要学习任何新的语言;每个开发者都会熟悉 JavaScript,并在他们的开发过程中使用它。

  • 当需要时,我们可以更改正在使用的任何插件以用于其他目的;在使用较大的库时,我们没有这种选择。

  • 它相对较低的学习门槛意味着我们可以非常容易地创建所需的任何插件,或者可能修改现有的插件以更好地满足我们的需求。

  • PostCSS 很快——在一个使用 postcss-benchmark 插件(可在 github.com/postcss/benchmark 获取)的测试中,该插件包含了解析后的代码、嵌套规则、混入、变量和数学,PostCSS 以明显的优势胜出:探索使用 PostCSS 的好处

  • 完美——无需不断更新 SASS,或下载 libsass 库的新版本,对吧?

考虑一些潜在的问题

嗯,使用自定义处理器有一些考虑因素;关键是要记住,PostCSS 既不是预处理器也不是后处理器,而更像是一个瑞士军刀式的工具箱,我们可以用它来处理我们的 CSS 代码。让我们来看看这些缺点:

  • 虽然我们不需要学习新语言来使用 PostCSS,但创建自定义处理器将给我们的开发过程增加一层复杂性。

  • 它的灵活方法意味着有些人可能会将 PostCSS 视为预处理器或后处理器;这种短视的方法意味着你会错过机会,因此,在考虑 PostCSS 可以为你带来的开发过程方面,保持开放的心态至关重要。

  • 将现有预处理器中的代码转换为使用 PostCSS 可能很痛苦;这个过程只有在我们不尝试显式转换,而是将其作为逐步转向使用 PostCSS 的基础时才有效。

  • PostCSS 从一开始就需要语法正确的 CSS;尽管我们可以使用任何语法(因为 PostCSS 文件只是纯文本),但编译很容易失败,即使是使用单行注释也可能如此!

  • 尽管如此,使用 PostCSS 的真正好处在于其与 Gulp 等工具的无缝集成——如果你愿意的话,想象一下这个场景:

你已经使用像 SASS 这样的预处理器开发网站。你可以使用独立的处理器来编译代码,但通常更喜欢使用 Node.js 和 Gulp 来完成任务。听起来合理吗?那么,转向使用 PostCSS 呢?

没问题,我们可以添加一个用于使用 PostCSS 处理 CSS 文件的章节。关键在于不要使用 PostCSS 来执行初始编译,而是执行后处理,例如添加供应商前缀或最小化结果。一旦确立了这一点,我们就可以开始整合一些可用的 PostCSS 插件,这些插件允许我们复制功能,例如从 SASS 内部。一旦我们将现有代码调整为使用插件所需的格式,我们就可以切换到使用 PostCSS,并开始减少对 SASS 的依赖。

澄清一些误解

在这一点上,花几分钟时间澄清一些关于 PostCSS 的常见误解是值得的,尽管许多人将其与预处理器或后处理器联系起来,但这并不是其初衷:

  • 将 PostCSS 归类为后处理器,而不是预处理器(如 Less 或 SASS)是错误的;PostCSS 能够在各种不同的使用场景中编译,可以处理使用任何预处理器编译的代码,或者直接处理纯 CSS 代码。

  • PostCSS 不应该被归类为应该与任何特定过程(如编写基于 SASS 的循环或条件语句)绑定的工具。有插件可以完成这两项任务,但这只是 PostCSS 在你的开发工作流程中可以扮演的角色的一小部分。

  • 如果你发现自己处于“PostCSS”似乎没有按预期表现的情况,这不太可能是 PostCSS 本身的问题,而更有可能是使用的某个插件导致了问题。尽管 PostCSS 仍然相对较新,但已经有大量的插件可供选择,所以如果你能尝试作为首选的替代方案,这是值得的。

好的,让我们继续前进,我认为现在是时候减少聊天,多做一些实际的工作了,对吧?让我们开始做一些实际的事情;现在安装 PostCSS 并准备好使用是再合适不过的时候了。

为本书的练习做准备

在我们这样做之前,我们只需要覆盖几个要求。首先,我们需要设置一个本地 Web 服务器。这不是关键,但会带来更好的效果。我个人使用 WAMP Server(适用于 PC,来自 www.wampserver.com/en),否则,Mac 用户可以尝试 MAMP (www.mamp.info/en),或者跨平台的 Apache Web 服务器(来自 www.apachefriends.org)。在每种情况下,默认设置都应该足够。

第二个要求是设置一个项目区域;假设你已经设置了一个 WAMP 作为本地 Web 服务器,那么请继续在 c:\wamp\www 中创建一个名为 postcss 的文件夹,如图所示:

为本书的练习做准备

好的,现在我们已经解决了这个问题,让我们开始安装 PostCSS 吧!

设置开发环境

我们旅程的第一步是安装 PostCSS——这需要通过 Node.js 来运行;我们可以使用几个任务运行器插件中的任何一个来安装它。为了本书中的练习,我们将使用 Gulp;如果你更喜欢,可以使用 Grunt 或 Broccoli 这样的替代品。

注意

当使用 Node.js 时,确保你使用 Node.js 命令提示符,而不是 node.exe;使用后者时练习将无法工作!

让我们从安装 Node 和 Gulp 开始:

  1. 我们首先需要安装 Node.js;它可以在 nodejs.org 获取。确保你选择适合你平台的正确版本:设置开发环境

    在安装时,接受所有默认设置;这将为本书中的所有练习足够。

  2. 接下来,打开一个 Node.js 命令提示符,输入以下命令,然后按 Enter

    node –v
    
    

    显示的输出是已安装的 Node 版本;这是一个快速检查,以确保 Node.js 已经正确安装:

    设置开发环境

  3. 现在 Node 已安装,我们需要创建一个 package.json 文件来存储我们的项目依赖项。在命令提示符中运行此命令,并按 Enter 键:

    npm init
    
    
  4. 当创建 package.json 文件时,Node 将会提示输入信息;请按照截图中的显示输入详细信息,或者按 Enter 键接受给定的默认值(括号内显示,每个问题之后):设置开发环境

现在我们已经配置了 Node 并放置了一个空的 package.json 文件,所以让我们添加我们的依赖项。我们将首先添加 Gulp:

  1. 回到 Node.js 命令提示符(或者如果关闭了上一个会话,则打开一个新的会话)。

  2. 请继续更改工作目录到 c:\wamp\www\postcss

  3. 在命令提示符中输入以下命令,然后按 Enter 键。这将全局安装 Gulp 并使其可用于使用:

    npm install --global gulp
    
    
  4. 完成后,我们需要在我们的项目区域安装 Gulp 以供使用——运行此命令,这将添加到我们在步骤 34 中创建的 package.json 文件中的一个条目:

    npm install --save-dev gulp
    
    

完成后,Gulp 现在已准备好使用;我们可以继续安装 PostCSS。

注意

关于使用 --save-dev 的小提示:这会安装开发特定插件所需的任何依赖项;如果我们只需要运行插件(在生产环境中)的依赖项,那么我们可以简单地使用 --save

安装 PostCSS

我们现在处于一个有趣阶段——安装 PostCSS。PostCSS 可从 github.com/postcss/postcss 获取,并且可以使用 Gulp 插件安装到 Node 中。现在让我们来做这件事:

  1. 我们将首先回到我们刚刚使用的 Node.js 命令提示符会话(或者如果上一个会话已关闭,则创建一个新的会话)。

  2. 在提示符下,请输入以下命令,然后按 Enter 键:

    npm install --save-dev gulp-postcss
    
    

    如果一切顺利,我们应该看到类似于以下截图的内容:

    安装 PostCSS

单独来看,PostCSS 什么也不做;为了使其更有用,我们将安装三个插件。我们将在本书的后面部分更详细地探讨插件的使用,但现在不必太担心正在发生的事情:

  1. 将以下命令逐个在 Node.js 命令提示符中输入,每个命令输入后按 Enter 键:

    npm install --save-dev autoprefixer
    
    
  2. 让我们检查我们的 package.json 文件;如果一切顺利,我们应该看到类似于以下截图的内容:安装 PostCSS

    提示

    为了在 Sublime Text 中更容易查看 JSON 文件,尝试安装并激活一个自定义主题,例如 MonokaiJSON Plus,可以从 github.com/ColibriApps/MonokaiJsonPlus 安装。

PostCSS 现已安装并可使用,但如果我们尝试使用它,可能不会走得很远,因为它需要配置后才能使用!现在让我们通过创建一个简单的示例来查看如何进行配置,该示例将为一些示例 CSS 规则添加供应商前缀,并自动压缩结果。

使用 PostCSS 创建一个简单示例

PostCSS 是一个令人着迷的工具;其模块化架构使其能够广泛应用于各种不同的使用场景,甚至可以混合使用几个!在这本书的整个过程中,我们将探讨不同的用法,最后将它们全部整合到一个可以在同一工作流程中预处理和后处理文件的处理器中。

为了让您体验其效果,我们现在将构建一个简单的处理器;这将在编译过程中自动添加供应商前缀并输出压缩版本。

让我们开始吧,我们已经安装了相关的插件,所以让我们创建我们的 Gulp 任务文件:

  1. 在一个新文件中,添加以下代码,将其保存为项目区域的根目录下的 gulpfile.js使用 PostCSS 创建一个简单示例

  2. 在项目区域中,创建一个名为 dest 的文件夹;其他文件夹将会在编译过程中自动创建。

  3. 在一个新文件中,添加以下代码,将其保存为项目区域 src 文件夹下的 example.css

    body {
      display: flex;
      background: green;
    }
    
  4. 返回 Node.js 命令提示符,然后在命令提示符中,输入以下命令并按 Enter

    gulp styles
    
    

    Gulp 将现在处理 gulpfile.js 中的指令:

    使用 PostCSS 创建一个简单示例

  5. 几秒钟内(几乎是瞬间),我们应该在我们的项目区域的 dest 文件夹中看到编译好的 example.css

  6. 我们可以证明 PostCSS 已经正确地完成了其工作;请打开文本编辑器中的 example.css:如果一切顺利,我们应该看到这个:使用 PostCSS 创建一个简单示例

完美,我们现在有一个工作的 PostCSS 安装;任何需要添加供应商前缀的时候,我们都可以启动我们的编译过程,然后就可以开始了…

添加源映射支持

或者呢?啊,PostCSS 的功能远不止添加供应商前缀!记得我提到 PostCSS 经常(不正确地)被标记为预处理器或后处理器吗?

好吧,我们还有很多可以做的;PostCSS 的一个关键好处是我们可以选择性地处理我们的代码。我们不必依赖于依赖项(如 Ruby 的 SASS);我们可以生产出非常轻量且快速的东西。在我们的上一个示例中,我们创建了一个名为 styles 的任务;我们将将其更改为使用任务名称 default,这将允许我们从单个命令中运行多个任务。这意味着我们可以简单地调用 gulp,而无需提供任务名称。

注意

从现在开始,我们所有的示例都将默认使用此约定。

让我们通过添加源映射支持来测试这一点,并开始扩展我们的编译过程——我们将使用 Florian Reiterer 提供的 Gulp 源映射插件,可从github.com/floridoo/gulp-sourcemaps获取:

  1. 我们将像往常一样,使用 Node 安装插件——启动一个 Node.js 命令提示符,然后切换到我们的项目区域。

  2. 接下来,在命令行中输入以下内容并按Enter键:

    npm install --save-dev gulp-sourcemaps
    
    
  3. 打开我们在使用 PostCSS 创建简单示例部分创建的gulp文件,然后添加对gulp-sourcemaps的引用作为一个变量:

    var autoprefixer = require('autoprefixer');
    var sourcemaps = require('gulp-sourcemaps');
    
    
  4. 然后我们需要添加创建源映射的命令——在同一个文件中,按照以下所示修改代码:

    .pipe(postcss([ autoprefixer ]))
    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write('maps/'))
    .pipe(gulp.dest('dest/'));
    
  5. 保存结果,然后从 Node.js 命令提示符中运行此命令,并按Enter键:

    gulp styles
    
    
  6. 如果一切顺利,我们应该在dest文件夹下的一个名为maps的子文件夹中看到一个新源映射出现。

    我们又向前迈出一步,朝着正确的方向前进;现在我们已经在maps文件夹中为我们的样式表创建了一个映射文件,这是在编译过程中自动创建的。

    注意

    值得注意的是,我们将充分利用这个区域——如果你在整本书中看到任何关于project area的提及,这将是我们对这个文件夹的指定名称。

    但是,我们可以做得更多:尽管我们这里只有一个小的 CSS 文件,但将其压缩以节省不必要的带宽使用仍然很重要。我们可以很容易地使用 PostCSS 来修复这个问题——让我们看看如何使用cssnano插件来操作。

创建最小化样式表

生成样式表的关键部分是输出最小化;这应该在任何开发者的工作流程中作为标准。最小化结果将减少带宽使用。在宽带或电缆使用时代,对于较小的网站来说,这可能不那么关键,但不应比大型网站的重要性低!

幸运的是,当使用 PostCSS 时,最小化文件变得非常简单。对于接下来的练习,我们将使用从cssnano.co/github.com/hparra/gulp-rename可用的cssnanogulp-rename插件。让我们继续安装它们:

  1. 我们将首先启动一个 Node.js 命令提示符,然后输入以下内容并按Enter键:

    npm install -–save-dev cssnano
    npm install -–save-dev gulp-rename
    
    

    不要关闭会话窗口,我们将在本练习的后面使用它。

  2. 切换到我们之前创建的gulpfile.js文件(它存储在我们的项目文件夹的根目录),然后在第 12 行或其附近的最后一个关闭})之后立即添加以下行:

    gulp.task('rename', ['styles'], function () {
      return gulp.src('dest/example.css')
       .pipe(postcss([ cssnano ]))
       .pipe(rename('example.min.css'))
        .pipe(gulp.dest("dest/"));
    });
    
    gulp.task('default', ['styles', 'rename']);
    
  3. 在文件顶部,我们需要添加两个声明,否则我们的代码将失败;请继续添加以下两条高亮显示的行:

    var sourcemaps = require('gulp-sourcemaps');
    var rename = require('gulp-rename');
    var cssnano = require('cssnano');
    
    
  4. 任何细心的读者现在可能已经发现了问题——在最后一行,我们有一个对styles的引用,但在代码中却没有显示这一点!为了修复它,我们需要更改我们的代码。在第 8 行,按照以下所示更改行:

    gulp.task('styles', function() {
    
  5. 保存文件,然后切换回 Node.js 命令提示符窗口,并输入以下命令,然后按 Enter

    gulp
    
    
  6. Gulp 现在将编译:创建压缩样式表

    如果一切顺利,我们应该在我们的项目区域的 dest 文件夹中看到编译后的输出:

    创建压缩样式表

在我们的项目区域,我们不仅创建了 maps 下的源映射文件,现在还创建了一个最小化样式表,后者是通过重命名 cssnano 的输出创建的(cssnano 本身不执行重命名,因此使用了 rename 插件)。

然而,不幸的是,我们仍然有一个小问题——看看 maps 文件夹的内容:你注意到什么了吗?希望你能发现,源映射文件在我们的样式表的未压缩版本中,但压缩版本中没有!现在让我们修复这个问题。要做到这一点,我们只需要在我们的 Gulp 文件中使用 rename 任务,如下所示:

    .pipe(rename('example.min.css'))
    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write('maps/'))
    .pipe(gulp.dest("dest/"));

现在尝试运行 Gulp。如果一切顺利,我们应该在我们的最小化样式表看到源映射:

创建压缩样式表

让我们完成我们的 gulp 文件;最后一步是添加一个 watch 功能,以便在文件修改后自动编译更改。

自动编译的修改

在使用 Gulp 时添加 watch 功能很简单。这有助于减少使用 Gulp 时所需的手动工作量,因为我们只需要运行一次 Gulp 任务文件,每次文件更改时它都会继续应用这些任务。

与其他插件不同,我们不需要为此安装任何插件;只需将以下高亮行添加到 gulpfile.js 文件中即可:

gulp.task('default', ['styles', 'rename', 'sourcemaps']);

var watcher = gulp.watch('src/*.css', ['default']);
watcher.on('change', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ',     running tasks...');
});

我们可以看到我们添加到 gulp 任务文件中的结果,以及它是如何结合在一起的,如下面的截图所示:

自动编译的修改

到目前为止,我们可以保存文件然后像之前一样重新运行 gulp 命令;这次它将自动重新编译 src 文件夹中更改的任何文件。在这个例子中,我们添加了一个事件处理程序来记录会话中的指示,这样我们就可以知道发生了什么;如果需要,我们可以轻松地修改它。

我们现在有一个基本的工作系统;我们将在接下来的几章中开始添加内容,以构建我们自己的处理器。不过,有一件小事我们应该注意:这不是必需的,但是一个在 PostCSS 开发中很有用的技巧。我指的是检查你的代码,以确保它是有效的;让我们深入探讨并设置好以供使用。

使用插件进行代码检查

不言而喻,代码检查应该是任何开发者工作流程的一部分。根据你使用的工具,有各种不同的方法来实现这一点。PostCSS 的美妙之处在于,我们可以轻松地为我们的处理器添加合适的代码检查功能,使用 PostCSS 的 stylelint 插件(可在 stylelint.io/ 获取)。

我们为什么要这样做呢?很简单:我们可以得到一个一致的结果。如果你作为团队的一员工作,这一点变得至关重要;而不是不同的团队成员使用不一致的设置,我们可以设置一个中心点进行处理,以保持一致的输出。将代码检查过程移至我们的中心工作流程意味着服务器可以为我们做苦力工作,并且随时为任何运行此过程的人提供一致的结果。

考虑到这一点,让我们看看我们如何设置我们的代码检查能力:

  1. 我们像往常一样开始安装插件。为此,打开 Node.js 命令提示符,然后切换到我们的项目区域根目录。

  2. 在命令提示符下,输入以下命令,然后按Enter

    npm install stylelint
    
    

    如果一切顺利,我们应该在提示符中看到这个:

    使用插件进行代码检查

  3. 接下来,我们需要安装第二个插件——stylelint中有一个报告函数,它会将任何消息发布到控制台(或在这种情况下,屏幕)。该插件是postcss-reporter,可在github.com/postcss/postcss-reporter找到。我们可以这样安装它:使用插件进行代码检查

  4. 插件安装完成后,我们需要更新我们的gulp文件;在显示的最后var行下面立即添加以下行:

    var cssnano = require('cssnano');
    var stylelint = require('stylelint');
    var reporter = require('postcss-reporter');
    
    
  5. 在 Gulp 文件中的重命名任务下面立即添加此任务——这个任务负责检查我们的代码,并在屏幕上标记任何错误:

    gulp.task("lint-styles", function() {
      return gulp.src("src/*.css")
        .pipe(postcss([ stylelint({ 
          "rules": {
            "color-no-invalid-hex": 2,
            "declaration-colon-space-before": [2, "never"],
            "indentation": [2, 2],
            "number-leading-zero": [2, "always"]
          }
        }),
        reporter({
          clearMessages: true,
        })
      ]))
    });
    
  6. 打开项目文件夹根目录下的example.css副本,并将color更改为#fff1az

  7. 在 Node.js 命令提示符中,输入此命令并按Enter

    gulp
    
    
  8. Gulp 将开始处理我们的代码;如果一切顺利,它应该会显示警告:使用插件进行代码检查

很容易就能发现#fff1az显然不是一个有效的数字!Stylelint 已经正确地识别了它,使用我们配置中的高亮规则:

    .pipe(postcss([ stylelint({ 
        "rules": {
          "color-no-invalid-hex": true,
          …
        }
      }),

让我们暂时探索一下这个插件的工作原理——它的好处是,有数十个规则可供选择(你可以在cdn.rawgit.com/stylelint/stylelint/1.0.0/docs/rules.md中看到)。它通过将检查的内容(在这种情况下,color)和对其运行的检查(在我们的情况下,-no-invalid-hex,或检查无效的十六进制数)连接起来工作。我们可以在配置对象中应用任意数量的规则,以确保所有项目的输出都是一致的。

小贴士

如果你想要了解规则是如何组合在一起的,那么请查看用户指南cdn.rawgit.com/stylelint/stylelint/1.0.0/docs/user-guide.md,其中提供了更多规则的示例,可在cdn.rawgit.com/stylelint/stylelint/1.0.0/docs/rules.md找到。

好的,让我们继续:从下一章开始,我们将更详细地探讨代码的编译过程,但就目前而言,让我们更详细地了解一下 PostCSS 是如何工作的,以及我们如何开始从现有的处理器迁移到 PostCSS。

探索 PostCSS 的工作原理

到目前为止,我们已经涵盖了设置和使用 PostCSS 的基础知识。花点时间了解它是如何工作的,这样我们就能更好地理解如何使用它,并为该平台开发自己的插件。

PostCSS 就像我周六早上在狂欢之后:什么也不做!是的,这是真的,单凭它本身,这个应用根本什么也不做;只有当我们添加插件进来,它才开始变得有用。

PostCSS 的关键是将其视为一个启用器,它不是作为你现有预处理器或后处理器的直接替代品,而是作为它们的补充。它基于解析代码,使用任何分配的插件处理代码,并渲染结果:

探索 PostCSS 的工作原理

它通过一系列节点将内容解析为抽象语法树(或 AST)。树中的每个节点都包含了对代码中元素的符号表示。换句话说,如果你有一个指向三个可能结果的条件语句,那么 AST 将有一个节点,有三个分支代表可能的结果。

注意

例如,查看jointjs.com/demos/javascript-ast,它展示了使用纯 JavaScript 分解一个简单的算术函数。

然后我们的 AST 通过一个或多个插件(我们必须始终使用一个插件,但可以在我们的gulp文件中有多个)。然后它将代码转换成长字符串,在通过任何分配的插件处理之前,并以有效 CSS 的形式输出结果。我们可以利用这一点作为创建我们自己的插件的基础,使用从 GitHub 上 PostCSS 主站提供的样板代码和 API。

插件阶段的技巧在于我们必须使用的插件组合来满足我们的需求;更好的插件应该只执行一个角色。任何执行多个任务的插件都不太理想,因为它们可能包含我们项目中不需要的额外功能。

从 SASS 迁移

假设我们决定使用 PostCSS,几乎每个人心中都有一个首要问题:我们如何进行迁移?

简而言之,关键不是简单假设现有的代码可以直接通过 PostCSS 过程,因为它可能不起作用。相反,我们应该采取迭代的过程,开始将低垂的果实转换为使用 PostCSS。这个过程当然需要一些工作,但有一些技巧可以帮助我们减少迁移到 PostCSS 时的痛苦。

实现迁移的关键是确定需要处理的功能,然后创建构建过程的初始框架(例如,一个 Gulp 或 Grunt 任务文件),然后逐步添加插件支持,直到你拥有一个完全工作的编译器。

我们可以更进一步,使用可以将 SASS 代码格式复制到 PostCSS 的插件;一个理想的开始插件是 Autoprefixer,随后可以尝试 postcss-mixinspostcss-partial-import 等插件。我们将在第十一章 “操纵自定义语法” 中探讨如何以 SASS 为基础创建自定义语法,我们将使用这两个插件以及更多插件,以帮助简化过渡过程并帮助移除对 SASS 或 Less 等预处理器依赖。

注意

现在许多用于 PostCSS 的 SASS 格式插件都包含在 PreCSS 套件中。我们将在第十章 “构建自定义预处理器” 中探讨如何使用这个套件。

好的,我们继续前进。在接下来的几章中,我们将探讨不同处理器元素,这些元素通常用于创建构建处理器,例如变量或混入(mixins)。我们将看到它们通常如何在 SASS 或 Less 等处理器中编写,然后努力将我们的代码转换为使用 PostCSS 等价物,在处理之前生成有效的 CSS。然后我们将完成所有这些,构建你自己的自定义处理器,用于未来的项目。

摘要

编写有效的 CSS 是一种自互联网诞生以来就存在的艺术;这需要技巧、耐心和时间来创作和完美任何杰作。SASS 或 Less 等处理器有助于使这个过程更高效,但并非没有缺点;PostCSS 允许更定制化的方法,但无需额外的负担。我们在本章中已经覆盖了关于 PostCSS 的几个关键点,所以让我们花点时间回顾一下我们学到了什么。

我们首先简要地看了看处理的艺术,然后介绍了 PostCSS 作为一种工具。接着我们探讨了使用它的利弊,以及如何通过一些周密的计划使其与现有的开发工作流程无缝结合。

接下来,我们介绍了 PostCSS 的安装,以及 Gulp 作为任务运行器/宿主进程,然后进行了一个简单的演示,介绍编译过程是如何工作的,以及通过正确选择插件,我们可以减少管理代码所需的某些手动工作(有意为之!)。随着我们的代码编译完成,我们接着关注添加监视功能,以及自动支持代码检查,以确保我们保持一致的标准。

我们接着通过了解 PostCSS 的工作原理和其架构的某些方面来完善本章,这样我们就可以开始从使用纯 CSS 或现有预处理器过渡到使用 PostCSS。

哎呀,我们确实已经涵盖了很多内容;现在是时候真正深入研究了,开始认真使用 PostCSS。在接下来的几章中,我们将探讨许多现有预处理器中常见的概念,并探讨我们如何从过渡到使用 PostCSS 中受益。我们必须从某个地方开始,所以我们将从下一章使用变量、函数和混入开始,看看我们如何使用一些处理器技术,但又不带相关的负担!

第二章:创建变量和混入

一个问题:你有多少次在代码中创建了像按钮这样的组件,使用了多次非常相似的颜色?手动修改真的很痛苦。使用预处理程序如 SASS 或 Less 可以使它更容易,但需要承担完整库的开销。

我们能换一种方法吗?绝对可以;在接下来的几章中,我们将探索 PostCSS 的不同元素,然后在本书的后面部分将它们全部整合,以创建一个预处理程序应用。我们的旅程将从查看使用变量和混入开始;我们首先将探讨创建它们的基础知识,然后再过渡到支持使用 PostCSS。在本章中,我们将涵盖以下主题:

  • 使用现有预处理程序创建变量和混入的概述

  • 转向使用 PostCSS 等效物

  • 将混入支持添加到 PostCSS

  • 检查标准预处理程序和 PostCSS 之间的差异

让我们开始吧!

介绍变量和混入

到目前为止,我们已经涵盖了安装和配置 PostCSS 的基础知识——尽管涉及几个步骤,但使用处理器开始的过程很容易。然而,要真正了解它,没有比在实际中使用它更好的替代品了;只需一点小心和计划,你就可以自动化很多事情!

让我们通过使用变量、函数和混入来创建几个简单的示例来测试它。我们将从使用 SASS 创建原始版本开始,然后再将其转换为使用 PostCSS 插件。这些演示假设您对使用 SASS 有一定的了解,如果您完全不熟悉,那么您可能想参考我的书籍《SASS Essentials》,由 Packt Publishing 出版。

注意

有一点需要注意:我们将充分利用我们在第一章中创建的项目文件夹,介绍 PostCSS,其中src将是我们的工作台,而dest将包含编译后的代码。确保你在桌面上某个窗口中打开了它!

好吧,这个过程的第一步是安装 SASS,所以现在让我们来看看。

设置 SASS

使用 Gulp 设置 SASS 非常简单;我们可以使用与其他插件相同的命令格式来安装它。该插件的源代码可在github.com/dlmanning/gulp-sass找到;它是一个轻量级的node-sass前端,而node-sass是一个 C+库的 Node 绑定,即libsass

让我们深入探讨并查看如何安装它:

  1. 我们像往常一样从 Node 开始。启动一个 Node.js 命令提示符会话,然后切换到工作目录。

  2. 在命令提示符中,输入以下内容,然后按Enter

    npm install --save-dev gulp-sass
    
    
  3. 如果一切顺利,我们应该会看到类似于以下截图的内容:设置 SASS

在我们继续之前,我建议在每个练习之后,将dest文件夹的内容清理或保存到其他地方以备安全起见:

  1. 接下来,在 Sublime Text 中打开gulpfile.js的副本;我们需要进行一些更改,首先是添加对gulp-sass插件的引用(如高亮所示):

    var reporter = require('postcss-reporter');
    var sass = require('gulp-sass');
    

    SASS 默认会以未压缩的格式生成代码;在任务中添加{outputStyle: 'compressed'}会将输出代码自动压缩。这使得这一行变得多余,所以请继续并移除它:

    var cssnano = require('cssnano');
    
  2. 我们还需要在约 19 行处移除对cssnano的引用,所以请移除这一行:

    .pipe(postcss([ cssnano ]))
    
  3. 大约在 10 行左右,将样式任务的名称更改为autoprefixer,将依赖项名称更改为lint-styles

    gulp.task('autoprefixer', ['lint-styles'], function() {
    return gulp.src('src/*.css')
    

    然后移除这两行:

    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write('maps/'))
    
  4. 在重命名任务中,修改rename任务以匹配以下内容:

    gulp.task('rename', ['lint-styles'], function () {
      return gulp.src('dest/*.css')
        .pipe(rename('style.min.css'))
        .pipe(sourcemaps.init())
        .pipe(sourcemaps.write('maps/'))
        .pipe(gulp.dest("dest/"));
    });
    
  5. 大约在 25 行左右,我们需要添加lint-styles任务——请添加以下代码块,这将检查我们的样式是否一致:

    gulp.task("lint-styles", ['sass'], function() {
      return gulp.src("src/*.css")
        .pipe(postcss([ stylelint({
          "rules": {
            "color-no-invalid-hex": 2,
            "declaration-colon-space-before": [2, "never"],
            "indentation": [2, 2],
            "number-leading-zero": [2, "always"]
          }
        }),
        reporter({
          clearMessages: true,
        })
      ]))
    });
    
  6. 我们几乎完成了。添加下一个任务;这告诉 Gulp 如何编译任务运行器提供的任何 SASS 文件:

    gulp.task('sass', function () {
      gulp.src('src/*.scss')
        .pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
        .pipe(gulp.dest('src/'));
    });
    
  7. 我们还需要进行一些其他更改。触发对每个子任务调用关键任务需要更新,以反映我们对任务的更改:

    gulp.task('default', ['sass', 'lint-styles', 'autoprefixer', 'rename']);
    
  8. 我们最后的更改是修改监视功能以检查 SASS 文件,而不是纯 CSS;请按照所示更改配置对象:

    var watcher = gulp.watch('src/*.scss', ['default']);
    

到目前为止,我们已经设置了处理器以将 SASS 文件编译成有效的 CSS。我们可以通过编译任何 SASS 文件来证明这一点。如果一切顺利,我们的处理器将自动生成有效的样式表和相应的源映射文件。让我们在下一个练习中将其付诸实践,我们将为图像创建一个有趣的悬停效果。

创建悬停效果示例

如果你看过我之前的一些书籍,那么你会看到我对花朵有特别的喜好,特别是兰花;事实上,我第一本书的封面就是飞燕草,或者说是蝴蝶兰!我们将使用几幅兰花图片作为我们下一个演示的基础,如页面上的截图所示,它显示了顶部图像的动态悬停效果。

小贴士

如果你更喜欢使用 Less,那么请跳到这个演示的末尾,看看使用 Less CSS 的示例。

对于这个演示,我们需要从本书附带的代码下载中获取tutorial1A文件夹的副本;确保在继续之前找到它:

  1. 打开src文件夹中tutorial1Astyle.scss副本;让我们看看它的内容。

  2. 在文件顶部,我们有一些变量。这些定义了代码中使用的某些颜色,并将$fullsize变量设置为 100%:创建悬停效果示例

    眼尖的读者会发现并非所有颜色都给出了值;这个原因将在本章后面的部分变得清晰。

  3. 接下来是一个简单 mixin 的示例,它将像素值转换为它们的 rem 单位等效值,以16px作为1rem单位的基准等效值:创建悬停效果示例

  4. 为了完成练习,我们需要下载一个字体。演示使用了可在www.fontsquirrel.com/fonts/source-sans-pro找到的 Source Sans Pro 字体。请继续下载它;您将需要使用黑色菜单中可用的生成器选项来生成一个可以在线使用的版本(它创建了我们在演示中使用的 CSS)。

  5. 在这一点上,请将tutorial1A文件夹中的style.scss文件复制到我们项目区域的src文件夹中。

  6. 我们还需要img文件夹和index.html文件——请将它们都复制到我们项目区域的根目录。

  7. 启动一个 Node.js 命令提示符窗口,然后在提示符中输入以下内容并按Enter

    gulp
    
    
  8. 如果一切顺利,我们应该在我们的项目区域的dest文件夹中看到压缩的 CSS 文件和源映射出现——将maps文件夹和style.min.css复制到tutorial1Acss文件夹中。

  9. 在浏览器中预览结果。如果一切顺利,我们应该在屏幕上看到两个兰花图像;如果您悬停在任何一个上,您将看到它飞向左或右,以显示有关兰花的信息框:创建悬停效果示例

有趣的效果,对吧?这是一个简单的动画,使用scale()将图像缩小到其大小的0.5(或 50%),然后向右滑动,在它后面的infobox立即滑动进入。将鼠标移出图像,就会发生相反的情况——这是第二张图像的示例原理——但方向相反;代码设置了一个ltrrtl类来决定图像在演示中应该向哪个方向移动。

使用 Less CSS 作为替代方案

包含来自 Less CSS 预处理器等效代码的此演示副本可在本书附带的代码下载中找到。如果您更喜欢使用 Less CSS 预处理器,它位于Tutorial1B文件夹中;您需要从github.com/plus3network/gulp-less安装gulp-less插件,使用 NodeJS(与其他我们已安装的插件相同的方式)。此文件夹还包括更新后的 Gulp 任务文件以及 CSS 代码的完成版本。

转向使用 PostCSS

到目前为止,我们一直使用 SASS 来构建我们的演示;当然,它并不特别复杂,但正如我经常说的,我们必须从某个地方开始!

我们的演示是一个完美的例子,展示了我们如何引入 PostCSS 来提供替换我们使用的 SASS 变量和混入的方法——为此,我们将使用三个插件,即postcss-variablespostcss-mixinspostcss-calc。前两个应该很容易理解;第三个是我们代码中使用的替换字体混入所必需的。

好了,闲话不多说,让我们开始修改代码;我们将从添加变量支持开始。

为 PostCSS 添加变量支持

PostCSS 插件的优点是大多数(如果不是所有)都可以使用与 PostCSS 本身相同的方法安装,我们可以使用 Node.js 的包管理器来处理这个过程。

我们将从postcss-css-variables开始,我们将使用它来处理变量支持;此插件的源代码可以从github.com/MadLittleMods/postcss-css-variables获取。让我们来安装它:

  1. 启动一个 NodeJS 命令提示符,然后更改工作目录到我们的项目区域。

  2. 在命令提示符中,输入以下命令,然后按Enter

    npm install --save-dev postcss-css-variables
    
    
  3. 如果一切顺利,我们应该看到安装的结果,如图所示:为 PostCSS 添加变量支持

到目前为止,Node 已经将新插件添加到了package.json文件中。太好了——我们现在可以好好利用它,并切换到使用插件而不是使用 SASS。让我们看看如何实现这一点,作为即将到来的练习的一部分。

更新我们的悬停效果演示

如果我们正在修改代码以首次使用 PostCSS,那么从简单的事情开始自然是有意义的;将变量和混入纳入我们的代码是开始的好地方。

在接下来的练习中,我们将创建一些变量来存储一些值,然后添加一个混入来处理演示中使用的字体样式。让我们开始:

  1. 我们将首先打开项目区域根目录下的gulpfile.js副本——我们首先需要做一些更改以适应使用新插件。

  2. gulpfile.js中,在第一个var语句块下方立即添加此行——这应该在约第 9 行左右:

    var cssvariables = require('postcss-css-variables');
    
  3. 现在,我们需要修改我们的 gulp 任务文件——我们将从最简单的开始,即移除对 SASS 的var引用,因为我们不再需要它:

    var sass = require('gulp-sass');
    

    现在我们已经有了对postcss-css-variables插件的引用,我们需要在我们的任务中使用它。按照指示,继续修改autoprefixer任务中高亮显示的代码行;这也消除了对lint-styles任务的依赖,因为现在不再需要它:

    gulp.task('autoprefixer', function() {
      return gulp.src('src/*.css')
      .pipe(postcss([ autoprefixer, cssnano, cssvariables(/* options */) ]))
      .pipe(gulp.dest('dest/'));
    
    
  4. 注意,我们还重新启用了cssnano命令——你还需要按照指示添加这一行作为变量声明:

    var rename = require('gulp-rename');
    var cssnano = require('cssnano');
    
    
  5. 在下面一点,大约在第 25 行左右,按照高亮显示的代码进行修改,因为我们将不再使用 SASS 来编译我们的代码;我们可以调整每个任务运行的顺序:

    gulp.task("lint-styles", ['autoprefixer'], function() {
    
  6. 接下来,我们可以完全删除 SASS 任务:

    gulp.task('sass', function () {
      gulp.src('src/*.scss')
      .pipe(sass({outputStyle: 'compressed'})
        .on('error', sass.logError))
        .pipe(gulp.dest('src/'));
    });
    
  7. 在文件末尾,按照指示更改默认任务——由于 SASS 任务已被移除,我们不需要调用 SASS 任务:

    gulp.task('default', ['lint-styles', 'autoprefixer', 'rename']);
    
  8. 修改gulp.watch命令以在src文件夹中查找纯 CSS 文件——我们不再使用 SASS,因此对scss格式文件的引用现在无效,需要更改:

    var watcher = gulp.watch('src/*.css', ['default']);
    watcher.on('change', function(event) {
    

    到目前为止,如果一切顺利,我们应该有一个可以现在使用的正在工作的 gulp 任务文件。让我们继续将我们的 orchid 演示中的代码转换为使用 PostCSS:

  9. 我们将首先将本书附带的代码下载中的Tutorial2文件夹的副本保存在我们之前在c:\wamp\www下创建的项目区域中,回到第一章,介绍 PostCSS

  10. 打开Tutorial2文件夹中的src文件夹内的style.css副本。在文件顶部,删除第 1 到 14 行(变量和 mixin 代码),使文件从font-face声明开始。

  11. 在其位置,添加以下行——这些是替换变量赋值:

    :root {
      --dark-grayish-green: #868a7b;
      --very-dark-gray: #333333;
      --white: #fff;
    
      --fullsize: 100%;
    }
    
  12. 在下面,寻找htmlbody {声明,并按照指示进行更改——注意var语句使用的语法;这不同于标准的 SASS。我们将其更改为postcss-css-variables插件支持的格式:

    html, body {
      width: var(--fullsize);
      padding: 0;
      margin: 0;
      height: var(--fullsize);
      min-width: var(--fullsize);
      max-width: var(--fullsize);
      overflow: hidden;
      background: var(--dark-grayish-green);
    }
    
  13. 我们在样式表的顶部添加了--fullsize变量——现在让我们利用它并相应地更新img规则:

    img {
      width: var(--fullsize);
      height: var(--fullsize);
    }
    
  14. 我们将进行的最后一个更改是对.info类的更改——按照指示更改背景属性:

    /* ------ Hover Effect Styles ------ */
    .info {
      background: var(--very-dark-gray);
    }
    

    我们的所有代码更改都已完成,所以请保存文件——完成后,启动 NodeJS 命令提示符,并切换到项目工作区。

  15. 将文件保存为styles.css到我们的项目区域的src文件夹中。

  16. 切换到 NodeJS 命令提示符,然后在提示符中输入通常的命令,并按Enter

    gulp
    
    
  17. 将编译后的代码复制回Tutorial2文件夹内的css文件夹。如果一切顺利,当我们在浏览器中预览结果时,我们应该看到我们的演示继续按照本练习第一部分开始时所示的方式工作。

呼吸一下,这里有很多步骤!本书附带的代码下载中提供了完成后的样式表副本,包括编译前和编译后,它们可以在css | completed文件夹中找到。您需要将两个样式表文件重命名为style.css才能正确工作。

如果你想在不提交代码更改的情况下查看编译变量的效果,那么请查看此插件提供的游乐场,在madlittlemods.github.io/postcss-css-variables/playground/。这是在深入编辑生产代码之前熟悉使用postcss-css-variables插件的好方法。

好吧,让我们改变一下方向;在我们的演示中,我们已经覆盖了许多关键概念,所以让我们花点时间让事情尘埃落定,并探索通过演示学到的内容。

进一步来说

在过去的几页中,我们创建了一个简单的演示,展示了几个兰花图像的动画信息框。我们做的事情既不夸张也不复杂,但无论如何,它有助于说明使用此插件以及 PostCSS 的一些关键点:

  • 尽管我们在转换之前使用 SASS 预编译了我们的 CSS 代码,但我们同样可以轻松地使用其他预处理器,如 Less CSS 或 Stylus。关键是要尽可能在任务运行器(如 Gulp)的范围内工作,这样我们就不需要将另一种技术引入其中。

  • 重要的是要注意,尽管将代码转换看起来很简单,但插件并不是像 SASS 这样的预处理器那样编译。对于比简单代码更复杂的情况,它直接从 SASS 转换到 Less CSS 变得困难。

  • 在这个例子中,理解其工作原理的关键是遵循 W3C 的 CSS 自定义属性模块 1 级文档,该文档可在drafts.csswg.org/css-variables/找到。这里的技巧是要意识到 CSS 的特定性,或者哪个元素比其他元素有优先权;在这方面,PostCSS 不仅仅是用值替换变量,而是根据计算 CSS 特定性来编译代码。当使用 PostCSS 时,你可能会经常看到:root伪元素被使用——了解它是如何工作的很有价值!

    注意

    关于 CSS 特定性如何工作的解释,请参阅vanseodesign.com/css/css-specificity-inheritance-cascade/。如果需要,我们可以考虑使用插件来提高特定性——查看postcss-increase-specificity插件github.com/MadLittleMods/postcss-increase-specificity

  • PostCSS 的模块化特性意味着我们可以有选择性地使用插件——在这个例子中,我们使用了postcss-css-variables插件,它比其他如postcss-custom-properties等插件提供了更多的灵活性。作为替代方案,我们可能会考虑将变量分离到单独的文档中,并使用postcss-constants插件(可在github.com/macropodhq/postcss-constants找到)导入它们。

  • 如果我们使用postcss-css-variables插件,我们可以将值存储在代码本身中,或者将其分离到 gulp 任务文件中;后者的一个例子如下:

    var postcss = require('postcss');
    var cssvariables = require('postcss-css-variables');
    
    postcss([
      cssvariables({
        variables: {
          '—foo-var': { '100px', isImportant: true },
          '--other-var': { value: '#00CC00' },
          '--important-var': { value: '#ffCC00' }
        }
      })
    ])
    .process(css, opts);
    

简而言之,我们在cssvariables配置对象中为每个变量创建一个引用,作为postcss-css-variables插件的别名。

使用这种方法创建对象映射可以带来混合的好处。对于一些人来说,它减少了关于关注点分离的问题,我们可以将更多的 PostCSS 代码保留在任务文件中,而将更少的代码放在我们的样式表中。这可能会使任务文件更难阅读;如果你有很多变量需要定义,这不是一个好的选择。在这种情况下,我们最好将它们导出到导入文件中,并在编译时引用它们。

如果此时有一个重要的信息,那就是灵活性——PostCSS 的模块化特性意味着我们可以自由选择如何进行;这真的是权衡使用插件利弊,并决定这是否最适合我们的需求,或者我们需要寻找替代解决方案的情况。

设置插件的顺序

在这一点上,我们需要介绍 PostCSS 的一个关键部分:在我们任务运行器文件中调用插件时使用的顺序。这可能会显得有些奇怪,但在使用 PostCSS 进行开发时考虑这一点有两个很好的理由:

  • 第一个原因是简单的——它关乎确保我们在编译时保持任务完成的逻辑顺序。

  • 第二个原因稍微有些晦涩,并且需要经验——一些插件需要按照特定的顺序在任务文件中定义,才能正确工作。

让我们来看看这意味着什么:

如果我们看一下我们逐渐构建的 gulp 任务文件,你将注意到第 13 行和第 19 行之间存在一个关键的区别;而且,在你问之前,这不是任务名称!区别在于 ['lint-styles'] 约束——这迫使 Gulp 在其前一个任务完成之前不运行此任务:

设置插件的顺序

我知道这听起来可能像常识,而且我只是在宣扬你可能已经知道的事情,但获取 PostCSS 中插件调用的顺序对于成功编译你的文件至关重要。

例如,在撰写这本书的过程中,我经常发现我的源映射只针对样式表的未压缩版本生成,或者压缩后的样式表没有在正确的点创建。这些问题很简单,但调整顺序可能会对发生的事情和发生的时间产生重大影响!

继续讨论顺序的主题,你可能会在浏览 PostCSS 插件的源站点时看到类似这样的注释:

设置插件的顺序

这强调了插件顺序对于有效结果的重要性:不仅任务将按正确顺序完成并产生预期的结果,而且某些插件甚至可能无法工作。这并不一定意味着是错误;会有一个合理的理由说明插件 X 必须在插件 Y 之前。关键是要考虑任何约束条件。值得检查,因为其他人可能会添加补丁支持以消除约束,或者通过分叉他们自己的插件版本来修复它。

好的,现在是时候改变焦点,看看一些不同的功能:混入。对于初学者来说,这是预处理程序(如 SASS)中常用的一个关键功能,我们可以 混入(是的,有双关意味!)代码块。

这里的想法是我们可以创建任何东西,从简单的几行代码到复杂的、动态的代码片段,PostCSS 会将其编译到我们的代码中并用于生成有效的 CSS。让我们深入了解一下。

使用 PostCSS 创建混入

到目前为止,我们的兰花演示使用了多个变量来定义代码中的值。虽然这效果不错,但有些限制;毕竟,创建大量变量来处理不同的值是一种资源浪费的做法!

使用混入是一种更智能的方法;当我们能够将几个语句组合成一个单独的定义,然后将其复制到多个规则集中时,这种方法效果很好。当然,使用预处理器的人会认识到这种功能;PostCSS 团队创建了一个插件,在 PostCSS 中提供类似的功能。

插件源代码可在 github.com/postcss/postcss-mixins 获取,并且可以通过 Node 使用本章中介绍的方法进行安装。我们还将使用 postcss-calc 插件(来自 github.com/postcss/postcss-calc)来创建一个简单的混入,用于处理代码中 rem 值的像素回退。让我们深入了解一下它是如何工作的:

  1. 我们将像往常一样开始安装 postcss-mixins 插件;为此,启动 NodeJS 命令提示符,然后更改工作文件夹到我们的项目区域。

  2. 在提示符下,逐个输入每个命令,每个命令后按 Enter 键:

    npm install --save-dev postcss-mixins
    npm install --save-dev postcss-calc
    
  3. 如果一切顺利,我们应该在屏幕上看到输出结果,如图所示:使用 PostCSS 创建混入

更新我们的悬停效果演示

到这一点,我们将安装 PostCSS 中的混入支持。让我们通过更新我们的 gulp 任务文件和样式表来利用它们。我们首先从 gulp 任务文件开始:

  1. 让我们先打开项目区域中的 gulpfile.js 的副本,然后在 var 声明块(大约在第 10 行)下方立即添加以下行:

    var cssmixins = require('postcss-mixins');
    var calc = require('postcss-calc');
    
  2. 接下来,请从 autoprefixer 任务中修改这一行:

    .pipe(postcss([ autoprefixer, cssvariables(/* options */), cssmixins(/* options */), calc(/*options*/) ]))
    
  3. 保存文件。我们现在需要从本书附带的代码下载中获取演示文件的副本——请将 Tutorial3 的副本保存到我们的项目区域中。

  4. src 文件夹内打开 style.css 的副本,然后在 --root 规则中声明的变量声明之后立即添加此代码块:

    @define-mixin fontstyle $size, $color {
      font-size: $(size)px;
      font-size: calc($size/16)rem;
      color: $color;
    }
    
  5. 在混合设置好之后,我们现在需要调整我们的代码以使用混合;这需要一些更改。第一个更改是在 h3 规则声明中:

    h3 {
      @mixin fontstyle 32, #fff;
      text-transform: uppercase;
    
  6. 在下面一点,按照指示更改 .info h3 规则的前两行:

    .info h3 {
      @mixin fontstyle 20, #fff;
    
    
  7. 第三和最后的更改是在 .info p 规则中。按照以下方式更改它:

    .info p {
      @mixin fontstyle 12, #bbb;
      padding: 50px 5px;
    
  8. 保存文件,然后将其复制到项目区域的 src 文件夹中。

  9. 打开 NodeJS 命令提示符,然后切换到项目区域,在提示符中输入常规命令,并按 Enter 键:

    gulp
    
    

    注意

    你可能会看到一些来自 stylelint 的选项消息:现在可以安全地忽略这些消息,但我们在本书的后面部分将探讨如何优化这些。

  10. 将编译后的代码复制回 Tutorial3 中的 src 文件夹。如果一切顺利,当我们在浏览器中预览结果时,我们应该看到我们的演示继续按照本练习第一部分开始时所示的方式工作。

虽然我们的演示看起来不会有任何不同,但代码中会有一个明显的差异——使用 DOM 检查器(如 Firebug)快速查看会显示使用了 rem 值:

更新我们的悬停效果演示

使用混合确实会引发一些重要问题。确实,有人可能会认为它们只是简单地复制 SASS 的功能。我们使用的插件格式并不相同,即使原则相同;让我们暂停一下,看看这些是如何与标准处理器相比的。

比较 PostCSS 与标准处理器

使用混合(mixins)是一种将预定义的代码块(无论是静态的还是动态的)自动插入到我们的样式表中的好方法,在编译阶段进行。

需要注意的关键点是,尽管最终结果可能相似,但这种相似性仅仅是名称上的;我们使用的混合插件并非旨在复制 SASS 中可用的现有功能。相反,此插件在 PostCSS 中暴露了 JavaScript 的力量,应将其用作定义函数混合(function mixins),以替代 PostCSS 中不可用的 if 或 while 语句。

这尤其适用于我们需要在混合中更改任何属性名称的情况;一个例子是引用多个图像,每个图像都需要应用类似样式类:

require('postcss-mixins')({
  mixins: {
    icons: function (mixin, dir) {
      fs.readdirSync('/images/' + dir).forEach(function (file) {
        var icon = file.replace(/\.svg$/, '');
        var rule = postcss.rule('.icon.icon-' + icon);
        rule.append({
          prop:  'background',
          value: 'url(' + dir + '/' + file + ')'
        });
        mixin.replaceWith(rule);
      });
    }
  }
});

如果我们从代码中调用此混合 @mixin icons signin;,我们会看到以下结果:

.icon.icon-back { background: url(signin/back.svg) }
.icon.icon-secret { background: url(signin/secret.svg) }

这确实提出了一个有趣的问题:与 CSS 相比,在任务文件中使用 JavaScript 的截止点在哪里?采取这种方法意味着我们可以使用标准的 JavaScript,但这是以牺牲简单性为代价的!

这是你作为开发者需要做出的一个决定。PostCSS 的灵活性意味着我们不仅需要选择正确的插件,而且它们被调用的顺序也可能影响我们代码的结果。在这个例子中,一个替代插件——postcss-simple-vars——与 postcss-mixins 具有相同的语法,但不支持属性名的更改。

注意

如果我们的 mixin 存储在嵌套语句中,我们还可以考虑使用 postcss-nested 插件;我们将在下一章中介绍这一点。

但是,让我们回到我们的例子:我们使用了经典的 mixin 来在旧版本的 IE 中提供像素回退。

我们在这里可以使用一个替代插件,形式为 postcss-simple-mixins(可在 www.npmjs.com/package/postcss-simple-mixin 获取)。这个插件旨在为 mixin 提供简单的支持,并且没有与 postcss-mixins 相关的负担。

注意

然而,有一个需要注意的点:postcss-simple-mixins 插件已被弃用,尽管它仍然可用于使用。它不支持嵌套或参数的使用。

然而,关键考虑因素将取决于你计划在代码中实现什么;选择正确的插件将减少冗余功能的包含,并帮助将我们的自定义处理器保持尽可能精简。

选择插件还有一个关键原因:我们不仅可以使用 mixin 来支持旧版本的 IE,还可以使用 postcss-pxtorem 插件在编译期间生成 rem 值。毕竟,尽管大多数浏览器已经支持 rem 单位一段时间了,但总有一两个是后来者:

比较 PostCSS 与标准处理器

截图来自 CanIUse 网站,www.caniuse.com

转而使用此插件的好处是简化了我们的代码,因为服务器可以处理将像素值替换为等效的 rem 单位的繁琐工作。这些繁琐的工作可以转移到中央位置,这样任何使用它的人都会得到一致的结果。

注意

还需要注意的是,在 PostCSS 中,mixins 和函数之间的交叉也存在。当我们学习如何使用 PostCSS 在处理颜色或媒体时使我们的生活更轻松时,我们将在第五章 管理颜色、图像和字体中更多地探讨使用函数。

好的,我们继续前进。现在是时候完全转换话题,看看 PostCSS 的另一个关键部分:创建循环。任何熟悉 SASS 或 Less 的人都会知道,当将非常相似的风格应用到相同对象上时,这可能会变得多么单调;一个完美的例子是经常出现在页面上的经典社交媒体图标。PostCSS 有一个插件允许我们模拟这种功能,所以让我们看看如何在实际操作中使用它。

使用 PostCSS 循环内容

一个问题:你有多少次遇到过这样的情况,即你有一系列非常相似且共享样式的图像,但同时又需要应用个别样式?听起来熟悉吗?

我当然是在谈论诸如列表项、网格布局以及我们在互联网上随处可见的典型社交媒体图标等实例。我们当然可以简单地编写规则来覆盖每个图像,但正如你肯定会的,这并不是最明智的方法!相反,我们可以利用@each语句的强大功能,遍历每个项目并使用字符串插值应用每个样式。

由 Alexander Madyankin 编写的@each插件是两种实现遍历内容功能的方法之一;此插件的源代码可以从github.com/outpunk/postcss-each获取。另一个插件 postcss-for(可从github.com/antyakushev/postcss-for获取),采用不同的方法——两者的区别在于前者作用于对象,而后者必须使用一系列数字来应用样式。

如果我们暂时考虑第二个插件,我们必须遍历一系列连续的数字才能生成我们的结果。所以,类似于以下内容:

@for $x from 1 to 3 {
  .grid-$x { width: $(x)px; }
}

…在编译时会产生以下结果:

.grid-1 {
  width: 1px
}
.grid-2 {
  width: 2px
}
.grid-3 {
  width: 3px
}

看起来很简单,对吧?但是,这里有一个问题:与 SASS 不同,我们默认不能使用变量来定义这个范围;此插件必须在postcss-nestedpostcss-simple-vars插件的任何实例之前定义。在 PostCSS 中,我们遍历所有的值(即,在我们的例子中是一到三),这与 SASS 不同。

在这种情况下,我们必须决定是单独使用此插件,还是与postcss-custom-propertiespostcss-for-variables一起使用。这就是为什么完全理解你需要实现什么,以及可用的插件功能至关重要,这样我们才能选择最适合我们需求的最高效组合。PostCSS 的伟大之处在于其灵活性、速度和模块化设计;这种模块化和灵活性也可以被视为其阿喀琉斯之踵,因为调整插件的选择和顺序可能会对我们的代码产生实际影响!

注意

当然,我们可以完全改变,并使用来自github.com/xori/postcss-for的 postcss-for 插件的独立分支。这适用于美元变量。

让我们将其付诸实践。在我们进入下一章使用 PostCSS 进行嵌套之前,我们将通过一个简单的示例来结束这一章,该示例使用一组社交媒体图标和 PostCSS 来自动设置样式。

遍历@each 语句

在循环主题的基础上,但采取不同的方法,我们可以在不使用for语句的情况下,通过@each实现类似的效果,但前提是目标是页面上的一个元素。

我当然是在谈论按钮或菜单项等元素;这些元素将共享相同的样式,但需要唯一的 ID 以便我们与之交互。不言而喻,我们可以简单地创建一个共享的基类并为每个元素添加多个类…

但我们可以做得更好:大多数预处理器都有内置功能,允许我们遍历元素并对每个元素应用 CSS 样式。幸运的是,PostCSS 也不例外;我们可以使用从github.com/outpunk/postcss-each提供的 postcss-each 插件,达到相同的效果。安装非常简单,我们可以用它来添加社交媒体图标等元素到页面的底部,并对它们进行样式化。我感觉一个演示即将到来,让我们深入看看:

  1. 我们将从安装插件开始,所以请启动一个 NodeJS 命令提示符,并将工作目录更改为我们的项目区域。

  2. 在提示符下,输入以下命令来安装postcss-each插件,然后按Enter

    npm install --save-dev postcss-each
    
    
  3. 如果一切顺利,我们应该看到插件已安装的常规确认:遍历@each 语句

现在插件已经安装好了,让我们继续并更新我们的 gulp 文件:

  1. 我们需要对 gulp 文件进行三项更改,所以请打开项目区域中的一个副本,使用你常用的文本编辑器。

  2. 首先,先删除第 9 到 11 行;它们包含了postcss-css-variablespostcss-mixins插件的变量声明。

  3. 在现在第 8 行或附近,我们应该看到postcss-calc的变量声明。紧接着,添加以下行:

    var eachloop = require('postcss-each');
    
  4. 在主要的autoprefixer任务中,我们需要修改postcss调用;从第 13 行删除以下内容:

    cssvariables(/* options */), cssmixins(/* options */), calc(/*options*/),
    

    我们应该剩下这个(更改已被突出显示):

    .pipe(postcss([ autoprefixer, cssnano(), foreach(/*options*/) ]))
    

到目前为止,我们可以保存文件。现在它已经准备好供我们处理下一个演示所需的 CSS 了。对于这个下一个练习,我们需要一些合适的社交媒体图标。我使用了 Nathan Brown 的作品,可在wegraphics.net/downloads/free-stained-and-faded-social-media-icons/找到。我们将使用 Twitter、LinkedIn 和 YouTube 的图片。

让我们开始吧:

  1. 我们将首先查看这个演示的 SASS 版本。这是一个简单的例子,但完美地说明了我们如何使用@each函数遍历每个图像并应用适当的样式:

    $social: twitter, linkedin, youtube;
    
    .social-icon {
      // shared information here
      background: 50% no-repeat;
      background-size: 100%;
      float: left;
      height: 50px;
      width: 50px;
    
      // unique information loops here
      @each $network in $social {
        &.#{$network} {
          background-image: url("../img/#{$network}.png");
        }
      }
    }
    
  2. 要编译代码,请将Tutorial4文件夹复制到我们的项目区域。

  3. Tutorial1A文件夹中的现有gulpfile.js替换——这包含编译代码所需的适当命令——我们需要使用原始版本来编译 SASS 代码,而不是 PostCSS,因此进行了更改。

  4. Tutorial4文件夹的src文件夹中复制style.scss,然后将其放入我们项目区域的src文件夹中。

  5. 接下来,打开一个 NodeJS 命令提示符窗口,然后将工作文件夹更改为我们的项目区域。

  6. 在提示符中,输入此命令,然后按Enter键:

    gulp
    
    

    目前请保持命令提示符窗口打开,我们很快还会用到它。

  7. 一旦代码编译完成,将dest文件夹的内容复制回Tutorial4文件夹中的css文件夹。

如果一切顺利,预览浏览器中的结果时应该显示三个图标。这里没有什么过分的:我们有一个适用于所有图标的基规则,后面跟着处理每个图标本身所需的个别类:

.social-icon {
  background: 50% no-repeat;
  background-size: 100%;
  float: left;
  height: 50px;
  width: 50px;
}

.social-icon.twitter {
  background-image: url("../img/twitter.png");
}

.social-icon.linkedin {
  background-image: url("../img/linkedin.png");
}

.social-icon.youtube {
  background-image: url("../img/youtube.png");
}

那么,在 PostCSS 中这会是什么样子呢?令人惊讶的是,实际上需要的改变并不多。

切换到使用 PostCSS

我们只需要在我们的 CSS 文件中的两个地方进行更改。我还将嵌套代码分开,以便更容易查看:

.social-icon {
  // shared information here
  background: 50% no-repeat;
  background-size: 100%;
  float: left;
  height: 50px;
  width: 50px;
}

我们需要做的更改在这个代码块中突出显示:

@each $media in twitter, linkedin, youtube {
  . $(img) {
    background: url('../img/$(media).png');
  }
}

我们的 gulp 文件也需要更改。让我们一步步完成切换到 PostCSS 的步骤:

  1. 我们首先需要替换 gulp 文件——请删除项目区域根目录下的副本,然后用代码下载中的Tutorial4文件夹中的副本替换它。

  2. 从本书附带的代码下载中提取style——pre compile.css的副本,并将其重命名为style.css。将其放入项目区域src文件夹中。

  3. 回到命令提示符,然后输入gulp并按Enter键。

  4. 如果一切顺利,我们应该能在dest文件夹中看到编译后的样式表,以及源映射。

  5. 将项目区域中dest文件夹的内容复制到Tutorial4文件夹本地副本的css文件夹中。

  6. 尝试在浏览器中预览结果;如果一切按预期工作,我们应该看到这些图标出现:切换到使用 PostCSS

承认,这是一个简单的练习,但我一直喜欢保持事情简单!任何人都可以编写 CSS 样式,但对我来说,“提升”在于知道数量并不总是胜过质量,而且遵循KISS原则(保持简单...)确实有话可说!是的,你明白了!

但是,为了展示这个插件有多灵活,试着做这个练习:

  • 浏览到dataurl.net/,然后依次上传每个图标,并使用该网站为每个图像生成 data-URI 等效代码。

  • 在 CSS 中,删除现有的背景图像链接,并用dataurl.net网站上的代码替换它们。

  • 按照本章中使用的相同原则继续编译代码。看起来一模一样,不是吗?我们不再需要拉取单独的资源,因为我们使用的是纯 CSS 解决方案...

但是,有一个需要注意的地方:当文件被编译后,检查文件大小。它应该告诉你,它比不包含 data-URI 等效代码的文件大得多。这是可以预料的:这是大小与我们所调用的资源数量之间的权衡。这仅仅显示了我们的 PostCSS 插件顺序是多么关键,以获得期望的结果!

摘要

任何花时间使用 SASS 等预处理器的人无疑都会熟悉变量和 mixins;这些是使用 PostCSS 的一个基本组成部分。我们在本章中介绍了大量与它们使用相关的材料,所以让我们休息一下,回顾一下我们已经学到的内容。

我们从对 SASS 中的变量和 mixins 的简要介绍开始,然后在 SASS(和 Less CSS)中设置了一个示例演示作为转换为 PostCSS 的基础。

接下来是开始过渡到 PostCSS 的过程。我们首先研究了添加变量支持,然后更新了我们的悬停演示以使用新插件并移除对使用 SASS 的依赖。然后,我们介绍了使用 PostCSS 的一些好处和考虑因素,最后发现简单地调整插件顺序可以对最终结果产生重大影响。

我们迅速地继续前进,开始研究 mixins。在用它来更新我们的演示之前,我们首先介绍了 postcss-mixins 插件的安装。在这个时候,我们暂停了一下,考虑了一些标准处理器和 PostCSS 之间的差异,并介绍了要记住的一个关键点是 PostCSS 的灵活性和强大功能。

然后,我们通过查看循环内容来结束这一章。我们首先探讨了 for 语句的使用,然后继续并查看我们如何使用@each函数轻松地样式化内容。然后,我们通过一个简单的演示来介绍其用于样式化社交媒体图标的使用。这始于 SASS,但最终使用 PostCSS 转换的结果完成。

呼吸一下,让我们继续前进!在这个快速掌握 PostCSS 的旅程中,我们的下一个停靠点是查看嵌套,而且,不是针对我们的羽毛朋友,而是如何(戏剧性地)减少我们编写的内容,或者至少让它更容易阅读!

第三章。嵌套规则

如果你花过时间使用预处理程序进行开发,那么你可能会遇到嵌套属性——我们不需要写多个带有对相同元素重复引用的样式规则,我们可以使用嵌套来创建简洁的样式规则,这些规则编译成有效的 CSS。

在本章中,我们将深入探讨使用嵌套的强大功能,向您展示如何从使用预处理程序(如 SASS 或 Less)过渡,并探索一些我们可以在没有预处理程序(如 SASS)的情况下使用的技巧:

  • 探索使用 BEM(块、元素、修饰符)或标准嵌套

  • 使用现有预处理程序创建嵌套规则和 BEM 等效规则

  • 转向使用 PostCSS 插件

  • 理解嵌套的陷阱以及我们如何改进代码

让我们开始吧…!

介绍嵌套

当使用 Less CSS 或 SASS 等处理器时,嵌套的概念并不新鲜;这是一种有用的技术,可以帮助我们减少需要编写的代码量,并以更易于人类阅读的格式组织代码。

另一方面,它经常被滥用——当第一次使用处理器时,许多开发者会陷入认为所有内容都应该嵌套的陷阱。如果代码非常简单,可能还能侥幸过关;更有可能的结果是产生脆弱的代码,难以阅读,并且简单更改代码中的一个或多个样式就容易被破坏。

如果嵌套做得正确,那么它可以非常有效;它有助于避免重复父选择器的需要,并允许我们将应用于相同选择器的规则组合在一起。为了了解这意味着什么,请看这个简单的 SASS 示例:

#main p {
  color: #00ff00;
  width: 97%;

  .redbox {
    background-color: #ff0000;
    color: #000000;
  }
}

如果使用 GUI 应用程序或通过命令行编译,它会产生以下样式:

#main p { 
  color: #00ff00; 
  width: 97%;
}

#main p .redbox { 
  background-color: #ff0000; 
  color: #000000; 
}

这段代码的伟大之处在于,我们没有试图将适用于相同父选择器或后代的所有规则都塞进来;这是我们在处理嵌套时应该考虑的事情。

注意

注意,在我们的 SASS 示例中,嵌套是在代码的末尾?将嵌套语句放在末尾,在闭合括号之前,被认为是良好的实践。

一些开发者建议不要使用嵌套,因为它会给在特定上下文中样式化的元素带来真正的问题;如果我们需要更改样式,代码就变得更难更改。我们将在本章后面进一步探讨嵌套充满风险的原因。

暂时抛开嵌套的风险,我们可以在使用 PostCSS 时使用相同的基本嵌套原则——为此,我们需要使用 Jonathan Neal 的postcss-nesting插件,该插件可在github.com/jonathantneal/postcss-nesting获取。为了给您一个 PostCSS 中嵌套外观的印象,请看这张截图——这是作者提供的在线游乐场,用于试验嵌套语句,我们可以在右侧自动看到结果:

介绍嵌套

关键行在左侧,从下往上数第五行:postcss-nesting插件使用@nest &作为嵌套代码的占位符。

为了帮助说明postcss-nesting插件的工作原理,我们将使用它创建一个相对独特的导航系统。我们的导航将结合使用 jQuery 和 CSS 来样式化和翻转一些演示页面,动画效果由 CSS3 样式提供。感兴趣吗?让我们深入探讨并看看。

在页面间导航

我一直渴望做一些与众不同的事情;和别人做同样的事情变得如此过时!本着这个想法,在为这本书进行研究时,我遇到了 Nikolay Talanov 的一个有趣的演示,其中页面从一个翻到下一个,如果支持,则使用 CSS3 动画,否则回退到标准的 jQuery。

注意

您可以在codepen.io/suez/pen/LCHlA看到原始的 pen 演示。

这为本章提供了一个完美的基础。为了演示的目的,我移除了供应商前缀(因为这些将由Autoprefixer处理),调整了第一页的设计,并切换到整个使用嵌套。jQuery 回退代码也已移除,因为大多数现代浏览器都能轻松支持这些动画。

准备我们的演示

对于我们的演示,我们将有四个页面——导航将在每个页面之间翻转,使用标准的 CSS3 动画:

准备我们的演示

设计可能有点独特,但为了帮助说明其可能的用途,我在首页添加了一个简单的线框草图,这个草图可以轻松扩展到剩余的页面,并发展成为更实质性的内容。

要看到它的实际效果,请从本书附带的代码下载中提取Tutorial5文件夹的副本,然后在浏览器中运行index.html,点击右侧的圆点或箭头图标——您将看到它向上或向下翻转,具体取决于您点击的方向。

从现有处理器转换

目前,我们的演示正在使用纯 CSS,这并没有什么问题,但我怀疑你们中的一些人可能会使用现有的处理器,比如 SASS 或 less CSS。使用 PostCSS 的真正好处是它能够模仿现有工具,而不需要依赖。

在这个前提下,代码下载中提供了使用 Less CSS 和 SASS 的演示副本。如果您更喜欢使用 SASS,请从代码下载文件夹中提取 Tutorial6A;对于 Less,请使用 Tutorial6B。代码可以很容易地使用来自 第二章 的 Tutorial1A 中的 gulpfile.js 文件编译(对于 SASS),或 Tutorial 1B(对于 Less CSS,在同一章文件夹中)。

注意

您需要安装列出的插件——其中大部分已经在前面的演示中存在,但 gulp-sassgulp-less 需要使用 NPM 安装,方式相同。

两者在编译后都会产生与纯 CSS 版本相同的结果,并且将 dest 文件夹的内容复制到教程文件夹中的 css 子文件夹中。在基础演示设置完成后,我们现在可以开始转换了——让我们从安装 postcss-nesting 插件开始。

过渡到使用 PostCSS 插件

我们已经看到,将代码调整为使用嵌套是一个简单的原则,但真正的艺术在于找到平衡,许多开发者在使用该技术第一次时,会陷入将代码中所有内容都嵌套的陷阱。

在这个前提下,让我们探讨如何将我们的代码转换为使用 PostCSS。我们将从安装 postcss-nesting 插件开始,这将为我们完成大部分工作:

  1. 启动 Node.js 命令提示符,然后更改工作目录到我们的项目区域。

  2. 在命令提示符中,输入以下命令,然后按 Enter

    npm install --save-dev postcss-nesting
    
    
  3. Node.js 将会移除并安装插件——当我们看到类似以下截图时,它就准备好使用了:过渡到使用 PostCSS 插件

  4. 插件安装后,我们需要配置 PostCSS 以使用它——打开项目区域中的一个 gulpfile.js 复制,准备编辑。

  5. 我们需要做一些修改——首先是分配一个引用插件的变量。在最后一个变量语句下方立即添加高亮显示的行:

    var cssnano = require('cssnano');
    var nesting = require('postcss-nesting');
    
  6. 需要修改 autoprefixer 任务——这次,我们将从编译嵌套代码并添加适当的供应商前缀开始。按照指示修改此任务的第 1 行:

    gulp.task('autoprefixer', function() {
            return gulp.src('src/*.css')
    
  7. 接下来,添加嵌套配置调用:

         .pipe(postcss([ autoprefixer, nesting({ /* options */ }) ]))
    
  8. SASS 默认情况下在编译时会压缩任何代码——由于我们不再使用它,我们需要提供一个替代方案。为此,我们将重用来自 第二章 的 cssnano 插件,创建变量和混入。请继续在 20 行处添加以下内容:

    .pipe(postcss([ cssnano() ]))
    
  9. 添加供应商前缀后,lint-styles 任务应该会运行;为了实现这一点,添加如下约束:

    gulp.task("lint-styles", ['autoprefixer'], function() {
    
  10. 我们不再需要 sass 任务,所以请将其完全删除,并且从默认任务条目中,我们应该剩下以下内容:

    gulp.task('default', ['lint-styles', 'autoprefixer', 'rename']);
    
  11. 最后,但同样重要的是,现在就切换重命名任务的顺序。而不是在 autoprefixer 任务之后立即运行它,我们将在 lint-styles 任务完成后运行它:

    gulp.task('rename', ['lint-styles'], function () {
    

到目前为止,我们的 gulp 任务文件现在已准备好使用。我们可以开始将我们的样式表转换为使用 PostCSS 嵌套,作为 SASS 的替代品。让我们在下一个练习中开始转换它。

注意

如果您遇到困难,本书附带的代码下载中有一个完成的 gulpfile.js 版本——只需提取副本并将其放置到我们的项目区域的根目录下即可使用。

将我们的演示转换为 PostCSS

将我们的代码更改为使用 PostCSS 非常简单。即使它需要一些更改,与 SASS 等处理器相比,格式并没有发生显著变化;让我们看看涉及的内容:

  1. 我们将首先打开本书附带的代码下载中 Tutorial6A 文件夹中的 style.scss 的副本——将其保存到我们的项目区域的 src 文件夹中。将其重命名为 style.css

  2. 在第 19 行,在 &: 前面立即添加 @nest——这是允许 postcss-nesting 插件正确编译每个嵌套语句所必需的:

    @nest &:before, &:after {
    
  3. 在第 53 行,在 h2 前面立即添加 @nest &,如下所示:

    @nest & h2 {
    
  4. 在第 61 行,在 &. 前面立即添加 @nest,如下所示:

    @nest &.page1 {
    

    对第 65、69 和 73 行重复步骤 4。

  5. 在第 119 行,在 &. 前面立即添加 @nest,如下所示:

    @nest &.invisible {
    
  6. 在第 123 行,在 ul 前面立即添加 @nest,如下所示:

    @nest & ul {
    
  7. 在第 125 行,在 & li 前面立即添加 @nest,如下所示:

    @nest & li {
    
  8. 在第 136 行,在 &. 前面立即添加 @nest,如下所示:

    @nest &:after {
    

    对第 150 行和 155 行重复相同的步骤。

  9. 在第 179 行,在 &. 前面立即添加 @nest,如下所示:

    @nest &.up {
    

    对第 183 行和 187 行重复相同的步骤,然后保存文件。

我们的风格表现在已转换为;为了证明它有效,我们需要通过 PostCSS 运行它,所以现在让我们作为下一个练习的一部分来做这件事。

编译我们的代码

在我们对代码所做的更改之后,我们需要对其进行编译——让我们现在就使用我们在 第二章 中看到的相同过程,创建变量和混入 来进行编译:

  1. 启动一个 Node.js 命令提示符会话,或者如果您仍然打开着,就使用之前的那个,并将工作文件夹更改为项目区域。

  2. 在提示符下,输入此命令,然后按 Enter

    gulp
    
    
  3. 如果一切顺利,我们应该看到类似于以下截图的内容:编译我们的代码

  4. 快速查看我们项目区域的 dest 文件夹应该会显示由 PostCSS 生成的相关编译后的 CSS 和源映射文件。

  5. 在这一点上,我们需要从本书附带的代码下载中提取 Tutorial7 文件夹的副本——将其保存到我们的项目区域。

  6. 将我们项目区域的 dest 文件夹的内容复制到 Tutorial7 下的 css 文件夹中——如果一切顺利,我们的演示应该继续工作,但不再依赖于 SASS。

    注意

    注意,确保将演示扩展到屏幕的全宽以正确查看!

尝试在浏览器中预览结果——如果一切顺利,我们应该看到与之前相同的结果,但这次使用 PostCSS,并且不再依赖于 SASS。我们现在可以将相同的技巧应用到任何项目中,放心地知道使用 postcss-nesting 插件将允许我们编译为有效的 CSS 代码——或者不是吗?

探索嵌套的陷阱

不得不说,尽管嵌套是一种易于理解的简单技术,但它可能很难做到正确,正如我们在演示的 SASS 版本中所示:

探索嵌套的陷阱

我们这里的问题有两个——多层嵌套导致代码的特定性很高;如果我们想更改 .nav-panel ul li(第 125 行的编译版本)的样式,它很可能会破坏我们前端代码的外观。为了说明我的意思,让我们拿一个任何开发者都可能创建的示例 HTML 页面:

<body>
  <div class="container">
    <div class="content">
      <div class="articles">
        <div class="post">
          <div class="title">
            <h1><a href="#">Hello World</a>
          </div>
          <div class="content">
            <p></p>
            <ul>
              <li>...</li>
            </ul>
          </div>
          <div class="author">
            <a href="#" class="display"><img src="img/..." /></a>
            <h4><a href="#">...</a></h4>
            <p>
              <a href="#">...</a>
              <ul>
                <li>...</li>
              </ul>
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

现在,在你们所有人对我大喊“呃,我绝不会那么做!”并声称(完全正确)我们应该使用语义元素,如 <header><section><article> 和 / 或 <footer> 来提供上下文和意义,而不是所有这些 <div> 声明之前,请停止!产生那种丑陋的代码混合是有原因的。让我来解释:

我们刚才看到的示例 HTML 很可能导致以下嵌套 CSS:

body {
  div.container {
    div.content {
      div.articles {
        & > div.post {
          div.title {
            h1 {
              a {
              }
            }
          }
          div.content {
            p { ... }
            ul {
              li { ... }
            }
          }
          div.author {
            a.display {
              img { ... }
            }
            h4 {
              a { ... }
            }
            p {
              a { ... }
            }
            ul {
              li { ... }
            }
          }
        }
      }
    }
  }
}

一些开发者可能会认为这是完全可以接受的——毕竟,他们不知道有其他选择,那么为什么这会成为问题,对吧?错误——虽然这段代码在技术上可能符合我们的 HTML 文档中的样式,但它有几个问题:

  • 阅读起来至多有些尴尬,而且当试图解析它时,足以让人头疼。

  • 尝试编译它;它将导致大量的重复父选择器,代码扩展到大约 20 行。

  • 渲染性能可能很差——例如,如果安装了像 Google 的 Page Speed 这样的工具,那么它很可能会触发优先显示可见内容规则,这需要额外的往返才能在屏幕上渲染折叠以上的内容。

  • 大小可能是一个问题——尽管我们生活在一个宽带连接的时代,但对内容采取轻率的态度是不礼貌的,而且不尽可能创建体积小的内容。

  • 可维护性将变成一个问题——我们的示例代码将样式绑定得太紧,这违背了层叠样式表的目的,我们应该能够在父选择器中放置常见的样式,并允许这些样式向下级联,或者根据需要覆盖。

我们如何解决这个问题?最简单的答案是,在嵌套代码时,要合理地考虑我们使用的层数——一些开发者认为不超过四层;根据我的经验,我认为两层应该足够(在这种情况下,将是 body div.content,如果我们编译了我们的巨型 CSS 样式表)。

如果我们绝对没有其他方法来实现我们想要的结果,我们不妨使用四层;如果我们经常这样做,那么显然我们需要重新审视我们的代码!

采用更好的方法

如果在编写代码时,我们被迫经常使用两层以上(超过两层)嵌套样式,那么我们可以使用一些技巧来减少 CSS 特定性,并减少超过两层嵌套样式的需求。让我们看看几个例子:

  • 你能给自己提供所需的类吗?如果我们在覆盖现有的选择器,特定性可能会渗透进来:

    .section-header {
      /* normal styles */
    }
    
    body.about-page .section-header {
      /* override with higher specificity */
    }
    

    为了避免特定性,能否通过使用服务器端代码或函数来发出一个类,我们可以用它来样式化元素?

    <header class="<%= header_class %>">
    

    这可能输出一个类,或者根据需要输出两个类:

    </header>
    .section-header {
      /* normal styles */
    }
    
    .about-section-header {
      /* override with same specificity */
      /* possibly extend the standard class */
    }
    
  • 你的样式表的顺序在这里可以发挥重要作用,即使你可能使用单个类来覆盖样式:

    <header class="section-header section-header-about">
    ...
    </header>
    

    你现有的类可能覆盖了你的覆盖;这两个选择器具有相同的特定性,所以最后应用的规定将具有优先权。解决这个问题只是简单地重新排列你的样式规则应用的顺序,以便覆盖类可以稍后应用。

  • 考虑降低你试图样式的元素的特定性;这个元素可以被替换,或者完全移除吗?然而,如果它被用于 JavaScript(或 jQuery)代码中,那么最好是保持原样,并添加第二个类(或者如果存在,使用已应用的现有类)。

  • 在可能的情况下,尽量使用尽可能扁平的结构来编写你的代码;样式化这样一个元素太容易了:

    .module > h2 {
    
    }
    

    在这个例子中,我们正在为父类.module的直接子元素的所有h2元素应用样式。然而,这将在我们需要为h2元素分配不同样式时停止工作。如果标记看起来类似于这个例子:

    <div class="module">
      <h2 class="unique">
        Special Header
      </h2>
    </div>
    

    由于 CSS 特定性(specificity)的渗透,将难以轻松应用样式:

    .module > h2 {
      /* normal styles */
    }
    .unique {
      /* I'm going to lose this specificity battle */
    }
    .module .unique {
      /* I'll work, but specificity creep! */
    }
    
  • 为了避免这种情况,建议尽可能使用扁平的结构——这将值得额外设置所需的努力:

    <div class="module">
      <h2 class="module-header">
      </h2>
      <div class="module-content">
      </div>
    </div>
    
  • 考虑使用一个已建立的样式库,或者原子设计(例如patternlab.io/上的),以帮助指导你如何构建网站——它们很可能是使用最小 CSS 特定性构建的,并且可能几乎不需要覆盖现有代码。

  • 当应用 CSS 样式时,如果你决定使用级联(cascading),要小心——如果我们对一个被多次重用的元素(或类)应用基本样式,这将会引起问题。为了避免这种情况,尽量在不必要的情况下避免使用级联;考虑将其限制在 2-3 级,以减少出现奇怪或意外样式的风险。

  • 有时候代码超出了你的控制——在类似这样的情况下,我们必须与之合作;我们可以尝试在可能的情况下使用低特定性选择器,或者使用!important关键字来覆盖代码。目前,我们可能需要在代码中留下注释来解释为什么选择器被设置为这样的;在一个理想的世界里,我们会尝试联系作者,看看他们是否可以更新或修改代码以消除这些问题。

  • 作为最后的手段,如果你必须进入 CSS 特定性的领域,那么尽量只采取轻柔的处理方式,而不是采取使用选择器 ID 或!important这样的锤子方法。

    我们可以尝试将一个类应用到现有的标签上,但这可能对某些人来说感觉不太对;一个替代方案是使用两个类:

    .nav .override {
    }
    .override .override {
    }
    .nav {
    }
    

    然而,关键是要避免使用超过一个额外的类!

  • 嵌套样式可能导致我们在代码中编写过于特定的选择器——一些开发者因为这一点而劝阻其使用,尽管嵌套可以帮助使我们的代码在视觉上更容易阅读和理解。而不是使用复合选择器,我们可以通过使用&符号来模拟一种命名空间的形式:

    .somestyle {
        color: darkred;
       &-so {
          color: blue;
          &-ever {
            color: green;
          }
       }
    }
    

    …这将编译成以下内容:

    .somestyle { color: darkred; }
    . somestyle-so { color: blue; }
    .somestyle-so-ever { color: green; }
    
  • 如果你的样式正在覆盖已经覆盖的样式——停止:你为什么要这样做?覆盖类或选择器元素可能是高效样式的途径,但应用第二个覆盖只会造成混淆。

我们已经看到了许多避免或减少嵌套中固有的 CSS 特定性问题的方式;然而,关键信息是,我们并非被迫必须嵌套我们的代码,而且,用前端架构师罗伊·托梅伊的话来说——嵌套的代码并不会产生糟糕的代码;糟糕的程序员才会产生糟糕的代码!

小贴士

你可以在罗伊·托梅伊的原始文章中看到更多内容:www.thesassway.com/editorial/sass-doesnt-create-bad-code-bad-coders-do

尽管有很好的理由,但我们还没有触及一种方法:这是许多刚开始使用处理器的开发者可能会第一次尝试的路线。好奇吗?这与使用转换工具有关,更具体地说,是我们如何使用它们将纯 CSS 转换为适合使用 PostCSS 编译的代码。

重新考虑我们的代码

假设这个场景,如果你愿意的话:

你接管了一个网站,并热衷于使用 PostCSS 来帮助维护你的代码。代码使用纯 CSS,所以作为转换的一步,你碰巧知道一些网站可以将纯 CSS 转换为 SASS。毕竟,PostCSS 和 SASS 代码之间有一些相似之处,所以为什么不呢?

你将结果提取到一个文本文件中,保存它,然后通过 SASS 编译过程。结果会生成一些新编译的 CSS,你将其放入服务器上的相关位置,然后 voilà!你现在有一个使用 SASS 的运行网站。一个运行网站,以及转换为 PostCSS 的完美基础……或者不是吗?

简短的回答应该是没有,但更长的回答是,这取决于你的代码。让我解释一下原因:

简单地将代码通过转换过程是不够的——当然,它将给你一段能工作的代码,但除非它非常简单,否则它很可能不会生成简洁高效的代码。为了理解我的意思,请仔细查看Tutorial5中的 CSS 样式表——特别是从第 132 行开始的.nav-panel的样式规则。

小贴士

由于空间原因,样式表太长,无法全部打印出来——我建议你用文本编辑器查看代码下载中的文件!

转换过程将没有问题处理它以生成有效的 SASS,但它看起来不会很美观——作为一个例子,试着将第 114 行到第 197 行的代码复制到css2sass.herokuapp.com/提供的转换器中。看起来不错,不是吗?肯定有改进的空间——我已经对代码做了一些修改,但我们还可以做得更多;让我们看看我们可以做些什么来改进代码。

更新我们的代码

当使用 CSS 到 SASS 转换器时,我们心中始终要牢记的一个关键点是,转换后的代码不应被视为最终文章。

不论你的代码多么简单或复杂——它应该是我们转换过程的第一步。这只是一个问题,即一旦代码通过转换器,我们有多少工作要做!作为一个例子,看看这段代码:

更新我们的代码

这是 Nikolay 编译版本的第 234 行到第 239 行的直接复制,我们将其作为我们早期演示的基础。现在快速看一下我从原始版本中调整并用于我版本中的等效代码:

更新我们的代码

注意到任何差异了吗?transform属性的供应商前缀版本已经被移除——大多数现代浏览器(当然是在过去一年到十八个月之内),应该能够处理这段代码而不需要供应商前缀。原始版本也遭受了高度 CSS 特定性的困扰——如果代码嵌套,这一点将变得更加明显!

为了改进它,我已将.nav-panel ul li作为.nav-panel ul .nav-btn的直接替代——代码相对简单,因为它不需要第二个类来识别元素进行样式化。下一步合乎逻辑的步骤是将源文件中的大嵌套块拆分;包含一个单独的大块很有吸引力,但这将牺牲可读性、维护性和性能。

我们甚至可以更进一步,考虑移除前缀.nav-panel;这不仅会使代码无限期地易于阅读,还会减少 CSS 特定性问题。当然,这种改变将取决于你的代码;这里的要点是彻底检查你的代码,并尽可能减少任何 CSS 特定性,以便你的嵌套看起来不那么糟糕!

我们还有一种替代方法可以使用,它可以消除 CSS 特定性问题——使用块元素修饰符标记(或简称BEM)。这是一种系统地使用 CSS 对元素进行样式的绝佳方法,值得花时间熟悉其工作方式。让我们深入探讨并查看一下。

转向 BEM

那么,什么是 BEM,它为什么能帮助减少或消除 CSS 特定性问题?

BEM,即块元素修饰符,帮助我们通过一种系统化的命名约定来对元素进行样式化,其结构如下:

  • .block:包含我们要更改的元素的顶层容器

  • .block__modifier:当元素的状态发生变化时分配的样式

  • .block__element:顶层容器内的元素

  • .block__element__modifier:当元素的状态发生变化时的元素的不同表示

这种编码风格背后的理念是使其易于移植和更容易维护。其基础在于,虽然标准、非 BEM CSS 更简洁,但更难推断出每条规则的作用。我们经常需要给一个元素分配多个类,这引入了 CSS 特定性的程度,并降低了 CSS 规则的可重用性。使用 BEM 允许我们将构成名称组合成一个样式类名,并消除任何关于 CSS 特定性的担忧。

如果我们使用这个概念,我们可以用它来编写如下样式规则:

.block {
  &__element {
  }
  &__modifier {
  }
}

这将编译成以下内容:

.block {}
.block__element {}
.block__modifier {}

上述代码使用的是纯 BEM 格式,但如果我们从 SASS 等处理器环境中构建 PostCSS 中的 BEM 规则,我们可以使用postcss-bem插件(可在github.com/ileri/postcss-bem获取)来使用@-rules生成我们的 BEM CSS。例如:

@component ComponentName {
  color: cyan;

  @modifier modifierName {
    color: purple;
  }

  @descendent descendentName {
    color: darkred;
  }

  @when stateName {
    color: yellow;
  }
}

在这种情况下,@component表示我们的块,@descendant表示我们的元素,而@modifier是我们的修饰符。编译后,我们的 CSS 将如下所示:

.ComponentName {
  color: cyan;
}

.ComponentName--modifierName {
  color: purple;
}

.ComponentName-descendentName {
  color: darkred;
}

.ComponentName.is-stateName {
  color: yellow;
}

使用 BEM 的好处在于,它有助于减少或甚至避免 CSS 特定性问题——尽管名称更长,但我们可以将元素和修饰符名称组合成一个类,而不是必须应用三个或更多单独的类。当然,可能会有一些情况我们需要应用第二个类,但通过仔细规划,我们应该能够将其减少到最小。

对了,我们继续前进:让我们开始编码!在接下来的几页中,我们将查看如何在简单示例中实现 BEM 样式,并看看我们如何使用 PostCSS 来编译我们的代码。

创建简单的消息框

对于我们的 BEM 演示,我们将通过 CSS 规则来展示一些简单的消息框,例如用于显示任务已完成或当某事不正确时的警告。

提示

这个演示的原版是由 Rene Spronk 制作的,可以从www.cssportal.com/blog/css-notification-boxes/获取。

这是一个简单的演示,但它完美地展示了 BEM CSS 背后的原理——继续提取Tutorial8文件夹的副本,然后运行index.html来感受我们将要制作的内容。这个版本使用标准 CSS;我们将以此为基础将其转换为使用 BEM。

让我们开始:

  1. 我们将首先从本书附带的代码下载中提取Tutorial9文件夹的副本——将其放入我们的项目区域。

  2. 接下来,在一个新文件中,从第 1 行开始添加以下 CSS 语句,并在每个语句之间留一个空行——它们应该是相当自解释的,但我们将依次通过每个块。

  3. 我们从每个对话框的核心样式开始——这是每个对话框的基础样式:

    .dlgBox {
      border-radius: 0.625rem;
      padding: 0.625rem 0.625rem 0.625rem 2.375rem;
      margin: 0.625rem;
      width: 14.5rem
    }
    
  4. 接下来是每个<span>元素的简单样式——这会将每个对话框的引导标题转换为大写并设置为粗体文本:

    span { font-weight: bold;text-transform: uppercase; }
    
  5. 我们现在需要添加我们的块元素——这是构成我们样式的起始行:

    @component content {
    
  6. 接下来是样式规则中的 Element 部分。这些规则需要作为嵌套(即缩进)规则立即添加到下面——使用 PostCSS 插件,我们将它添加为@component@descendent

       @descendent alert {
         font-family: Tahoma, Geneva, Arial, sans-serif;
         font-size: 0.6875rem;
         color: #555;
         border-radius: 0.625rem; 
       }
    
  7. 接下来是我们的第一个状态消息——我们首先开始为错误消息添加样式;主要规则添加了一个错误图标并设置了边框样式。:hover伪元素在鼠标悬停在框上时减少不透明度:

       @modifier error {
         background: #ffecec url("../img/error.png") no-repeat 0.625rem 50%;
         border: 0.0625rem solid #f5aca6; 
       }
    
          @modifier error:hover { opacity: 0.8; }
    
  8. 这迅速被成功消息的样式所跟随:

       @modifier success {
         background: #e9ffd9 url("../img/success.png") no-repeat 0.625rem 50%;
         border: 0.0625rem solid #a6ca8a; 
       }
    
       @modifier success:hover { opacity: 0.8; }
    
  9. 我们不能忘记必选的警告消息,所以这里是该状态样式的规则:

       @modifier warning {
         background: #fff8c4 url("../img/warning.png") no-repeat 0.625rem 50%;
         border: 0.0625rem solid #f2c779; 
          }
    
          @modifier warning:hover { opacity: 0.8; }
    
  10. 最后但同样重要的是,这是最后一个,即通知;它包含了 BEM 嵌套的闭合括号:

          @modifier notice {
            background: #e3f7fc url("../img/info.png") no-repeat 0.625rem 50%;
            border: 0.0625rem solid #8ed9f6; 
          }
    
       @modifier notice:hover { opacity: 0.8; }
    }
    
  11. 将文件保存为style.scss到我们顶级项目区域的src文件夹中(而不是Tutorial8文件夹中!)。

我们这个简单的演示在样式方面不会让世界变得光明;如果我们现在预览它,结果当然不会很好看;让我们通过在 PostCSS 中设置编译和 linting 任务来修复它。

注意

如果你使用 SASS,那么你可以在 GitHub 上看到适合该处理器的代码版本——代码可在:gist.github.com/alibby251/45eab822a6a619467279找到。注意当你比较编译版本和我们在下一个练习中将得到的版本时,结果是多么相似!

编译和 linting 我们的代码

我们的代码已经就位,但盒子看起来并不特别吸引人——大多数样式仍然使用 PostCSS @-rules编写。我们可以通过编译代码来修复这个问题,所以让我们深入了解一下如何安装 BEM 支持。

安装 BEM 支持

在 PostCSS 中设置 BEM 支持非常简单——我们可以使用两个插件来编译和检查我们的代码。我们需要为此任务的两个插件是postcss-bem(可在github.com/ileri/postcss-bem获取),以及postcss-bem-linter(可在github.com/postcss/postcss-bem-linter获取)。这两个插件都可以通过 Node.js 使用相同的过程安装。

希望到现在为止,这个过程已经变得熟悉了,所以我们不再拖延,现在就开始吧:

  1. 我们将首先启动一个 Node.js 命令提示符,并导航到我们的工作文件夹。

  2. 在命令提示符下,输入以下命令然后按Enter

    npm install --save-dev postcss-bem
    
    
  3. Node.js 将安装所需的每个元素;如果一切顺利,我们应该看到这个结果,以表明安装成功:安装 BEM 支持

  4. 对于postcss-bem-linter重复相同的步骤,使用以下命令:

    npm install --save-dev postcss-bem-linter
    
    

    安装 BEM 支持

  5. 保持命令提示符会话开启,但最小化。我们很快就会再次使用它!

现在插件已经安装好了,我们可以继续添加对 gulp 任务文件的支持,并开始解析我们的代码:

  1. 首先,请先删除我们项目区域根目录下的现有gulpfile.js文件。

  2. 在一个新文件中,添加以下行并将其保存为gulpfile.js,位于我们项目区域的根目录。我们首先设置一些变量,这些变量调用每个插件:

    var gulp = require('gulp');
    var postcss = require('gulp-postcss');
    var bem = require('postcss-bem');
    var bemLinter = require('postcss-bem-linter');
    var reporter = require('postcss-reporter');
    
  3. 文件中的第一个任务检查代码与 BEM 标准的兼容性,并在屏幕上显示任何错误:

    gulp.task('lint', function() {
      return gulp.src('dest/*.css')
        .pipe(postcss([
          bemLinter({ preset: 'bem' }),
          reporter({ clearMessages: true })
        ]))
        .pipe(gulp.dest('dest/'));
    });
    
  4. 文件中的第二个任务将 BEM 代码编译为有效的 CSS:

    gulp.task('bem', function() {
      return gulp.src("src/*.css")
        .pipe(postcss([bem({
          style: 'bem',
          separators: { descendent: '__' }
        })]))
        .pipe(gulp.dest('dest/'));
    });
    
  5. 这个任务是在我们从命令行运行 gulp 时默认调用的任务;它依次调用每个任务:

    gulp.task('default', ['bem', 'lint']);
    
  6. 我们在gulpfile.js中添加一个监视功能,以便在代码有任何更改时启动并编译我们的代码:

    var watcher = gulp.watch('src/*.css', ['default']);
    watcher.on('change', function(event) {
      console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
    });
    
  7. 我们还将替换package.json文件——将这些行添加到新文件中,并将其保存到项目区域的根目录。这些行简单地告诉 gulp 在编译代码时使用我们插件的哪个版本:

    {
      "name": "postcss",
      "version": "1.0.0",
      "description": "Configuration file for PostCSS",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "Alex Libby",
      "license": "ISC",
      "devDependencies": {
        "gulp": "³.9.0",
        "gulp-postcss": "⁶.0.0",
        "postcss-bem-linter": "².0.0",
        "postcss-reporter": "¹.3.0"
      }
    }
    
  8. 从本书附带的代码下载中,请从Tutorial9下的css – completed version文件夹中提取style.css的副本——将其保存到我们项目区域的src文件夹下。

  9. 回到我们之前的 Node.js 命令提示符会话,然后在提示符下,输入以下命令并按Enter

    gulp
    
    
  10. 如果一切顺利,代码将被检查,结果将在屏幕上显示。你可能会看到错误出现,例如这个屏幕截图所示。如果出现,现在可以忽略它们(我们将在稍后更详细地探讨这个问题):安装 BEM 支持

  11. 好吧,把dest文件夹的内容复制到Tutorial9下的css文件夹里——如果一切顺利,我们在浏览器中预览结果时应该能看到类似这样的截图:安装 BEM 支持

我们简单的演示展示了我们可以用作更复杂基础的一些有用的消息框;它完美地说明了我们如何使用 BEM 来样式化我们的代码,同时避免 CSS 特定性问题。我们在整个练习中介绍了一些有用的技术,所以让我们花点时间更详细地探索它们。

更详细地探索我们的更改

花时间真正熟悉 BEM 样式原则是值得的。这是那些不花时间可能会轻易让你放弃使用这种技术的领域之一;原则容易理解,但实施起来可能需要时间!让我们更详细地看看我们的代码。

在使用 PostCSS 插件时,BEM 的关键原则是嵌套——在这个例子中,我们创建核心组件内容,这导致我们的样式表顶部出现一个空的样式规则。缩进到下一级的是我们的@descendant——这表示我们的消息框被样式化为警告。然后我们使用多个@modifiers来样式化每种类型的警告,如成功、警告、错误或通知(即信息)。这包括一个单独的样式规则来覆盖我们代码中使用的每个 hover 伪元素的实例。

这对我们意味着什么?这意味着我们不仅要将每个元素(例如,消息框)视为一个单一实体,并简单地应用很多类;相反,我们应该考虑每个元素的组成部分,并为每个部分应用一个单独的类。等等,这不意味着我们仍然在使用三个类(就像我们在这里可能做的那样)吗?

小贴士

要了解更多关于 BEM 命名约定的信息,请查看在en.bem.info/tools/bem/bem-naming/发布的有用文章。

嗯,答案是是,也不是:这里的技巧是 PostCSS 会将每个嵌套样式组合成有效的 CSS;例如,这个提取(改编自我们的演示):

@component content {
  @descendent alert {
    font-family: Tahoma, Geneva, Arial, sans-serif;
    font-size: 0.6875rem;
    color: #555;
    border-radius: 0.625rem; 

    @modifier error {
      background: #ffecec url("../img/error.png") no-repeat 0.625rem 50%;
      border: 0.0625rem solid #f5aca6; 
    }
  }
}

编译后,这将显示为以下 CSS:

.content {}
.content__alert {
    font-family: Tahoma, Geneva, Arial, sans-serif;
    font-size: 0.6875rem;
    color: #555;
    border-radius: 0.625rem;
}
.content__alert_error {
    background: #ffecec url("../img/error.png") no-repeat 0.625rem 50%;
    border: 0.0625rem solid #f5aca6;
}

眼尖的你们中的一些人可能已经注意到,当我们的代码编译时仍然会生成错误:

更详细地探索我们的更改

看到错误总是让人不安,但它们有合理的理由。我们可以安全地忽略两个弃用警告(这些应该在未来的版本中修复),但这两个错误更令人关注。

修复我们的错误

这两个错误是由postcss-bem-linter引起的,它没有将这两种样式识别为有效的 BEM 符号。这引发了一个问题:我们能否修改我们的代码来消除这些问题?

为了回答这个问题,我们需要权衡受影响的代码量与修改它所需的时间和精力。在我们的演示中,受影响的代码非常少;要解决这个问题,我们需要修改.dlgBoxspan样式为等效的 BEM 命名。

这值得付出努力吗?在我们的这个小演示中,答案很可能是“不”,对于更大的演示,我们可能会考虑改变这两种样式。相反,我们可以在第 48 行添加一个简单的指令,如下所示:

修复我们的错误

当代码重新编译时,错误会被移除:

修复我们的错误

纯粹主义者可能会说这是作弊。确实,我们的代码在技术上仍然不是全部的 BEM。然而,尽管如此,决定权在每位开发者手中;可能有一些元素必须保持为标准 CSS,我们无法转换。在这种情况下,使用 PostCSS 导入插件导入这些样式可能是明智的——我们将在第十章“构建自定义处理器”中进一步探讨使用这种方法,第十章。

值得注意的是,postcss-bem-linter 插件本身不会显示任何错误的结果——为了做到这一点,我们需要使用像postcss-reporter(可在github.com/postcss/postcss-reporter找到,适用于命令行)或postcss-browser-reporter(来自github.com/postcss/postcss-browser-reporter)这样的插件,后者在浏览器窗口中显示内容)。两者都有许多值得调查的选项,可以帮助我们微调通过 PostCSS 处理代码时显示的内容。

摘要

几年来,开发者不得不经常编写重复选择器全部或部分代码的代码——一个完美的例子是样式化列表或导航项。编写这么多额外代码真的很痛苦;相反,我们可以使用嵌套原则来帮助移除一些这些代码。我们在本章中已经介绍了 PostCSS 中关于嵌套的多种技术,所以让我们花点时间回顾一下我们学到了什么。

我们从嵌套的介绍开始,帮助我们跟上进度,然后转向使用postcss-nesting插件在 PostCSS 中创建嵌套样式。然后我们继续创建我们的嵌套演示。我们开始准备一个纯 CSS 版本,然后查看将其转换为现有的处理器,如 SASS。

接下来,我们使用postcss-nesting插件转换我们的代码,然后探讨了与嵌套相关的一些陷阱,以及我们可以使用的技巧和窍门来减少 CSS 特定性,这是与嵌套相关的一个关键问题。

我们在章节的最后部分探讨了 BEM(块、元素、修饰符)及其在 PostCSS 中的应用。我们介绍了这一方法的基本原则,并在一个简单示例中应用了它们。我们还了解到,这种方法并不总是适用于所有情况;对于适用的情况,我们简要地探讨了如何设置 PostCSS 来自动检查我们的 BEM 代码。

呼,这真是一场真正的快速浏览!不过,请系好你的帽子,因为旅程不会就此结束:在下一章中,我们将探讨编写媒体查询的方法,以及 PostCSS 如何帮助将这些查询编译成有效的 CSS。

第四章:构建媒体查询

单一设备浏览在线网站的日子已经一去不复返了:响应式网站将在各种设备上运行,从智能手机到数字电视和笔记本电脑。使网站响应式的一个关键要素是使用媒体查询。在本章中,我们将探讨如何使用 PostCSS 创建它们,看看它们与 Less 和 SASS 等工具相比如何,以及 PostCSS 的使用如何使方法比标准预处理器更加灵活。本章将涵盖以下技术主题:

  • 重新审视媒体查询

  • 使用 PostCSS 掌握媒体查询的基础

  • 添加响应式支持

  • 优化媒体查询

  • 为旧浏览器添加支持

  • 进一步探索——在 CSS4 媒体查询中探索悬停功能

让我们开始吧…!

重新审视媒体查询

如果你花时间在不同的设备上查看网站,那么在样式表中看到媒体查询几乎不会令人惊讶——它们是响应式设计的基础,并且声明允许我们根据可用的屏幕宽度控制屏幕上显示的内容。

媒体查询背后的原理很简单。简而言之,我们必须定义设备或媒体,以及规则(或断点)开始应用或停止应用的分辨率(或宽度)。以下是一个简单的例子:

@media only screen and (max-width: 768px) {
  /* CSS Styles */
  ...
}

在文档中使用的任何样式都仅在屏幕上查看时应用,并且我们的可用屏幕空间为768px或更小。这是一个简单的例子,它们可以像所需的那样简单或复杂;这取决于我们作为开发者来确定我们的内容在哪里断裂,并构建一个合适的断点来管理变化。

注意

为了感受一些最近可能的媒体查询,请查看 Chris Coyier 的这篇帖子,他提供了针对笔记本电脑、PC 甚至可穿戴设备的查询!列表可在css-tricks.com/snippets/css/media-queries-for-standard-devices/找到。

好的,让我们开始吧:PostCSS 使得管理文本和图像的查询变得简单;我们将从查看如何处理图像开始我们的旅程。

探索 PostCSS 中的自定义媒体查询

转向使用 PostCSS 非常简单,我们可以使用postcss-custom-media插件来完成此目的,可在github.com/postcss/postcss-custom-media找到。

插件易于安装,它遵循我们之前介绍的所有其他插件的原则,所以无需多言,我们现在就把它解决掉:

  1. 启动 Node.js 命令提示符,然后导航到工作目录。

  2. 在提示符中输入以下命令,然后按Enter键:

    npm install --save-dev postcss-custom-media
    
    
  3. 目前请保持命令提示符打开,我们将在接下来的几个步骤中使用它。

    安装插件后,我们现在可以使用它了,在我们开始转换之前的演示之前,让我们通过一个简单的例子来操作,这样你就可以看到它的实际效果:

  4. 在一个新文件中,添加以下代码,将其保存为项目区域根目录下的src文件夹中的style.css

    @custom-media --apple-watch (max-device-width: 42mm) and (min-device-width: 38mm);
    
    @media (--apple-watch) {
      h2 {
        font-size: 0.8rem;
      }
    }
    
  5. 从项目区域的根目录中删除现有的gulpfile.js文件。

  6. 在一个新文件中,添加以下代码,这将形成一个新的gulpfile.js文件;将其保存到项目区域的根目录:

    var gulp = require('gulp');
    var postcss = require('gulp-postcss');
    var customMedia = require('postcss-custom-media');
    
    gulp.task('default', function() {
        return gulp.src('src/*.css')
        .pipe(postcss([ customMedia() ]))
        .pipe(gulp.dest('dest/'));
    });
    
    var watcher = gulp.watch('src/*.css', ['default']);
    watcher.on('change', function(event) {
      console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
    });
    
  7. 返回我们之前打开的命令提示符会话,然后在命令提示符中输入gulp,然后按Enter键。

  8. 如果一切顺利,我们应该在项目区域的dest文件夹中打开编译后的style.css时看到此代码:

    @media (max-device-width: 42mm) and (min-device-width: 38mm) {
      h2 {
        font-size: 0.8rem;
      }
    }
    

信不信由你,这就是使用插件所需的所有内容;让我们花点时间考虑一下通过这个演示我们涵盖了什么。

初看之下,你可能认为这个插件实际上并没有做什么来帮助我们——这是一个合理的观点,但使用这个插件有一个关键的好处。我们可以将媒体断点分离成单独的变量声明,并将这些存储在样式表的顶部。这意味着如果我们需要更新特定的断点,我们只需要做一次。然后在编译阶段,我们的代码会自动更新。

考虑到这一点,让我们开始一个演示;我们将处理之前平铺滚动的 CSS 版本,并将其转换为使用 PostCSS。

从纯 CSS 开始

在接下来的几页中,我们将使用一种相对较新的技术作为我们演示的基础——平铺滚动。以防你躲在岩洞里,平铺滚动是一个单页应用程序,它允许我们在滚动内容的同时显示一系列固定在内容后面的图像:

从纯 CSS 开始

我们将使用由 Nick Salloum 创建的演示,该演示可在callmenick.com/_development/simple-parallax-effect/找到(我已经简化了一些 CSS 样式,移除了供应商前缀,并减少了示例中调用的单独文件的数量)。我们将从演示的纯 CSS 版本开始——请将Tutorial11的副本提取到我们的项目区域。尝试运行index.html;如果一切顺利,我们应该看到类似于本节开头截图的内容。

当使用得当的时候,这是一个很棒的效果,我们的兴趣在于 CSS 文件的最后部分,大约在第 133 行;这一部分包含我们将要在下一个演示中转换的媒体查询。

将我们的演示修改为使用 PostCSS

如果正确使用媒体查询,这可以打开一个充满可能性的世界;我们可以调整我们的样式表以适应从 iPhone 到打印机等各种设备。在我们的演示中,我们使用了一些来调整在显示宽度大于 600px960px 的网站上内容的显示方式;将这些调整到在 PostCSS 中工作非常简单。

注意

CSS3 媒体查询网站列出了大量不同类型的查询;如果您在目标 PC 或设备上查看该网站,它将显示该查询是否在该设备上受支持。完整列表可在www.cssmediaqueries.com找到。

我们只需要在样式表中做一些更改即可切换到使用 PostCSS,让我们开始:

让我们开始进行更改:

  1. 我们将首先将 Tutorial11 文件夹中的 style.css 文件复制到我们的项目区域的 src 文件夹中。

  2. 我们需要编辑文件,以将我们的媒体查询转换为使用 PostCSS 插件——请在这些行的 4 和 5 行处添加以下两行:

    @custom-media --small-viewport all and (min-width: 600px);
    @custom-media --medium-viewport all and (min-width: 960px);
    
  3. 在下面,将第 161 行和第 182 行替换为以下代码:

    @media (--small-viewport) {
    
  4. 在第 200 行,将此行替换为以下代码:

    @media (--medium-viewport) {
    
  5. 保存文件——接下来,请将当前的 gulpfile.js 文件替换为 Tutorial12 文件夹根目录中的版本。它具有相同的初始 PostCSS 任务,但已被重命名并扩展了我们在前面的章节中已经使用过的附加任务。

  6. 接下来,请将同一位置的 package.json 文件副本保存到我们的项目区域的根目录——它包含此演示中使用的插件的更新链接。

    启动 Node.js 命令提示符窗口,然后更改工作目录到我们的项目区域。在提示符下,输入 gulp 然后按 Enter

  7. 如果一切顺利,我们应该在 dest 文件夹中看到一个编译后的 CSS 文件——请将其复制到 Tutorial12 文件夹的 css 文件夹中。

  8. 在我们的项目区域中运行 index.html 以预览结果——如果一切顺利,我们不应该看到任何不同,但在源代码中快速检查应该显示我们正在使用代码的压缩版本:调整我们的演示以使用 PostCSS

值得注意的是,在我们的演示中,我们使用了媒体查询的典型格式:例如,我们可以扩展或修改样式表以在 Galaxy 平板等手持设备上工作;同样的原则适用,但显然需要使用不同的宽度值!有关使用值的详细信息,请查看cssmediaqueries.com,它列出了用于最新设备的查询列表。

如果我们想要拓展可能的边界,我们可以考虑几个选项:

  • postcss-media-variables:此插件(可在github.com/WolfgangKluge/postcss-media-variables找到)以相同的方式工作,但它允许我们在媒体查询中使用变量。使用此插件的好处是我们可以将宽度值分离到一个中央的:root规则中;我们可以使用一个固定的值,但基于此值计算出其他值:

    /* input */
    :root {
        --min-width: 1000px;
        --smallscreen: 480px;
    }
    @media (min-width: var(--min-width)) {}
    @media (max-width: calc(var(--min-width) - 1px)) {}
    
    @custom-media --small-device (max-width: var(--smallscreen));
    @media (--small-device) {}
    /* output */
    @media (min-width: 1000px) {}
    @media (max-width: 999px) {}
    @media (max-width: 480px) {}
    

    缺点是,这被认为是非标准的,插件必须调用两次,并且如果使用其他插件,必须按照一定的顺序调用——这意味着它可能只适合特定的情况!

  • postcss-quantity-queries:此插件(可在github.com/pascalduez/postcss-quantity-queries找到)基于 Daniel Guillan 的 SASS 数量查询混入。这使我们能够使用如下规则:

    ul > li:at-least(4) { color: rebeccapurple; }
    

    这将编译成如下:

    ul > li:nth-last-child(n+4),
    ul > li:nth-last-child(n+4) ~ li {
      color: rebeccapurple;
    }
    

这是我们可以与该插件一起使用的四个伪选择器扩展之一,这是为导航条目等元素进行样式化的完美方式,或者如果我们想要一个带有不同样式的偶数和奇数项的编号列表。

注意

对于一篇关于在 CSS 中使用数量查询的有用参考文章,请访问 Heydon Pickering 在alistapart.com/article/quantity-queries-for-css上的帖子。

现在让我们改变方向,专注于我们的内容。到目前为止,我们一直专注于页面布局,但我们可以通过使图片真正响应式来更进一步;让我们深入探讨并看看。

制作响应式图片

使任何网站响应式的一个关键要素当然是图片——毕竟,我们总是可以构建一个没有图片的网站,但这样真的有效吗?

当然,人们总是可以使用数据统一资源标识符URI)将图片转换为 CSS 等效物,但这会使我们的样式表急剧膨胀,以至于变得难以管理。现实是,我们必须以某种形式拥有图片——如果我们想让它们表现良好,我们显然需要确保它们根据可用的屏幕空间扩展或收缩大小。

适应响应式布局的最简单方法是为图片设置max-width值为100%,同时设置height: autodisplay: block,并移除任何定义该图片元素固定高度或宽度的属性。我们可以手动进行更改,但这很耗时;相反,让我们看看一个 PostCSS 插件,它允许我们在编译时通过为每个图片添加一行代码来设置这三个值。

使用 PostCSS 制作响应式图片

使用 PostCSS 为网站添加响应式功能很简单;我们将主要根据您的需求来决定如何使图片响应式,但有两个关键插件需要关注:postcss-responsive-images(可在 github.com/azat-io/postcss-responsive-images 获取),以及 postcss-at2x(可在 github.com/simonsmith/postcss-at2x 获取)。

我们将在稍后介绍 postcss-at2x 插件的使用,但现在,让我们看看如何使用 postcss-re sponsive-images 插件。

实现图片的响应式功能

使我们的图片响应式只需要在基于图片的规则中添加一行代码;让我们深入探讨,并将此功能添加到本书附带的代码下载中的 Tutorial13 文件夹的副本中:

  1. 我们将像往常一样,首先安装插件——为此,打开 Node.js 命令提示符,然后运行此截图所示的命令:为图片实现响应式功能

  2. 我们将首先从本书附带的代码下载中提取 Tutorial13 文件夹的副本,然后将其保存到我们的项目区域。

  3. 打开 Tutorial13 文件夹中 css 文件夹内的 style.css,然后删除此规则:

    img { 
      width: 584px; 
      height: 389px;
    }
    
  4. 在其位置,添加以下行:

    #retina img { image-size: responsive; }
    
  5. 保存文件,然后将其复制到项目区域下方的 src 文件夹中(不在 Tutorial 文件夹内!)。

  6. 对于这个练习,我们将替换 Gulp 任务文件——请将此代码添加到新文件中,并将其保存为 gulpfile.js,位于我们的项目区域根目录下:

    var gulp = require('gulp');
    var postcss = require('gulp-postcss');
    var responsiveimages = require('postcss-responsive-images');
    
    gulp.task('default', function() {
        return gulp.src('src/*.css')
        .pipe(postcss([ responsiveimages ]))
        .pipe(gulp.dest('dest/'));
    });
    
    var watcher = gulp.watch('src/*.css', ['default']);
    watcher.on('change', function(event) {
      console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
    });
    

    注意,我们在这个 gulp 文件中只专注于使我们的图片响应式,这就是为什么它比我们迄今为止使用的先前版本要短得多。

  7. 打开 Node.js 命令提示符,然后更改工作目录到我们的项目区域——在提示符中,输入 gulp 然后按 Enter

  8. Node 将编译我们的代码——如果一切顺利,#retina img 的编译代码将看起来像这样:为图片实现响应式功能

  9. dest 文件夹中的编译 CSS 文件复制到 Tutorial13 文件夹的 css 文件夹中。

  10. 好吧,预览一下结果——尝试调整浏览器大小;如果一切顺利,图片将自动为我们调整大小。

虽然安装和使用此插件很容易,但它最好是在我们的 HTML 代码中直接引用图片,而不是通过在 CSS 中使用 background:content: url(…) 属性。

这对我们意味着什么?它有点限制性,因为在我们中间的纯粹主义者可能更喜欢将资产属性分离到 CSS 样式表中,就像开源软件那样,尽管这是一个注定会在未来得到解决的限制!

留意观察的你们中的一些人会发现图像呈现显然需要进一步的工作——例如,当窗口大小调整时,回形针没有重新定位,我们需要设置一个最小宽度,以便在调整大小时图像周围有一些空白空间:

实现图像的响应式功能

不论是哪种呈现方式,关键原则保持不变,移除固定图像大小,并用max-width100%替换,是使图像响应式的好步骤。

然而,要获得真正的响应式图像,我们理想情况下会使用新的 HTML5 <picture> 标签——问题是,PostCSS 还没有插件来实现这些标签!

注意

如果你感兴趣于一些更通用的图像响应式制作技术(并且超出 PostCSS 的世界),那么请查看jakearchibald.com/2015/anatomy-of-responsive-images/

在 PostCSS 中处理<picture>标签的任何可用功能缺失的情况下,我们可以采取更传统的路线,并使用媒体查询来帮助在不同图像之间切换,这取决于可用的屏幕空间。

我们可以更进一步,如果设备支持的话,甚至可以切换到更高分辨率的图像——我当然是在想苹果的 iPad 或 iPhone,它们支持视网膜图像。我们可以轻松地在使用 PostCSS 时使用这种格式;为此,我们需要使用 Simon Smith 的postcss-at2x插件,该插件可在github.com/simonsmith/postcss-at2x找到。我感觉有几个演示即将到来,所以不再多言,让我们去探索使用这个插件吧。

添加对视网膜图像的支持

视网膜图像,这个术语是由苹果市场营销团队提出的,它们在同一空间内包含的像素数量是标准图像的两倍。这使得我们能够自动切换到更高品质(或分辨率)的图像,前提是我们使用的设备支持它们的用途。

这可能只是一个 iPhone,或者更实质性的 iPad——苹果的市场影响力意味着它们可能是人们拥有的最受欢迎的便携式设备中的两个!但我在这里跑题了…

在技术层面上,我们有两条路线可以用来添加视网膜图像,在我们更详细地探讨这些之前,让我们先回顾一下基础知识:

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { 
  #retina img {
    content: url("../img/mothorchid@2x.png");
  } 
}

这段代码是从Tutorial15文件夹中的 CSS 样式表中提取的,该文件夹包含在这本书的代码下载中;尝试在浏览器中预览index.html

小贴士

为了获得最佳效果,强烈建议您使用 Google Chrome——这是一个模拟低分辨率和高分辨率图像切换效果的优秀浏览器。

显示的图像显示的是文本8 位版本——要切换,请尝试这样做:

  1. Shift + Ctrl + I显示开发者工具栏。

  2. 点击手机图标以启用响应式设计模式添加对视网膜图像的支持

然后,我们可以通过下拉菜单在不同设备之间切换——尝试切换到苹果 iPad;你可能需要按F5键刷新显示。如果一切顺利,它将在 8 位和 24 位版本的兰花图像之间切换。

进行下一步

这一切都很正常,但我们显然没有在这里使用 PostCSS——我们的选项是什么?嗯,我们有两种可以使用:customMedia()postcss-at2x插件。我们已经在探索 PostCSS 中的自定义媒体查询部分介绍了使用customMedia的基本知识;对于这个,我们会使用一个变量,例如这个:

/* media query for hi-resolution image support */
@custom-media --hi-resolution screen and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi);

这将与如下查询相结合:

@media (--hi-resolution) { 
  #retina img {
    content: url("../img/mothorchid@2x.png");
  } 
}

当编译并在 Google Chrome 中运行(以利用其响应式设计工具)时,我们可以看到图像从 8 位:

进行下一步

…到一个 24 位的图像版本:

进行下一步

查看活动样式规则视图,会自动显示媒体查询更新:

进行下一步

这很好,但仍然是一种耗时的人工方法——相反,我们可以使用更快捷的途径来达到类似的效果。使用postcss-at2x的替代途径是一个更简单的选项——我们不需要确定使用什么分辨率比率,我们只需在我们的样式规则中添加at-2x这个术语:

#retina img { background: url("../img/mothorchid.png") at-2x; }

这会自动编译,为我们生成样式表中的相对分辨率语句。当与 iPad 和其他支持高分辨率图像的设备一起工作时,这是一个有用的技巧。

提示

在开始此演示之前,确保项目区域的根目录下的srcdest文件夹中没有文件,否则你可能会在编译过程中发现它们有一些不期望的效果!

让我们深入探讨并更详细地查看这一点。

  1. 我们像往常一样开始安装插件——打开 Node.js 命令提示符,然后更改工作目录到我们的项目区域。

  2. 在提示符中,输入此截图所示的命令,在每个命令后按Enter键:进行下一步

    保持窗口在手边,我们将在接下来的几个步骤中需要它!

  3. 现在,让我们设置我们的标记,从本书附带的代码下载中提取Tutorial17文件夹的副本,并将其保存到我们的项目区域。

  4. 从这个文件夹中提取gulp文件的副本,并使用它替换项目区域根目录下现有的文件。

  5. Tutorial17文件夹中提取style – pre-compile.css的副本,然后将其复制到项目区域的根目录下的src文件夹中。将其重命名为style.css

  6. 切换回我们之前打开的 Node.js 命令提示符窗口——在提示符中输入gulp然后按Enter键。

  7. PostCSS 将编译我们的代码——如果一切顺利,我们应该在dest文件夹内的编译文件中看到类似以下内容的提取:

    #retina img {
      padding: 4px;
      border: solid 1px #bbb;
      background: #fff;
      box-shadow: 0 1px 2px rgba(0,0,0,.2);
      content: url("../img/mothorchid.png"); 
    }
    …
    @media screen and (-webkit-min-device-pixel-ratio: 2), (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { 
      #retina img {
        content: url("../img/mothorchid@2x.png");
      } 
    }
    
  8. dest 文件夹的内容复制到 Tutorial17 文件夹内的 css 文件夹中。

  9. 尝试预览演示——如果一切顺利,我们应该看到之前的花朵,并强制 Chrome 显示高分辨率版本,就像我们在之前的演示中所做的那样。

这个插件的好处是它为我们处理创建媒体查询;我们只需要将 at2x 标签添加到任何我们希望在浏览器中显示高分辨率版本的图像上。我们总是存在这样的风险,即我们可能会产生不是 100%优化的查询(例如,将相同的断点组合到一个块中等等);我们将在本章末尾探索一些选项,以帮助我们的查询高效地工作。

作为旁白,一个经常被遗忘的、处理高分辨率图像的更简洁选项是使用 image-set();它以类似的方式执行,为支持高分辨率图像的设备提供不同版本。PostCSS 提供了一个回退选项,形式为 postcss-image-set(可在 github.com/alex499/postcss-image-set 获取),它设置了一个基本图像,该图像将在不支持在样式表中使用 image- set() 的浏览器中工作。

探索其他媒体可能性

因此,我们已经使用媒体查询覆盖了使内容响应式的一些关键主题;当使用 PostCSS 时这意味着什么?简单的答案是它打开了一个可能性的世界——如果你的网站需要使用媒体查询,那么我们很可能可以使用 PostCSS 将我们的查询编译成有效的 CSS 规则。为了激发你的兴趣,这里有一些可以考虑的选项:

  • 使用来自 www.bxslider.combxSlider 插件创建一个响应式滑块。诚然,它使用 jQuery 在每个幻灯片之间移动,但谁能说最终不能将其转换为全 CSS 选项?

  • 使用响应式图像精灵怎么样?这个的经典用途是在电子商务购物车上的信用卡符号,如果我们稍加注意,甚至可以使图像适应显示高分辨率版本,如果使用的设备支持的话。如果你想尝试一下,可以查看来自 github.com/2createStudio/postcss-sprites 的 postcss-sprites 插件。

好的,我们已经介绍了使用 PostCSS 使图像响应式的方法,但关于文本呢?如果内容调整大小时文本不能正确流动,页面看起来就不会很好。幸运的是,我们可以使用 Sean King 的 postcss-responsive-type 插件将类似的原则应用于文本,让我们看看它的实际应用。

添加响应式文本支持

在 PostCSS 中使文本具有响应性的过程与我们已经使用的 postcss-responsive-images 插件有相似之处,在这两种情况下,我们只需要添加一个简单的属性来使我们的内容具有响应性。

对于文本处理,我们需要使用的插件是 Sean King 开发的 PostCSS-responsive-type 插件(可在github.com/seaneking/postcss-responsive-type找到);添加字体大小,使样式表中的规则具有响应性,就足以开始使用了。当然,我们几乎肯定想要指定自己的规则;例如,我们可以使用如下代码:

html {
  font-size: responsive 12px 21px; /* min-size, max-size */
  font-range: 420px 1280px; /* range of viewport widths */
}

这将编译成两个媒体查询——一个在 480px,另一个在 1280px;前者设置文本大小为 12px,后者设置字体大小为 21px。无需多言,让我们开始使用这个插件吧:

  1. 启动 Node.js 命令行并更改工作目录到项目区域。

  2. 输入此截图所示的命令,然后按 Enter添加响应式文本支持

到目前为止,插件已安装——我们可以开始使用它了:

  1. 首先,从本书附带的代码下载中提取 Tutorial18 文件夹的副本;将其保存到我们项目文件夹的根目录。

  2. 在一个新文件中,添加以下代码——这包含了一些简单的字体样式用于我们的演示;将其保存为 style.css 并放置在我们项目区域的 src 文件夹中:

    @font-face {
      font-family: 'robotoregular';
      src: url('Roboto-Regular-webfont.eot');
      src: url('Roboto-Regular-webfont.eot?#iefix') format('embedded-opentype'),
           url('Roboto-Regular-webfont.woff') format('woff'),
           url('Roboto-Regular-webfont.ttf') format('truetype'),
           url('Roboto-Regular-webfont.svg#robotoregular') format('svg');
      font-weight: normal;
      font-style: normal;
    }
    
    body {
      font-family: "robotoregular", sans-serif;
      font-size: responsive 12px 21px; 
      font-range: 420px 1280px; 
    }
    
  3. 接下来,打开我们项目区域根目录下的 gulpfile.js 文件。

  4. 注意如何添加对 postcss-responsive-type 插件的引用,如下所示:

    var at2x = require('postcss-at2x');
    var responsivetype = require('postcss-responsive-type');
    
  5. autoprefixer 任务也已更新——它引用了 postcss-responsive-type 插件,使用文件顶部声明的变量:

    gulp.task('autoprefixer', function() {
      return gulp.src('src/*.css')
      .pipe(postcss([at2x(), responsivetype(), autoprefixer]))
      .pipe(gulp.dest('dest/'));
    });
    
  6. 现在,我们可以从 Node.js 命令提示符编译代码,更改工作目录到项目区域,并运行以下命令:

    gulp
    
    
  7. 一旦代码编译完成,将 dest 文件夹的内容复制到 Tutorial18 文件夹的 css 文件夹中;如果一切顺利,我们在浏览器中预览结果时应看到如下内容:添加响应式文本支持

尝试调整窗口大小,使其变大或变小——你应该会注意到文本大小会根据可用屏幕空间的大小而增加或减少。然后我们可以以此为基础添加图片;如果我们应用了 postcss-responsive-imagespostcss-responsive-type 插件,我们可以将其作为为我们的网站添加响应式功能的基础。

虽然我们一直在代码中使用像素值。历史惯例推荐使用em(或者更好的rem)值,因为这些值比标准像素值缩放得更好。然而,一些开发者现在认为这种惯例不再有效;在某些情况下,应该使用像素emrem值。决定使用哪个单位值以及何时使用它取决于我们!

注意

关于使用像素值与 rem 值优点的良好讨论,请参阅 Gion Kunz 在mindtheshift.wordpress.com/2015/04/02/r-i-p-rem-viva-css-reference-pixel/上的这篇文章。

在讨论使用媒体查询时,我们可以忽略一些可能的情况,但有两个关键主题我们应该探讨——优化媒体查询以及我们如何为旧浏览器提供某种形式的支持。我们将从优化查询开始——PostCSS 提供了一些有用的插件来帮助我们维护代码。

优化媒体查询

在本章中,我们探讨了使用 PostCSS 来编译我们的媒体查询;虽然我们在创建内容方面有很多选择,但我们应该注意我们创建的内容,以确保我们不会创建一个减慢网站速度的怪物!

PostCSS 提供了一些插件来帮助我们。它们是:

  • postcss-mq-keyframes:可在github.com/TCotton/postcss-mq-keyframes找到),这是一个简单的插件,它将所有关键帧从现有查询中移出,放到样式表的底部。这允许我们合理化关键帧规则——如果我们有多个媒体查询,我们可以将这些规则应用到每个媒体查询上。

    例如,下面高亮的代码将会从查询中移出,成为一个独立的规则:

    @media only screen and (min-width: 415px) {
      .pace {
        animation: pace-anim 5s;
      }
    
     @keyframes pace-anim {
     100% {
     opacity
    
    
  • css-mqpacker:可在github.com/hail2u/node-css-mqpacker找到),这个插件解析我们的代码并将任何相同的规则合并为一个媒体查询规则。我们可能从这个插件中获得多少好处是有争议的;我们可能只有在较大的、更复杂的网站上使用它时才会看到任何显著的益处!

这两个插件都可以使用与我们迄今为止使用的所有插件相同的安装过程来安装;值得注意的是,我们不应该过度使用查询。而不是为特定的平台设计,尝试为内容明显断裂且无法使用的情况设计。查询越少,管理代码就越容易;简洁对于成功网站至关重要。

从更广泛的角度来看,在优化我们的代码时,有一个领域我们不应该忘记——我们应该支持哪些浏览器?我完全支持尽可能推动传统浏览器的发展,使用现代浏览器。然而,有些人可能仍然需要支持旧浏览器(请不要告诉我这包括 IE6!)。让我们探索 PostCSS 中可用的一个选项,以帮助那些仍然需要支持那些实际上应该被淘汰的应用程序的用户。

支持旧浏览器的更新

对于那些仍然需要支持旧浏览器,例如 IE6-8 的用户,PostCSS 可以提供帮助——我们可以使用postcss-mqwidth-to-class插件来生成基于我们指定的媒体查询的硬编码类规则,例如这个例子:

@media (min-width: 1024px) and (max-width: 1298px) {
  .bar { float: left; }
}

如果我们使用这个插件进行编译,它将产生这个结果:

.min-width-1024px.max-width-1298px .bar { float: left; }

任何人注意到这里的危险了吗?代码在技术上可能是正确的,但它存在一些局限性,使其不那么吸引人:CSS 特定性级别高,忽略了媒体类型(如屏幕或打印);如果向<body><html>标签添加类,可能需要 JavaScript。

最终决定我们使用什么取决于我们自己,但我们应该始终注意,如果我们必须支持旧浏览器,我们的代码不会引入新的问题!在这个例子中,一个更好的选择是考虑使用postcss-unmq插件(源代码可在github.com/jonathantneal/postcss-unmq找到);这个插件通过允许我们创建遵循特定屏幕尺寸的规则来移除媒体查询。

现在,虽然像 IE8 这样的浏览器确实应该(强制性地)从活跃服务中退役,但考虑如果我们能更进一步,开始远离使用响应式设计技术,这也是有话可说的。

例如,传统智慧认为使用 rem 值比使用像素更好。现在有一种新的转变表明,广泛使用 rem 单位(许多开发者可能已经这样做)可能不是最佳选择,我们可能需要考虑不同单位的混合,以确保内容正确缩放并保持足够的清晰度。这是我们 PostCSS 开发工作中需要考虑的一个重要概念,所以让我们花点时间考虑这可能会对我们意味着什么。

转向非响应式设计

"远离响应式设计??你真的失去理智了吗……??"

对于许多人可能认为的一个完全合理的问题,答案是“不”——或者正如波洛纽斯哈姆雷特中可能说的那样,“尽管有疯狂,但其中也有方法”。

简而言之,考虑这个话题有很好的理由,因为使用 PostCSS 创建断点非常容易,但确定它们应该是什么是代码成功的关键。许多开发者已经在网上博客中讨论了不同类型的媒体查询——例如平板电脑、桌面和笔记本电脑的示例广泛可用,并且经常更新或替换,如果硬件发生变化。

自从 Ethan Marcotte 在 2010 年普及“响应式网页设计”这个术语以来,许多人已经接受响应式设计作为为多个设备或平台创建内容的公认标准。然而,作为一个概念,它开始失去开发者的青睐;一个固有的弱点是需要下载多个资产,即使只有选择性的版本可能被使用(例如图像的大或小版本)。这当然增加了带宽使用,并最终使网站导航速度变慢。

那么,我们是否应该完全不使用响应式设计呢?嗯,不完全是这样,这取决于你的情况。不要盲目地添加会增加复杂性的媒体查询,花点时间考虑你是否真的需要那个媒体查询。

作为一种替代方案,考虑使用内容特定的断点,而不是媒体等效断点;而不是将我们的设计绑定到特定设备上,我们可以找出内容无法正确消费的地方,并在此基础上构建我们的断点,而不是一个可能改变的已知设备宽度。

图片,或者更具体地说,高分辨率版本,已经不再是问题;在不再使用每个图片的低分辨率和高分辨率版本的情况下,考虑切换到 SVG 格式。这种格式可以很好地缩放(不受设备影响),并且一举解决了设备上可伸缩性的任何问题。当然,对于 IE 的兼容性存在一些已知问题,但大多数其他浏览器应该能够处理 SVG 而不会引起太多问题!

字体是另一个我们可以开始减少使用媒体查询的领域——在这里,我们需要考虑使用vwvhvminvmax单位;文本将在浏览器视口大小调整时自动缩放。适应我们的代码将需要一些手动更改;我们可以使用postcss-vmin插件为旧版本的 Internet Explorer 提供一些回退。

希望这能给你一些思考——这里的关键信息是,虽然媒体查询的 PostCSS 插件使得实现变得非常容易,但我们不应该盲目地实施大量的媒体查询,而不考虑是否有其他方法可以达到相同的结果。

好的,让我们继续前进:现在是时候把事情做得更彻底一些;让我们看看如何进一步利用 CSS4(正如其广为人知的那样)。我们将通过一个示例来模拟 CSS 第 4 级媒体查询中可以使用的新的大于或小于运算符。

进一步探讨 CSS4

在使用媒体查询时,有一个小的不满是查询本身并不真正具有语义性;大多数查询将显示类似max-width: 1024px的内容,而我们的真正意图是…小于……大于…

幸运的是,随着 CSS 即将到来的变化,大多数人称之为 CSS4,我们将能够使用>, <, 或=符号来表达我们代码中的真正意图。PostCSS 的美丽之处在于,我们现在可以使用postcss-media-minmax插件(可在github.com/postcss/postcss-media-minmax找到)来模拟这种功能;该插件将这些转换为更熟悉的 min-或 max-语句,这是我们已知的。

这是一个非常容易使用的插件——我们在这里将打破常规,使用 CodePen 来演示插件的实际应用。CodePen 将支持有限数量的插件,其中之一就是它——这是一个观察查询实际效果的完美机会。对于我们的演示,我们将使用 Font Awesome 库来创建一些社交媒体图标——我们的演示是基于 Amey Raut 的一个版本:

使用 CSS4 进一步扩展功能

您可以在codepen.io/alibby251/pen/wKNMGL查看演示——对我们有意义的代码是从第 70 行到第 79 行——注意第 71 行中<=>=的使用:

使用 CSS4 进一步扩展功能

编译后,它显示以下有效的 CSS:

使用 CSS4 进一步扩展功能

在媒体查询中使用操作符如<>只是 CSS4 中即将到来的一部分;更多详情,请查看 W3C 编辑草案dev.w3.org/csswg/——注意,它可能是一段枯燥的阅读材料!

摘要

对于任何创建响应式网站的人来说,媒体查询是这个过程中的核心部分——PostCSS 可以轻松帮助我们创建项目所需的适当媒体查询。在过去的几页中,我们已经覆盖了许多关键主题,所以让我们花点时间考虑一下本章我们涵盖了哪些内容。

对于任何创建响应式网站的人来说,媒体查询是这个过程中的核心部分——PostCSS 可以轻松帮助我们创建项目所需的适当媒体查询。在过去的几页中,我们已经覆盖了许多关键主题,所以让我们花点时间考虑一下本章我们涵盖了哪些内容。

我们首先快速回顾了 CSS 中的标准媒体查询,然后修改了我们的代码,使用 PostCSS 作为查询的基础。然后我们将其用于使图像响应式,首先查看 PostCSS 中可用的选项,然后通过一个 PostCSS 的示例来操作。然后我们转向媒体查询在图像中的常见用途,查看为支持其使用的设备切换到高分辨率版本。

我们随后转向制作响应式文本,并发现这是一个类似的过程,尽管使用了不同的插件。然后我们转向使用 PostCSS 优化查询,在快速回顾了为旧浏览器添加支持时可用的一些选项之后。接着,我们完成了这一章节,探讨了如何使用替代技术使我们的网站响应式,而无需使用媒体查询,最后以发现 CSS4 中的一部分内容以及如何使用 PostCSS 使这些技术今天可用作为结束。

呼,我们确实覆盖了很多内容:但这还没有结束!我们旅程的下一站承诺将同样有趣;每个网站或在线应用都会以某种形式在整个网站上使用不同的字体、图像或颜色。我们将探讨如何使用 PostCSS 使我们的生活变得稍微轻松一些……。

第五章. 管理颜色、图像和字体

一个网站如果没有某种形式的颜色、图像或字体,就不会是一个伟大的网站——这些元素的混合将增加兴趣,在文字可能不足够清晰的地方表达内容,并且通常有助于保持访客的参与度。

现有预处理器用户当然熟悉来自 www.compass-style.org 的 Compass 编写框架等库;如果我们能够产生类似的效果,但速度更快,且无需依赖,那会怎么样?没问题,使用 PostCSS,我们可以挑选和选择我们网站所需的插件,并开始构建一个满足我们需求的处理器。在本章中,我们将涵盖多个主题,包括:

  • 可用于处理颜色、图像和字体的插件的概述

  • 使用现有预处理器创建图像精灵

  • 使用现有预处理器添加 SVG 支持

  • 转向使用 PostCSS 插件

  • 使用 PostCSS 插件操作颜色和调色板

让我们开始吧…!

向网站添加颜色、字体和媒体

一张图片胜过千言万语…

这个短语最初是在 1920 年代创造的,在数字内容的世界里非常贴切——如果我们能用一张图片替换一百个字,并且仍然传达相同的意思,那么写一百个字就没有同样的吸引力了!

任何开发人员或设计师的工作的一部分将是获取正确的图像或字体,或者选择正确的颜色,并将它们包含在他们正在构建的网站上,以便在适当的位置引用。我们将探讨我们可以使用的某些插件和技巧来修改颜色,但到目前为止,让我们看看一些可用于在网站内操作图像和字体的插件。

维护资产链接

当为网站获取媒体资源时,通常的过程是创建一个字体文件夹,另一个图像文件夹,等等,如果过程中的任何部分可能失败,那么很可能是我们在代码中应用了不正确的链接。如果我们有一个特别复杂的文件夹结构,这种风险当然会增加!

相反,我们可以采取另一种方法:为什么不让我们让 PostCSS(或一个插件)为我们做这项工作呢?

我们可以使用 postcss-assets 插件来完成这个目的;如果我们指定一个名称,它将在源文件相关的文件中查找,然后是 loadPaths 配置选项中指定的文件路径,最后是在 basePath 配置路径中指定的 URL 中搜索。这种美妙的特性在于,我们可以简单地引用图像名称,只要 PostCSS 在这些预先指定的位置之一找到一个同名图像,它就会在编译时为我们替换适当的路径。

如果一个链接需要更改,那么没问题,我们可以在其中添加一个新的,或者修改现有的一个;CSS 样式将在下一次编译时更新。让我们通过使用 postcss-assets 插件,在一个简单的演示中将其付诸实践。

自动化资产链接

记得第四章中的情绪化风景图像吗,构建媒体查询

在我们的第一个例子中,我们将重构这个演示,但这次使用postcss-assets插件(可在github.com/borodean/postcss-assets找到)来自动插入所有资产的链接。我们将专注于图像和字体,但这同样适用于视频等媒体。

这里有一个截图来提醒我们那个图像:

自动化链接到资产

让我们开始:

  1. 从本书附带的代码下载中下载Tuturial19文件夹的副本,并将其保存到我们项目区域的根目录。这包含了对第四章中演示的部分重构版本,即构建媒体查询

  2. 接下来,请从我们项目区域的根目录中删除任何gulpfile.jspackage.json的副本——我们将从这个代码下载中开始这一章的新副本。

  3. 现在,我们需要安装postcss-assets插件,所以启动一个 Node.js 命令提示符会话,输入以下命令,然后按Enter键:

    npm install postcss-assets --save-dev
    
    

    不要关闭它,我们很快还会用到它!

  4. 我们需要从代码下载中提取gulpfile.jspackage.json文件的副本——请将它们保存到我们项目区域的根目录。

    眼尖的你们会注意到我们没有安装任何其他插件——我们正在使用之前练习中已经安装的插件;package.json文件将包含对这些插件和postcss-assets文件的引用。

  5. Tutorial19文件夹中,找到并复制styles – pre-compile.css文件到我们项目区域的src文件夹中;将其重命名为styles.css

  6. 返回到 Node.js 命令提示符窗口,然后在提示符中输入gulp并按Enter键。

  7. 如果一切顺利,我们应该有一个maps文件夹和两个 CSS 样式表(一个完整版本,一个压缩版本)——如果我们将这些文件复制回Tutorial19文件夹中的css文件夹,然后运行演示,我们应该看到一幅熟悉的风景图像,就像在这个演示的开始部分所展示的那样。

好的,图像已经显示,同时还有 Roboto 字体中的文本,但这一切是如何工作的呢?花几分钟时间探索代码是值得的;正确设置它将帮助你们节省大量时间!

gulp文件中,你们大多数人都会从之前的演示中认出大部分内容——我们包括了之前的 linting、重命名和源映射创建。除了新的资产任务(用于处理我们的资产链接)之外,我们还移除了autoprefixer任务;我们没有调用需要供应商前缀的任何内容,因此不需要使用它。

Gulp 文件中的关键过程集中在以下代码上——它创建并替换正确的资产链接。我们首先从选项配置对象开始——loadPaths负责资产位置,而relativeTo告诉插件相对于dest/文件夹设置相对链接。在这种情况下,loadPaths定义了要使用的特定文件夹;我们使用relativeTo使这些路径成为相对路径:

var options = {
  loadPaths: ['img/', 'fonts/'],
  relativeTo: 'dest/'
};

dest/文件夹用于我们的创建过程中——实际上,这将是我们的生产服务器上 CSS 样式表的存放位置。接下来的这个简单任务只是调用postcss-assets插件,并处理在src文件夹中找到的每个样式表:

gulp.task('assets', function() {
  return gulp.src('src/*.css')
    .pipe(postcss([ assets(options) ]))
    .pipe(gulp.dest('dest/'));
});

然后,我们只需调用任务即可,如果我们从命令提示符中调用 gulp,它将运行所有这些任务:

gulp.task('default', ['assets', 'lint-styles', 'rename', 'sourcemap']);

总的来说,这是一个非常简单但非常有效的工具,它消除了手动插入链接的需求,前提是我们已经将它们包含在配置对象中。

好吧…让我们继续:我们已经介绍了一种简单的方法来确保我们始终有正确的字体或图像文件的链接。尽管如此,仍然需要一些手动工作——我们真的需要包含为我们的自定义字体添加的所有行吗?

好吧,我们总是可以使用托管在 Google 上的字体,但这破坏了使用 PostCSS 的意义!相反,我们可以在样式表中简单地使用自定义字体名称,但让 PostCSS 在编译时自动添加自定义 font-face 声明。感兴趣吗?让我们看看如何,作为我们下一个练习的一部分。

使用 PostCSS 管理字体

在我们之前的演示中,我们探索了一种使用 PostCSS 自动添加链接的方法——它简化了提供文件正确位置的需求。但是,当与自定义字体一起使用时,它仍然需要太多的工作(是的,我知道,我们人类天生就懒惰!)。有一个更好的选择:

输入postcss-fontpath插件,可从github.com/seaneking/postcss-fontpath获取;这是一个简单的插件,它只需要我们提供有关自定义字体的一些有限信息,并在编译阶段生成完整的 font-face 声明。

所以,与其谈论它,不如让我们实际应用它?让我们回顾一下我们在上一个演示中覆盖的响应式图片演示,并修改我们的样式表以使用 fontpath 插件来处理我们的自定义字体:

  1. 我们首先从本书附带的代码下载中提取Tutorial20文件夹的副本,并将文件夹保存到我们的项目区域根目录。

  2. 接下来,从Tutorial20文件夹中复制package.jsongulpfile.js文件,并替换我们项目区域根目录中现有的版本。

  3. 现在启动一个 Node.js 命令提示符,并将工作文件夹更改为我们的项目区域。

  4. 在命令提示符中,输入以下命令,然后按Enter键:

    npm install postcss-fontpath --save-dev
    
    

    尽管我们已经明确安装了插件,但我们只需使用 npm install 就可以轻松安装它;文件夹中存在的 package.json 文件将告诉 NPM 要安装什么(在这种情况下是缺失的 postcss-fontpath 插件)。保持会话开启,我们很快还会用到它。

  5. css – completed version 文件夹中复制 styles – pre-compile.css,并将其保存为 styles.css 到项目区域的根目录下的 src 文件夹中。

  6. 返回 Node.js 命令提示符窗口,然后在提示符中输入 gulp,然后按 Enter 键。

  7. 如果一切顺利,我们应该会在 dest 文件夹中看到现在熟悉的样式表和源映射;将这些复制到 Tutorial20 文件夹内的 css 文件夹中。

到目前为止,我们应该已经有一个可以工作的演示了;我们可能不会看到任何本质上的不同,但要知道在编译时,PostCSS 已经自动为我们添加了正确的字体声明。

这个插件的优点在于其简单性——它不需要在主任务中添加任何额外的命令:

gulp.task('fonts', function () {
  return gulp.src('src/*.css').pipe(
    postcss([ fontpath() ])
  ).pipe(
    gulp.dest('dest/')
  );
});

没有必要指定任何额外的配置元素或规则,插件确实做到了它所说的那样!虽然我们在这个例子中没有取得任何突破性的成就,但它确实有助于说明使用 PostCSS 的一些关键点:

  • 当插件专注于单一任务而不是试图一次性完成所有事情时,PostCSS 工作得最好。遵循单一责任原则意味着我们可以减少重复,使插件更健壮,并避免更改最终导致处理器其他地方功能中断的情况!这个插件是完美的——它只为指定的字体提供字体声明,没有其他任何功能。

  • 有时候,在选择 PostCSS 中的正确插件时,可能会遇到我们选择的东西后来发现并不如预期工作的情况。一个例子是 postcss-font-magician 插件(可在 github.com/jonathantneal/postcss-font-magician 获取);它有提供字体声明的好想法,但试图为 Google 托管的字体、本地托管字体、Bootstrap 等提供字体声明。

    注意

    不幸的是,在撰写本文时,并非所有功能似乎都按预期工作,因此我们不得不在这个时候寻找替代方案。

如果你想探索更多,那么postcss.parts目录(在www.postcss.parts)提供了更多选项;其中两个可能对你有吸引力的是Assets Rebase插件(来自github.com/devex-web-frontend/postcss-assets-rebase),以及 PostCSS 的Font Pack插件,来自github.com/jedmao/postcss-font-pack。我们将在第八章创建 PostCSS 插件中更详细地介绍这个插件。

好的,所以我们的文本已经准备好了:它看起来有点无聊,不是吗?嗯,我们可以通过添加图像来解决这个问题。那么,PostCSS 究竟如何帮助我们呢?我听到你问道?

它可以通过多种方式帮助——例如,我们可以开始混合一些纯色,而不是使用纯色。或者,使用图像精灵怎么样?手动创建很痛苦,对吧?但不是用 PostCSS。我敢打赌,你已经看到了一些可以在图像上使用的图像过滤器(例如,棕褐色或着色),但发现它们并不是在所有浏览器上都有效?

这些只是 PostCSS 能帮助我们的一些方式,我们将在本章中涵盖所有这些以及更多内容。不过,让我们先从处理图像开始:我们的第一个演示将涵盖图像精灵的创建。我们首先快速回顾一下 SASS 过程,然后再切换到使用 PostCSS。

创建图像精灵

让我们从简单的事情开始:我敢肯定,在某个时候,你要么使用过,要么创建过图像精灵,对吧?如果你是 SASS 开发者,毫无疑问,你已经使用了 Compass 的 sprite 混入,并使用像Koala这样的应用程序进行编译,或者直接从命令行编译。

注意

使用 Compass 创建精灵的相关文件可以在本书的代码下载中找到,位于Tutorial21A文件夹。

这个过程相对简单,但你仍然需要设置一个Compass项目,安装一个 GUI 应用程序(如果你在使用的话),等等,这真是个麻烦!我们可以使用一个在线应用程序,例如SpritePad(spritepad.wearekiss.com/),但同样,这又是一个手动过程,而且容易出错。相反,我们可以轻松地使用 PostCSS 来帮助我们——除了在 gulp 文件顶部声明的正常变量之外,为了生成基本的图像精灵,所需的东西非常少。现在让我们看看如何使用postcss-sprites插件创建一个。

演示 - 创建信用卡图标栏

你有多少次在电子商务网站上购买过东西?如果你在网上购买的东西和我一样多,那么毫无疑问,你一定见过带有各种支付卡图标的购物车。这些图标可能很小,但它们对我们网站来说却至关重要——毕竟,如果在线零售商不接受万事达卡,我们如何知道使用特定的信用卡可能会失败呢?这似乎很明显,但并不总是容易判断。

不考虑这一点,使用 PostCSS 创建图像精灵非常简单;SASS 的依赖已经消失:我们可以使用 postcss-sprites 插件(可在 github.com/2createStudio/postcss-sprites 获取)来生成我们的组合图像。让我们深入探讨并查看一下。

对于这个演示,我们将使用在 findicons.com/pack/2102/credit_card_debit_card 可用的信用卡图标;如果您想使用不同的图标,请随意替换。

本教程的所有代码都可以在 Tutorial21B 文件夹中找到,在代码下载中——我们将通过安装 postcss-sprites 插件来从头开始:

  1. 启动 Node.js 命令提示符,并将工作文件夹更改为我们项目区域。

  2. 在命令提示符下,输入此截图所示的命令,然后按 Enter 键,一旦 Node 确认安装成功,请最小化窗口,因为我们将在本练习的稍后部分再次回到它:演示 – 创建信用卡图标栏

  3. 现在启动你的文本编辑器,然后添加以下行——这些代表我们通常添加到任何在线电子商务网站上的四个信用卡图标:

    .amex { background: #fff url(img/amex.png) no-repeat 0 0; }
    .cirrus { background: url(img/cirrus.png) no-repeat 0 0; }
    
    .delta { background: url(img/delta.png) no-repeat 0 0; }
    .solo { background: url(img/solo.png) no-repeat 0 0; }
    
  4. 将文件保存为 style.css,并将其存储在我们项目区域的 src 文件夹中。

  5. 在同一文件夹中,在项目区域的根目录创建一个名为 img 的文件夹;提取本书附带的代码下载中存储的图标副本,并将它们保存到 img 文件夹中。

  6. 从本书附带的代码下载中,提取 gulpfile.js 的副本,并将其保存到我们项目区域的根目录。

  7. 返回 Node.js 窗口,然后在提示符下输入 gulp 并按 Enter 键。

  8. 如果一切顺利,我们的代码现在将被编译,我们应该在查看 dest 文件夹中的 style.css 文件时看到类似以下内容:

    .amex { background-image: url(../img/sprite.png); background-position: 0 0; background-color: #fff; }
    .cirrus { background-image: url(../img/sprite.png); background-position: -102px 0; }
    .delta { background-image: url(../img/sprite.png); background-position: 0 -64px; }
    .solo { background-image: url(../img/sprite.png); background-position: -102px -64px; }
    

在此阶段,我们可以将代码连同图片一起复制到我们的网站上——而不是使用四个独立的图标(每个图标都需要单独调用服务器),我们可以缓存单个图标。这将导致响应时间更快,并且对服务器的调用次数更少。编译后的样式表可以在 dest 文件夹中找到,组合图像位于 img 文件夹的上一级:

演示 – 创建信用卡图标栏

尽管这是一个简单的过程,但值得注意的是我们 gulp 文件配置的一个关键点——使用配置对象为sprites插件:

var opts = {
  stylesheetPath: 'dest/',
  spritePath    : 'img/sprite.png',
  path          : 'src/img/'
};

这并不是我们迄今为止使用过的过程,但这并不意味着它不那么有用——这仅仅是一个个人偏好和可读性的问题。它确实使得阅读我们分配给每个插件的调用变得更容易;在这个例子中,我们只使用了一个,但你可以想象如果有多个插件在使用会是什么样子:

gulp.task('autoprefixer', function() {
  return gulp.src('src/*.css')
    .pipe(postcss([ sprites(opts) ]))
    .pipe(gulp.dest('dest/'));
});

好吧,让我们改变方向,看看使用 PostCSS 处理图像的不同方面:使用 SVG 格式的图像。标准图像并不总是很好地缩放,尤其是在响应式环境中使用时;有时我们可能会使用视网膜图像,但可以考虑的一个替代方案是使用 SVG 图像。

在 PostCSS 中使用 SVG

移动设备的快速增加使得创建响应式内容变得必须;传统的做法是使用类似max-width: 100%的东西来控制屏幕上元素的大小。

一个更好的选择是使用 SVG——即使在调整大小时也能保持质量;标准图像格式如果调整到过大的尺寸将会变得像素化。对于那些之前使用过 SASS 的人来说,那么并没有内置对 SVG 的支持;我们最多希望在我们的样式表中实现高效的嵌套。

我们可能会使用的例子可以在本书附带的代码下载中Tutorial22文件夹内的sass文件夹中找到。

如果我们是在 SASS 中经常使用 SVG 图像的用户,那么我们可能会使用像sass-svg这样的库,来自github.com/davidkpiano/sass-svg。从 SASS 迁移到 PostCSS 很容易;PostCSS 生态系统有许多插件我们可以用来操作图像。让我们看看如何使用postcss-svg插件来实现。

使用 PostCSS 修改图标图像

我们将使用postcss-svg插件(来自github.com/Pavliko/postcss-svg),来操作Evil Icon包(可在github.com/outpunk/gulp-evil-icons找到)中的某些图标,作为下一个演示的一部分:

  1. 我们将开始从本书附带的代码下载中提取Tutorial22文件夹的副本。将其保存到我们的项目区域根目录。

  2. Tutorial22文件夹内部,提取gulpfile.jspackage.json文件的副本,并使用它们替换我们项目区域根目录下存储的任何文件。

  3. 从同一个文件夹中提取style – pre-compile.css的副本;将其保存为src文件夹内的style.css。同样,对index.html文件也做同样的处理。

  4. 在与之前的演示中断之后,我们需要一个额外的css文件夹——在dest文件夹内创建一个。

  5. 接下来,启动一个 Node.js 命令提示符,并将工作文件夹更改为我们的项目区域。

  6. 我们需要安装postcss-svg插件,所以请在命令提示符中输入此命令,然后按Enter

    npm install postcss-svg --save-dev
    
    
  7. 完成后,在命令提示符中输入gulp,然后按Enter

  8. 如果一切顺利,我们应该在/dest/css文件夹中看到通常的两个样式表,以及一个源map文件夹。HTML 标记文件将出现在dest文件夹中。

    如果您没有看到源映射或最小化版本出现,那么请重新运行gulp——有时这些文件只有在存在编译后的style.css文件时才会出现。

  9. dest文件夹的内容复制到Tutorial22文件夹内的css文件夹中——如果一切顺利,当在浏览器中预览结果时,我们应该会看到这些图标:使用 PostCSS 更改图标图像

虽然这是一个简单的演示,但我们已经涵盖了一些有用的技巧和窍门;花些时间探索如何更详细地构建这个演示是值得的。

探索结果的详细信息

这个练习有几个值得注意的关键元素,包括使用 CDN 链接和 Node 提供 Evil Icons 的样式表和图标、编译后的 HTML 文件以及在我们自定义样式表中使用的引用。我们将涵盖所有这些,但首先让我们更详细地探索 gulp 文件。

我们从这两行开始:

var evilIcons = require("gulp-evil-icons");
var postcssSVG = require('postcss-svg')

您看到后者可能不会感到惊讶,但前者存在,因为可以使用gulp-evil-icons包安装Evil Icons库。安装有多种不同的选项可用,但既然我们已经在使用 Gulp,继续使用任务运行器是有意义的。

接下来,我们将工作分散到两个任务中——第一个任务将 HTML 代码编译,将相关的图标图像分配给我们的标记中的<icon>语句:

gulp.task('icons', function () {
  return gulp.src('src/index.html')
.pipe(evilIcons())
    .pipe(gulp.dest('dest/'));
});

要更改颜色,需要使用postcss-svg插件,这里通过postcssSVG引用:

gulp.task('changecolor', ['icons'], function() {
  gulp.src('src/style.css')
  .pipe(postcss([ postcssSVG() ]))
    .pipe(gulp.dest('dest/'));
});

我们当然必须更新我们的默认任务,如果我们简单地从命令行调用gulp,那么它将知道依次运行所有这些任务:

gulp.task('default', ['icons', 'changecolor', 'lint-styles' , 'rename', 'sourcemap' ]);

最后一步也更新了我们的监视功能:

var watcher = gulp.watch('src/*.*', ['default', 'icons', 'changecolor', 'lint-styles', 'rename', 'sourcemap']);

如果我们然后查看 HTML 标记,我们可以看到一个使用 Node.js 安装的Evil Icons库的链接:

<link rel="stylesheet" href="../node_modules/gulp-evil-icons/node_modules/evil-icons/assets/evil-icons.css">

然后,我们将自定义设置放入单独的样式表中:

<link rel="stylesheet" type="text/css" href="css/style.css">

它们看起来像这样:

探索结果的详细信息

在这个阶段,CSS 样式可能看起来很简单,但 HTML 标记却并非如此;postcss-svg插件已将我们的图标以内联形式添加到 HTML 标记中,并从我们的自定义样式表进行了适当的编辑:

探索结果的详细信息

有时,很容易怀疑使用 SVG 是否值得额外的标记,主要好处是如果将其内联添加,那么我们可以减少对外部资源的调用;任何需要更改的内容都可以完成,而不会牺牲我们图像的质量。

考虑替代选项

在我们的练习中,我们专注于使用 postcss-svg 插件,作为在 PostCSS 系统中操作 SVG 图像的起点;还有一些其他选项可供选择,可能对你感兴趣:

如果你需要为 SVG 文件提供一个后备位置,那么你可以尝试使用 postcss-svg-fallback 插件,它可在 github.com/justim/postcss-svg-fallback 获取——我们将在 第八章 创建 PostCSS 插件 中使用这个插件。

好吧,让我们改变一下方向:如果我们只需要一个简单的图像显示格式,使用 SVG 图像可能会有些过于复杂,对吧?嗯,我们可以使用标准格式,或者一个在保持较小尺寸的同时具有优越质量的格式。我指的是谷歌不太为人所知的 WebP 格式——让我们深入了解这个格式,并找出为什么它值得更多的关注。

添加对 WebP 图像的支持

操作 SVG 图像是一门需要掌握的技艺,在某些情况下,它可能对我们需要达成的目标来说过于复杂。

相反,对于那些需要图像细节的情况,我们通常可能会使用 JPEG 格式,或者可能作为替代的 PNG。这两种格式都没有问题,但它们已经过时了,我喜欢推动可能性的边界!此外,JPEG 图像格式是有损的,不支持 alpha 通道;PNG 图像是无损的,但对于更复杂的图像来说,文件大小会更大。如果我们只是简单地在一个页面上插入图像,那么 PostCSS 在这里可能没有帮助;相反,我们为什么不考虑一个完全不同的格式呢?

进入谷歌的 WebP。你可能会想“Web…什么?”因为它不是一个常见的格式!这部分的缺失可以归因于缺乏采用;唯一原生支持它的浏览器是 Chrome、Android 和 Opera。但这并不意味着我们应该将其排除在外。这种格式可以在保持优越质量的同时,比标准图像格式如 JPEG 或 PNG 节省更多的空间。我们甚至可以让 PostCSS 为我们完成大部分工作!让我们更详细地探讨这一点,通过一个简单的示例。

切换 WebP 图像的进与出

图像切换并不是什么新鲜事,我们在第四章中讨论了其中一个方面,即使用 PostCSS 在浏览器支持时切换高分辨率图像。

我们可以使用类似的技术,但这次是针对图像格式,Google 的 WebP 格式被设计为替代网络上可用的众多其他图像格式。在一个理想的世界里,我们会使用新的<picture>标签来自动切换图像:

<picture>
  <source srcset="../img/landscape.webp" type="image/webp">
  <img src="img/landscape.jpg" alt="The Oslo Opera House">
</picture>

这在所有浏览器中都不受支持,因此,我们可以使用 PostCSS 和 Modernizr 的混合来应用相同的效果。我们需要用于此任务的插件是webpcss插件(可在github.com/lexich/webpcss获取)——我们需要在 Node.js 命令提示符会话中运行npm install gulp-webp --save-dev来安装插件。让我们深入探讨并更详细地查看它。

小贴士

为了获得最佳效果,我建议在整个这两个演示过程中使用 Chrome,通过访问developers.google.com/speed/webp/可以为 Windows 和其他浏览器添加支持。

查看文件大小差异

在我们开始使用 PostCSS 之前,让我们花一点时间进行快速测试。本教程的文件位于Tutorial 23文件夹中:

  1. 在本书附带的代码下载中,请提取landscape – original version.jpg的副本,并将其重命名为landscape.jpg。其大小应约为 11.5 MB。

  2. 将图像保存到我们的项目区域根目录——我们还需要cwebp.exe的副本,所以请将其也提取到我们的项目区域。

  3. 启动一个命令提示符会话,将工作文件夹更改为我们的项目区域,输入gulp,然后按Enter

  4. 如果一切顺利,我们应该看到我们转换的结果,新的 WebP 格式图像将出现在我们的项目区域中:查看文件大小差异

  5. 尝试使用 PNG 格式图像执行相同的操作;以下是我进行的一个类似测试的结果,使用的是我们风景图像的 PNG 版本:查看文件大小差异

在这两种情况下,图像大小都显著减小,JPEG 版本从大约 12.5 MB 降至略超过 7 MB;PNG 格式从庞大的 25 MB 缩小到大约相同的大小!

注意

要了解更多关于使用 WebP 格式的信息,请查看 Google 开发者网站上的文档developers.google.com/speed/webp/

好的,现在是时候进行另一个演示了!让我们现在使用 PostCSS 来创建标准 JPEG 格式和 WebP 等价物的样式:

查看文件大小差异

对于这个演示,我们将使用来自github.com/lexich/webpcssgulp-webpcss插件:

  1. 请从本书附带的代码下载中下载Tuturial23文件夹的副本,将其保存到我们项目区域的根目录。

  2. 接下来,请从我们项目区域的根目录中删除任何gulpfile.jspackage.json的副本;我们需要用Tutorial23文件夹中的副本来替换它们。

  3. 在这些文件就位后,我们仍然需要安装插件,在 Node.js 命令提示符窗口中,将工作文件夹更改为我们的项目,然后运行以下命令,每条命令后按Enter键:

    npm install --save-dev gulp-webp 
    npm install --save-dev gulp-webpcss 
    
    

    注意这些命令中参数的顺序,如果顺序不同,它们将无法安装。

  4. Tutorial23文件夹中的style – pre-compile.css文件复制到我们项目区域根目录下的src文件夹中,然后将其重命名为style.css

  5. 启动 Node.js 命令提示符,将工作文件夹更改为我们的项目区域,然后在提示符中输入gulp并按Enter键。

  6. 如果一切顺利,在查看编译文件的内容时,我们应该看到这个截图所示的代码;转换后的图像也将出现在img文件夹中:查看文件大小差异

  7. img文件夹的内容复制到Tutorial23文件夹内的img文件夹中。

  8. dest文件夹中的style.css文件复制到Tutorial23文件夹内的css文件夹中。

  9. 在浏览器中运行index.html,如果一切顺利,我们应该看到与这个练习开始时的截图类似的内容。

如果我们在 Google Chrome 或 Firefox 中运行相同的index.html,一开始我们不应该看到任何差异——我们只有在 Chrome 的开发者工具栏中查看编译的源代码时才会看到差异:

查看文件大小差异

真正的好处在于我们项目区域的img文件夹中,我们使用的原始 JPEG 图像大小为 222 KB;然而,WebP 的大小只是这个大小的几分之一:它只有 82 KB。我说的节省空间的意思是什么?

好的,继续前进:现在是时候专注于网站建设的另一个领域了,那就是操作颜色。颜色在任何一个网站上都扮演着关键角色,因为它们构成了对最终用户信息的一部分;让我们深入探讨一下使用 PostCSS 时可用的一些选项。

操作颜色和颜色调板

任何开发人员或设计师都会面临的一个挑战是在网站上使用哪种颜色——比如一个漂亮的红色调,或者海蓝色如何?无论他们是否负责选择要使用的色调,或者他们是否需要选择正确的 RGB 或 HEX 颜色来使用,都无关紧要。

无论责任在哪里,我们仍然需要选择一种颜色,而且有很大可能性我们不会选择默认的 256 色调板中的颜色,而是选择一种更浅或更深的色调,或者可能是两种颜色的混合:

操作颜色和颜色调板

任何习惯于使用 SASS 的人都已经熟悉lighten()darken()saturate()等函数——PostCSS 的伟大之处在于我们可以为那些想要摆脱 SASS 依赖的人复制类似的功能。

为了看到使用起来有多简单,我们将结合两个 PostCSS 插件的强大功能——postcss-color-palette(可在github.com/zaim/postcss-color-palette获取),以及postcss-color-mix(来自github.com/iamstarkov/postcss-color-mix)。前者允许我们从任意三个调色板中选择一个或多个颜色,而postcss-color-mix将混合特定颜色以生成新的颜色。使用这些插件的原因将会变得清晰;现在,让我们深入其中,看看这些插件的实际效果。

使用调色板显示和混合颜色

在这个练习中,我们将研究颜色混合;postcss-color-palette允许我们通过名称选择多个颜色(而不是通过数字!),然后将其转换为 HEX 等效值。然后我们可以创建渐变效果,或者简单地混合颜色(使用postcss-color-mix)以生成新的颜色。

让我们开始吧:

  1. 我们将首先从本书附带的代码下载中提取Tutorial24文件夹的副本;将文件夹保存到我们项目区域的根目录。

  2. Tutorial24文件夹中,将package.jsongulpfile.js文件复制到我们项目区域的根目录。

  3. 我们还需要我们的样式表,为此,请从同一文件夹中复制style – pre-compile.css文件,并将其放入我们项目区域的src文件夹中。将其重命名为style.css

  4. 到目前为止,我们需要安装插件,为此,请启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  5. 在提示符下,输入此截图所示的命令,然后按Enter,如果一切顺利,我们应该会看到确认插件已正确安装的消息:使用调色板显示和混合颜色

  6. 重复步骤 5,但这次,运行此截图所示的命令:使用调色板显示和混合颜色

  7. 在提示符下,输入gulp,然后按Enter——PostCSS 将退出并编译样式表,并将编译结果放入dest文件夹。

  8. dest文件夹的内容(将是不压缩和最小化的样式表,以及一个源映射文件)复制到Tutorial24文件夹内的css文件夹中。

  9. 尝试预览Tutorial24文件夹根目录下的index.html;如果一切顺利,我们应该会看到我们混合的颜色,如图所示:使用调色板显示和混合颜色

好吧,我选择的颜色显然不会很快赢得任何风格奖项,但它们有助于实现一个目的:如果愿意,使用正确的颜色名称非常容易,同时仍然允许 PostCSS 将它们编译成有效的十六进制值。抛开这一点,让我们花点时间考虑一下我们在演示中使用的代码——它确实提出了几个关键点,我们应该在使用这些插件时讨论。

更详细地剖析我们的演示

我们创建的演示遵循了我们迄今为止构建的大多数其他演示的类似原则;我们首先声明变量来存储插件的实例,因此:

var palette = require('postcss-color-palette');
var colormix = require('postcss-color-mix')

魔法就在这个任务中发生,在我们的 gulp 文件中:

gulp.task('palette', function () {
  return gulp.src('src/*.css')
  .pipe(postcss([ autoprefixer, palette({ palette: 'mrmrs' }), colormix() ]))
  .pipe(gulp.dest('dest/'));
});

注意,我们已经指定了一个要使用的颜色板,mrmrs 选项是默认的,但我们同样可以使用 materialflatui 作为替代。所有三个都引用了来自 github.com/zaim/webcolors/webcolors 插件;如果需要,这个包可以扩展以包含其他颜色板。

在放置好我们两个插件的链接并设置好任务后,我们就可以开始在样式表中指定规则,这些规则将使用插件。我们创建了三个,并且所有三个都使用 postcss-color-palette 来确定每个颜色的十六进制值;第三个和最后一个在分配了十六进制值后将两种颜色混合在一起:

#box0 { background: linear-gradient(aqua, blue 50%, purple); }

#box1 { background: linear-gradient(to right, orange, red, yellow); }

#box2 { background: mix(teal, navy, 80%); }

对于第三条规则正确地混合颜色并不容易,成功混合的关键是避免使用同一光谱中的颜色;它们越接近,混合的影响就越小!

如果你想快速评估颜色混合的效果,那么试试 jackiebalzer.com/color——这个演示有一个 mix() 选项,它将在浏览器中编译它们,避免需要手动运行编译过程。

我们介绍了一些可能更受欢迎的插件;更多插件可以通过 PostCSS.parts 目录获得,这可能很有趣:

  • colorguard: 帮助保持颜色板的一致性

  • postcss-ase-colors: 将颜色名称替换为从 ASE 颜色板文件中读取的值;如果你恰好是 Adobe PhotoShop、InDesign 或 Illustrator 的用户,这将是完美的。

  • postcss-shades-of-gray: 帮助保持灰度颜色与灰度颜色板的一致性

  • postcss-color-pantone: 将潘通颜色转换为 RGB。

同时,让我们继续前进:我们探索了使用颜色板来选择颜色,然后再混合它们以创建新的颜色。这仅仅是触及了可能性的表面;我们能否使用诸如 darken()tint()lightness() 等函数来创建不同 色调 的颜色?这些函数在大多数预处理器中已经存在,例如 SASS;让我们探索如何使用 PostCSS 插件实现相同的结果。

使用 PostCSS 创建颜色函数

在我们通过 PostCSS 操作颜色的旅程中,我们迄今为止已经看到了如何使用调色板来定义颜色——这可能在某些情况下有效,但有时我们需要指定不在调色板中的颜色。

我们可以尝试手动指定值,但如果我们需要更改它怎么办?我们是否尝试找到它的每一个实例,并冒着错过一个实例的风险?

答案是否定的。相反,我们可以使用 postcss-color-function 插件来动态创建我们的颜色;如果我们经常使用这种颜色,我们可以将生成的值分配给一个变量。我们可以使用这种方法来产生一些漂亮的颜色阴影,所以让我们深入探索这个插件。

使用函数调整颜色

大多数 CSS 预处理器中的一个有用功能是能够动态地创建新颜色,我们可以通过调整颜色通道或应用颜色滤镜来实现,例如使其变暗:

使用函数调整颜色

这种方法的优点很简单,它允许我们减少默认分配的基本颜色数量;剩余的颜色可以自动创建。如果我们需要更改我们的基本颜色之一,那么任何动态创建的颜色仍然可以使用。

幸运的是,我们可以在 PostCSS 中实现相同的效果,为此,我们需要使用 postcss-color-function 插件,该插件可在 github.com/postcss/postcss-color-function 获取。我们还将使用 css-color-converter 插件,以帮助管理不同颜色格式之间的转换。

让我们通过一个简单的演示来更详细地探讨这个问题:

  1. 我们首先从本书附带的代码下载中提取 Tutorial25 文件夹的副本——请将其保存到我们的项目区域。

  2. 如果项目区域已经存在 package.json 和/或 gulpfile.js 文件,那么请删除它们;用 Tutorial25 文件夹中的文件替换它们。

  3. 尽管我们已经设置了正确的配置文件,但我们仍然需要安装插件——请启动一个 Node.js 命令提示符会话,然后将工作文件夹更改为我们的项目区域。

  4. 在提示符中输入以下命令,每输入一个命令后按 Enter 键:

    npm install postcss-color-function --save-dev
    npm install css-color-converter --save-dev
    
    
  5. 到目前为止,我们现在可以继续编译我们的样式表了——在 css – completed version 子文件夹中查找 styles – pre-compile.css,并将其保存到项目区域内的 src 文件夹中作为 style.css

  6. 从之前的 Node.js 命令提示符切换过来,然后在提示符中输入 gulp 并按 Enter 键。

  7. 如果一切顺利,我们应该会看到现在编译好的样式表出现在 dest 文件夹中(包括未压缩和压缩版本),以及源映射文件。将此文件夹的内容复制到 Tutorial25 文件夹内的 css 文件夹中。

尝试在浏览器中预览结果,如果编译成功,我们应该看到四个不同红色阴影的盒子出现,就像在这个练习的开始部分所示。问题是,我们已经看到了结果的出现,但 PostCSS 是如何知道创建这些颜色的?

分析我们的演示

这是一个很好的问题,转换过程非常简单;但其中的技巧不在于编译,而在于如何实现颜色!虽然听起来很奇怪,但选择颜色并不像看起来那么简单;让我详细解释一下:

编译过程,就像其他 PostCSS 插件一样,非常容易配置——我们当然是从创建一个定义 color-function 插件的变量开始的:

var colorfunction = require('postcss-color-function');

接下来,我们添加了对我们的主要 gulp 任务的引用,在这里我们同时使用了 autoprefixercolor-function 插件,但前者并不是严格必要的,因为我们没有添加任何供应商前缀:

gulp.task('autoprefixer', function() {
  return gulp.src('src/*.css')
    .pipe(postcss([  autoprefixer, colorfunction() ]))
    .pipe(gulp.dest('dest/'));
});

真正的魔法在于我们在样式表中指定的颜色——我们的第一个盒子是一个控制,具有标准的红色:

#box0 { background-color: #ff0000; }

接下来,我们在 box1 上添加 60% 的色调,这使得它变成了浅粉色:

#box1 { background-color: color(red tint(60%)); }

Box2 则相反,尽管我们使用了亮度过滤器(你可能期望与 box1 类似的结果),但负数使其变成了棕色-红色:

#box2 { background-color: color(red lightness(-20%)); }

最后一个盒子 box3 继续从 box2 的棕色主题,但使其更浅。注意,在注释中,这种色调是我们应用棕褐色色调时会产生的结果:

#box3 { background-color: sepia(red, 0.7);}

问题在于,我们如何知道这确实是一个正在应用的棕褐色过滤器?

从表面上看,我们似乎选择了红色,然后通过特定的量改变每个通道以获得最终结果。

使用此插件的一个缺点是它没有函数来支持今天可用的所有等效 CSS3 过滤器;这意味着我们必须机智,直接计算颜色应该是什么。我们将在下一个演示中能够改变这一点——会有时候我们需要创建自己的自定义过滤器;一个很好的例子是棕褐色。这确实意味着前期需要更多的工作,但它允许我们通过名称调用 sepia() 函数,而不是近似最终结果。

如果你发现很难确定应用过滤器后颜色应该是什么,请查看 jackiebalzer.com/color;这是一个很棒的网站,允许我们选择一种颜色并查看应用过滤器后的结果。它是为 SASS 编写的,但最终结果对于 PostCSS 将是相同的。ColorHexa.com (www.colorhexa.com) 这样的网站也很有帮助,我们可以用它来验证应用过滤器后应该是什么颜色值。

我们继续前进。在我们的练习中,我们发现 postcss-color-function 插件并没有涵盖我们可以在 CSS 中使用的所有 CSS3 过滤器;对于 sepia 示例,我们必须分配一个计算后的颜色值,而不是应用过滤器效果。现在让我们解决这个问题。通过一点对演示的前期修改,我们可以创建自己的自定义函数。这意味着,例如,如果我们想要棕褐色效果,我们可以调用 sepia(),而不是计算最终的颜色应该是什么!

使用 PostCSS 过滤器创建颜色

在我们之前的演示中,我们查看了一种通过编程方式更改颜色的方法——这是一个在大多数 CSS 处理器(如 SASS 或 Less)中已经存在一段时间的功能。

有时候,我们可能需要更精细地控制颜色变化,仅仅使用 postcss-color-function 插件提供的现有函数是不够的,或者所需的过滤器不可用。如果我们愿意,我们可以创建自己的颜色函数;为此,我们可以使用来自 github.com/andyjansson/postcss-functionspostcss-functions 插件,在任务文件中暴露 JavaScript 函数的使用。

然而,值得注意的是,如果 CSS3 过滤器不存在,那么大多数可以通过不同的计算组合(如上一个演示中的 sepia 示例)来创建。技术上这可能可以正常工作,但简单地通过名称引用棕褐色过滤器,而不是计算出 #box3 应用了棕褐色效果,会更容易一些!

我感觉一个演示即将到来,所以不再多言,以下是我们要创建的内容的截图:

使用 PostCSS 过滤器创建颜色

简而言之,我们使用标准的红色色调 (#ff0000,以便清楚!),并使用色调、变暗或棕褐色过滤器计算各种色调。

让我们更详细地看看如何创建这些颜色:

  1. 我们将首先从本书附带的代码下载中提取 Tutorial26 文件夹的副本;将其保存到我们项目区域的根目录。

  2. 接下来,请从我们项目区域的根目录中删除任何 gulpfile.jspackage.json 的副本。

  3. Tutorial26 文件夹中,将 package.jsongulpfile.js 复制到我们项目区域的根目录。

  4. 在这些文件就位后,我们仍然需要安装插件。在 Node.js 命令提示符窗口中,将工作文件夹更改为我们的项目,然后输入以下命令,并在每个命令后按 Enter 键:

    npm install postcss-functions --save-dev
    npm install css-color-converter --save-dev
    
    
  5. Tutorial26 文件夹中,将 style – pre-compile.css 复制到项目区域的 src 文件夹中;将其重命名为 style.css

  6. 返回 Node.js 命令提示符窗口,然后在提示符下输入 gulp 并按 Enter 键。

  7. 如果一切顺利,我们应该在 dest 文件夹中看到一个源映射和两个编译后的样式表;将这些文件复制到 Tutorial26 文件夹内的 css 文件夹中。

  8. 尝试在浏览器中运行演示。如果一切顺利,我们应该看到四个盒子出现,它们有各种深浅的红色调,就像在这个练习的开始部分所展示的那样。

如果我们更详细地查看gulp任务文件的内容,它看起来会比之前的练习更大;它可能看起来我们做了更多,但实际上,其中很多我们在早期的演示中已经见过。让我们更详细地看看它。

更详细地探索我们的演示

如果我们打开我们的 gulp 任务文件,我们可以看到它包含了许多函数,以及我们在之前的演示中使用过的任务,例如lint-styles。在这个演示中的关键是三个颜色函数,以及autoprefixer任务的主要部分。

让我们从color函数开始,以darkenColor为例:

function darkenColor (value, frac) {
  var darken = 1 - parseFloat(frac);
  var rgba = color(value).toRgbaArray();
  var r = rgba[0] * darken;
  var g = rgba[1] * darken;
  var b = rgba[2] * darken;
  return color([r,g,b]).toHexString();
}

我们首先提取十进制值,然后从1中减去它(作为frac),这给了我们调整值,或者说是我们将要加深颜色的值。接下来,我们将使用的颜色(在这种情况下,红色)转换为有效的 RGBA 值,并将其拆分为 RGBA 数组。然后我们将rgba数组中的每个值乘以加深值,并将其重新格式化为有效的颜色,然后再将其转换为 HEX 值。

一旦创建了每个函数,我们就可以从我们的 gulp 任务中引用它,如下所示:

gulp.task('autoprefixer', function() {
  return gulp.src('src/*.css')
  .pipe(postcss([  autoprefixer, functions({
    functions: {
      tint: tintColor,
      darken: darkenColor,
      sepia: sepiaColor
    }
  })
  ]))
  .pipe(gulp.dest('dest/'));
});

所有这些函数都使用类似的过程,但使用rgba[]数组中的值进行的主要计算,例如添加色调(tintColor),或者应用棕褐色效果(sepiaColor),将会不同。

你可能会问的问题,不过,我们是从哪里得到这些计算的?嗯,互联网上有许多可用的资源,例如这个 Stack Overflow 上的链接:stackoverflow.com/questions/6615002/given-an-rgb-value-how-do-i-create-a-tint-or-shade。另一个可能值得一看的替代方案是 Chris Coyier 的 CSS Tricks 网站,在css-tricks.com/snippets/javascript/lighten-darken-color/。然而,实际上,我迄今为止看到最好的网站是在 CamanJS 库中,在www.camanjs.com;这个演示中的示例是基于这个库中可用的函数,在camanjs.com/docs/filters.html

小贴士

如果你想要检查特定色调或阴影应该显示的颜色值,一个有用的提示是查看highintegritydesign.com/tools/tinter-shader/

与 CSS3 过滤器比较

在这个阶段我们必须问的一个关键问题是:“为什么我们要费劲去创建单独的函数,当我们可以轻松地使用像 CamanJS 这样的库呢?”

嗯,我们选择在演示中使用的那种方法有一些关键原因:

  • CamanJS 是一个很棒的库,可以产生一些奇妙的效果,但它是一个外部依赖;我们面临的风险是未来可能会停止开发,这可能会影响我们的代码。

  • 使用 PostCSS 意味着我们可以移除对外部库的依赖,我们可以控制哪些效果应该包含,哪些是多余的。如果我们使用像CamanJS这样的库,我们可能被迫包含大量不必要的额外代码。

  • 并非每个浏览器都支持标准的 CSS3 过滤器——使用 PostCSS 给我们提供了一个机会来设计我们自己的过滤器,从而应用类似的效果。

  • 我们始终可以使用现有的处理器,例如 SASS,但同样,我们依赖于外部库;使用 PostCSS 意味着我们仍然可以应用相同的原则,但无需依赖。

然而,关键在于浏览器对过滤器的支持非常好,除了 IE——我们应该始终考虑首先使用 CSS3 过滤器,但可以创建一个针对 IE 的特定样式表,允许我们在 PostCSS 中使用自己的版本。

为你的图片添加 Instagram 效果

使用 PostCSS 创建过滤器不应该总是无聊的,我们可以绝对地通过过滤器玩得开心!通过使用 Instagram 过滤器为图片添加一些额外样式是一个快速简单的方法——幸运的是,有一个预构建的插件可以用于此目的。

进入可从github.com/azat-io/postcss-instagram获取的Instagram插件。让我们开始并创建一个简单的演示:

  1. 我们将像往常一样,从本书附带的代码下载中提取Tutorial27文件夹的副本——将其保存到项目区域。

  2. 接下来,提取gulpfile.jspackage.json的副本,用这些新副本替换存储在我们项目区域根目录中的任何副本。

  3. 现在,我们需要安装postcss-instagram插件,所以请启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  4. 在提示符下,输入以下命令,然后按Enter键:

    npm install postcss-instagram --save-dev
    
    
  5. style – pre-compile.css文件复制到项目区域根目录下的src文件夹中,然后将其重命名为style.css

  6. 一旦插件安装完成,在提示符下输入gulp,然后按Enter键。

  7. 如果一切顺利,PostCSS 将编译我们的代码,我们应该在dest文件夹中看到通常的文件;将这些文件复制到Tutorial27文件夹内的css文件夹中。

在这一点上,如果我们尝试在浏览器中预览结果,我们应该看到类似于这个屏幕截图的内容:

为你的图片添加 Instagram 效果

这个问题的关键在于主 CSS 样式表中,我们可以在规则中使用这个来应用所需的过滤器:

为你的图片添加 Instagram 效果

这将应用1977过滤效果(插件提供的过滤效果之一)。如果我们查看编译后的代码,我们可以看到插件添加了一些额外的规则;一个用于创建过滤效果,两个用于在图像上定位过滤效果。

如果我们查看编译后的代码,我们可以看到插件所做的更改:

将 Instagram 效果添加到你的图像

如果你真的想深入了解,那么查看这个插件的源代码是值得的,可以在github.com/azat-io/postcss-instagram/blob/master/index.js找到。它相当复杂,但如果你仔细观察,可以看到用于将效果应用到我们图像上的过滤代码的迹象。

摘要

处理图像和颜色可能非常有成就感,也可能有些令人畏惧,这取决于我们使我们的过程多么简单或复杂!幸运的是,PostCSS 可以帮助自动化我们相当一部分的过程,所以让我们花点时间考虑一下在这一章中我们涵盖了哪些内容。

我们从添加媒体资源开始,并使用 PostCSS 自动更新资源链接,这有助于消除我们意外使用错误链接的风险!

我们接着转向图像处理,首先研究了创建图像精灵,最初使用 SASS,然后过渡到使用 PostCSS。接下来,我们更深入地研究了如何修改图像,我们使用了Evil Icons SVG 库,并设置了 PostCSS 在编译时改变每个图标的颜色。然后我们继续学习如何切换到 WebP 图像格式;虽然大多数人可能会使用标准格式图像,但我们学习了在使用受支持的浏览器时切换 WebP 图像是多么容易。

接下来,我们将注意力转向通过使用特定的调色板来处理颜色,我们介绍了如何使用 PostCSS 编译人类可读的颜色名称,然后在样式表中混合或修改它们。然后我们稍微提高了一些难度,研究了使用 PostCSS 应用特定的颜色过滤效果,以改变所选颜色的颜色级别。然后我们探讨了使用标准插件的一些缺点,以及为什么我们可能需要创建我们自己的自定义过滤效果,这些过滤效果可以在代码编译期间应用。然后我们以快速查看一些有趣的 Instagram 过滤效果结束本章,我们可以轻松地看到如何将多个过滤效果组合在一起来处理我们网站中的图像。

哇,我们确实覆盖了大量的内容!但我们的旅程还没有结束:在下一章中,我们将探讨如何创建网格,然后我们可以使用这些网格在我们的项目中构建布局。

第六章。创建网格

在创建基本网站布局时,有几种不同的路径可以选择,在许多情况下,开发者可能会决定使用 CSS 网格。

对于使用 CSS 预处理器的人来说,一个经典的例子当然是 SASS 网格系统,Bourbon Neat——一个很棒的包,但需要安装 Ruby 才能使用。我们可以在 PostCSS 中轻松解决这个问题,通过使用几个可用的插件之一,无需额外的依赖。在本章中,我们将探讨可用的选项,并通过一些示例来演示如何使用 PostCSS 内置的插件创建网格。

在本章中,我们将涵盖多个主题,包括:

  • 介绍使用 CSS 网格的基本原则

  • 探索 PostCSS 中可用的网格插件

  • 使用 Bourbon Neat 通过一些简单的示例进行操作

  • 使用 PostCSS 插件 PostCSS-Neat 复制纯 SCSS 示例

  • 使用 PostCSS-media-minmax 插件添加响应式功能

让我们开始吧…!

介绍网格设计

在设计中使用网格的原则并不新颖,它们可以追溯到第二次世界大战,当时许多平面设计师对传统页面布局的设计提出了质疑,转而设计一个既灵活又连贯的布局系统。

这些原则已经转移到网络上,从最初的纯 HTML 和基于 CSS 的设计开始,然后是更新的框架接管并帮助简化构建过程。

无论设计如何构建,我们当然可以使用 HTML 和 CSS,或者我们可能更喜欢图像模板方法(使用如 PhotoShop 的包),尤其是如果前端设计的责任落在不同的团队上。

这些方法是完全可以接受的,但需要大量的手动工作——在这个网页设计时代,时间至关重要;我们可以利用更新的框架(如 SASS、Bourbon Neat 或 Bootstrap)来创建我们的网格,如本例所示(使用纯 SASS):

介绍网格设计

如果我们从这个书的代码下载中提取 Tutorial28 文件夹,然后使用浏览器查看它,我们就可以看到这种布局的实际效果。这个演示使用的 style.css 文件是在线 SASS 游乐场 Sassmeister 上创建的,网址为:www.sassmeister.com

在这个演示中使用的代码大部分都与每个列宽和整体 .wrapper 容器有关;如果你查看代码,你会注意到列宽没有静态值。有几个静态值,但它们的大小对整体效果并不关键。

我们演示的关键在于这个 CSS 样式块:

介绍网格设计

在这里,我们使用 SASS 的插值功能首先构建我们的媒体查询(使其响应),然后是形成网格的一系列列的样式。编译后,它创建了一系列应用于我们网格各个部分的样式:

介绍网格设计

这只是一个将样式与网格上显示的数字匹配的问题。如果我们想更改宽度,我们只需增加列数,我们的for语句将在下一次编译时自动计算出一组新值。

好了,闲话少说:我认为现在是时候进行演示了!在本章中,我们将从使用 SASS 的一些基本示例开始,通过使用 Bourbon Neat,最后转换为使用 PostCSS 插件。我们总是要从某个地方开始,所以让我们从使用 SASS 自动化编译过程开始。

自动化编译过程

“安装 SASS?”我听到你问为什么,当这本书是关于 PostCSS 的时候?

我听到了,这是一个好问题:虽然这看起来有些疯狂,但其中确实有逻辑——让我来解释一下:

当我们在安装 SASS 时,我们不会使用标准的安装路径;相反,我们将使用gulp-sass插件。这允许我们开始使用gulp文件;这使我们更接近将我们的流程转换为使用 PostCSS。使用gulp文件提供了一个方便的框架,我们可以在此过渡到使用 PostCSS 时切换组件。

注意

在第十二章中,我们将看到 混合预处理器 如何与 PostCSS 很好地协同工作,作为采用一致方法编译代码的基础。

所以,无需多言,让我们开始安装gulp-sass插件,然后再投入使用:

  1. 我们首先启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  2. 在提示符下,请输入以下命令,然后按 Enter 键:

    npm install gulp-sass --save-dev
    
    

    不要关闭窗口,我们很快就会用到它!

  3. Node 将退出并安装gulp-sass;安装完成后,它将返回到提示符。

  4. 插件安装完成后,我们现在需要编译我们的代码——请将Tutorial29文件夹的副本提取到我们的项目区域。

  5. sass – pre-compile文件夹的内容复制到我们的项目区域根目录下的src文件夹。

  6. 我们还需要将Tutorial29文件夹中的gulpfile.jspackage.json文件添加到我们的项目区域的根目录。

  7. 回到我们之前打开的 Node.js 窗口,然后在提示符下输入gulp并按 Enter 键。

  8. 文件现在将进行编译——一旦完成,请将它们复制到Tutorial29文件夹内的css文件夹中。

  9. 尝试在浏览器中预览我们工作的结果;如果一切顺利,我们应该看到类似于这个截图的内容:自动化编译过程

好的,我们现在有了自动编译的支持;“接下来是什么?”我听到你问。我们又近了一步,因为我们的代码现在可以自动编译了:

自动化编译过程

然而,仍然需要手动努力来构建我们的网格!让我们现在开始改变这一点,有几个框架可供我们使用,但在我看来,最干净的是 SASS 的 Bourbon Neat。我们将以此为基础进行接下来的几个练习,然后再迁移到使用该框架的 PostCSS 版本。

添加 Bourbon Neat 支持

对于初学者来说,SASS 的网格功能由 Bourbon Neat 插件提供(可在neat.bourbon.io/找到)。为了我们练习的目的,我们将使用框架的 Node 版本——这需要完成两个安装,所以现在我们就来做吧:

  1. 如果它仍然打开,请从上一个演示中的 Node.js 命令提示符会话中恢复;否则,打开一个新的会话并将工作文件夹更改为我们的项目区域。

  2. 在提示符下,依次输入这两个命令,每次输入后按Enter键:

    npm install node-bourbon --save-dev
    npm install node-neat --save-dev
    
    
  3. 当我们看到类似于以下截图的结果时,这两个插件都将正确安装:添加 Bourbon Neat 支持

  4. 现在插件已经安装,我们需要修改我们的gulp文件——请将以下内容添加到第 5 行:

    var neat = require('node-neat').includePaths;
    
  5. 接下来,留一行空白,然后在第 7 行添加如下所示的代码:

    var paths = {
      scss: 'src/*.scss'
    };
    
  6. 可以用这个新任务替换原始的 SASS 任务:

    gulp.task('styles', function () {
      return gulp.src(paths.scss)
      .pipe(sass({
        includePaths: require('node-neat').includePaths
      }))
      .pipe(gulp.dest('dest/'));
    });
    
  7. 默认任务中对 SASS 的引用现在是不正确的——请将其更改为:gulp.task('default', ['styles']);

  8. 最后,按照指示更改此行:

    var watcher = gulp.watch('src/*.scss', ['styles']);
    
  9. 我们现在可以测试我们的编译过程了——请从代码下载中提取style – pre-compile.scss的内容的副本,并将其保存到src文件夹中。

  10. 到目前为止,我们可以从 Node.js 命令提示符中运行gulp。如果一切顺利,我们应该在dest文件夹中看到一个style.css文件出现。如果我们打开它,我们应该看到一些编译后的样式,如下所示,这证明了 Neat 已安装并正在工作:

    @media only screen and (min-width: 30rem) {
      .wrapper {
        width: 95%;
        max-width: 72rem; }
      .col-1 {
        width: 8.33333%; }
    

到目前为止,我们现在有一个工作的编译过程,我们可以开始构建一个工作的网站了!现在,不要过于担心编译后的test.css文件中的单个样式,我们将在接下来的几页中更详细地介绍这一点。让我们将我们的新编译过程付诸实践,并组装一个工作示例,以便我们可以看到网格功能在实际中的应用。

使用 Bourbon Neat 创建示例

使用 Bourbon Neat 构建网站是一个简单的过程,它不需要在我们的网页上添加任何特殊的标记;所有的努力都在编译后的样式表中。

为了证明这一点,我们将构建一个简单的网页,它很容易成为任何网站的一部分——我已经使用日本主题作为我页面的基础,但我们将使用的原则可以适用于任何网站。你会看到(除了标准 SASS 代码风格外),我们只使用了三个 Bourbon Neat 特定的代码实例。

让我们开始吧:

  1. 从本书附带的代码下载中,请提取Tutorial30的副本,并将其保存到我们的项目区域的根目录下。

  2. Tutorial30文件夹内的sample site – pre-compile的内容复制到我们的项目区域内的src文件夹中。然后将其重命名为sample.scss

  3. 接下来,启动一个 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  4. 在提示符下,输入gulp,然后按Enter——Node.js 将编译代码;如果一切顺利,我们应该在dest文件夹中看到两个编译后的样式表和一个源映射。

  5. 请将dest文件夹的内容复制到Tutorial30文件夹根目录下的css文件夹中。

如果我们尝试预览我们的工作结果,我们应该看到一个时尚的页面出现,带有我们的日本主题:

使用 Bourbon Neat 创建示例

这个演示涵盖了几个关键点和有用的技巧,所以让我们深入探讨它们。

更详细地探索我们的演示

到目前为止,你可能惊讶地发现,我们的第一个技巧与 SASS 或甚至 Bourbon Neat 没有直接关系,而是与配色方案有关!

“为什么”,我听到你问,“我们首先讨论的是配色方案?” 这有一个很好的原因:我们使用了变量来引用我们的颜色,但同样可以使用 SASS 函数来创建这些值。我们已经在第五章中讨论过这一点,即管理颜色、图像和字体,其中我们介绍了使用postcss-color-function插件来构建这些值;我们将在本章后面再次使用它。

然而,真正的技巧是使用 Lokesh Dhakar 的一个巧妙的小程序,名为Color Thief(托管在lokeshdhakar.com/projects/color-thief/)。我们可以简单地拖放我们的标题图像进去,并得到一整套合适的颜色:

更详细地探索我们的演示

唯一的缺点是它不提供颜色值;我们可以从页面的源代码中获取这些值。

注意

如果你更喜欢使用 RGB(A)颜色,那么像Color Hexa(colorhexa.com)这样的网站将非常有帮助。

我们演示的关键在于第 33 行、第 63 行和第 69 行——这些是控制外部容器格式的 Bourbon Neat 混入(第 33 行):

更详细地探索我们的演示

它们还控制着内部两个内容区域(第 63 行和第 69 行)的格式:

更详细地探索我们的演示

编译时,outer-container 混合器会给 .wrapper 类添加一个 72% 的 max-width,而 span-columns() 混合器会给每个元素添加 floatdisplaywidthmargin-right 属性,如下所示:

探索我们的演示细节

除了 outer-container()span-columns() 混合器之外,演示尽可能多地使用了百分比值,当指定了 rempixel 值时,然后在调整这些元素的大小时保持一致的设计就不那么关键了。

然而,我们将在本章的后面部分进行一些改进,当我们提高演示的响应能力时。现在,让我们继续我们的转换,并介绍将 PostCSS 插件的使用引入我们的流程中。

探索 PostCSS 的网格插件

在本章中,我们使用 SASS 和 Bourbon Neat 来生成我们的网格。这是一个完全有效的选择,但并非唯一的选择。我们可能更喜欢使用像 Bootstrap 或语义网格系统这样的东西;最终,选择使用哪个网格系统取决于我们的个人选择,基于我们的偏好和需求。

到目前为止,我们一直专注于使用 Neat。这主要是因为熟悉和易用。然而,将来的某个时刻,我们需要转向使用 PostCSS——美的是,有一个专门的插件可以在 PostCSS 中使用 Neat,在 github.com/jo-asakura/postcss-neat。这不是 PostCSS 可用的唯一网格系统插件,所以让我们花点时间来介绍其他可以使用的插件:

  • Grid: 可从 https://github.com/andyjansson/postcss-grid 下载,这个插件将一些配置在 PostCSS 和样式表中之间分割,这有助于简化格式化每个列所需的计算。

  • Lost: 可从 github.com/corysimmons/lost 获取,它将自己描述为网格系统的 Autoprefixer;它为大多数预处理器提供支持,例如 Less、SASS 或 Stylus。

  • Simple-grid: 从 github.com/admdh/postcss-simple-grid 获取,这个插件采取了不同的路线:所有的配置都是在 CSS 中完成的,而不是在任务配置中。

不再拖延,是我们进行转换的时候了——让我们从安装和配置插件开始使用。

转向使用 PostCSS-Neat

转向使用 PostCSS 相对直接。我们当然需要更新我们的编译过程,以移除对 SASS 的链接,并引入我们的 PostCSS 插件。

小贴士

转换过程将在本节和接下来的两节中完成。

在更改 CSS 方面,稍微复杂一些,因为我们必须确定每个网格块所需的列数。幸运的是,我们的例子相对简单,因为我们已经用适当的列数对原始块进行了编号,因此我们可以将其作为更改 CSS 的基础。

让我们从更新我们的编译过程开始:

  1. 我们将首先从本书附带的代码下载中提取 Tutorial31 文件夹的副本。将其保存到我们的项目区域根目录。

  2. Tutorial31 文件夹中,提取 package.jsongulpfile.js 文件的副本。将这些文件保存在我们的项目区域根目录下。

  3. 接下来,我们需要安装 postcss-neat 插件。为此,打开 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  4. 在提示符下,请输入以下命令,然后按 Enter 键:

    npm install postcss-neat --save-dev
    
    
  5. Node 将安装我们的插件——当我们看到这个确认信息时,插件已安装:过渡到使用 PostCSS-Neat

现在我们已经安装并配置了一个插件供使用。在我们创建测试以确认它正常工作之前,让我们快速查看我们的 gulp 文件,位于我们的项目区域根目录。

如果你期待一个复杂的配置,那么很抱歉让你失望——它甚至比在他们的网站上使用常规方法安装 Bourbon 和 Neat 更简单!我们的 gulp 文件在文件开始处包含了每个插件所需的变量调用,在文件末尾有一个监视功能。对我们来说,感兴趣的这部分是:

gulp.task('neat', function () {
  var processors = [
    require('autoprefixer-core')({ browsers: ['last 1 version'] }),
    require('postcss-neat')(/* { options } */)
  ];
  return gulp.src('src/*.css')
    .pipe(require('gulp-postcss')(processors))
    .pipe(gulp.dest('dest/'));
});

这种设置应该能满足大多数场景,默认为 12 列;如果需要覆盖它,我们可以在配置对象中指定适当的选项:

postcss([
  ...
  require('postcss-neat')({
    neatMaxWidth: '128em'
  })
  ...
])

我们将在本章的 测试我们的配置部分 中使用此选项,当我们构建测试示例时。

注意

要查看可以修改的完整属性列表,请访问 github.com/jo-asakura/postcss-neat#custom-settings

我们现在已经建立了一个基本的配置,但等等……它看起来有点短!那些有敏锐眼光的人应该会注意到,我们在之前的练习中的 gulp 文件中包含了额外的选项,例如创建源映射或压缩我们的 CSS 文件。现在让我们修正我们的 gulp 文件,包括这些缺失的选项。然后,一切都将准备就绪,以便我们创建示例网站。

精炼我们的任务列表

我们当前的 gulp 文件已经可以使用了,但它并不像它本可以做到的那样有用——我们在之前的练习中构建了一些任务,但这里缺少了哪些。

一个完美的例子是添加源映射,但我们也想对代码进行压缩?让我们花点时间来精炼我们的任务列表,并添加缺失的任务:

  1. 第一个任务是添加一些变量,它们将作为我们将要使用的各种插件的参考——这些变量直接放在gulp文件顶部的最后一个var语句之后:

    var cssnano = require('cssnano');
    var sourcemaps = require('gulp-sourcemaps');
    var rename = require('gulp-rename');
    var stylelint = require('stylelint');
    var reporter = require('postcss-reporter');
    
  2. 首先要添加的任务是添加一个用于检查我们样式的工具:

    gulp.task("lint-styles", ['neat'], function() {
      return gulp.src("dest/css/*.css")
        .pipe(postcss([ stylelint({
          "rules": {
            "color-no-invalid-hex": 2,
            "declaration-colon-space-before": [2, "never"],
            "indentation": [2, 2],
            "number-leading-zero": [2, "always"]
          }
        }),
        reporter({
          clearMessages: true,
        })
      ]))
    });
    
  3. 在检查了样式的准确性和一致性之后,我们现在可以压缩我们的代码。添加以下代码块:

    gulp.task('rename', ['lint-styles'], function () {
      return gulp.src('dest/css/*.css')
        .pipe(postcss([ cssnano() ]))
        .pipe(rename('style.css'))
        .pipe(gulp.dest("dest/css"));
    });
    
  4. 下一步是添加一个源映射选项:

    gulp.task('sourcemap', ['rename'], function () {
      return gulp.src('dest/css/*.css')
        .pipe(sourcemaps.init())
        .pipe(sourcemaps.write('maps/'))
        .pipe(gulp.dest("dest/css"));
    });
    
  5. 在我们的gulp文件中添加了这些功能后,我们需要调整主要默认任务以调用这些附加任务:

    gulp.task('default', ['neat', 'lint-styles', 'rename', 'sourcemap']);
    
  6. 我们已经设置了一个监视功能,但它对这些额外任务一无所知;现在让我们添加它们:

    var watcher = gulp.watch('src/*.css', ['default',  'lint-styles', 'rename', 'sourcemap']);
    

现在我们有一个工作的gulp文件,它包括了我们的练习所需的全部配置任务——让我们通过编译一些示例代码来测试它,以确认它按预期工作。

测试我们的配置

我们流程的关键部分是测试我们的gulp文件以确保它正常工作;它不仅应该运行所有必需的任务,而且应该按照正确的顺序运行,并产生预期的结果。尽管我们已经在gulp文件中重用了现有代码,但我们已经对gulp文件进行了重大修改——让我们花点时间测试它是否正常工作,使用我们之前的演示中的代码。

为了让我们的演示在 PostCSS 下工作,我们需要对我们的代码做一些修改:

  1. 我们首先将style.scss文件(位于Tutorial31文件夹中的css文件夹内)重新保存为纯 CSS 文件,而不是 SASS 样式表,我们在之前的演示中已经移除了 SASS 的使用,使得.scss扩展名变得冗余。

  2. 在之前的演示中,我们使用了.wrapper类。这需要按照指示进行修改:

    .wrapper {
      @neat-outer-container;
      margin: 0 auto;
    }
    
  3. 我们的col-*类规则也需要更改。我们将用以下内容替换旧演示中的静态百分比:

    .col-1 { @neat-span-columns 1; }
    .col-2 { @neat-span-columns 2; }
    .col-3 { @neat-span-columns 3; }
    .col-4 { @neat-span-columns 4; }
    .col-5 { @neat-span-columns 5; }
    .col-6 { @neat-span-columns 6; }
    .col-7 { @neat-span-columns 7; }
    .col-8 { @neat-span-columns 8; }
    .col-9 { @neat-span-columns 9; }
    .col-10 { @neat-span-columns 10;}
    .col-11 { @neat-span-columns 11; }
    .col-12 { @neat-span-columns 12; }
    
  4. 我们的代码现在准备好了,所以请将style.css文件复制到项目区域的根目录下的src文件夹中。

  5. 接下来,启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  6. 在命令提示符中,输入gulp然后按Enter

  7. 如果一切顺利,我们应该在dest文件夹中看到一个编译好的style.css文件。如果我们打开它,我们应该看到一系列与每个列相关的样式显示,就像这个截图所示:测试我们的配置

  8. 如果我们尝试在浏览器中预览演示,我们应该看到类似于这个截图的内容。注意它与我们用 SASS 构建的原始版本多么相似:测试我们的配置

我们构建的演示几乎与原始版本相同。这证明了我们具备工作能力,我们可以用它来构建我们的网站。我们对代码所做的更改非常简单,我们添加了一个@neat-outer-container来定义我们的网站应该有多宽,然后是多个@neat-span-columns实例,来定义每个元素应该跨越多少列。

让我们把一些新的知识应用到构建一个更有用的东西上,以一个包含内容的示例网站的形式。我们将重用本章中早期创建的示例网站页面,并完成将其转换为使用 PostCSS 插件的转换。

使用 Neat 和 PostCSS 创建一个网站

记得我们之前在使用 Bourbon Neat 创建示例中提到的具有日本主题的演示。这是一个简单的演示,使用 Bourbon Neat 帮助我们创建网格。然而,缺点当然是依赖于 SASS!

好吧,我们可以解决这个问题:PostCSS 有一个插件可以模拟 Bourbon Neat,但它完全是用 JavaScript 编写的,因此没有对 SASS 的依赖。它易于安装和使用,在接下来的几页中,我们将讨论切换到这个插件所需的更改。

首先,让我们设置一下:

  1. 我们首先从本书附带的代码下载中提取Tutorial32文件夹的副本。将其保存到我们项目区域的根目录。

  2. sample pre-compile.css文件复制到我们项目区域根目录下的src文件夹。

  3. gulpfile.jssamplesite.htmlpackage.json文件复制到我们项目区域的根目录。这些文件应该替换掉任何现有的版本。

  4. 接下来,我们需要安装两个插件,尽管我们在本书的早期部分已经介绍了如何使用postcss-css-variables,但安装它们将确保正确地将引用添加到package.json文件中。请启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  5. 在命令提示符中,依次输入这两个语句,每输入一个后按Enter键:

    npm install postcss-css-variables --save-dev
    npm install postcss-nested
    
    
  6. 当两个插件都安装好后,输入gulp,然后按Enter键启动样式表的编译。

  7. 如果一切顺利,我们应该在dest文件夹中看到两个样式表和一个源map文件夹。将这些文件复制到我们项目区域根目录下的css文件夹。

  8. 如果我们启动samplesite.html的副本,我们应该看到我们的演示与之前一样出现,但这次没有对 SASS 的依赖:使用 Neat 和 PostCSS 创建网站

你是否注意到与之前这个演示的 SASS-only 版本有什么不同?希望没有;虽然它可能不会与原始版本像素完全一致,但它离得很近!然而,它确实表明,只要有一点独创性,就可以实现向使用 PostCSS 的过渡,同时仍然保持相同的结果。这将对你的代码和流程进行一些修改,所以让我们更详细地看看这些修改,从样式表开始。

将更改应用到 PostCSS

切换到 PostCSS 需要在gulp任务文件和样式表中做出一些更改。这些更改不是为了改变页面的外观,而是为了保持原始演示中的相同主题。对样式表所做的关键更改包括:

  • 我们导入的 _reset.scss 部分样式表将不再工作,因为我们正在移除对 SASS 的依赖。为了保持其使用,我们使用 Sassmeister 在线沙盒(www.sassmeister.com)创建了一个编译版本;然后我们可以从我们的标记页面链接到它。

  • 如果你查看 sample.css 的源版本,你会在文件顶部看到一个 :root 块;这取代了我们使用的 import 语句。这个块可以用来存储任何使用的变量,我们将在探索对 gulp 任务文件所做的更改时详细介绍这一点。

  • 我们不再需要以下三个语句;它们用于调试 Bourbon Neat 的 SASS 版本,然后被移除:

    $visual-grid: true;
    $visual-grid-color: #E6F6FF;
    $visual-grid-opacity: 0.4;
    
  • 我们使用 PostCSS 的等效语句来处理所有的变量声明。SASS 版本通过搜索和替换从 $...var(--….) 进行了修改,其中 ... 代表变量名。

  • 我们原始代码中有许多对 Bourbon 混合的引用需要更新。我们使用了相同的搜索和替换原则,这次将 @include outer… 改为 @neat-outer… 整个代码中。

  • 为了保持简单,我们手动计算了任何 $body-line-height 的引用实例,并用计算结果替换了计算。我们本可以继续使用计算,但这将需要使用另一个插件,这对于我们代码中有限的用途来说将是过度杀鸡用牛刀。

  • 我们还调整了页面中主要区域的宽度;这是一个小问题,但确保我们有两个区域并排而不是一个在上一个在下是必要的!

除了更改样式表,我们还必须对 gulp 任务文件进行一些更改。它们主要集中在替换主要编译任务,并添加额外的任务来管理源文件的生产和压缩:

  • 我们添加了在早期演示中覆盖的 rename、lint-styles 和 sourcemap 任务。这些任务已经工作得很好,并且不需要修改。

  • 我们移除了原始的样式任务,并用这个替换了它:将更改应用到 PostCSS

    这次,我们称它们为 nested()cssvariables() 和 Neat 插件。这些是引用的变量,并且在我们的 gulp 文件顶部添加。

  • 我们最后的更改是在 gulp 文件的末尾,在那里我们必须调整默认和监视任务以包括我们添加到 gulp 文件中的额外任务。

在这个阶段,我们是否有一个可以使用的演示工作版本?好吧,还不完全,但让我们尝试调整我们的演示大小:

将更改应用到 PostCSS

嗯,我们的内容怎么了?看起来并不好,不是吗?不过我们可以轻松地修复它;这只需要添加一些媒体查询来重新组织内容在屏幕上的显示方式。让我们深入了解一下,看看需要什么来让我们的演示在更小的尺寸下看起来更好。

添加响应式功能

虽然 Bourbon 确实为我们的代码添加了一定程度的响应性,但这还不足以满足我们的需求。如果我们尝试调整我们的演示,很快就会很明显,元素并没有达到我们想要的位置,至少可以说是不够理想!

要快速查看设计在调整以适应较小设备时的外观,可以使用 Google Chrome。按 Shift + Ctrl + I 启用 Chrome 的开发者工具:

添加响应式功能

当在 1280px x 1024px 的分辨率下查看时,设计效果良好,但如果我们将可用的查看区域更改为适合苹果 iPhone 6 的 375px x 627px,情况很快就会改变:

添加响应式功能

你明白我的意思吗?这看起来就是不对劲,不是吗?幸运的是,使用 PostCSS 很容易修复,所以让我们深入了解一下需要做什么才能让我们的设计恢复正常工作。

修正设计

当使用 PostCSS 时,让我们的设计在较小的设备(如 iPhone)上正常工作很容易:我们可以使用从 github.com/postcss/postcss-media-minmax 可用的 postcss-media-minmax 插件。

“PostCSS 如何帮助我们呢?”你可能会问。很简单,当使用媒体查询时,大多数人会在这里犯错,即在设置断点或确定我们的设计在特定尺寸下会断裂的地方。postcss-media-minmax 插件有助于使文本更符合人类习惯;毕竟,如果一个设计在尺寸大于或等于某个值时工作,为什么不在我们的代码中这样表达呢?

为了说明我的意思,让我们着手修复我们的代码。为了简单起见,我们将完全专注于调整 iPhone 6 的内容大小,使用 375px x 627px 作为我们的断点(如使用 Google Chrome 的响应式设计视图所确定)。我们将继续从上一个演示结束的地方开始:

  1. 我们首先需要安装 postcss-media-minmax 插件——为此,启动 Node.js 命令提示符会话,然后在提示符中添加此命令并按 Enter

    npm install postcss-media-minmax --save-dev
    
    
  2. 接下来,打开项目区域 src 文件夹中的 sample.css 文件副本。我们首先添加媒体查询,并调整以确保我们捕捉到正确的断点:

    /* media queries */
    @media screen and (width >= 370px) and (width <= 630px) {
    
    }
    
  3. 在查询立即内部,添加这条规则。我们不想将其调整到低于 375px 的最小值:

      body {
        min-width: 375px;
      }
    
  4. 标题图像文本需要调整到更小的空间,我们还可以将其缩小并稍微向左移动:

      header {
        width: 50%;
        font-size: 2rem;
        margin-left: 45%;
      }
    
  5. #alpha 内容区域(或菜单)已经自动调整大小,但主要内容区域(#beta)太宽了;让我们将其调整到适合的大小。我们的区域无法容纳所有文本,因此我们将添加一个溢出属性,并将其设置为隐藏可视区域外的文本:

      #beta {
        margin-right: 2.35765160%;
        width: 55.1748%;
        overflow: auto;
      }
    
  6. 在这一点上,我们需要安装 postcss-media-minmax 插件,所以打开 Node.js 命令提示符,并将工作文件夹更改为我们的项目区域。

  7. 在提示符中,输入以下命令,然后按 Enter

    npm install postcss-media-minmax --save-dev
    
    
  8. 当插件安装完成后,在命令提示符中输入gulp,然后按Enter键。

  9. 现在,PostCSS 将编译代码,如果一切顺利,我们应该在dest文件夹中看到更新的样式表和源map文件。

  10. 将这些内容复制到Tutorial32文件夹中的css文件夹,然后在浏览器中预览结果。

如果一切顺利,当启用 Chrome 的响应式设计视图,并将设备设置切换到 Apple iPhone 6 时,我们应该看到以下类似截图:

纠正设计

我们对代码所做的更改很简单,仅限于支持 iPhone。但这只是冰山一角:我们还能做更多的事情!

例如,我们不必指定确切的宽度值作为我们的min-width属性(或者对于#beta 的宽度来说也是如此),我们可以考虑使用@neat-span-columns来为我们提供这个值。当然,我们不能只限制自己使用一个媒体查询,我们需要确保我们有足够的媒体查询来满足我们需要支持的设备。

这并不意味着我们需要在查询和设备之间建立 1:1 的关系。只要我们精心设计我们的查询,我们就可以将现有的查询设置为覆盖多个设备。然而,最终的原则仍然是相同的,但在使用 PostCSS 处理查询时,我们不需要使用标准的冒号表示法,而是可以使用更易读的>=<=符号来定义断点范围。

摘要

对于许多开发人员或设计师来说,使用基于网格的开发构成了他们工作流程的关键部分。许多人熟悉 Bootstrap 或 Bourbon Neat;我们可以在 PostCSS 中轻松复制相同的功能。让我们花点时间回顾一下本章中我们所学到的内容。

我们首先简要介绍了基于网格的开发方法,然后迅速过渡到开始使用 PostCSS 的过程。我们的第一个目标是查看自动化编译过程,以便我们可以切换到使用 Gulp。

接下来,我们探讨了从使用纯 SASS 切换到使用基于 SASS 的网格系统 Bourbon Neat 的过程;我们介绍了 Bourbon 如何以最小的努力构建我们的网格系统结构。

然后,我们转向探索 PostCSS 内部可用的插件选项,在过渡到使用postcss-neat插件之前。我们接着探讨了如何通过添加书中早期介绍的任务来简化我们的 Gulp 任务流程,以构建一个更接近现实世界开发的流程。为了确认流程有效,我们使用 Bourbon Neat 原始演示的改编版本进行了测试,然后继续将我们的日式演示转换为使用 PostCSS 等价插件。最后,我们简要地审视了设计中的响应能力优化,以确保它在较小的设备上表现更好。

哎呀,可能看起来并不多,但我们在过去的几页中确实覆盖了大量的内容!但是,就像往常一样,我们继续稳步前进:在下一章中,我们将真正地变得生动起来(抱歉,这里用了双关语!),我们将探讨 PostCSS 如何帮助实现内容的动画效果。

第七章:动画元素

一个问题:如果你有三个网站的选择:一个静态网站,一个动画做得不好的网站,以及一个通过微妙地使用动画增强的网站,你会选择哪一个?嗯,我的希望是那个问题的答案应该是第三个:如果做得好,动画真的可以使网站脱颖而出,如果做得不好,则可能彻底失败!

到目前为止,我们的内容相对静态,除了使用媒体查询。然而,现在是时候看看 PostCSS 如何帮助使动画内容变得更容易一些了。我们将在快速回顾动画的基本知识之后,探索从纯动画转向 SASS,最终转向 PostCSS 的路径。在本章中,我们将涵盖多个主题,包括:

使用 jQuery 动画内容的回顾:

  • 切换到基于 CSS 的动画

  • 探索使用预构建库(如Animate.css)的应用

  • 探索在切换到使用 PostCSS 时的可用选项

  • 使用 PostCSS 创建基于动画的演示

  • 学习如何使用 PostCSS 优化动画

让我们开始吧…!

回顾基本动画

动画正迅速成为 Web 开发的王者,越来越多的网站正在使用动画来帮助使内容生动并保持新鲜。如果做得正确,它们将为最终用户添加一个额外的体验层次;如果做得不好,网站很快就会失去比筛子更多的客户!

在本章的整个过程中,我们将探讨从编写标准动画到使用处理器(如 SASS),最后切换到使用 PostCSS 的转变。我不能保证我们会创建像Caaaat动画(roxik.com/cat/—尝试调整窗口大小!)这样的复杂基于 JavaScript 的演示,但我们将看到,在为浏览器创建动画时使用 PostCSS 实际上是非常容易的。

为了开始我们的旅程,我们将快速回顾传统的动画——这些年来你有多少次不得不在 jQuery 中使用.animate()?幸运的是,我们有 CSS3 的力量来帮助我们处理简单的动画,但曾经有一段时间,我们必须使用 jQuery 来动画化内容。

作为快速提醒,尝试从T34 – 使用 jQuery animate()的基本动画文件夹中运行animate.html。它可能不会让世界着火,但它是一个美好的提醒,让我们想起那些我们不知道更好的时光:

回顾基本动画

如果我们从浏览器(如 Firefox)的 DOM 检查器中查看这个动画的轮廓,它看起来可能就像这个截图:

回顾基本动画

虽然数字并不关键,但关键点在于两条绿色的虚线,以及结果显示出高度的不一致活动。这是一个很好的指标,表明活动是断断续续的,帧数低,导致动画跳跃且不够 100% 平滑。

然而,好消息是,有一些选项可以帮助提供更平滑的动画;在转向使用 PostCSS 之前,我们将简要地查看一些可用的选项。现在,让我们迈出第一步,开始减少对 .animate() 或 jQuery 的依赖,从查看减少对 .animate() 或 jQuery 的使用选项开始。

离开 jQuery

动画内容可能是一个有争议的话题,尤其是如果使用了 jQuery 或 JavaScript——如果我们对 100 个人进行一次简单的民意调查,询问他们使用的是哪种技术,我们可能会得到各种各样的答案!一个关键的回答是“视情况而定”,这很可能会出现在答案列表的前列;许多人会争论说,内容动画应该使用 CSS 来完成,而其他人则会肯定基于 JavaScript 的解决方案仍然有价值。

把那场……我们可以说……热烈的辩论放在一边,如果我们想要摆脱使用 jQuery,特别是 .animate(),那么我们有一些选项可供选择:

  • 升级您的 jQuery 版本!是的——这听起来可能与我们本章的主题相矛盾,但 jQuery 的最新版本引入了 requestAnimationFrame 的使用,这提高了性能,尤其是在移动设备上。

  • 一种快速且简单的方法是使用 jQuery Animate Enhanced 插件,可在 playground.benbarnett.net/jquery-animate-enhanced/ 获取;虽然有点过时,但它仍然发挥着有用的作用。它会在可能的情况下将 .animate() 调用转换为 CSS3 等效,但无法转换所有调用,因此未转换的调用将保持为 .animate() 调用。

  • 使用相同的原理,我们甚至可以利用 JavaScript 动画库 GSAP——Greensock 团队提供了一个插件(来自 greensock.com/jquery-gsap-plugin),该插件用他们自己的 GSAP 库替换了 jQuery.animate()。据说后者比标准的 jQuery 快 20 倍!只要稍加努力,我们就可以重新编写现有的代码——在用 .animate() 代替的情况下,我们可以将等效的 CSS3 样式添加到我们的样式表中,并将现有的 .animate() 调用替换为 .removeClass().addClass(),视情况而定。

  • 我们可以切换到使用库,如 Transit (ricostacruz.com/jquery.transit/),它仍然需要使用 jQuery,但比使用标准的 .animate() 命令性能更好。

  • 另一个替代方案是 Jonathan Shapiro 的 Velocity JS,可在 julian.com/research/velocity/ 获取;这个方案的好处是不依赖于 jQuery。甚至有传言说将库的全部或部分集成到 jQuery 中,作为 .animate() 的替代——更多详情,请查看问题日志 github.com/jquery/jquery/issues/2053

许多人自动认为 CSS 动画比 JavaScript(甚至 jQuery)更快。毕竟,我们不需要调用外部库(jQuery);我们可以使用浏览器中已经内置的样式,对吧?但事实并非如此简单——简而言之,正确使用任何一种方法都将取决于你的需求和每种方法的限制。例如,CSS 动画非常适合简单的状态变化,但如果需要排序,那么你可能不得不求助于使用 JavaScript 路径。

然而,关键不在于使用的方法,而在于屏幕上每秒显示的帧数。大多数人无法区分 60FPS 以上的画面——这会产生非常平滑的体验。任何低于大约 25FPS 的画面都会产生模糊,偶尔还会出现卡顿——这取决于我们选择最佳方法,以产生最有效的解决方案。

小贴士

要查看帧率的差异,请查看 frames-per-second.appspot.com/——这个页面上的动画可以控制;很容易看出为什么 60FPS 会产生更优越的体验!

那么,我们应该选择哪条路径呢?好吧,在接下来的几页中,我们将简要地查看这些选项中的每一个。在这个时候,你可能正在想,“这与 PostCSS 有什么关系呢,毕竟这是本书的主题?”

简而言之,它们都是改进动画运行方式或允许我们移除对 .animate() 依赖的方法,我们知道这并不高效!诚然,一些这些替代方案仍然使用 jQuery,但关键在于你的现有代码可以使用这些方法中的任何一种或它们的组合。在本章的后面部分,我们将探讨如何开始移除 jQuery,并更多地关注使用 PostCSS 插件系统来使用 CSS3 动画。

注意

值得注意的是,接下来的几页中的所有演示都是在运行 YouTube 视频的同时进行的;这是为了帮助模拟一点负载,以获得更真实的比较。在负载下运行动画意味着可用的图形处理能力更少,这会导致帧率计数降低。

让我们从查看我们的第一个选项,Transit.js 库开始。

使用 Transit.js 动画内容

在一个理想的世界里,我们构建的任何项目都将尽可能少地依赖外部库;这同样适用于基于 JavaScript 或 jQuery 的内容,也适用于 CSS 样式。

为了帮助减少依赖,我们可以使用像 TransitJS 或 Velocity 这样的库来构建我们的动画。关键在于利用这些库创建的动画,作为应用样式的基础,然后我们可以使用.addClass().removeClass()来操作这些样式。为了说明这一点,让我们通过一个简单的演示来探索这个概念:

  1. 我们首先打开animate.html的副本——为了简化,我们需要将square-small的引用从类更改为选择器:

    <div id="square-small"></div>
    
  2. 接下来,在</head>标签之前立即添加对 Transit 库的引用:

    <script src="img/jquery.transit.min.js"></script>
    
  3. Transit 库使用稍微不同的语法,所以请按照指示更新对.animate()的调用:

    smallsquare.transition({x: 280}, 'slow');
    

保存文件,然后在浏览器中预览结果——如果一切顺利,我们应该在演示中看到没有实质性的变化:

使用 Transit.js 进行内容动画

然而,动画将更加平滑——帧数更高,达到 44.28FPS,下降的次数更少:

使用 Transit.js 进行内容动画

让我们比较一下本章前面重新审视基本动画部分所取的相同配置文件截图——您注意到什么了吗?

分析浏览器活动可能很复杂,但我们只需要关注两件事:FPS 值和绿色线的状态。FPS 值,即每秒帧数,是原来的三倍多,而且大部分时间绿色线更加一致,下降的次数更少,持续时间更短。

这意味着我们有一个更平滑、更一致的性能;大约 44FPS 的平均帧率比使用标准的 jQuery 要好得多——但我们仍然在使用 jQuery!

然而,有一个区别——像 Transit 或 Velocity 这样的库会将动画尽可能转换为 CSS3 等价物——如果我们查看底层,我们可以看到这一点:

使用 Transit.js 进行内容动画

我们可以利用这一点,通过移除使用.animate()的需求,直接使用.addClass().removeClass()——我们将在本章后面的使用 jQuery 切换类部分看到这一点。

小贴士

如果您想比较使用 Transit 或 Velocity 时我们的简单动画,代码下载中提供了示例,分别是演示T35AT35B

为了更进一步,我们可以使用 Velocity 库创建一个使用纯 JavaScript 的版本——我们将在下一个演示中看到如何实现。但请注意——这并不是继续使用 JavaScript 的借口;正如我们将看到的,帧数之间的差异很小!

使用纯 JavaScript 进行动画

许多开发者习惯于使用 jQuery——毕竟,它使得引用页面上的任何元素都变得非常简单!然而,有时在原生 JavaScript 中工作可能更可取;这可能是为了速度。如果我们只需要支持较新的浏览器(如 IE11 或 Edge,以及 Chrome 或 Firefox 的最新版本),那么添加 jQuery 作为依赖项并不总是必要的。

Transit(或 Velocity)等库的优点在于,我们不必总是使用 jQuery 就能达到相同的效果;正如我们很快将看到的,移除 jQuery 可以帮助改善情况!让我们来测试一下,并将之前的示例修改为不使用 jQuery:

  1. 我们首先从本书附带的代码下载中提取T35B文件夹的副本。将其保存到我们的项目区域根目录下。

  2. 接下来,我们需要编辑此文件夹内animate.html的副本——请移除 jQuery 的链接,然后移除velocity.ui.min.js的链接;我们应该在文件的<head>中留下以下内容:

      <link rel="stylesheet" type="text/css" href="css/style.css">
      <script src="img/velocity.min.js"></script>
    </head>
    
  3. 在下面一点,按照所示修改<script>块:

      <script>
        var smallsquare = document.getElementById('square-small');
        var animbutton = document.getElementById('animation-button'); 
        animbutton.addEventListener("click", function() {
          Velocity(document.getElementById('square-small'), {left: 280}, {duration: 'slow'});
        });
    </script>
    
  4. 保存文件后,在浏览器中预览结果——如果我们使用 DOM 检查器监控我们示例的性能,我们可以在我们的示例中看到类似的帧率:使用纯 JavaScript 进行动画

由于 jQuery 作为依赖项不再存在,我们可以清楚地看到帧率有所提高;然而,缺点是,对某些浏览器(如 IE8 或 9)的支持减少了。这可能不会成为你网站的问题——微软和 jQuery 核心团队都已宣布将停止支持IE8 - 10IE8,这将有助于鼓励用户升级到较新的浏览器。

虽然说,出于速度和尽可能保持页面轻量化的考虑,使用 CSS3 是首选,但使用 Velocity 确实提供了一系列额外的机会,这些机会可能对你的项目有所帮助。不过,关键在于仔细考虑你是否真的需要它们,或者 CSS3 是否足够,并允许你使用 PostCSS。

使用 jQuery 切换类

到目前为止,有一个问题浮现在脑海中:关于基于类的动画怎么办?我的意思是,放弃对外部动画库的任何依赖,并切换到使用纯 jQuery 的.addClass().removeClass()方法。

从理论上讲,这听起来是个不错的想法——我们可以消除使用.animate()的需求,只需根据需要切换类即可,对吧?嗯,这是一个改进,但它仍然低于使用纯 JavaScript 和切换类的方法。这归结为使用 jQuery 的便捷性来引用元素与纯 JavaScript 的速度之间的权衡:

  1. 我们将首先打开之前练习中的animate.html副本——首先,请在我们文档的<head>中用以下行替换对 Velocity.JS 的调用:

    <script src="img/jquery.min.js"></script>
    
  2. 接下来,请移除<script>标签之间的代码,并用以下内容替换:

    var smallsquare = $('.rectangle').find('.square-small');
    $('#animation-button').on("click", function() {
          smallsquare.addClass("move");
    
          smallsquare.one('transitionend', function(e) {
            $('.rectangle').find('.square-small')
            .removeClass("move");
          });
        });
    
  3. 保存文件——如果我们在一个浏览器中预览结果,我们应该看到演示看起来没有明显的变化,但过渡比使用 jQuery 和 Transit 的组合性能更好:使用 jQuery 切换类

尽管如此,如果我们使用 DOM 检查器查看底层代码,真正的代码变化将变得明显:

使用 jQuery 切换类

我们不是使用.animate(),而是使用 CSS3 动画样式来移动我们的square-small <div>。大多数浏览器都会接受使用过渡和变换,但值得通过像 Autocomplete 这样的过程运行我们的代码,以确保我们为代码应用正确的供应商前缀。

在这里使用 CSS3 的优点是,虽然它可能不适合大型、复杂的动画,但我们至少可以开始使用外部样式表,如Animate.css,或者甚至使用预处理器如 SASS 来创建我们的样式。

这是一个很容易做出的改变,所以无需多言,作为我们使用 PostCSS 旅程的下一步,让我们更详细地看看这一点。

小贴士

如果你想要创建基于关键帧的自定义动画,那么请查看cssanimate.com/,它提供了一个基于 GUI 的界面来设计它们,并在请求时输出适当的代码。

利用预构建库

到目前为止,我们所有的动画都有一个共同点——它们是单独创建的,并且存储在每个项目相同样式的同一样式表中。

这将完美工作,但我们能做得更好——毕竟,我们可能会创建出其他人已经构建的动画!随着时间的推移,我们也可能积累一系列动画,这些动画可以成为库的基础,该库可以用于未来的项目。

已经有许多开发者这样做。一个值得注意的例子是由 Dan Eden 创建的Animate.css库。了解这个库是值得的,因为我们在本书中稍后将使用它作为 PostCSS 的 postcss-animation 插件。在此期间,让我们快速演示它是如何工作的,作为在 PostCSS 中使用它的先导。

注意

在这个演示中使用的图像直接引用自 LoremPixem 网站,作为占位符图像。

让我们开始:

  1. 我们将首先从本书附带的代码下载中提取T37文件夹的副本——将文件夹保存到我们的项目区域。

  2. 接下来,打开一个新文件并添加以下代码:

    body { background: #eee; }
    
    #gallery {
      width: 745px;
      height: 500px;
      margin-left: auto;
      margin-right: auto;
    }
    
    #gallery img {
      border: 0.25rem solid #fff;
      margin: 20px;
      box-shadow: 0.25rem 0.25rem 0.3125rem #999;
      float: left;
    }
    
    .animated {
      animation-duration: 1s;
      animation-fill-mode: both;
    }
    
    .animated:hover {
      animation-duration: 1s;
      animation-fill-mode: both;
    }
    
  3. 将其保存为style.css,在T37文件夹内的css子文件夹中。

  4. 在浏览器中预览结果——如果一切顺利,我们应该看到类似于以下截图的内容:利用预构建库

如果我们运行演示,应该会看到图像通过不同类型的动画运行——这里没有什么特别或复杂的地方;然而,问题是,这一切是如何与 PostCSS 结合起来的?

嗯,这有一个很好的原因——有些开发者以前使用过Animate.css,并且熟悉它是如何工作的;我们稍后也会在更新代码以使用 PostCSS中使用postcss-animation插件,它基于Animate.css样式表库。对于那些不熟悉样式表库的你们,让我们快速了解一下它的工作原理,在演示的上下文中。

分析我们的演示代码

我们演示中使用的效果非常引人注目——确实,有人可能会认为它们需要大量的复杂 JavaScript!

然而,这与事实相去甚远——Animate.css文件包含了许多基于@keyframe的动画,类似于这个:

@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {transform: translateY(0);}
  40% {transform: translateY(-1.875rem);}
  60% {transform: translateY(-0.9375rem);}
}

我们使用常规的库调用方法,在代码的<head>部分引入动画。然后我们可以在代码中通过名称调用任何动画:

  <div id="gallery">
    <a href="#"><img class="animated bounce" src="img/1" alt="" /></a>
...
  </div>
  </body>

你会注意到我们在代码中添加了.animated类——这控制了动画的持续时间和时机,并按照添加到代码中的动画名称来设置。

不使用 JavaScript(或 jQuery)的缺点是,当演示加载时动画只会运行一次;我们可以通过给被动画化的元素添加.infinite类来让它连续运行(这是 Animate 库的一部分)。我们可以在 CSS 中模拟点击选项,但这是一种实验性的技巧,并不支持所有浏览器——要实现任何形式的控制,我们实际上需要使用 JavaScript(甚至 jQuery)!

注意

如果你对这个技巧的细节感兴趣,那么请查看 Stack Overflow 上的这个回答,stackoverflow.com/questions/13630229/can-i-have-an-onclick-effect-in-css/32721572#32721572

好的,继续前进:我们已经介绍了预构建库的基本用法,例如 Animate。现在是时候提升一个档位,过渡到 PostCSS。作为开始,我们将使用 Gulp 作为我们的任务运行器,配合 SASS。后者是一个完美的选择,因为它与我们在本章后面将要使用的插件相匹配。让我们看看为我们的 PostCSS 转换打下基础需要哪些内容。

转向使用 SASS

作为开发者或设计师,如果我们的开发工作流程包括使用 SASS,那么使用这样的混入函数来构建我们的样式的诱惑是存在的:

@mixin transition ($value...) {
  @if length($value) >= 1 { // If we set value
    @include prefixer($property: transition, $value: $value);
  } @else { // If value is not set, return default value
    @include prefixer($property: transition, $value: all 0.15s ease-in 0.05s);
  }
}

这并没有什么问题,但如果我们需要使用不仅仅是少量的话,管理我们的混入函数将需要付出努力!更简单的选择是探索使用预构建的动画库,作为减少我们开发工作量的方式。

有许多开发者创建了用于处理动画的 mixin 库;一个完美的例子是 Geoff Graham 的 Animate 的 SASS 版本,可在github.com/geoffgraham/animate.scss下载。

然而,当我们与 mixin 一起工作时,我们必须注意一些事情——使用它们来管理供应商前缀真是太容易了,比如这样:

@mixin prefixer($property, $value) {
  -webkit-#{$property}: $value; // Attach webkit prefix (Chrome, Safary, Some Android native browsers)
  -moz-#{$property}: $value; // Attach moz prefix (FireFox)
  -o-#{$property}: $value; // Attach o prefix (Opera)
  #{$property}: $value; // no prefix (modern browsers and latest IE versions)
}

虽然在代码编译时会添加相关的供应商前缀,但这并不被认为是最佳实践。

我们的责任是确保每个动画都包含所有相关的供应商前缀——即使我们用心良苦,这也是一个挑战!还有添加不会产生任何效果规则的问题——例如,添加–o作为过渡的前缀是没有意义的;这个前缀不再需要。

幸运的是,有一种更好的方法来处理前缀——我们可以使用 Autoprefixer(来自twitter.com/autoprefixer)来自动为我们处理供应商前缀。Autoprefixer 的伟大之处在于它使用 Can I Use (www.caniuse.com)数据库来保持信息的更新。

有各种插件可供使用,允许我们使用任务运行器,如 Grunt 或 Gulp。当然,我们可以使用独立的基于 GUI 的编译器来完成这个目的,但为什么要在可以使用任务运行器提供更多功能的时候运行这样的东西呢?我们甚至可以使用几个插件中的任何一个来在运行新的编译之前移除供应商前缀——这将确保移除任何冗余的前缀。

考虑到这一点,让我们来实际操作一下。在接下来的演示中,我们将构建一个简单的画廊效果,展示我们之前看到的相同动画,但这次使用Animate.css的 SASS 版本。我们将使用 Gulp 作为我们的任务运行器来编译 SASS 代码,然后添加源映射,检查代码的一致性,添加供应商前缀等等。你明白我的意思!

话不多说,我们可以使用任务运行器做很多事情,所以让我们开始构建我们的画廊吧。

创建一个动画画廊

动画内容可能是一把双刃剑。如果使用得当,它真的可以将网站提升到下一个层次。如果做得不好,那么网站的访问量可能会急剧下降!

在我们上一个演示中,我们构建了一个简单的画廊效果——这更多的是为了展示我们可以添加的不同类型的动画效果,而不是制作出能获奖的东西。在接下来的几页中,我们将继续我们的演示,但这次将重新配置它以使用Animate.css的 SASS 版本。我们还将介绍使用任务运行器来编译我们的代码——因为这是使用 PostCSS 的要求,这似乎是一个开始使用它的完美时机,作为我们过渡到使用动画和 PostCSS 的最后一部分。

注意

本教程的文件位于附带的代码下载中的 T38 文件夹中。

不再拖延,让我们将更改添加到我们之前的演示中:

  1. 我们需要下载 Animate 的 SASS 版本——它可以从 github.com/geoffgraham/animate.scss/archive/master.zip 获取。请继续操作,将 Animate.scss-master 文件的内容提取到我们项目区域的根目录下的 src 文件夹中。

  2. src 文件夹中,将 Animate.scss 文件重命名为 _animate.scss——这是为了表明它是一个 部分,这防止了 SASS 编译器将其编译为单独的文件。

  3. src 文件夹中,请将其重命名为 style.scss——这将其更改为一个 SASS 文件,这在练习的后续编译中是必需的。我们的 src 文件夹中应该有类似以下截图的内容:创建动画画廊

  4. 请继续打开 style.scss 文件。在底部,将此行添加到样式表的顶部:

    @import "_animate.scss";
    
  5. 接下来,在样式表的末尾添加以下附加行——这些行从 Animate.css 的 SASS 版本中引入动画;时间也被延长到五秒,因为原始示例太快了:

    .bounce { @include bounce(); }
    .flip { @include flip(); }
    .hinge { @include hinge(); }
    .flash { @include flash(); }
    .shake { @include shake(); }
    .swing { @include swing(); }
    
    .animated:hover {
      animation-duration: 5s;
      animation-fill-mode: both;
    }

    
  6. 保存文件,然后将 T38 文件夹下 src 文件夹的内容复制到我们项目区域的根目录下的 src 文件夹中——我们很快将编译此文件。

  7. 在一个新文件中,请继续添加以下代码,然后将其保存为 gulpfile.js 到我们项目区域的根目录——这将形成我们的 gulp 文件,我们将使用它来编译我们的代码:

    'use strict';
    
    var gulp = require('gulp');
    var postcss = require('gulp-postcss');
    var sass = require('gulp-sass');
    
    gulp.task('sass', function () {
      return gulp.src('src/*.scss')
        .pipe(sass().on('error', sass.logError))
        .pipe(gulp.dest('dest/'));
    });
    
    gulp.task('default', ['sass']);
    
    var watcher = gulp.watch('src/*.scss', ['sass']);
    watcher.on('change', function(event) {
      console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
    });
    
  8. 我们还需要一个 package.json 文件——这个文件将存储我们将要使用的插件详情。目前,我们将限制自己使用 gulp-sass,但很快就会改变!请继续操作,将以下行添加到一个新文件中,并将其保存为 package.json,位于我们项目区域的根目录:

    {
      "name": "postcss",
      "version": "1.0.0",
      "description": "Configuration file for PostCSS",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "Alex Libby",
      "license": "ISC",
      "dependencies": {
        "postcss": "⁵.0.8"
      },
      "devDependencies": {
        "gulp": "³.9.0",
        "gulp-postcss": "⁶.0.0",
        "gulp-sass": "².1.1"
      }
    }
    
  9. 眼尖的你们应该注意到我们没有安装 gulp-sass 插件。现在让我们通过启动一个 Node.js 命令提示符,然后更改工作目录到项目区域来解决这个问题。请从提示符中运行以下命令:

    npm install gulp-sass --save-dev
    
    
  10. 在提示符中,输入 gulp 然后按 Enter 键——Gulp 将现在消失并编译我们的文件;如果一切顺利,我们应该在我们的项目区域的 dest 文件夹中看到一个编译后的样式表。

  11. 到目前为止,尝试在浏览器中运行 animate.html——如果一切顺利,我们应该看到我们的画廊效果没有变化,但可以放心,我们现在正在使用 Animate.css 的 SASS 版本。

我们的演示已经转换为使用Animate.scss——我们本可以选择使用任何一种编译器(例如 Koala—www.koala-app.com),但选择使用 Gulp。它作为过渡到使用 PostCSS 的完美途径——正如我们在早期的演示中所见,我们已经使用 Gulp 作为任务运行器来完成这个目的。这允许我们逐步过渡——当所有的 SASS 元素都转换为后,我们只需从 gulp 文件中删除任务即可完成过渡。

添加完成 touches

那么,接下来是什么?我们已经创建了一个基本的 gulp 任务文件,我们用它来编译我们的 SASS 代码到有效的样式。

但这只是故事的一小部分;我们需要添加更多内容,使我们的编译过程变得有用,并准备好转换为使用 PostCSS。

让我们开始吧:

  1. 我们需要做的第一个更改是在package.json文件中——请立即添加突出显示的行:

        "cssnano": "³.2.0",
        "gulp": "³.9.0",
        "gulp-postcss": "⁶.0.0",
        "gulp-rename": "¹.2.2",
        "gulp-sass": "².1.1",
        "gulp-sourcemaps": "¹.5.2",
        "postcss-reporter": "¹.3.0",
        "stylelint": "².3.7"
      }
    }
    
  2. 接下来,我们需要配置我们的 gulp 文件以添加一些额外的任务——第一个任务是添加对一些我们在本书早期已经使用过的额外插件的引用。请立即添加以下突出显示的行:

    var sass = require('gulp-sass');
    var autoprefixer = require('autoprefixer');
    var cssnano = require('cssnano');
    var sourcemaps = require('gulp-sourcemaps');
    var rename = require('gulp-rename');
    var stylelint = require('stylelint');
    var reporter = require('postcss-reporter');
    
  3. 在添加了额外的插件引用后,我们现在需要添加额外的任务——在 SASS 任务下方立即添加此任务;这管理着我们的代码的 linting,以确保一致性:

    gulp.task("lint-styles", ['sass'], function() {
      return gulp.src("dest/*.css")
        .pipe(postcss([ stylelint({
          "rules": {
            "color-no-invalid-hex": 2,
            "declaration-colon-space-before": [2, "never"],
            "indentation": [2, 2], 
            "number-leading-zero": [2, "always"]
          }
        }),
        reporter({
          clearMessages: true,
        })
      ]))
    });
    
  4. 立即将此任务添加到上一个步骤下方——这将在压缩过程中重命名文件:

    gulp.task('rename', ['lint-styles'], function () {
      return gulp.src('dest/*.css')
        .pipe(postcss([ cssnano() ]))
        .pipe(rename('style.min.css'))
        .pipe(gulp.dest("dest/"));
    });
    
  5. 我们接下来的 gulp 任务负责生成源映射——这可以在 SASS 中自动完成,但使用单独的插件可以在编译过程中提供灵活性。请立即将此任务添加到上一个任务下方:

    gulp.task('sourcemap', ['rename'], function () {
      return gulp.src('dest/*.css')
        .pipe(sourcemaps.init())
        .pipe(sourcemaps.write('maps/'))
        .pipe(gulp.dest("dest/"));
    });
    
  6. 我们几乎完成了——请按照指示更改这些行:

    gulp.task('default', ['sass', 'lint-styles', 'rename', 'sourcemap']);
    
    var watcher = gulp.watch('src/*.scss', ['sass', 'lint-styles', 'rename', 'sourcemap']);
    watcher.on('change', function(event) {
      console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
    });
    
  7. 保存文件。请启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  8. 在提示符下,键入gulp然后按Enter——Gulp 将退出并编译我们的文件。

  9. 如果我们查看dest文件夹,应该会看到相同的编译后的style.css文件,但这次还有源映射和样式表的压缩版本:添加完成 touches

如果我们预览我们的工作结果,我们应该看到在演示中没有功能上的变化,但可以放心,我们现在有可用的文件压缩版本——毕竟,在生产环境中始终使用压缩文件更好!

我们现在已经为我们的 PostCSS 转换奠定了基础——那些细心的读者应该已经注意到,PostCSS 的插件引用已经添加到我们的 gulp 文件中,为转换过程的下一阶段做好了准备。现在,我们的 gulp 文件中一切就绪,除了 SASS 任务——在适当的时候,我们将移除 SASS 任务,并用 PostCSS 的等效任务替换它;这将在我们的下一个练习中进行。在我们这样做之前,花点时间探索 PostCSS 生态系统内可用的功能是值得的——尽管提供的选项不多,我们仍然可以在 PostCSS 中生成可用的编译代码。

转向使用 PostCSS

好吧……是时候做出改变,转向 PostCSS 了!

在我们开始探索可用的功能之前,有一个关键问题我确信你们会问——既然这本书明显是关于 PostCSS 的,我们为什么还在探索使用 JavaScript 进行动画?

对于这个问题有一个非常好的答案——我们不仅正在探索可能采取的不同路线以过渡到 PostCSS,而且在更基本层面上,我们是否能够进行这种改变。这听起来可能有些矛盾,让我来解释一下:

动画的一个关键限制是 FPS 计数,即每秒帧数——jQuery 的标准.animate()方法臭名昭著地慢,并且没有针对速度进行优化。本章开头我们的动画的 FPS 计数显著低于 Velocity。同样适用于我们在本章早些时候也介绍过的替代 Transit 库。

转向使用 CSS 将提高帧率,但基于 CSS 的动画还不足以处理复杂的动画。这意味着作为开发者,我们必须评估项目的任何需求,权衡基于 CSS 的动画是否可行,或者我们是否需要回退到使用基于 JavaScript 的库。

这同样适用于使用 PostCSS——对于项目来说,使用基于 CSS 的动画可能很有吸引力,但这只会在我们的动画需求不会导致过于复杂、难以管理的解决方案时才有效。

探索 PostCSS 中的插件选项

假设 CSS3 动画适合我们的项目,那么我们现在可以开始过渡到使用 PostCSS。

如果你们期待看到一系列插件,那么很抱歉让你们失望——目前,只有四个插件可用于动画:

对于如此有限的插件范围,人们可能会想知道它能做什么!不过,我们稍后会继续添加到列表中。在章节的末尾,我们将创建一个postcss-animation-data插件的版本,允许我们使用来自www.minimamente.com/example/magic_animations/的 Magic 动画。

好的,让我们开始使用一些这些工具:现在是时候进行演示了!

更新代码以使用 PostCSS

虽然 PostCSS 生态系统目前还没有大量基于动画的插件,但这不应该阻止我们使用它来编译我们的动画样式。为了证明这一点,我们将修改之前演示的 jQuery 和.add/remove 类版本——我们将使用 PostCSS 从 Dan Eden 创建的Animate.css库中添加动画缓动。

我们需要的插件是postcss-animation插件,它可以从github.com/zhouwenbin/postcss-animation获取,并使用来自github.com/zhouwenbin/postcss-animation-datapostcss-animation-data插件。安装这个插件非常简单,它使用的方法与我们迄今为止安装的所有其他插件相同。

让我们开始演示:

  1. 我们将首先安装postcss-animation插件——为此,请打开一个 Node.js 会话,然后更改工作目录到我们的项目区域。

  2. 在提示符中输入以下命令并按 Enter 键:

    npm install postcss-animation --save-dev
    
    

    如果一切顺利,我们应该会看到插件安装成功:

    更新代码以使用 PostCSS

  3. 从本书附带的代码下载中,提取并保存T39文件夹的内容到我们的项目区域——我们将以此为基础转换为 PostCSS。

  4. 从教程文件夹的css子文件夹中打开style.css文件,然后在底部,按照指示修改.move规则:

    .move {
      animation-name: bounce;
      transform: translate(17.5rem, 0rem);
      transition-duration: 3.5s;
    }
    
  5. 将此文件保存到src文件夹中,然后启动 Node.js 命令提示符,并将工作文件夹更改为我们的项目区域。

  6. 在提示符中输入gulp然后按 Enter 键——如果一切顺利,我们应该会在dest文件夹中看到这些文件:更新代码以使用 PostCSS

  7. 最后一步是将此文件夹的内容复制到T39文件夹中的css文件夹内。

到目前为止,我们已经准备好测试我们的演示——如果我们尝试预览我们工作的结果,我们应该看到我们的演示外观没有变化,但可以放心,我们现在正在使用 PostCSS 编译我们的代码。

测试我们的修改后的代码

虽然我们可能看不到我们的演示外观有任何变化,但它的行为肯定会有所不同。为了查看这一点,我们需要查看我们的演示背后的代码。

对于这个演示,我们添加了一个动画名称属性,并将其命名为bounce;当编译时,PostCSS 会在代码中添加适当的@keyframes规则:

测试我们的修改后的代码

那么,如果我们看看性能,它如何比较?即使分配了额外的动画属性,与使用标准的.animate()相比,它仍然能拉出一个令人尊重的帧率 48.29FPS:

测试我们的修改后的代码

这有助于加强这样一个观点:在可能的情况下,我们可以通过移除代码中对.animate()的任何依赖来提高性能。使用 CSS 样式来动画化内容还远未准备好取代 JavaScript,但它正在慢慢接近!

好的,我们继续前进:我们简要地探讨了各种动画内容的方法;现在是时候过渡到使用 PostCSS 了。你见过多少次表单显示每个字段的标签位于上方或左侧的情况?当然,看久了会感到无聊,看到同样的老设计!这很容易改变,所以没有不这么做的理由。为了证明这一点,我们将使用 PostCSS 在字段获得焦点时将每个标签向上滑动。是的,你没听错…向上滑动。让我们看看我们如何为任何网站提供对这个备受推崇的功能的新视角。

在 PostCSS 中创建一个演示

作为之前练习的后续思考,我提出了问题:“你见过多少次表单显示标签位于字段上方或右侧的情况?”如果我为每个答案收集一角硬币,我怀疑我可能会在一个异国岛屿上,富有,而且无忧无虑——我已经数不清见过这种表单的次数,更不用说其他使用互联网的人了!

没有任何理由让表单平淡无奇。为了证明这一点,我们将创建一个简单的演示,使用由 Jonathan Neal 提供的postcss-transform-shortcut插件,可在github.com/jonathantneal/postcss-transform-shortcut获取。这是一个简单的插件,允许我们指定单个属性,该插件将它们组合成我们样式表中的一行代码。让我们快速安装它:

  1. 首先,启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  2. 在提示符中,输入此命令,然后按 Enter 键:

    npm install postcss-transform-shortcut --save-dev
    
    
  3. Node 现在将安装插件——当完成时,它将返回到一个闪烁的提示。

没有必要进行配置,尽管在我们可以使用它之前,我们必须完成一个小任务。

更新插件

在为本书进行研究时,我发现当前版本(1.0.0)中存在一个问题,即如果样式表中有多个规则,则无法正确编译;有时插件可能或可能不会在您的环境中工作,这就是其中之一!

幸运的是,这是一个简单的修复——如果我们查看项目区域node_modules文件夹中的postcss-transform-shortcut文件夹,我们应该能看到这个:

更新插件

简单地复制raw.githubusercontent.com/pc035860/postcss-transform-shortcut/07af8a78d1fb5e7fdeebc8c7f56c0c9ecdd83efb/index.js文件的内容,并将其直接粘贴到index.js的顶部;这应该可以解决该问题。

注意

这已经在开发者的 GitHub 网站上记录为一个问题,在github.com/jonathantneal/postcss-transform-shortcut/issues/4,如果您想了解更多关于这个问题的细节。

构建我们的演示

现在我们已经安装了更新的插件,我们可以继续构建我们的演示!下一个练习将是一个简单的信用卡表单——我不建议您在生产环境中使用它,因为这个表单纯粹是为了展示动画效果,并且没有与表单相关的任何安全措施!

除了这些,以下是使用 PostCSS 将要生成的截图:

构建我们的演示

这是一个简单的演示,基于 Michael Arestad 创建的 codepen,您可以在codepen.io/MichaelArestad/pen/ohLIa查看——我已经简化并重新制作了这个演示,以展示我们如何使用 PostCSS 将代码编译成有效的 CSS 样式。

好的,让我们开始设置我们的演示:

  1. 我们将首先从本书附带的代码下载中提取T40 – Creating a demo in PostCSS文件夹的副本;将其保存到我们的项目区域。

  2. 在文件夹内部,将package.jsongulpfile.js文件移动到项目区域的根目录。

  3. css – completed versions文件夹中,将style – pre-compile version.css复制到src文件夹,并重命名为style.css

  4. 接下来,启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  5. 在提示符下,输入 gulp,然后按 Enter 键——PostCSS 将消失并编译我们的代码;如果一切顺利,我们应该在dest文件夹中看到编译后的样式表文件和source maps

  6. dest文件夹的内容复制到原始T40 – Creating a demo in PostCSS文件夹中的css文件夹。

  7. 好吧,先预览一下结果——如果一切顺利,我们应该能看到我们在练习开始时展示的截图类似的内容。

这是一个简单的演示,但它展示了我们如何完美地使用动画——它为标签添加了微妙的视觉效果,并没有破坏我们表单的整体使用。使用该插件确实提出了几个有用的观点,所以让我们花点时间更详细地探索我们刚刚创建的内容。

更详细地剖析我们的演示

PostCSS 中成功插件的关键是遵循1:1原则——一个插件对应一个任务。postcss-transform-shortcut插件也不例外:它将构成过渡规则的各个元素按照正确的顺序组合在一起。为了理解我们的意思,请查看在编译之前的样式表中的这些行:

更详细地剖析我们的演示

我们的transform:语句在哪里?嗯,当使用这个插件时,它是不需要的——相反,我们只需指定各种属性,如下所示:

.transform {
  transform: skewX(25deg);
  rotate: 180deg;
  scale: 2 2;
  translate: 10px 10px;
}

插件被设置为识别这四个属性并将它们编译成一条单独的规则,如下面的代码摘录所示:

.transform {
  transform: skewX(25deg) rotate3d(180deg,0,1) scale3d(2,2,1) translate3d(10px,10px,0px);
}

属性中的任何间隙都将自动用插件内的默认值填充。我们甚至可以使用这个插件作为创建等价过渡的基础——我们将在下一章的末尾这样做。

优化我们的动画

当处理动画时,可能会有需要使用自定义效果的情况;实现这一目标的一种方法是通过使用@keyframes。问题是,一些浏览器不支持在媒体查询中使用它们(是的,我在看你们,IE10IE11!)。

你可能会问这会影响到我们什么?嗯,如果你正在构建任何响应式网站,那么这绝对是我们需要牢记的事情;媒体查询构成了任何响应式功能的基本结构。

尽管这是一个简单的修复,但开发者 Andy Walpole 创建了一个简单的 PostCSS 插件,名为mq-keyframes,可在github.com/TCotton/postcss-mq-keyframes找到。

想象一下,在我们的样式表中我们有这样的代码:

@media only screen and (min-width: 375px) {
  .custom-bounce {
    animation: side-bounce 5s;
  }

  @keyframes side-bounce {
    100% {
      opacity: 0;
    }
  }
}

插件所做的只是将代码移动到我们的样式表底部,这使得阅读更加容易,并允许 IE 继续正确工作:

@media only screen and (min-width: 375px) {
  .pace {
    animation: pace-anim 5s;
  }
}

@keyframes pace-anim {
  100% {
    opacity: 0;
  }
}

这可能是 PostCSS 中最简单易用的插件之一,尤其是在处理动画内容时;如果你需要支持这些版本的 Internet Explorer,那么使用它是有价值的!该插件可以像安装大多数其他 PostCSS 插件一样安装,并且不需要在配置过程中添加任何额外的属性。

注意

作为一项挑战,不妨尝试一下在urbaninfluence.com/2015/05/make-a-background-image-slider-with-css-keyframes/可用的演示。

使用我们自己的动画插件

在本章的整个过程中,我们使用了 PostCSS 可用的少量基于动画的插件,并演示了一些可能的效果。这些都很好,但人们不禁会感到这有点限制——我们能做些什么来改变这种情况吗?

当然,PostCSS 的美妙之处在于,如果需要插件,我们就可以创建一些东西来填补这个空白。一个很好的例子是缺乏基于 CSS 的动画插件;目前,我们只有 postcss-animations,它从 Dan Eden 创建的 Animate.css 样式表中插入动画。我已经用它作为新插件的基础——我们将使用相同的框架,但将其转换为使用来自 www.minimamente.com/example/magic_animations/ 的 Magic 动画集。

我们将在第八章创建 PostCSS 插件中更详细地介绍插件的构建,创建 PostCSS 插件。让我们开始吧:

  1. 从本书附带的代码下载中,请提取 T41 文件夹的副本,并将其内容保存到我们项目区域的根目录中。

  2. postcss-animationpostcss-animation-data 文件夹复制到我们项目区域的 node_modules 文件夹中。

  3. gulpfile.jspackage.json 文件复制到我们项目区域的根目录中——如果已经存在,则替换它们(或者保留副本以备安全起见)。

  4. 打开你的文本编辑器,并添加以下代码,将其保存为 style.css,位于我们项目区域的 src 文件夹中:

    .foo {
      animation-name: openDownLeft;
    }
    
  5. 启动一个 Node.js 文件夹,然后更改工作文件夹到我们的项目区域。

  6. 在提示符下,输入 gulp,然后按 Enter 键——PostCSS 将会退出并编译代码;如果一切顺利,我们应该能看到 @keyframes 代码被添加到我们的编译样式表中(在 dest 文件夹中),如图所示:使用我们自己的动画插件

虽然我们的示例只展示了单个样式,但这并不重要——任何使用 animation-name 的样式表都可以使用,只要使用的 animation-name 值与 postcss-animation-data 插件中的某个值匹配。不过,还有一些关键点我们需要讨论,让我们花点时间更详细地探讨这些点。

更详细地探索插件

我们的新插件是我们可以如何适应现有框架以使用不同值的完美例子——在使用此插件时,我们应该注意以下几点:

  • 在构建任何插件时需要考虑的一个关键点:不要担心添加供应商前缀。这些前缀应该在插件在项目中使用时作为编译阶段的一部分添加;这将处理所需的任何供应商前缀。

  • 目前,该插件仅列出了来自 Magic Animations 库的两种动画类型作为示例——原始动画的完整列表可在 Magic Animations GitHub 仓库中找到,网址为github.com/miniMAC/magic/blob/master/magic.css。我们可以轻松地添加我们需要的任何动画,使用格式"<动画名称>" : "<要使用的关键帧>",如本截图所示:探索插件细节

    作为实验,我们不妨尝试将来自 Motion UI 库的动画从zurb.com/playground/motion-ui转换过来,例如?或者我们可以尝试 AngularJS 的动画,网址为www.justinaguilar.com/animations/#——这完全取决于你!

  • 或者,将相同的原理应用于从github.com/postcss/postcss-easings提供的postcss-easings插件也是值得的;它内置了一些知名的缓动效果,但它们可以很容易地被替换。这个目的的一个很好的工具是www.cubic-bezier.com网站。例如,如果我们取easeInExpo缓动效果,我们创建一个看起来像这样的贝塞尔曲线:探索插件细节

注意

这对应于cubic-bezier(.95,.05,.79,.35)的值,我们可以在我们的代码中使用它。值得注意的是,一些网站会显示这个缓动效果为(0.05, 0.795, 0.035)——cubic-bezier.com/只显示到小数点后两位的值。

我们有很多方法可以扩展、修改或创建新的插件——任何插件的关键都应该是它们保持简单,仅限于一个任务,并且尽可能使用 PostCSS 插件模板作为创建插件的基础。在这个练习中我们使用的插件是手动创建的——如果你是为自己的需求创建它,并且不打算发布插件,这并不是一个问题。在下一章中,我们将探讨使用模板代码创建东西有多容易——它避免了将来很多问题!

摘要

动画内容几乎是构建现代网站的一个事实上的部分——这可以是从表单标签上提供微妙的视觉效果,到提供复杂的背景滑块。我们在本章中介绍了很多有用的技巧和窍门,所以让我们花点时间回顾一下我们学到了什么。

本章的主题是关于从使用纯 CSS 或 SASS 过渡到 PostCSS;我们首先快速回顾了可用的动画类型。

这迅速被查看一些可用于开始过渡到标准 CSS 的方法所跟随,例如使用预构建的动画库,或使用 CSS3 过渡。然后,我们转向介绍在 SASS 中类似动画的样式,这样我们就可以比较使用 Animate.css 等库的方法,然后再开始转向使用 PostCSS。

我们通过探索可用的各种插件开始了这段旅程,然后转换我们的代码以使用 PostCSS 相当的样式。接着,我们通过创建一个简单的 PostCSS 示例来进一步推进,最后在本章的结尾,我们查看了一个为 PostCSS 创建的简单动画插件,该插件基于 Magic Animation 动画集。

哇,我们确实已经覆盖了很多内容!但我们的旅程还没有结束。在下一章中,我们将探讨一些我们可以用来在 PostCSS 中创建插件的技巧和窍门。我们不再局限于他人提供的内容;现在我们可以开始创建我们自己的插件了...

第八章。创建 PostCSS 插件

插件,插件……我们无法避开它们;到目前为止,你已经看到它们是 PostCSS 开发中不可或缺的一部分。它的模块化特性完全围绕插件构建,这就是我们如何通过选择性地使用这些插件来简化我们的处理过程。

在本章中,我们将介绍 PostCSS 插件的解剖结构,并查看一些预构建的示例,然后着手构建一个简单的插件,然后对其进行测试并提交以包含在 PostCSS 系统中。

在本章中,我们将涵盖多个主题,包括以下内容:

  • 发现如何使用插件扩展 PostCSS

  • 检查标准插件的架构

  • 使用插件模板创建 PostCSS 插件

  • 构建一些示例插件

  • 测试并将你的插件提交到 PostCSS 插件库

  • 探索一些现有的 PostCSS 插件示例

让我们开始吧…!

使用插件扩展 PostCSS

一个问题,你有多少次与那些实际上并没有真正实现任何功能的插件一起工作过?

我敢打赌答案不会是一个很高的数字——不,我并没有包括那些声称执行操作但实际上似乎因为某些原因不起作用的插件!PostCSS 的力量不在于核心系统,而在于我们用来操作 CSS 样式表的插件。

在撰写本文时,PostCSS 中有超过 200 个可用的插件——这些插件的范围从扩展 PostCSS(如postcss-nestedpostcss-mixins),到操作颜色(如postcss-color-hclpostcss-rgba-hex),到为未来 CSS 语法提供支持的插件(如添加@extend支持)。

注意

完整列表可在github.com/postcss/postcss/blob/master/docs/plugins.md或通过托管在www.postcss.parts的可搜索目录中找到。

到目前为止,我们在示例中使用了相当多的插件——我们已为它们配置了使用,但它们仍然带有一定的黑盒特性,我们并不总是了解内部是如何工作的。是时候改变这一点了。作为第一步,让我们简要地认识一下帮助插件成为可能的工具集,即 PostCSS API。

为 PostCSS 创建的任何插件都将使用 API 构建。这个 API 的关键将是NodeContainer方法,这些方法可以在插件中初始化postcss对象后用来操作内容。我们将在本章中更详细地探讨这些内容,但在这样做之前,先探索一下 PostCSS 插件的架构是有意义的,让我们来看看是什么让它运转起来。

分析标准插件的架构

创建一个 PostCSS 插件是一个简单的过程——PostCSS 的美妙之处在于,作为开发者,我们可以自由地设计和构建我们想要的任何插件;这也意味着并非每个插件的质量都与其他插件相同!

除了这些,构建任何 PostCSS 插件的推荐方式是使用模板代码,这些模板代码可以从 github.com/postcss/postcss-plugin-boilerplate 获取;我们可以在下面的截图中看到一个例子:

剖析标准插件的架构

如果我们探索托管在 GitHub 上的任何 PostCSS 插件源代码,将存在许多不同的文件;并不是每个插件都会完全相同!

然而,如果我们进一步深入,我们会期望看到一些文件作为任何插件架构的一部分;它们如下所示:

  • index.js: 这个文件包含了每个插件的主要功能

  • package.json: 这个文件用于配置和管理本地安装的 NPM 包

  • test.js: 这包含了确保插件按预期工作所需的测试

让我们更详细地探讨这些内容,从 index.js 开始。

探索 index.js

任何插件的精髓都集中在 index.js 上——我们从一个对 PostCSS(作为我们插件的依赖项)的引用开始;接着是导出函数,它向使用插件的任何人公开功能:

var postcss = require('postcss');

module.exports = postcss.plugin('myplugin', function(options) {

  return function (css) {
    options = options || {};

    // Processing code will be added here
  }
});

发现 package.json

接下来,我们有 package.json——这个文件用于配置和管理本地安装的 Node 包;鉴于 PostCSS 基于 Node.js,我们将在任何作为 PostCSS 生态系统一部分安装的插件中看到类似的内容:

{
  "name": "PLUGIN_NAME",
  "version": "0.0.0",
  "description": "PostCSS plugin PLUGIN_DESC",
  "keywords": [
    "postcss",
    "css",
    "postcss-plugin"KEYWORDS
  ],

第一部分包含有关插件名称、描述和版本的一些基本细节。如果我们查看 package.json 文件,会发现一些大写关键词——乍一看,可能会误以为它们是无效的 JSON。

这是有原因的——使用这个模板插件的一个步骤是运行一个脚本,该脚本将替换这些关键字以提供信息;脚本将将其转换为有效的 JSON。我们将在后面的 创建过渡插件 部分更详细地介绍这一点。现在,假设这个文件将在构建过程中转换为有效的 JSON。

然后,我们存储作者的名称、插件的许可证以及我们可以获取源代码或报告与插件相关的错误的地方:

  "author": "AUTHOR_NAME <AUTHOR_EMAIL>",
  "license": "MIT",
  "repository": "GITHUB_NAME/PLUGIN_NAME",
  "bugs": {
    "url": "https://github.com/GITHUB_NAME/PLUGIN_NAME/issues"
},
"homepage": "https://github.com/GITHUB_NAME/PLUGIN_NAME",

这一节是最关键的——依赖关系部分存储了在生产环境中使用的任何依赖项的详细信息;devDependencies 部分负责开发环境中的依赖项:

  "dependencies": {
    "postcss": "⁵.0.10"
  },
  "devDependencies": {
    "ava": "⁰.7.0",
    "eslint": "¹.10.2"
  },
  "scripts": {
    "test": "ava && eslint *.js"
  }
}

PostCSS 团队给出的一个关键指南是每个插件都应该经过测试——这应该是一个理所当然的要求,以确保我们创建的东西是稳固的,不太可能给用户带来问题。样板代码的一部分包含了一个适合此目的的测试脚本,所以现在让我们快速看一下。

探索 test.js

任何插件的关键第三个元素是测试——这应该存储在 test.js 中,看起来类似于以下内容:

import postcss from 'postcss';
import test from 'ava';

import plugin from './';

function run(t, input, output, opts = { }) {
  return postcss([ plugin(opts) ]).process(input)
    .then( result => {
      t.same(result.css, output);
      t.same(result.warnings().length, 0);
    });
}

/* Write tests here
test('does something', t => {
  return run(t, 'a{ }', 'a{ }', { });
});
*/

我们将在本章的“测试和提交插件”部分更详细地介绍这部分内容——现在,让我们专注于创建一个基于 PostCSS 的插件。我们将首先快速查看 API,然后深入创建一个基于所选字体的特定字体堆栈的插件,并在需要将其中一个字体导入我们的网站时添加更新声明。

在框架就绪后,我们可以使用 PostCSS API 来构建我们的插件;它包含了许多类、模块和方法,我们可以使用。API 中的关键函数当然是 postcss——这是 PostCSS 的主要入口点,对于所有插件都是必需的:

var postcss = require('postcss');

让我们快速浏览一下 API 中还提供了哪些其他功能,从供应商模块开始。

供应商模块

此模块包含用于处理供应商前缀的辅助工具——我们可以使用此对象来启动它:

var vendor = postcss.vendor;

该模块包含两个方法,如表所示:

模块 格式 返回的值

| vendor.prefix | 字符串 | 从输入字符串中提取的供应商前缀:

// prefix extracted = '-webkit-'
var vp = postcss.vendor;
vp.prefix('-webkit-clip-path')

|

| vendor.unprefixed | 字符串 | 去除了供应商前缀的输入字符串:

// value extracted = 'tab-size'
var vp = postcss.vendor;
vp.unprefixed('-moz-tab-size')

|

列表模块

此模块包含用于安全分割 CSS 值列表的辅助工具,同时保留括号和引号。我们可以使用此对象来启动它:

var list = postcss.list;

该模块包含两个方法,如表所示:

模块 格式 设计用于分割

| list.space | 字符串 | 以空格分隔的值(例如背景、border-radius 和其他缩写属性):

// expected result:
// ['1px', 'calc(10% + 1px)']
var ls = postcss.list;
ls.space('1px calc(10% + 1px)')

|

| list.comma | 字符串 | 以逗号分隔的值(例如 transition-* 和背景属性):

// Expected result:
// ['black', 'linear-gradient(white, black)']
var ls = postcss.list;
ls.comma('black, linear-gradient(white, black)')

|

API 中可用的类

一旦在插件中将 PostCSS 对象定义为依赖项,我们就可以开始操作其内容——为此,有多个类可用以协助,如表所示:

类名 插件中的角色
Processor 创建一个处理器实例,初始化任何插件,然后使用此实例处理配置中指定的 CSS 文件。
LazyResult 作为 PostCSS 转换结果的承诺代理。承诺是使用 Node.js 时的一个关键部分——如果你不熟悉这个概念,请查看 www.promisejs.org/ 以获取详细解释。
Result 提供任何 PostCSS 转换的结果。
Warning 允许用户在插件中管理警告。
CssSyntaxError 允许用户检索由 CSS 解析器生成的任何 CSS 错误:
Input 表示 PostCSS 插件正在操作的源 CSS:

API 中可用的节点

当然,如果没有访问每个 CSS 节点的内容,我们无法在 PostCSS 插件中操作内容——API 包含一组有用的节点,有助于解析和操作内容:

节点 表示:

| Root | 一个 CSS 文件及其解析后的节点:

var root = postcss.parse('a{color: darkred}');
root.type         //=> 'root'
root.nodes.length //=> 1

|

AtRule CSS 中的基于@的规则,例如@media print {…}

| Rule | 一个 CSS 规则,包含选择器和声明块:

var root = postcss.parse('h1{}');
var rule = root.first;
rule.type       //=> 'rule'
rule.toString() //=> 'h1{}'

|

| Declaration | 一个 CSS 声明:

var root = postcss.parse('a{color: darkred}');
var decl = root.first.first;
decl.type       //=> 'decl'
decl.toString() //=> 'color: darkred'

|

| Comment | 在声明或语句之间(在规则和@规则中)的注释:

var root = postcss.parse('a { color: /* inner */ darkred; /* outer */ }');
var decl    = root.first.first;
var comment = root.first.last;

comment.type //=> 'comment'
decl.between //=> ': /* inner */'

|

API 中可用的方法

插件的一个关键作用是遍历每个节点以帮助确定是否应该执行某些操作;API 包含一些方法来协助解析节点:

方法组 目的

| 节点 | 这些方法用于处理每个 CSS 节点——这包括以下方法:

  • node.type: 返回表示节点类型的字符串

  • node.parent: 返回作为字符串的父节点

  • node.next()node.prev(): 返回节点父的下一个或前一个子节点。

更多详细信息请参阅:github.com/postcss/postcss/blob/master/docs/api.md#nodes-common-methods |

| 容器 | 这些方法包含用于在容器节点中处理子节点的方法——这包括以下方法:

  • container.nodes: 返回包含容器子节点的数组。

  • container.first: 返回容器的第一个子节点。

  • container.last: 返回容器的最后一个子节点。

更多详细信息请参阅:github.com/postcss/postcss/blob/master/docs/api.md#containers-common-methods |

主站点包含 API 中所有方法和类的详细信息及示例——花时间熟悉这些选项是值得的。

小贴士

每个方法或类的详细信息可在 PostCSS API 页面找到:github.com/postcss/postcss/blob/master/docs/api.md

好了,理论就到这里:让我们开始吧!让我们改变方向,将我们刚刚学到的一些知识应用到构建几个 PostCSS 插件中;这些插件将使用我们在本章前面简要查看过的 API 命令的真实混合;我们的第一个演示围绕一个简写插件,用于在样式表中的 CSS 规则内创建过渡语句,所以让我们开始看看它是如何工作的。

创建一个过渡插件

这个插件的创意并不新颖;它松散地基于 Jonathan Neal 的 postcss-transform-shortcut 插件,可在 github.com/jonathantneal/postcss-transform-shortcut 获取。这个概念并不一定是一个创建过渡语句的更短方法,但它通过允许作者独立指定值来简化了过程。然后这些值会自动插入到过渡声明中的正确顺序。

注意

这个插件的源代码也托管在 GitHub 上,网址为 github.com/alexlibby/postcss-transition-shortcut;NPM 包也可在 www.npmjs.com/package/postcss-transition-shortcut 获取。

让我们深入了解一下它是如何组合在一起的,更详细地看看:

  1. 我们首先安装 Git——这是安装插件模板所必需的。为此,浏览到 git-scm.com/book/en/v2/Getting-Started-Installing-Git,并遵循您平台上的说明。

  2. 打开 Node.js 命令提示符,然后更改工作目录到我们的项目目录。

  3. 在提示符中输入以下命令然后按 Enter

    git clone https://github.com/postcss/postcss-plugin-boilerplate.git
    
    
  4. Git 将 postcss-plugin-boilerplate 仓库克隆到我们的项目区域,如图所示:创建过渡插件

  5. 插件模板包括一个脚本来自动生成我们插件的基本框架——请在提示符中运行此命令:

    node ./postcss-plugin-boilerplate/start
    
    
  6. 它将显示一系列用于各种信息的提示。请填写适当的响应,类似于这个截图所示。请注意,拥有 GitHub 账户不是强制性的,因为信息只是添加到 package.json 文件中;如果您未来花时间开发插件,那么建议您创建一个:创建过渡插件

  7. 一旦创建了插件文件夹,我们可以从项目根目录中删除 postcss-plugin-boilerplate 文件夹,因为不再需要它了。

  8. 如果一切顺利,我们在浏览插件文件夹内容时应该能看到类似这样的截图:创建过渡插件

  9. 到这一步,我们现在可以将我们的插件代码添加到 index.js 中——为此,打开项目区域内的 postcss-transition-shortcut 插件的一个文件副本,并按照所示修改代码:

    var postcss = require('postcss');
    
    module.exports = postcss.plugin('postcss-transition-shortcut', function (opts) {
      opts = opts || {};
    
      var defaults = {
        property: 'all',
        duration: '1s',
        timing: 'ease-in-out',
        delay: '1s'
      };
    
      return function (css, result) {
        css.walkRules(function (rule) {
          var transitionRule;
          var transitionValues = [];
          var index = -1, node;
          var attributes = /^ (property|duration|timing|delay)$/;
    
          while (node = rule.nodes[++index]) {
             if (attributes.test(node.prop)) {
              transitionRule = transitionRule || node.cloneBefore({ prop: 'transition' });
              var transValues = postcss.list.space(node.value);
              transitionValues.push(transValues.join(','));
              node.remove();
              --index;
            }
          }
          transitionRule.value =
            transitionValues.join(' '); 
          });
      };
    });
    

在这个阶段,我们将拥有一个可以工作的插件——虽然(引用一句古老的英语谚语),证明是甜点:插件是否按预期工作?好吧,没有比尝试它更好的方法来找出答案了,所以让我们快速设置一个演示来确认它按预期工作。不过,在我们这样做之前,有一个重要的问题需要说明,那就是关于 PostCSS 插件生成的问题。

为我们的插件创建测试

眼尖的您会注意到,如果我们没有为我们的过渡插件指定四个值之一,那么当前的代码将不会使用默认值;希望未来的插件版本中会有更新。

除了这个之外,测试我们的插件的过程使用的是来自 github.com/sindresorhus/ava 的 AVA 测试运行器。测试框架已经在插件模板中创建好了,我们只需要将测试代码添加到 test.js 文件中。让我们看看需要什么:

  1. 我们将开始安装 AVA 测试运行器——为此,打开 Node.js 命令提示符,并将工作文件夹更改为插件文件夹的根目录。

  2. 在提示符中,输入以下命令,每输入一个命令后按 Enter 键——第一个安装 AVA,第二个将其添加到我们的 package.json 文件:

    npm install --global ava
    ava --init
    
    
  3. 在您选择的文本编辑器中打开一个新文件——请将以下高亮行添加到之前练习中创建的插件文件夹中的 test.js 文件:

          t.same(result.warnings().length, 0);
        }); 
    } 
    
    test('transitionShtct', t => { 
     return run( t, 'div { property: all; duration: 1s; timing: ease-in-out; delay: 1s; }', 'div { transition: all 1s ease-in-out 1s; }', { }); 
    });
    
    
  4. 接下来,打开 Node.js 命令提示符,然后更改工作目录到我们的插件项目文件夹。

  5. 在提示符中,输入 npm test 并按 Enter 键。

  6. AVA 将执行测试,然后使用 ESLint 进行代码检查。如果一切顺利,我们应该会看到这个截图中的结果——假设测试没有出现任何问题:为我们的插件创建测试

到目前为止一切顺利,对吧——在这个阶段,我们可以创建一个简单的演示来证明插件可以工作……或者不是吗?好吧,测试结果显示通过,所以代码应该是没有问题的。但是往下看,会显示一大堆错误,类似于这个截图——这是怎么回事?

为我们的插件创建测试

测试已经通过,但测试结果似乎表明相反;进一步查看会发现更多的错误:

为我们的插件创建测试

这提出了关于测试的一些重要观点,所以在继续我们的演示之前,让我们先来谈谈这些。

修正错误

主要错误,或 Exported linebreaks to be 'LF'…,是一个简单的错误,很容易修复——这是由于 Sublime Text 被设置为使用 Windows 作为默认的行结束符设置引起的。假设我们使用的是 Sublime Text,让我们来处理这个错误:

  1. 打开 Sublime Text,然后打开插件文件夹中的 index.js 文件。

  2. 点击视图 | 行结束符。

  3. 将选定的选项更改为 Unix,并保存文件。

  4. test.js 重复步骤 1 到 3——完成后,关闭这两个文件。

如果我们重新运行测试,我们应该会看到列出的错误数量显著减少——在index.jstest.js中,我们还会剩下一些需要修复的错误,类似于下面的截图:

纠正错误

大多数错误都是显而易见的——两个不太明显的是Expected indentation of X spaces…Line X exceeds the maximum line length…。我们可以通过将所有制表符替换为每个制表符四个空格来修复第一个错误。第二个错误很容易修复——只需将代码行拆分为两行即可。

我们需要尽可能解决所有剩余的错误——这些错误不会完全相同于你的插件版本,但其中一些将是相似的。

小贴士

如果你遇到任何你想了解错误原因的错误,请查看jslinterrors.com/——这是一个定义错误含义的好资源!

假设我们已经清除了大多数错误,我们应该只剩下最后一个:

纠正错误

这是一个我们应该修复的错误,因此可以从报告中清除吗?简单的回答是这取决于——它强调了使用代码检查的一个重要观点,所以让我们花点时间更详细地讨论这一点。

清除最后一个错误

报告中显示的最后一个错误带来了一些挑战——代码是有效的,但 ESLint 标记了错误。原因是它在一个 while 语句初始化器中找到了一个赋值表达式;这被视为代码中的一个可能的错误,并且可能会对代码产生意外的影响。

在某些方面,它可以被视为警告,而不是错误。在 2013 年 7 月之前,我们可以配置测试来忽略它,但自那时起对 ESLint 所做的更改意味着这个错误不能不重新编写代码就清除。

注意

如果你想了解更多关于这个错误原因的信息,请参阅jslinterrors.com/unexpected-assignment-expression/

在我们的例子中,代码是有效的,不会引起任何错误——这让我们在接下来的操作中有了几个选择:

  • 我们可以简单地忽略这个错误并继续——测试失败并不好,但在这个情况下,它不会对我们的代码造成任何伤害。

  • 我们可以关闭对这个测试的检查,这样即使这个条件没有被测试,测试至少会显示 100%通过。

  • 我们可以尝试修改代码来设计出错误/警告——这是理想的解决方案,但根据我们需要做出的更改的性质,这可能是一个长期的道路。

目前,我们将关闭这个错误的测试——我们可以在插件内部编辑.eslintrc文件,并将方括号中的值设置为0

清除最后一个错误

这在短期内是可行的,但考虑到将来某个时候重新审视代码以消除歧义。

使用插件进行测试

在我们的插件就位后,让我们测试它——为此,我们需要从本书附带的代码下载中获取几个文件;这些文件位于 T43 – building a transition shortcut plugin 文件夹中:

  1. 前往并提取 gulpfile.jspackage.json 的副本,然后将它们保存到我们项目区域的根目录。

  2. 在一个新文件中,添加以下 CSS 样式,并将其保存为 style.css,位于我们项目区域的 src 文件夹中:

    div {
      property: all;
      duration: 1s;
      timing: ease-in-out;
      delay: 1s;
    }
    
  3. 启动 Node.js 命令提示符,然后更改工作目录到我们的项目区域。

  4. 在提示符中,输入 gulp 然后按 Enter 键——PostCSS 将消失并编译源样式表。如果一切顺利,我们应该在我们的项目区域的 dest 文件夹中看到样式表的编译结果:使用插件进行测试

在这个阶段,我们已经为我们的插件运行了测试——我们再进一步,将我们的插件添加到测试运行器服务,如 Travis CI(在 travis-ci.org)。尽管这是创建任何 PostCSS 插件过程中的一个强制性部分,但学习曲线相当陡峭,任何在 Windows 上工作的人可能会遇到困难!如果你是 Windows 用户,你将不得不通过命令行使 test.js 可执行——这需要先了解 Git 的使用,而这超出了本书的范围。

现在,我们将跳过 Travis CI 的过程——插件足够简单,使用 test.js 进行本地测试就足够了。让我们改变方向——我们的插件包含了一些 PostCSS 中的有用概念,因此让我们更详细地探索它是如何组合在一起的。

详细剖析我们的插件

这个插件的灵感有两个方面——在撰写本文时,PostCSS 没有太多基于动画的插件,并且它借鉴了 postcss-transform-shortcut 插件中使用的相同概念。

我们从初始化 PostCSS 作为我们插件的依赖项的通用调用开始:

var postcss = require('postcss');

接下来,我们初始化 postcss.plugin,以便在我们的插件中公开功能给生态系统:

module.exports = postcss.plugin('postcss-transition-shortcut', function (options) {

目前,我们的插件不包含任何选项,因此它将被设置为空白;如果我们已经设置了一些选项,那么这些选项将存储在选项数组中:

options = options || {};

我们插件的关键部分是设置一些默认选项——如果我们没有指定一个或多个值,我们需要设置一些默认值:

var defaults = {
  property: 'all',
  duration: '1s',
  timing: 'ease-in-out',
  delay: '1s'
};

接下来是插件的核心——它返回此函数的结果:

  return function (css) {
     css.walkRules(function (rule) {
       var transitionRule;
       var transitionValues = [];
       var index = -1, node;
       var attributes = /^(property|duration|timing|delay)$/;

我们使用 css.walkRules 遍历每个规则——它设置了一些变量和一个数组;我们还设置了一个搜索字符串,用于查找任何我们的过渡属性实例。

如果我们找到一个合适的属性实例,我们就克隆它,在其前面添加属性名 transition。然后我们逐个处理可能设置的最多四个属性,将它们组合成最终的过渡声明:

       while (node = rule.nodes[++index]) {
        if (attributes.test(node.prop)) {
          transitionRule = transitionRule || node.cloneBefore({ prop: 'transition' });
           var transValues = postcss.list.space(node.value);
           transitionValues.push(transValues.join(','));
           node.remove();
           --index;
         }
       }
       transitionRule.value = transitionValues.join(' ');
    });
  };
});

让我们继续前进。我们的第一个示例是一个简单的插件;尽管它还需要进一步开发(如 测试我们的插件 所示),但它仍然有其用途。在我们的下一个示例中,我们将采取不同的方法:我们将使用现有插件作为我们新版本的基础。这个插件与第一个不同,它不会在 GitHub 上亮相——我们将探索这个原因,以及更多内容,作为我们下一个练习的一部分。

构建自定义字体插件

对于我们的下一个演示,我们不会构建原创内容,而是从已经可用于 PostCSS 的现有插件开始。我们将使用的插件是 Seane King 的 postcss-fontpath(可在 github.com/seaneking/postcss-fontpath 获取);我们将集成一个自动完成功能,根据提供的名称自动添加相关的字体堆栈,并使用在 www.cssfontstack.com/ 可用的列表。

“为什么这样做?”你可能会问?为了证明一个观点——并不是总是需要重新发明轮子;有时简单地适应现有的东西,虽然它并不完全符合我们的需求,可能更可取。在这个例子中,我们添加的代码将使其更有用;它还需要进一步开发以允许错误检查,但无论如何仍然有其用途。

注意

一个需要注意的点——推荐的做法是使用我们在上一节中提到的插件模板。对于接下来的练习,我们将手动构建它——这是为了展示这个过程的一部分,尽管它不是我们会发布到野外的版本。

好的,先不提这个,让我们开始着手开发我们的插件:

  1. 我们将首先在项目区域的根目录下创建一个文件夹——请将这个文件夹命名为 postcss-custom-fonts

  2. 接下来,我们需要将文件夹设置为一个 Node 模块,因此请启动 Node.js 命令提示符并将工作文件夹更改为我们的插件文件夹。

    在命令提示符中输入 npm init 以开始创建 package.json 文件的过程——使用此截图显示的详细信息,在适当的提示下:

    构建自定义字体插件

  3. 在命令提示符仍然打开的情况下,输入以下命令,然后按 Enter 键——第一个命令是安装 PostCSS 作为我们插件的依赖项,第二个命令安装 underscore.js 作为第二个依赖项(它用于扩展方法):

    npm install postcss --save
    npm install underscore --save
    
    

    保持会话打开——我们将在练习的末尾需要它。

  4. 从本书附带的代码下载中,我们需要提取 index.js 的副本——将其复制到插件文件夹中。

  5. 如果一切顺利,当浏览我们插件文件夹的内容时,我们应该看到类似于以下截图的内容:构建自定义字体插件

  6. 在我们的插件就位后,让我们测试一下。为此,我们需要从本书附带的代码下载中获取几个文件。请从 T42 – 构建自定义字体 插件文件夹(而不是插件文件夹!)中提取 gulpfile.jspackage.json 的副本,然后将它们保存到我们项目区域的根目录。

  7. 在一个新文件中,添加以下 CSS 样式,将其保存为 src 文件夹中我们项目区域的 style.css

    @font-face {
      font-family: 'robotoregular';
      font-path: '/fonts/Roboto-Regular-webfont';
      font-weight: normal;
      font-style: normal;
    }
    
    h1 { font-family: robotoregular,  fontstack("Extra Stack"); }
    
  8. 回到我们在本练习开始时打开的 Node.js 命令提示符会话。在继续之前,请确保工作文件夹设置为我们的项目区域。

  9. 在提示符下,输入 gulp 然后按 Enter 键——PostCSS 将消失并编译源样式表。如果一切顺利,我们应该在我们的项目区域的 dest 文件夹中看到样式表的编译结果:

构建自定义字体插件

在这个阶段,我们现在有一个可工作的插件——即使这不是一个原创作品,但它仍然突出了围绕 PostCSS 插件构建的许多关键概念。让我们花点时间更详细地探索一下我们插件的功能。

分析我们插件的功能

初看起来,我们插件的代码可能看起来很复杂,但实际上它相对容易理解——让我们分部分来看,从定义 postcss 对象和我们将要在插件中使用的 fontstacks_config 对象的实例开始:

var postcss = require('postcss');
var _ = require('underscore');

// Font stacks from http://www.cssfontstack.com/
var fontstacks_config = {
  'Arial': 'Arial, "Helvetica Neue", Helvetica, sans-serif',
  'Times New Roman': 'TimesNewRoman, "Times New Roman", Times,
  Baskerville, Georgia, serif'
}

接下来,我们添加一个简单的辅助函数——这个函数用于将字体名称转换为标题格式;fontstacks_config 中列出的名称是区分大小写的,如果不匹配将失败:

// Credit for this function: http://stackoverflow.com/a/196991
function toTitleCase(str) {
  return str.replace(/\w\S*/g, function(txt){
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  });
}

这是插件的开头——前两行是必须的初始化,以便插件可供使用,然后定义一个选项对象。然后我们使用 _.extend 将我们选择的字体堆栈中的预定义值与在运行插件时添加到配置对象的值扩展:

module.exports = postcss.plugin('customfonts', function (options) {
  return function (css) {

    options = options || {};
    fontstacks_config = _.extend(fontstacks_config, options.fontstacks);

然后,我们遍历每个规则和节点,确定它们是否首先包含字体声明,然后确定它们是否包含与预定义字体堆栈中匹配的字体名称。如果匹配,则将字体名称转换为适当的字体堆栈,并插入任何指定的附加字体,但这些字体不匹配我们的字体堆栈:

    css.walkRules(function (rule) {
      rule.walkDecls(function (decl, i) {
        var value = decl.value;
        if (value.indexOf( 'fontstack(' ) !== -1) {
          var fontstack_requested = value.match(/\(([^)]+)\)/)[1].replace(/["']/g, "");
          fontstack_requested = toTitleCase(fontstack_requested);

          var fontstack = fontstacks_config[fontstack_requested];
          var first_font =  value.substr(0, value.indexOf('fontstack('));

          var new_value = first_font + fontstack;
          decl.value = first_font + fontstack;
        }
      });
    });

在插件的下半部分,我们执行一个更简单的任务——我们逐个处理每个规则和声明,寻找代码中任何 @font-face 的实例。然后我们定义一个 fontpath 变量,它从提供的值中删除任何引号,并定义一个 format 数组来管理可用的不同字体格式:

    css.walkAtRules('font-face', function(rule) {
      rule.walkDecls('font-path', function(decl) {
        var fontPath = decl.value.replace(/'/g, ''),
        src = '',
        formats = [
          { type: 'woff', ext: '.woff' },
          { type: 'truetype', ext: '.ttf' },
          { type: 'svg', ext: '.svg' }
        ];

然后,我们为每种字体类型构建相关的语句,然后将自定义字体声明组装起来,并重新插入到样式表中的适当位置:

        formats.forEach(function(format, index, array) {
          if (index === array.length - 1){
            src += 'url("' + fontPath + format.ext + '")
        format(\'' + format.type + '\')';
          } else {
            src += 'url("' + fontPath + format.ext + '")
        format(\'' + format.type + '\'),\n ';
          }
        });

        decl.cloneBefore({ prop: 'src', value: src });
        decl.remove();
      });
    });
  }
});

我们插件暴露了 PostCSS 插件设计中的某些关键概念——主要的是使用 .WalkDecls.WalkRules(或 .WalkAtRules)。我强烈建议您熟悉 API 文档,网址为 github.com/postcss/postcss/blob/master/docs/api.md,其中概述了 API 中所有可用的命令,并简要说明了它们的目的。

尽管创建了一个本应很有用的插件,但我不会推荐将其发布到公共领域。在这个时候,你可能认为我已经完全迷失了方向,但正如我经常说的,“疯狂之中有方法”——不发布这个插件有很好的理由,让我们花点时间来探讨为什么以当前格式发布这个插件可能不是一个明智的决定。

探讨发布此插件的风险

在过去的几页中,我们创建了一个本应很有用的插件来操作自定义字体——它根据预定义的设置自动构建正确的字体堆栈,并将为我们填写适当的 @font-face 代码。到这一点,我们应该有一个可以发布到公共领域的插件,任何人都可以使用……难道不是吗?

好吧,是的,也不是——尽管这个插件有其用途,但我不会推荐将其公开……至少现在还不行!有几个原因,这也有助于说明使用本章前面提到的样板代码的好处:

  • 该插件没有 test.js 文件或与之相关的配置——发布插件的一个要求是每个插件都必须经过测试,使用 test.js 文件。理想情况下,我们可能会使用像 Travis CI 这样的服务来帮助完成这项工作,但这仅在您使用基于 Unix 的开发环境时才有效。

  • 该插件本身执行两个不同的角色,这并不推荐——PostCSS 插件的最佳实践是尽可能将角色限制为一个任务。当使用任务运行器时,我们可以选择使用哪些插件,而不会引入太多额外的、不希望的功能。

  • 我们代码的架构并不理想——这主要是由于使用了 css.WalkRules(第 16 行)和 css.WalkAtRules(第 28 行)。这两个命令解析容器内的每个节点,并为每个规则节点和规则节点调用回调函数。这里的区别在于 css.WalkRules 在每个规则上工作;css.WalkAtRules 只会在 @- 规则(如 @font-face)上工作。它们不可互换,这使得在编译时非常低效。

  • 如果我们不使用插件模板,那么发布代码所需的大多数文件将不会存在——这些文件要么需要手动创建,要么在提交到 GitHub 时创建。如果我们使用模板,那么这将会自动完成,包括为我们配置 package.json 文件——我们只需要添加一个合适的任务运行器,如 Gulp 或 Grunt。

人们可能会问,为什么我们甚至会考虑这条路线,如果它在开发过程中可能会出现问题的简单答案是,它帮助我们了解插件应该如何构建。如果我们只为个人使用构建插件,那么就没有必要使用我们在发布通用插件时必须使用的某些文件或流程。

简化开发过程

在不考虑插件目标受众的情况下,我们开发过程中使用了不同的技术组合,但有一个共同点——我们的插件依赖项非常少!这并非微不足道,因为显然任何变化的依赖项都可能对我们的插件产生连锁反应。

除了这一点,有时缺乏任何依赖项可能需要比合理的(或实际的)更多的开发努力——这时我们可能需要考虑使用额外的插件来处理一些处理工作。一个完美的例子是解析字体:语句,以便我们可以处理其组成部分;还有一些其他值得注意的例子:

解析器或助手类型 插件源代码 URL 插件用途
选择器 github.com/postcss/postcss-selector-parser 管理选择器字符串。
github.com/TrySound/postcss-value-parser 将 CSS 值和 @- 规则参数转换为节点树,以便于遍历。
属性 github.com/jedmao/postcss-resolve-prop 解析规则属性值——如果为同一属性指定了多个值,这特别有用。
字体 github.com/jedmao/parse-css-font 解析 font 属性,并为每个元素返回值,例如 font-sizefamilystylelineHeight
尺寸 github.com/jedmao/parse-css-dimension 解析 CSS 尺寸,如 numberlengthpercentage,并返回一个 JavaScript 对象。
边界 github.com/jedmao/parse-css-sides 解析元素的边界属性(如 marginpaddingborder 属性),并返回所有四边的字符串值。
字体辅助工具 github.com/jedmao/postcss-font-helpers 用于操作 CSS 中的字体声明——它根据需要返回单个元素或组合的字体对象。
边距辅助工具 github.com/jedmao/postcss-margin-helpers 用于操作任何指定元素的边距值。

那么,我们应该使用它们吗?这并不是强制性的,但它们可能有助于减少创建我们插件所需的工作。如果使用了任何指南,那么密切关注对插件所做的任何更改是值得的,这样我们就可以及时纠正任何出现的问题;毕竟,没有人喜欢开发者没有妥善维护的插件!

让我们继续前进,插件构建的一个关键部分是一致性;PostCSS 的力量允许创建任何插件,因此保持一致性至关重要。为了帮助实现这一点,PostCSS 的开发者发布了一套指南:让我们深入探讨并更详细地查看这些指南。

插件构建指南

PostCSS 生态系统的一个关键优势是其灵活性——它允许任何开发者创建任何插件,或者修改现有的插件,只要许可证允许进一步开发!

为了保持一致性,开发者发布了一系列强制性的指南,应在实际情况下遵循:

  • 您插件的名称应清楚地表明该插件的目的——例如,如果您构建了一个用于模拟 CSS4 :hover伪类的插件,那么postcss-hover就是一个很好的例子。

  • 创建一个只做一件事且做得好的插件比尝试同时执行多个任务要好。

  • 创建插件时始终使用postcss.plugin方法——这样您就连接到了一个通用的插件 API。

  • 在可能的情况下,尝试使用异步方法——您还应该为每个节点设置node.source,以便 PostCSS 可以生成准确的源映射。

  • 不要在显示错误时使用控制台——一些 PostCSS 运行器不允许控制台输出。请使用result.warn来管理错误。

  • 任何创建并发布的插件都必须经过测试,并提供英文文档化的示例(如果可能)以及变更日志。

  • 如果您正在为 Node 编写插件,那么postcss-plugin关键字必须在package.json文件中体现——这用于对 PostCSS 生态系统提供反馈。

    小贴士

    更多关于这些指南的详细信息可在github.com/postcss/postcss/blob/master/docs/guidelines/plugin.md找到。

此外,我们可能会使用某种任务运行器,例如 Broccoli、Grunt 或 Brunch,或者在我们的情况下,Gulp。为了帮助保持一致性,开发者发布了一系列应在适当情况下遵循的指南:

  • 如果你的插件使用 config 文件,那么它必须始终用 JavaScript 编写,并设置为支持参数中的函数。

  • 在使用运行者时,始终设置到和从选项,即使你的运行者不处理写入磁盘——这是为了确保 PostCSS 生成准确的源映射并显示更好的语法错误。

  • PostCSS 运行者必须仅使用公开可用的异步 API——运行者不应依赖于可能在未来版本中删除的未记录的方法或属性。

  • 在处理 CssSyntaxError 消息时,不要简单地显示完整的 JavaScript 栈——并不是每个开发者都熟悉 JavaScript!相反,确保任何错误都能得到优雅的处理。

  • result.warnings() 出现的任何警告都应由 PostCSS 运行者显示;如果需要,可以使用 postcss-logs-warningspostcss-messages 插件来简化这个过程。

  • 如果你的插件使用了源映射选项,那么默认情况下,这将由 PostCSS 生成一个内联映射。如果需要,运行者必须提供一个选项来将映射保存到单独的文件中。

    小贴士

    更多关于这些指南的详细信息可在github.com/postcss/postcss/blob/master/docs/guidelines/runner.md找到。

提供给插件的指南是强制性的,但在某些情况下,它们可以被视为一个起点——例如,变更日志应该始终维护,但具体是 HISTORY.mdCHANGELOG.md 还是 GitHub 发布文档取决于开发者。这里的技巧是仔细规划,并保持简单——首先关注基础,然后再转向更复杂的项目。这样我们就可以习惯于每个插件必须提供的最低要求,然后再扩展以涵盖任务运行器的使用。

好的,让我们在这里改变方向:我们之前提到的指南之一指出,每个插件都应该作为常规实践的一部分进行测试;现在是一个深入了解这究竟意味着什么的完美机会。

使插件可用

创建任何插件的关键部分是测试——一旦测试通过,我们就可以决定是否将其发布到 GitHub 和 Node 的包管理器目录以供通用使用。这不是强制性的,但如果我们创建了一些可能对他人有用的东西,那么使其可用是公平的!

这个过程涉及几个步骤——它们可以分为三个组:测试插件、添加最终细节(在 GitHub 上),以及提交到 PostCSS 插件目录。我们将使用我们刚刚创建的 postcss-transition-shortcut 插件作为发布供通用使用的基准。

我们已经介绍了测试插件的要求,现在让我们探讨使我们的插件可供开发者普遍使用的剩余步骤。第一步是将我们的插件发布到 GitHub 的一个合适的仓库。这个过程超出了本书的范围,但简而言之,使插件可用的过程如下:

  • README.md文件中添加代码示例——这应该展示源文件的示例,以及当该文件被处理后我们期望看到的内容。

  • CHANGELOG.md文件中,添加插件的初始版本号。

  • 所有更改都需要提交到 GitHub——我首选的选择是 GitHub Desktop,可在 Windows 或 Mac 上从desktop.github.com/获取。对于 Linux 用户,git-scm.com/download/gui/linux提供了几个选项。

  • 到目前为止,我们现在需要将我们的插件发布到 Node 的包目录 NPM 中。这个过程包括向 NPM 添加新用户,然后将所有文件发布到 NPM;详细内容可以在docs.npmjs.com/getting-started/publishing-npm-packages中找到。

一旦插件准备就绪、测试并通过发布,剩下的就是将 PostCSS 进行分支,将你的插件添加到README.md中的Plugins部分,并发送一个pull请求。然后我们可以监控网站的 Twitter 动态,以获取关于我们插件更新的信息。

小贴士

如果你对这个话题感兴趣,你可能想参考Achilleas Pipinellis的《GitHub Essentials》,可在www.packtpub.com/找到。

摘要

与其他处理器不同,插件在 PostCSS 中扮演着核心角色——我们可以挑选和选择我们想要使用的功能;如果不存在,那么我们可以自由地创建自己的版本。在本章的整个过程中,我们已经介绍了一些关于插件使用的关键概念,现在让我们花点时间回顾一下我们已经学到的内容。

我们首先快速介绍了插件的使用方法,紧接着就探讨了标准插件的架构,包括查看构成标准插件的一些关键文件。然后,我们转向查看作为 API 一部分可用的类、模块和方法。

接下来,我们开始构建一个示例插件,然后构建一个合适的测试过程,并纠正测试中从代码检查中产生的错误。然后我们更详细地审视我们的插件,以了解其工作背后的关键概念。

接下来,我们介绍了构建第二个插件的过程,但这次我们探索了手动过程,并分析了为什么这不是一个推荐的做法。我们查看了一些可能由此产生的問題,以及为什么使用插件模板可以使开发更容易。

我们通过探索一些可以帮助简化开发的辅助插件,以及推荐的开发指南,以及使插件可供其他开发者在未来使用的流程,来完善这一章。

好的,我们继续前进:到目前为止,我们在整本书中使用了各种插件。有三个特定的插件组特别有用——它们是用于后备支持、实现创建 CSS 的快捷方式以及插件包。我们将在下一章中介绍这三个(以及更多)插件。

第九章。使用快捷方式、回退和打包

如果你花时间工作与 CSS,那么你可能会遇到一些情况,你希望有一种更快的方法将特定的代码块添加到你的页面中,应用供应商前缀,或者可能为页面上的元素设置预定义的边框。

我们可以通过使用一个或多个可用的快捷方式、回退或打包插件轻松实现这一点。在本章中,我们将探讨一些需要插件的一些常见场景,在下一章中我们将学习如何创建它们。本章将涵盖多个主题,包括以下内容:

  • 探索一些可用的 PostCSS 快捷方式和打包

  • 使用插件来检查和优化你的 CSS 代码

  • 补充 PostCSS 中现有的快捷方式

  • 将回退应用到 PostCSS 代码以保持对旧浏览器的支持

让我们开始吧!

在 PostCSS 中使用快捷插件

构建一个基于网络的程序或网站可能是一个漫长的过程——有如此多的元素需要考虑,创建引人入胜且信息丰富的内容需要时间。

自然地,一个聪明的设计师或开发者总是会寻找捷径来节省时间——毕竟,为什么要花一个小时来做某事,如果有一个捷径只需要一半的时间?PostCSS 的好处在于它提供了一系列的快捷插件供我们使用;这些包括例如以下示例:

  • postcss-focus:此插件可在 github.com/postcss/postcss-focus 找到,这个简单的插件为任何在样式规则中遇到的 :hover 属性添加一个 :focus 伪选择器。

  • postcss-border:如果指定为缩写版本,此插件将为现有的 border: 属性添加一个 border-width 属性。插件源代码可在 github.com/andrepolischuk/postcss-border 获取。

  • postcss-short-data:此插件可在 github.com/jonathantneal/postcss-short-data 找到,这个有趣的插件允许我们编写类似于伪选择器的缩写数据属性选择器,这些选择器被编译成 data- 属性。

我们已经介绍了或讨论了 PostCSS 中可用的许多快捷插件——这些包括我们之前在书中用来构建响应式页面的 postcss-responsive-typeeasings,当然还有来自第七章(第七章。元素动画 获取,这个插件是九个独立插件的接口;它处理了对旧版 IE 的支持。

  • 简写插件(Short): 位于 github.com/jonathantneal/postcss-short,这个插件允许我们使用自己的缩写属性来编写样式。

  • AtCSS: 这个插件可在 github.com/morishitter/atcss 获取,这个有趣的插件通过允许我们创建从基础规则继承的规则,为 SASS 的@extend提供了一个新的视角。

  • precss: 来自 github.com/jonathantneal/precss 的这个插件允许我们在样式表中使用类似 SASS 的标记。我们将在第十一章“操作自定义语法”中更详细地探讨这一点。

  • Stylelint: 这个插件包应该是任何 PostCSS 开发者的工具包的一部分:它允许我们自动检查我们的样式表。这个插件包可在 github.com/stylelint/stylelint 获取。

  • cssnano: 类似地,cssnano 应该是任何开发者的工具包的一部分:这个包非常适合压缩和优化我们样式表中的代码。你可以从 github.com/ben-eb/cssnano 获取这个插件。

  • Rucksack: 最后,但绝不是最不重要的,Rucksack(如开发者所描述的),是一个“……CSS 超级能力的口袋”。这个包增加了对诸如font src生成、提供 RGBA 值的回退支持或clearfix技巧等功能的支持。

这些插件包提供了真正的功能混合——自然是我们决定要使用哪些插件。然而,此时,有人可能会想问“为什么使用插件包——这难道不是添加了不必要的冗余功能,而我们试图避免添加这些功能?”

这是一个好问题,简单的答案是这取决于你的需求。如果我们只对添加供应商前缀感兴趣,然后检查和压缩我们的代码,那么我们可能会使用autoprefixercssnanostylelint。但如果我们想添加属性别名,那么可以放弃使用autoprefixer,转而使用带有cssnanostylelint的 Rucksack。不过,关键在于仔细评估你的需求,并确定从 PostCSS 提供的插件中选择最佳组合。

好了,是时候改变一下了:让我们继续前进!在接下来的几页中,我们将探索一些这些插件包:让我们从一个简单的插件开始,形式为postcss-short

使用缩写编写样式

这个插件可以从github.com/jonathantneal/postcss-short获取,它是 PostCSS 生态系统中的几个插件的包装器;这些包括缩写边框、缩写颜色和缩写大小。安装这个插件非常简单——它使用与大多数其他 PostCSS 插件相同的格式,并且可以在我们的项目根区域内的 Node.js 命令提示符会话中使用此命令安装:

npm install postcss-short --save-dev

这个插件(以及其他插件包)的伟大之处在于,它消除了调用大量单独插件的需求。但我们必须记住,为了使这变得有价值,我们需要以某种形式调用大多数插件。如果我们只从postcss-short中调用一个或两个,那么我们可能更愿意单独调用它们,而不是使用postcss-short插件。

放下对使用插件的任何顾虑,让我们来看看它在实际应用中的几个例子。体验它的最佳方式是使用jonathantneal.github.io/postcss-short/上的在线编辑器。我们可以用它来实验,在编译前将最终结果添加到我们的样式表中:

使用缩写编写样式

在这个例子(来自插件网站)中,我们使用了所有插件,除了缩写文本和缩写数据。在我们的代码中,我们使用了插件指定的相关缩写——PostCSS 将编译成有效的 CSS 样式,正如每个插件中概述的那样。

我们使用哪些插件当然取决于我们的需求——完全有可能你将发现自己在某些插件上比其他插件使用得更多。然而,就缩写主题而言,有一个插件包可能会经常出现在你的工具箱中——Rucksack。不,我并不建议这是一个去度假的机会(没有开玩笑),而是一个使用 PostCSS 中将是一个非常有用的插件集的机会。

使用 Rucksack 添加快捷方式

提到 Rucksack 这个词,人们可能会认为我们即将开始一段旅程或假期——虽然这种愿望可能存在,但还有更多实际的事情需要先处理!

话虽如此,使用 PostCSS 很容易被视为一次旅行;当与插件一起工作时,这一点尤其正确。当你与 PostCSS 一起工作时,你可能会遇到的一个插件(或者更准确地说,是一组插件)是 Rucksack(那里有文字游戏?)。这个有用的插件组,可在simplaio.github.io/rucksack/找到,包含了一系列相互链接的插件,为我们提供了在编译样式表时使用 Rucksack 的附加功能,例如以下示例:

  • 别名:此功能可在github.com/seaneking/postcss-alias找到,此插件允许我们创建简写 CSS 属性。

  • 清除浮动:作为一名开发者,我确信你一定熟悉清除浮动的技巧:Sean King 的这个插件提供了 PostCSS 的等效功能,可在github.com/seaneking/postcss-clearfix找到。

  • 字体源扩展:你有多少次在代码中使用自定义字体声明?它们编写起来很痛苦——Sean King(在github.com/seaneking/postcss-fontpath)的另一个插件,使得将其添加到代码中变得轻而易举。

然而,这里的讽刺之处在于,我们已经在不知不觉中使用了 Rucksack——还记得我们在第四章中使用的postcss-responsive-type插件吗?或者我们在本书的几乎所有章节中使用的autoprefixer插件?这两个插件都可通过 Rucksack 获得——Rucksack 实际上是一个抽象层,它将多个插件整合到一个一致的用户界面中,供我们使用。

好了,闲话少说:让我们开始一个演示,看看一些实际操作!对于我们的下一个演示,我们将使用一些标准的 HTML 标记和 CSS3 样式构建一个简单的滑块;根本不会使用 JavaScript。在我们将样式表转换为使用 Rucksack 之前,我们将快速浏览一下我们的滑块。

介绍我们的演示

对于接下来的演示,我们将打破传统,在创建我们的演示之前,不安装我们即将使用的插件。相反,我们将首先设置我们的演示——一旦我们设定了基线解决方案,我们就可以确定 Rucksack 可以用在哪里。

我们的演示集中在简单的图片滑块上,它使用纯 CSS3 样式来控制动画。这是我们即将创建的截图:

介绍我们的演示

要查看演示的实际效果,请从本书附带的代码下载副本中提取T45 – 转换为使用 Rucksack文件夹——将其保存到我们的项目区域。然后在浏览器中运行slider.html来预览结果,然后点击左下角的数字在不同的图像之间切换。

注意

您需要将样式文件 post-completed.css 重命名为 style.css 以确保其正确运行。

将 Rucksack 作为插件安装

在我们的演示就绪后,是时候安装 Rucksack 并确定我们可以在我们的演示中使用它的位置!Rucksack,像大多数其他 PostCSS 插件一样,可以使用相同的方法安装——我们可以使用 NPM:

  1. 启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  2. 在提示符中输入 npm install rucksack-css --save-dev,然后按 Enter

  3. NPM 将安装插件——如果一切顺利,我们应该看到类似这样的东西:安装 Rucksack 作为插件

注意:Rucksack 有几个插件可用:确保安装正确的插件!有一个 Gulp 插件,但它似乎在 PostCSS 中不起作用,尽管我们使用 Gulp 作为任务运行器。

除了这个之外,让我们继续前进。在我们通过将我们的滑块转换为使用 Rucksack 的过程之前,让我们快速看一下使用它进行简单缓动演示的情况。

慢慢使用 Rucksack

任何在网站上花费时间动画内容的开发者无疑都创建过规则来控制内容如何进入或退出页面。在内容进入和退出页面以及网站效果过载之间找到正确的平衡需要时间才能做到!

不考虑最后一条评论中的糟糕的双关语,这正是 Rucksack 可以帮助的地方——这个包中包含的几个简单插件之一是 postcss-easings。这个插件可以从 github.com/postcss/postcss-easings 获取,我们在 第七章 动画内容 中也提到了它,它的唯一作用就是将任何已识别的缓动名称转换为 cubic-bezier 等价值。

小贴士

例如,要查看贝塞尔曲线,请访问 cubic-bezier.com/#.17,.67,.83,.67

你问这样做有什么好处吗?嗯,我想到了两个:一致性和中心源点。让我解释一下我的意思。

中心源点借鉴了 CSS 预处理器(如 SASS 或 less)的原则,其中在文件顶部定义了一个单一值;文件中其他地方找到的任何实例都将自动替换为其值(在这个例子中,是一个 cubic-bezier 缓动)。这有助于保持一致性:我们可以在配置中指定任何自定义缓动的名称,这将替换在编译过程中找到的任何实例。

这样做的优点是,我们只需要更新一个中心点(即源点),并且可以避免在我们的代码中混合不同类型的缓动值——它们将在编译时转换为 cubic-bezier 值。

好的,让我们继续:现在是演示时间!在我们深入之前,让我们快速了解一下我们将要构建的内容。我们的演示很简单,只有四个方块,我们将使用纯 HTML 和 CSS(是的,没有 JavaScript)来动画化它们。我们将使用一些效果,例如 easeOutBack,它看起来像这样:

开始使用 Rucksack

小贴士

你可以在 easings.net/#easeOutBack 上了解更多关于这个特定缓动细节的信息——当在代码中使用时,它转换为 cubic-bezier(0.175, 0.885, 0.32, 1.275)

让我们开始构建那个演示…

使用插件动画内容

如果你期待戏剧性的效果,那么我很抱歉让你失望——这个练习被故意保持简单,以展示使用 Rucksack 是多么容易。我们之前提到,整体结果将是四个我们可以随意动画化的简单方块——它们看起来可能像这样:

使用插件动画内容

使用此插件确实会引发一个重要问题——我们将在构建完我们的演示后讨论这个问题:

  1. 从这本书附带的代码下载中,请提取 T44 - postcss-easings 文件夹的副本,并将其保存到项目区域的根目录。

  2. 接下来,将 T44 - postcss-easings 文件夹中的 gulpfile.jspackage.json 文件复制到项目区域的根目录——请替换该位置已存在的任何文件,或者将它们保存在安全的地方。

  3. css – completed versions 文件夹中的 style – pre-comile.css 文件复制到项目区域内的 src 文件夹中;这将使其准备好进行编译。将其重命名为 style.css

  4. 好的,请启动一个 Node.js 命令提示符会话,然后将工作文件夹更改为我们的项目区域。

  5. 在提示符中输入 Gulp 然后按 Enter 键——我们的文件将被编译,如下所示:使用插件动画内容

  6. 假设编译过程中没有出现任何问题,将 dest 文件夹的内容复制到 T44 - postcss-easings 文件夹内的 css 文件夹中。

  7. 尝试在浏览器中预览我们手工制作的成果——如果我们悬停在每个方块上,动画将启动;它们应该看起来与这个练习开始时显示的图相似。

我们的演示从未打算变得复杂——目的是展示如果配置对象设置正确,获得一致效果是多么容易!然而,这确实引发了一个关于我们插件选择的重要问题,因此让我们更详细地探讨这个问题。

更详细地剖析我们的演示

这就是简单性带来巨大回报的一个例子;postcss-easings插件在标准使用情况下无需配置,只有当我们使用的缓动效果不是其核心库的一部分时才需要配置。我们为这个演示选择的缓动效果已经在插件中定义了——如果我们打开编译后的样式表副本,我们应该能看到类似以下的内容:

更详细地剖析我们的演示

配置此插件的关键在于两行代码,大约在第 11 行和第 16 行:

var rucksack = require('rucksack-css');
  .pipe(postcss([ rucksack() ]))

在这个演示中使用的 Gulp 任务文件中存在的大多数代码是我们之前已经见过的;提前思考通常是有益的,这样我们就可以构建一个可以用于未来项目的 gulp 文件。一旦配置完成,任何被插件识别的样式都将编译成有效的 CSS。

如果我们决定使用自定义的缓动样式,那么我们可以很容易地相应地更新配置对象:

更详细地剖析我们的演示

如果你对此名称感到好奇——这个效果模仿了在你取得好成绩后击打空气时的动作,尤其是如果它是一个棘手的问题的话!

在我们进行下一个练习之前,我们应该回答我之前提到的那个问题:我们应该使用哪个插件?但是等等,我们正在使用postcss-easings插件,对吧?

不,我并没有完全失去方向:postcss-easings插件可以单独使用,并且在 Rucksack 插件包中被引用。然而,关键在于,如果我们只需要使用postcss-easings,那么调用 Rucksack 的插件就没有意义了,这只会给我们的工作流程增加不必要的负担。相反,我们可以将 gulp 任务文件中的第 11 行改为以下内容:

var easings = require('postcss-easings');

我们还可以将第 16 行改为以下内容:

.pipe(postcss([ easings() ]))

只要插件还是之前安装的,代码就会像以前一样编译,但不会包含 Rucksack 中其他插件带来的额外开销。

将我们的滑块转换为使用 Rucksack

如果我们在使用 Rucksack,我们已经看到,成功使用的关键与其说是为我们的 Gulp 文件配置它,不如说是决定使用哪些插件。为了理解我们的意思,请仔细查看介绍我们的演示的原始样式表;它应该揭示我们可以使用多个插件来改进现有功能:

  • 响应式排版:我们的演示已经部分是响应式的,但是当我们改变滑块的大小,标签文本不会调整大小。我们可以通过修改我们的代码来触发 Rucksack 使我们的字体响应式。

  • 简写定位:这是一个添加位置属性的出色插件;为什么要在一行代码中添加 top、left、right 和 bottom 属性呢?添加这一行代码,我们就可以让 PostCSS 为我们做繁重的工作。

  • 属性别名:继续简写主题,我们可以使用此插件设置任何我们关心的属性的简写版本;这意味着我们只需要输入一个或两个字母,PostCSS 就会将其转换为该属性的完整版本。

  • 字体源扩展:如果我们仔细查看我们的源样式表,我们应该在第 6 行或附近看到一个小的错误。代码要求使用 Open Sans 作为字体,但这不是标准字体!幸运的是,我们可以通过告诉浏览器从哪里下载它来轻松修复它——Rucksack 为我们提供了方便的简写形式,以便将此详细信息添加到我们的代码中。

  • Hex RGBA 快捷方式:我们在样式表中混合使用了 RGBA 和 HEX 代码来表示颜色,但一些较旧的浏览器不支持前者。这已经不再是问题,但由于 Rucksack 会自动添加这些值,因此添加后者也没有坏处!

  • 缓动效果:我们的滑块演示使用了一个缓动效果的实例,形式为 ease-in-out-back。在上一个演示中,我们使用了postcss-easings(这是 Rucksack 这部分的基础)将名称转换为cubic-bezier值;在更新滑块时,我们应该继续这个主题。

  • 自动前缀:默认情况下,这并未启用,因此我们决定是否要使用它。它引用了我们之前在早期演示中已经使用的相同的autoprefixer插件;如果我们打算充分利用 Rucksack,那么启用它并删除我们代码中已经存在的任何现有引用是有意义的。我们将使用 Rucksack 中引用的大多数插件,因此我们将启用它以供使用。然而,如果我们只需要一个或两个,或者我们不需要对旧浏览器的支持,那么它可以保持关闭状态。

  • 遗留回退机制:Rucksack 中的laggard插件提供了对旧浏览器的支持。它提供了一系列回退机制,例如为 rem 值添加回退支持,为 RGBA 值提供 HEX 回退,或为 will-change 的 3D 转换提供 hack。我们将添加 rem 回退支持,因此我们将启用此插件以供使用。

现在我们已经涵盖了我们要使用的元素,是我们进行更改的时候了。无需多言,让我们开始吧:

  1. 从本书附带的代码下载中,请继续提取T45 - 转换为使用 Rucksack文件夹的副本,并将其保存到我们的项目区域。

  2. T45 - 转换为使用 Rucksack文件夹内,将gulpfile.jspackage.json文件复制到我们项目区域的根目录。

  3. 接下来,将T45 - 转换为使用 Rucksack文件夹内的css-completed版本文件夹的内容复制到我们项目区域的根目录的src文件夹。将预编译的version.css重命名为style.css,然后在文本编辑器中打开此文件——我们需要对文件中的样式进行一些修改。

  4. 我们的第一项更改是使演示中的文本响应式——搜索 font-size,并将任何实例更改为 font-size: responsive。这应该涵盖每个五个数字标签,以及 div.slide-content > figcaption 规则。

  5. 接下来,添加我们针对位置属性的简写版本——在这个例子中,我们只能更改一行,即第 42 行。注释掉第 33-36 行指定的 bottomleftrighttop 属性,然后将 position: 属性替换为以下内容:

    position: 427px 0 0 0;
    

    注意

    注意,其他位置实例不能更改,因为我们没有在这些规则中指定单独的位置值。

  6. 我们接下来的转换是添加一些别名——这只是一个输入更多文本的快捷方式!对于我们的演示,请在我们的样式表顶部添加以下内容:

    @alias {
      pb: padding-bottom;
      bs: box-shadow;
      bgc: background-color;
    }
    

接下来,对这三个样式分别进行搜索和替换——将全名替换为 @alias 块中给出的快捷名称。

  1. 记得我之前指出的那个小问题,关于 Open Sans 字体的缺失支持吗?嗯,我们可以轻松解决这个问题——在我们的样式表文件顶部,添加以下代码块;这告诉浏览器在哪里找到 Open Sans 字体:

    @font-face {
      font-family: 'open_sansregular';
      font-path: '../fonts/OpenSans-Regular-webfont';
      font-weight: normal;
      font-style: normal;
    }
    
  2. 我们简要提到了 Rucksack 将缓动名称转换为 cubic-bezier 值的能力。这个演示使用了一个缓动名称——这已经设置为 Rucksack 内部支持的值,因此我们不需要更改我们的代码。同样适用于 RGBA 回退支持——Rucksack 会自动将代码中看到的任何 RGBA 值转换为十六进制等效值。

  3. 剩下的两个更改是为了支持旧版本和自动添加前缀——为了启用这些功能,我们必须修改我们的 gulp 文件,如图所示:将我们的滑块转换为使用 Rucksack

  4. 保存文件后,切换到 Node.js 命令提示符——在提示符下,确保工作文件夹设置为我们的项目区域。

  5. 在提示符下,输入 gulp,然后按 Enter——PostCSS 将消失并编译代码;如果一切顺利,我们应该在 dest 文件夹中看到我们的编译文件:将我们的滑块转换为使用 Rucksack

在这个阶段,我们有一组编译后的文件。为了确认演示是否正常工作,请将它们复制到 T45 - 转换为使用 Rucksack 文件夹内的 css 文件夹中;通过运行 slider.html 预览我们工作的结果。如果一切顺利,我们应该看到相同的滑块效果:

将我们的滑块转换为使用 Rucksack

一切应该都很好,我们有一个正常工作的演示,并且我们的代码已成功编译。在这个时候,我们可以继续进行下一个任务,对吧…?

分析我们的代码

嗯,先看看我们的编译代码是值得的:Rucksack 对我们的代码进行了一些额外的更改,这些我们可能没有预料到。

例如,Rucksack 为我们代码中列出的 rem 单位提供了基于像素的回退支持,以及我们之前讨论的 HEX 回退支持:

body {
  font-family: "open_sansregular";
  line-height: 25.888px;
  line-height: 1.618rem;
  background-color: #ecf0f1;
  background-color: rgba(236, 240, 241, 1.0);
  color: #44466a;
  color: rgba(68, 68, 618, 1.0);
}

接下来,看看第 96 行——还记得我们添加的font-size: responsive属性吗?这是编译后的结果:

font-size: calc(12px + 9 * ( (100vw - 420px) / 860));

在底部三分之二的部分,添加了许多媒体查询;这些查询是为了使我们的字体样式响应式而添加的。进一步向下,大约在第 226 行,我们有这个代码块:

-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
 -webkit-transition: opacity 0.35s;
 transition: opacity 0.35s;

初看起来,你可能会想知道这从哪里来,因为我们没有在我们的代码中指定ms-filter属性。好吧,这要归功于 Rucksack——它自动为 IE 添加了透明度支持。

然而,这个小探索的关键是,选择插件应该是一个迭代的过程,只有当网站不再需要时,这个过程才能真正结束。例如,我们可以轻松地在我们工作流程中添加另一个步骤,将calc()操作减少到静态值(在允许的情况下——这个插件的名称是postcss-calc)。我们应该定期考虑使用postcss-remove-prefixes来保持我们的代码更新;总会有那么一天,我们不再需要添加前缀,或者现有的前缀变得冗余。

除了对我们样式表的更改之外,还有一点需要考虑——你会注意到在我们的代码中,Autoprefixer 插件已经被注释掉了:

Dissecting our code

这是有充分理由的——Rucksack 内置了对autoprefixer的支持,因此没有必要调用两次;具有讽刺意味的是,它只是调用了我们代码中注释掉的同一个插件!我们决定是否从 Rucksack 内部调用它,或者单独调用;这主要取决于 Rucksack 中还有其他什么被调用,以及添加autopre fixer是否有助于为使用 Rucksack 提供更强的理由。

Linting and optimizing your code

带宽使用一直是网站成功的关键;还记得那些 56K 调制解调器的美好时光吗?自从那时以来,我们已经走了很长的路,但这并不是生产出像过时一样消耗带宽的网站的借口!

其中一部分是通过在部署到生产使用之前对样式表进行 linting 和 minifying 来实现的——不言而喻,这应该成为任何开发者的工作流程过程的一部分。我们可以手动完成这项工作,但这项手动工作容易错过机会,这可能导致我们的代码出现不一致。

相反,我们可以利用 PostCSS 的力量为我们完成繁重的工作;stylelintcssnano插件包提供了一个强大的优化工具!如果我们仔细查看本书过程中创建的大多数 gulp 任务文件,这两个过程都在进行;在这个例子中,stylelint在第 22 行使用,cssnano在第 38 行使用:

Linting and optimizing your code

探索 cssnano 的使用

对于第一次使用 PostCSS 的人来说,只需将cssnano()指定为 PostCSS 的一个处理器就足够了:

Exploring the use of cssnano

如果我们查看T45 – 转换为使用 Rucksack演示,我们的原始样式表文件在编译后重量为 4KB,但在压缩后降至 3KB。当然,这是一个很小的文件,但 25%的尺寸减少仍然不是微不足道的减少!

提示

到目前为止,值得注意的是,尽管我们正在使用 Gulp,但使用的插件是 PostCSS 版本,而不是gulp-cssnano

cssnano插件不是一个单独的插件,而是一系列插件的包装器,包括这些示例:

  • postcss-reduce-idents: 这将任何自定义标识符名称(例如在@keyframes中使用的名称)减少到两个字母的等效代码;这有助于代码压缩。

  • postcss-zindex: 此插件减少了任何不必要的比应有的z-index声明更高的声明。

  • postcss-convert-values: 如果我们的 CSS 使用了任何数量的不同单位,那么我们可以通过不同的方式表达值来减少 CSS 的大小。例如,400ms可以表示为.4s(减少了两个字符)。有些人可能会认为这有点极端,但任何一点帮助都是好的!

  • postcss-colormin: 类似地,我们可以使用此插件来减少颜色名称的长度:如果我们的代码中使用了rgba(255, 0, 0, 1),那么我们可以将其替换为red。虽然名称确实更短,但这是以牺牲与颜色命名的连贯性为代价的,这可能并不那么理想。

接下来,当使用cssnano时,我们应该注意一些关键点:

  • 你会注意到在我们的 Gulp 文件中使用 gulp-rename 插件——cssnano没有能力将压缩文件重命名为我们希望在代码中看到的名称。我们可以使用gulp-rename创建一个开发人员期望在代码中看到的版本;如果需要,它会在原文件处留下副本。

  • cssnano中的大多数选项默认启用;我们可以在配置对象中关闭个别选项,如本例所示:

    var nano = require('gulp-cssnano');
    
    gulp.task('default', function () {
        return gulp.src('./main.css')
            .pipe(nano({discardComments: {removeAll: true}}))
            .pipe(gulp.dest('./out'));
    });
    

    提示

    要查看完整的转换选项列表,请查看cssnano.co/options/。点击链接查看该插件的个别配置选项。

  • 此插件自动包含 autoprefixer。技术上,我们不需要单独包含它,就像我们在之前的练习中所做的那样,因此理想情况下应该将其删除。我们将在第十章 Building a Custom Processor 中更多地关注这一点,构建自定义处理器

  • cssnano中有些转换是可用的,但默认情况下并未启用;这些不被认为是安全的,并且只有在您 100%确信它没有影响您的代码时才应包含。不安全转换的详细信息可在options页面cssnano.co/options/上找到。

好吧,让我们继续:我们双打表演的第二部分是 stylelint 插件包;与 cssnano 不同,stylelint 采取相反的方法,允许你根据需要从 100 多条可用规则中选择任何规则。让我们深入探讨并更详细地看看。

将 Stylelint 配置为我们的代码检查器

如果你之前从未遇到过 Stylelint,该如何描述它呢?好吧,引用其网站上的话,“stylelint 是一个强大、现代的 CSS 代码检查器”。

无论我们是否认同这样的大胆声明,了解 Stylelint 作为代码检查器的价值无疑是值得的。它可以从 stylelint.io/ 获取,这个插件的关键不在于插件本身,而在于定义我们想要检查代码中哪些内容的规则。目前,我们可以使用 100 多条规则中的任意一条,或者混合使用几条;这些规则可以在 .styleintrc 文件中、package.json 文件内,或者作为一个导出 JavaScript 对象的 stylelint.config.js 文件中指定。

我们已经在早期项目中使用了 stylelint;为了方便,我们的 Gulp 任务中已经指定了一些规则:

将 Stylelint 配置为我们的代码检查器

我选择了一些规则来展示我们如何使用 Stylelint;当然,作为开发者,选择哪些规则作为代码检查的一部分是你们自己的决定。Stylelint 并不包含一组默认启用的核心规则——它所做的任何检查都将取决于规则配置中指定的内容。

提示

一个有用的书签来源是 stylelint.io/——它包含了一整套可以在编译代码之前添加到 Stylelint 配置中的规则。

例如,如果我们正在构建一个大量使用黄金法则的响应式网站,我们可能希望将任何百分比值限制在三个或四个小数位以内。为此,我们将指定 number-max-precision 规则——它接受一个整数值;指定 3 将会对这两个属性发出警告:

.foo { top: 3.2456px; }
.foo { top: 3.245634px; }

对于这个例子来说,情况并非如此:

@media (min-width: 3.234em) {...}

我强烈建议阅读规则列表,以了解可用的选项;熟悉内容需要时间,但回报将是优化并检查后用于生产环境的代码。不过,有一点需要注意——即使我们无限期地优化代码,仍然有可能需要包含对旧浏览器的某些支持。

在理想的世界里,我们会说服我们的客户限制这种支持的好处(或者甚至不提供支持)。然而,如果客户坚持这样做,尽管我们判断不佳,PostCSS 也可以轻松地帮助我们提供这种支持。让我们探索一下可用的选项——其中大部分将集中在 IE(因为这是最大的罪魁祸首),但同样适用于其他浏览器。

提供回退支持

在设计网页内容时,一个关键的关注点是浏览器支持——我们需要支持哪些浏览器?如果我们要支持的唯一浏览器是 Firefox 或 Chrome 这样的,那么我们的工作就会容易得多。问题是,我们还得支持 IE、Safari、Edge……更不用说移动设备了!否则生活就会变得无聊……

但我跑题了,回到现实:对于那些拒绝遵守标准的旧版浏览器(是的,我特别指的是 IE),我们必须考虑提供某种形式的支持或优雅降级。幸运的是,在 PostCSS 生态系统中,我们可以使用许多插件——我们已经使用了一个,就是 Autoprefixer;还有其他可用的插件,所以让我们深入了解一下这些插件的一些详细情况。在我们这样做之前,有一个有用的技巧我想探讨,这个技巧可以帮助我们在浏览器中检查和提供旧版支持。

检测对特性的支持

开发过程中的一个关键部分是确保我们的代码能在那些我们必须支持的浏览器上运行。如果我们足够幸运,这个浏览器范围仅限于较新的产品,那么这就不成问题了。

对于一些开发者来说,可能需要支持旧版浏览器;如果环境中包含其他基于浏览器的应用程序,这些应用程序需要使用这些旧版浏览器,并且无法被替换,那么这一点尤其正确。

为了解决这个问题,我们可以使用像 Modernizr(www.modernizr.com)这样的库,但更有效的方法是使用 CSS 的@supports指令。简而言之,它的工作方式与媒体查询类似;我们可以指定一个所有浏览器都支持的备用属性,如果我们使用的是可以支持增强属性的浏览器,就可以取消它:

section {
  float: left;
}

@supports (display: -webkit-flex) or (display: flex) {
  section {
    display: -webkit-flex;
    display: flex;
    float: none;
  }
}

主要来说,这更有可能对那些需要为旧版 IE 提供支持的人有用(鉴于其他浏览器已经提供了一段时间的支持)。理想情况下,我们会试图说服客户不支持旧版 IE 的好处(至少是 8,可能是 9)。如果我们必须支持它们,这种方法结合使用 autoprefixer 可能会非常有用。

提供对旧版浏览器的支持

当谈到为旧版浏览器提供支持时,最大的罪魁祸首可能是微软的 IE。虽然必须说,在新版本中的支持正在改善,但它的流行意味着仍然有足够的旧版本需要提供支持!

值得称赞的是,微软已经宣布他们不再支持 IE8 到 10——这是朝着鼓励用户升级的正确方向迈出的一步,尽管这些版本完全消失还需要一段时间。

那么,如果你仍然需要支持旧版本的 IE,我们能做什么呢?一个值得尝试的好方法是使用旧版插件包;它的名字是对某些人所说的 oldIE 的引用,或者那些本应很久以前就被归入历史的 IE 版本!

这个插件可以从github.com/jonathantneal/oldie获取,是 PostCSS 内众多插件的包装器;以下是一些示例:

  • post-calc:尽可能将任何calc()实例减少到单个值;使用混合单位的calc()实例可能不会被替换

  • postcss-unroot:如果我们的 CSS 使用了:root选择器,那么旧版本的 IE 将失败;这个插件将它们替换为 HTML,以便我们的代码可以编译

  • postcss-unnot:在类似的情况下,如果我们指定了包含使用:not伪元素的元素的规则,那么这些选择器将被移除

  • postcss-unopacity:将任何透明度属性的实例转换为使用filter: alpha(opacity=XX),其中XX是原始透明度属性的等效值

该包还包括其他插件——为了了解包含哪些插件,查看插件包的index.js文件是值得的;它看起来可能像这样:

为旧版浏览器提供支持

然而,美中不足的是,我们并不被迫完全使用 oldie 插件。如果我们由于某种命运的怪癖,成功地创建了一些主要避免了可能导致旧 IE 崩溃的 CSS 属性的代码,那么我们可以选择仅引用我们需要使用的那些插件。

当然,这取决于我们使用了多少——更可能的情况是我们最终需要使用所有插件,因此使用 oldie 更有意义!当然,我们总是可以鼓励我们的客户放弃旧 IE——这可能不像听起来那么简单…

从代码中移除样式黑客

如果我们足够幸运,拥有理解我们的客户——机会总是美好的——那么我们可能需要执行一项任务:从我们的代码中移除与不再支持的浏览器相关的任何样式黑客。如果只有一个小样式表,这些黑客的移除可能微不足道;但现实是,对于更大的样式表,这很可能是漫长且需要手动处理的过程,存在我们遗漏黑客的风险。

相反,我们可以利用一个插件,即 stylehacks;它与我们在本书中的演示中已经使用的stylelint插件配合得非常好。可以从github.com/ben-eb/stylehacks获取该插件,它使用browserhacks.com上列出的黑客,安装起来非常简单——让我们看看它的实际效果:

  1. 我们首先从本书附带的代码下载中提取T47 - 使用 stylehacks文件夹的副本;将其保存到我们的项目文件夹根目录。

  2. gulpfile.jspackage.json文件复制到项目文件夹的根目录。

  3. 在文本编辑器中,添加以下代码,并将其保存为src文件夹中的style.css不在 T47 - 使用 stylehacks文件夹内!):

    h1 {
      _color: white;
      color: rgba(255, 255, 255, 0.5);
    }
    
  4. 接下来,启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  5. 我们现在需要安装这个插件——在提示符中输入以下命令,然后按 Enter

    npm install stylehacks --save-dev
    
    
  6. 当插件安装完成后,继续在提示符中输入 gulp,然后按 Enter

  7. PostCSS 将编译我们的代码——如果一切顺利,我们应该在我们的项目区域根目录的 dest 文件夹中看到这个:从代码中移除样式漏洞

假设我们成功编译了代码,尝试在文本编辑器中打开 style.css 文件——我们当然会在代码底部有必要的源映射指令,但请注意它如何移除了颜色漏洞:

从代码中移除样式漏洞

这个过程的关键在于我们 Gulp 文件中的这个任务:

gulp.task('styles', function () {
  return gulp.src('src/*.css')
    .pipe(postcss([ stylehacks({browsers: 'last 1 version, > 10%'}) ]))
    .pipe(gulp.dest('dest/'));
});

在我们的 Gulp 文件中,我们添加了浏览器属性——这告诉 stylehacks 移除任何对于现代浏览器或那些全球使用率超过 10% 的浏览器来说不是必需的漏洞。这个设置基于从 github.com/ai/browserslist 可用的 Browserslist 查询列表——值得注意的是,这也可以用于像 Autoprefixer 这样的插件。

注意

如果我们开始包含更多使用 Browserslist 查询的插件,那么考虑使用变量来代替查询;从中央位置更新它将自动更新所有使用它的插件。有关更多详细信息,请参阅 GitHub 上的 Browserslist 网站。

尽管这个插件使用起来很简单,但在为这本书进行研究的过程中,有一个令人烦恼的问题一直萦绕在心头:这个插件在现实中到底有多有用?对于一些人来说,你可能觉得我需要检查一下我的脑袋,但这是有原因的——让我来解释一下。

如果你花过任何时间用 jQuery 进行开发,那么你应该知道它从 jQuery 2.x 版本开始已经不再支持 IE6-8,并且只会在 1.x 分支中继续支持它。1.x 分支中 jQuery 的许多负担都是基于为浏览器漏洞而设计的;这些漏洞的最大罪魁祸首就是 IE!

在撰写本文时,微软已经公开声明将停止支持 IE10 及以下版本(至少对于 Windows 8 而言)——他们只会在 Windows 8.1 平台上支持 IE11,以及他们新的浏览器 MS Edge。

由于 CSS 中发现的许多漏洞都是为了 IE(这也适用于 jQuery),人们不禁要 wonder 如果 stylehacks 插件在不久的将来仍然有用!我们也应该问自己,使用漏洞是否是一个好的设计决策。我们是否在为将来设计一个问题,或者我们应该重新考虑我们的原始设计,也许重新审视我们是否需要为旧浏览器提供专用的样式表,而不是在为较新浏览器设计的代码中引入漏洞?

摘要

PostCSS 的关键部分是探索 PostCSS 生态系统中可用的插件不断增加的数组;在某些方面,它可以被比作一次发现之旅。对于单个插件或包含在包中的插件来说,情况也是如此——我们已经看到,许多这些包由可单独使用的相同插件组成!让我们花点时间回顾一下我们已经学到的内容。

我们的旅程始于查看那些可以帮助我们节省写作时间的插件——这些插件可以是那些允许我们使用缩写来编写,或者基于我们在代码中指定的样式添加缺失的样式。这些也包括一些帮助为旧浏览器提供回退支持的插件。

然后,我们转向使用postcss-short插件,作为一个例子,说明我们如何减少开发时间,然后继续探索 Rucksack 插件套件,这些插件可以帮助添加我们可能在 CSS 中需要使用的某些缺失元素。为了探索 Rucksack 的工作原理,我们从一个简单的缓动演示开始,然后逐步通过一个更复杂的滑块演示,将其转换为使用 Rucksack,并探讨这个插件包在开发中可以帮助的一些方式。

在我们的旅程中,接下来我们关注了对于开发者来说应该是一个关键部分的发展工作流程——检查我们的代码一致性,并对其进行优化。我们介绍了cssnanostylelint插件的用法,以及它们如何根据我们的需求进行定制。

然后,我们通过回顾对旧浏览器的支持来结束这一章——我们在之前的访问基础上,对 PostCSS 中可用的内容进行了更深入的探讨;我们探讨了许多回退主要是由于 IE 引起的,并讨论了我们可以如何帮助支持这个浏览器的旧版本,或者我们是否应该考虑将一些旧浏览器的支持归入历史。

呼吸,这真是一次对 PostCSS 可用的插件包的快速浏览!我们的旅程并未到此结束,因为我们现在需要将最后几章的内容综合起来,制作出一个完整的自定义处理器——这将是下一章的主题。

第十章:构建自定义处理器

使用 PostCSS 的一个关键好处是其模块化方法——我们不必使用一个大型的库,尤其是如果我们只需要使用其功能的一小部分!在本章中,我们将汇集我们在前几章中讨论的一些主题,并创建一个完全符合我们需求的完整预处理器。

我们将用它来编译一个简单网站的代码,探索使用它为 WordPress 等 CMS 系统,然后看看如何将其扩展到与 CSStyle 等框架一起工作。在本章中,我们将涵盖多个主题,包括以下内容:

  • 创建我们的处理器

  • 优化输出

  • 添加源映射和供应商前缀支持

  • 在一个简单的网站系统上测试最终的预处理器

  • 将我们的预处理器扩展到使用 CSStyle 框架

让我们开始吧!

创建你的处理器

许多与现有处理器(如 SASS、less 或 Stylus)一起工作的开发者将习惯于使用一个必要的依赖库,并且不太可能使用他们选择的处理器提供的 100%的功能。

这并不是 PostCSS 的情况。一个主要的吸引力是其灵活性;我们不再需要携带那些对我们需求来说是冗余的处理器中的额外负担!灵活性的力量也可能是一个缺点,你可能会问,我们如何开始决定在我们的处理器中包含什么?

在本章的整个过程中,我们将汇集我们在演示中使用的处理器的各种元素,并探讨我们可以做出的改进或扩展功能的变化。然而,任何处理器的关键在于没有正确或错误答案;每个都会不同,并且它们将取决于你的需求。

随着时间的推移,你可能会发现可以在项目之间重用的常见元素,最终,作为开发者,找到满足你需求的组合取决于你。在此之外,让我们开始详细研究我们在最近示例中使用的处理器,并探索我们可以用来创建我们自己的版本的一些想法和技巧。

探索我们的处理器

作为本书中创建的演示的一部分,我们专注于确保插件已安装,并且我们有正确的文件在正确的位置。然而,还有一些东西缺失,那就是——文件中实际上发生了什么?为什么我们有特定的任务顺序?选择我们使用的某些插件背后的推理是什么……你大概明白了!

在接下来的几页中,我们将通过探索我们在一些最近示例中使用的处理器来尝试回答这些问题(以及更多),你会发现没有一种一刀切的方法,而更多的是根据你的需求进行工作,并选择适合你需求的插件。

在我们深入探讨之前,让我们快速回顾一下我们处理器的组成,从 package.json 文件开始。

解析 package.json 文件

package.json 文件告诉 PostCSS 使用哪些插件,并可能包含一些在编译期间使用的键配置设置:

{
  "name": "postcss",
  "version": "1.0.0",
  "description": "Configuration file for PostCSS",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Alex Libby",
  "license": "ISC",
  "dependencies": { "postcss": "⁵.0.8" },

我们处理器的上半部分包含许多关键属性,这些属性告诉我们有关版本、描述、创建者、任何依赖项以及项目使用的许可证等详细信息:

  "devDependencies": {
    "autoprefixer": "⁶.0.3",
    "cssnano": "³.2.0",
    "gulp": "³.9.0",
    "gulp-postcss": "⁶.0.0",
    "gulp-rename": "¹.2.2",
    "gulp-sourcemaps": "¹.5.2",
    "postcss-reporter": "¹.3.0",
    "stylelint": "².3.7"
  }
}

与此相比,对我们来说关键部分在底部;这部分列出了我们项目中将使用的所有插件。在我们的许多项目中,我们在安装时安装了插件——插件将添加一个包含名称和所需最小版本(由 ^ 符号表示)的条目到此文件中。

值得注意的是,我们可以手动向此文件添加条目,或从该文件中删除条目,甚至在需要时将 package.json 文件从一个项目复制到另一个项目。如果我们知道新项目与现有项目具有相同(或非常相似)的需求,这尤其有用;插件在安装时只会将条目添加到此文件中,如果该条目尚未存在。

探索 Gulp 任务文件

gulpfile.js 文件是真正魔法发生的地方——它包含了对我们项目中每个样式表需要执行的所有任务。除了样式表之外,这是我们简单地从代码下载复制到项目区域的第二个文件。现在我们已经在使用它了,值得花点时间更详细地探索它的工作原理。

gulpfile.js 文件由几个部分组成——在我们的示例中,我们从一个定义每个插件的引用变量的列表开始:

'use strict';

var gulp = require('gulp');
var postcss = require('gulp-postcss');
//var autoprefixer = require('autoprefixer');
var cssnano = require('gulp-cssnano');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
var stylelint = require('stylelint');
var reporter = require('postcss-reporter');
var rucksack = require('rucksack-css');

我们列表中的第一个任务是最重要的一个——它将源代码拾取并编译成有效的 CSS 文件,并将其存放在 dest 文件夹中。作为此过程的一部分,我们提供了任何需要用于转换我们代码的 PostCSS 插件的链接——在这个例子中,我们使用 Rucksack,设置为包含回退支持但不添加供应商前缀:

gulp.task('styles', function () {
  return gulp.src('src/*.css')
    .pipe(postcss([ rucksack({ fallbacks: true, autoprefixer: true }) ]))
    .pipe(gulp.dest('dest/'));
});

这个看起来复杂的任务实际上并不复杂——它根据设置的规则检查我们的代码的一致性,使用报告插件在屏幕上输出任何警告或错误。这里的关键是 ['styles'] 属性——这告诉 PostCSS 在样式任务完成之前不要执行此任务:

gulp.task("lint-styles", ['styles'], function() {
    return gulp.src("dest/*.css")
    .pipe(postcss([ stylelint({
        "rules": {
          "color-no-invalid-hex": 2,
          "declaration-colon-space-before": [2, "never"],
          "indentation": [2, 2],
          "number-leading-zero": [2, "always"]
        }
      }),
      reporter({ clearMessages: true, })
    ]))
});

与此相比,接下来的两个任务相对简单直接——这个任务负责压缩我们的编译代码,并将其重命名为 .min.css 扩展名:

gulp.task('rename', ['lint-styles'], function () {
  return gulp.src('dest/*.css')
    .pipe(postcss([ cssnano() ]))
    .pipe(rename('style.min.css'))
    .pipe(gulp.dest("dest/"));
});

此任务同样简单直接——它创建我们的样式表的源映射,并将其设置为 PostCSS 可以将其发布到我们项目区域 dest 文件夹中的文件格式:

gulp.task('sourcemap', ['rename'], function () {
  return gulp.src('dest/*.css')
    .pipe(sourcemaps.init())
    .pipe(sourcemaps.write('maps/'))
    .pipe(gulp.dest("dest/"));
});

在任何 Gulp 任务文件中,最后两个步骤扮演着最重要的角色——第一个步骤将在命令行提示符中输入 gulp 时触发对每个任务的调用:

gulp.task('default', ['styles', 'lint-styles',  'rename', 'sourcemap']);

这个任务虽然不是强制性的,但它会监视代码的任何更改,并自动触发 Gulp 文件中的任务。它将尊重任何设置的约束,尽管为了保持一致性,最好按照文件中显示的顺序列出正在执行的任务:

var watcher = gulp.watch('src/*.css', ['styles',
'lint-styles', 'rename', 'sourcemap']);
watcher.on('change', function(event) {
  console.log('File ' + event.path + ' was ' +
event.type + ', running tasks...');
});

编译过程不仅仅是这两个文件——我们也应该考虑如何构建我们的工作环境。快速查看 Gulp 任务文件应该会揭示我们已经使用了一个简单的输入/输出方法;代码从 src 文件夹中提取,并在编译结束时将结果放入 dest 文件夹。

这是流程的重要部分——毕竟,如果我们不对项目区域的结构进行思考,使用 PostCSS 就没有好处!这包括保持源文件和编译文件之间的分离,也可能决定将图像缩小等任务纳入编译过程。在这个区域的结构上没有对错之分——这将由我们的项目需求决定。

分析处理器的问题

在放置了 Gulp 任务文件和相关的 package.json 文件后,我们应该可以顺利进行了,对吧?嗯,并不完全是这样——是的,我们的处理器在整本书的演示中都被使用,效果很好。但我们可以做得更多:我们的 Gulp 文件永远不应该是静态的;我们应该定期审查它,以确保它以最佳效率运行。

注意

要查看 Gulp 文件的更新版本,请查看随本书提供的代码下载中的 T49 – fixing issues in Gulpfile 文件夹。

我们的项目 Gulp 文件确实存在一些需要解决的问题,所以现在让我们来看看这些问题:

  • 我们的一些任务命名不正确——例如,样式任务可以被重命名为更好地反映我们在该任务中使用 Rucksack。

  • 关于源映射的使用有一个问题;到目前为止,我们使用了一个专用的源映射插件来创建它们。Gulp 4 的即将到来的更改将减少对插件的需求——创建它们的支持被添加到 Gulp 核心中,因此不需要经常使用单独的插件!

  • 在重命名任务中,我们硬编码了一个 style.min.css 文件名作为输出;这并不适合所有需求,所以我们应该将其改为动态的。

  • 在重命名任务中保持——我们将两个任务合并在一起,而它们应该被分成两个独立的过程。

  • 看一下第 16 行样式任务中的处理器列表;现在这还不算太糟糕,但长期来看,它可能会变得很长且难以阅读!相反,我们需要更改它,以便在调用 PostCSS 的点上,我们可以使用数组来提供名称。

  • 当创建源映射时,我们当前的设置提供了完整和压缩版本;这真的有必要吗?问题来自 cssnano,它会压缩它看到的每一个 .css 文件;这不是必要的,所以需要更改。

  • cssnano的使用作为 PostCSS 中的一个任务运行正在引起问题——尽管以这种方式运行是有道理的,但它需要独立运行,以满足我们的需求。

  • 我们应该决定是否使用一个专门的插件来提供供应商前缀支持,还是依赖使用可能已经内置了这一功能的其他插件。

  • 在编译源文件时,我们的处理器正在生成两个最小化文件;一个是正确命名的,但另一个是为了开发目的而打算的不压缩版本。

在接下来的几页中,我们将探讨修复和改进我们的 Gulp 任务文件的方法——关键是要理解,虽然许多这些更改是针对我们的任务文件的,但它们也可能是你未来项目可能会遇到的问题。最重要的是,我们应该不断审查我们的生产过程,以确保它按需工作。

在我们将 Gulp 文件放在一个示例网站上测试之前,让我们开始修复和改进 Gulp 文件的过程。

修复我们的 Gulp 任务文件

不得不说,我们有一些问题需要解决——关键在于这些问题都不会阻止我们的编译过程;我们应该把它们看作是钻石的粗糙边缘,需要抛光才能使我们的过程更加闪耀(当然,这里是一个双关语!)。

注意

请注意,下一项练习中的行号指的是来自T48 – existing processor文件夹的源代码的未修改版本,在做出任何更改之前。如果您想保留现有文件的副本,请在开始练习之前将它们移动。

好的,让我们开始吧:有一些更改要做,所以我们将从关键任务开始,该任务编译源文件:

  1. 为了这个过程,我们需要从代码下载中的T48 – existing processor文件夹中的gulpfile.js文件的副本;请将其保存为gulpfile.js到我们的项目区域根目录。

  2. 我们需要做的第一个更改是启用文件中的autoprefixer支持——你应该在那里找到它,但在第 5 行被注释掉了;请继续移除注释。

  3. 在第 16 行或附近,寻找以下行:

    .pipe(postcss([ rucksack({ fallbacks: true, autoprefixer: true }) ]))
    

    我们不会包括回退支持,并将单独处理autoprefixer,所以现在,按照以下方式更改它:

    .pipe(postcss([ rucksack(), autoprefixer() ]))
    
  4. 我们接下来的更改是在 lint-styles 任务中——这里需要两个更改;首先,在 Rucksack 声明下方第 13 行添加以下代码块:

    var stylerules = {
      "color-no-invalid-hex": 2,
      "declaration-colon-space-before": [2, "never"],
      "indentation": [2, 2],
      "number-leading-zero": [2, "always"]
    };
    
  5. 接下来,将整个 lint-styles 任务替换为以下内容:

    gulp.task('lint', ['styles'], function() {
      return gulp.src("dest/*.css")
        .pipe(postcss([ stylelint({ "rules": stylerules }), 
        reporter({ clearMessages: true })
      ]))
    });
    
  6. 在重命名任务中,我们需要进行三个更改——首先,在第 38 行删除cssnano行;我们将任务拆分为两个,这将在新任务中处理。

  7. 这个任务有一个先决条件,我们已经将其重命名——请将第 36 行改为以下内容:

    gulp.task('rename', ['lint'], function () {
    
  8. 接下来,按照指示更改重命名命令——这是在第 39 行:

    .pipe(rename(renameFunction))
    
  9. 在下一个任务sourcemap中,我们需要进行一个更改——在第 47 行或附近,按照以下方式更改该行:

    gulp.task('sourcemap', ['rename'], function () {
      return gulp.src(sourceMapLocation)
    
  10. 我们已经讨论了将最小化任务拆分出来——请在此处的sourcemap任务下方添加以下内容:

    gulp.task('minifyCSS', ['sourcemap'], function () {
      return gulp.src('dest/*.min.css')
        .pipe(cssnano({ autoprefixer: false }))
        .pipe(gulp.dest("dest/"));
    });
    
  11. 我们已经更改了任务名称,因此我们需要更新默认任务和监视设施——在 50 行和 52 行附近或周围查找方括号中的名称字符串。将其替换为以下字符串:

    ['styles', 'lint' , 'rename' , 'sourcemap', 'minifyCSS']
    
  12. 我们的任务监视器也可以进行减肥——没有必要两次指定所有任务!相反,按照指示更改代码——当进行更改时,监视设施将运行默认任务,该任务已经包含了必要的任务:

    gulp.task('default', ['styles', 'lint' , 'rename' , 'minifyCSS', 'sourcemap']);
    
    var watcher = gulp.watch('src/*.css', ['default']);
    watcher.on('change', function(event) {
    
  13. 我们几乎完成了——我们还需要在文件顶部添加一些额外的声明,以确保一切按预期工作。在步骤 4 中添加的 stylerules 声明下方,继续添加以下额外行:

    var renameFunction = function (path) {
      path.extname = ".min.css";
      return path;
    };
    
    var sourceMapLocation = ['dest/*.css', '!dest/*.min.css'];
    

我们现在有一个更新的 Gulp 任务文件——我们现在需要将 style.cssT49 – fixing issues in Gulpfile 下的 src 文件夹复制到我们项目区域的根目录下的 src 文件夹。如果一切顺利,当我们编译文件时,我们项目区域的 dest 文件夹中应该会有类似的内容,并且在地图文件夹中有一个名为 style.css.map 的文件:

修复我们的 Gulp 任务文件

到目前为止,我相信你们肯定对我们所做的某些修改有一些疑问——演示突出了几个关键点,所以花时间详细探索这些内容是值得的。

小贴士

如果你在更改 gulp 文件时遇到任何问题,请查看本书附带的代码下载中 T49 - fixing issues in Gulpfile 文件夹中的完整版本。

理解所做的更改

在我们的演示过程中,我们对 Gulp 任务文件做了一些修改——需要注意的是,这些修改都不是强制性的。在做出这些修改之前,我们的任务文件运行得非常完美,所以如果不是强制性的,我们为什么要做出这些修改呢?

这个问题的答案很简单——使用像 Gulp 这样的任务运行器是为了自动化流程,以便你得到所需的内容。我们已经有这个了,但任务运行器产生了额外的文件,没有按预期压缩它们,并且我们的 Gulp 文件中包含了在同一任务内有多个步骤的任务。我们完成的工作是给这个过程添加一些润色——尽管我们的 Gulp 任务文件可以工作,但我们探索了如何通过调整一些流程来改进它。

我们从修改添加供应商前缀的方式开始——我们现有的任务在编译时使用 Rucksack 插件来完成这一部分。Rucksack 插件是为了提供回退支持——我不喜欢与旧浏览器一起工作,所以我们不需要它。这使得从这样一个大型插件中整合供应商前缀支持变得不那么有益,因此支持未启用。

注意

对于 PostCSS,还有一个处理供应商前缀的插件可用——doiuse,可在 github.com/anandthakker/doiuse 找到。只是另一个尝试的选项!

lint-styles 任务运行良好——我们所做的更改集中在使任务文件中的代码更容易阅读。我们将配置块移动到文件开头,并重新排列了任务的格式;这意味着我们不应该需要更改任务,即使我们可能更改配置!

大多数剩余的更改集中在将多个角色拆分为单个任务,并纠正输出中的一些异常。我们的编译过程生成了一个带有正确扩展名的压缩文件,但也压缩了原始源文件。我们还以类似的方式生成了两个源映射文件——这显然不是理想的!我们现在所做的更改意味着我们的原始源文件没有被压缩,但只生成了一个压缩文件,并且在过程中创建了一个单一的未压缩样式表。

完美,我们现在有一个经过抛光的编译过程,在适当的时候生成正确的文件;接下来是什么?好吧,我们现在可以向我们的编译过程添加额外的功能。使用任务运行器如 Gulp 是关于自动化繁琐的任务,让我们更详细地探索我们可以实现什么。

优化输出

PostCSS 系统将非常乐意与其他插件友好地协作,无论是基于 Gulp 的,还是使用其他任务运行器,如 Grunt 或 Broccoli。这打开了一个真实世界中的可能性,仅限于您的想象力!不过,有一个小但重要的观点——它使得我们不仅优化处理器输出(如我们所做的),而且还需要对其进行微调,以确保我们添加了符合我们需求的功能。

那么,我们可以添加什么呢?好吧,这里有一个起点:压缩图片怎么样?另一个常见任务与添加响应式内容相关——我们在本书中已经使用 postcss-responsive-type 插件对此进行了介绍。我们可以进一步扩展,通过添加一个自动调整图片大小的任务;然后我们可以根据需要将这些图片用于响应式网站。

最终,这取决于您——随着时间的推移,您越来越习惯使用 PostCSS,您可能会发现自己比其他插件更频繁地使用某些插件。不过,这里的关键并不是简单地随意添加插件——相反,我们正在寻找那些我们会在我们的开发工作流程中经常使用的插件,并可以作为基本处理器的基石。任何需要支持特定项目的附加功能都可以在适当的时候添加。

一个很好的查找地方是 PostCSS 插件目录,位于 postcss.parts——看看那里有什么,尝试一下是值得的!为了让我们开始,我们将探讨一些可能对您的处理器有用的想法,首先是改进对源映射的支持。

修改我们的源映射支持

如果我们回顾一下在修复我们的 gulp 任务文件练习之前我们的 gulp 文件,我们可以看到它确实工作,但它有一个主要的缺点。编译过程产生了一个额外的源映射文件,按名称进行了压缩,但实际上并没有!这显然是我们不需要的东西——对这个任务所做的更改将其转变成了我们现在拥有的样子:

var sourceMapLocation = ['dest/*.css', '!dest/*.min.css'];
...
gulp.task('sourcemap', ['rename'], function () {
  return gulp.src(sourceMapLocation)
...
});

这是一个更好的版本——它只产生一个源映射文件,该文件未压缩;不需要压缩。话虽如此,我们仍然可以改进它;这只是一个通过查阅文档真正探索可用内容,并看看它是否可以帮助我们的简单问题。作为起点,尝试这个。

在编译我们的代码和创建源映射时,我们可能需要控制完整的 URL,例如,如果我们正在从测试环境转移到生产环境。在理想的世界里,我们会使用相对文件结构来避免这个问题,但对于那些不可能的情况,对我们的 Gulp 任务进行简单的更改就足够了:

    .pipe(sourcemaps.write('maps/', {
      sourceMappingURLPrefix: 'https://www.mydomain.com/'
    }))

我们可以在下面的屏幕截图中看到结果:

修改我们的源映射支持

最终,我们项目的需求将决定源映射支持需要如何配置——如果我们的项目需求要求,我们甚至可能需要将多个文件编译成一个更大的主文件。

然而,有一点需要注意——计划在 Gulp 4 中包含原生源映射支持;这可能会意味着不再需要单独的插件。了解变化是有益的,尤其是如果它可能影响我们的处理器!

小贴士

对于包含这些更改的我们的 Gulp 文件的完整版本,请查看随本书附带的代码下载中的T50 - 调整源映射设置文件夹。

在这里,让我们改变策略,改变源映射编译过程是一个简单的修改。我们可以通过另一个关键领域——供应商前缀来提高一个档次。我们已经涵盖了基础知识,所以我们将看看我们如何可以改进支持。

注意前缀

哎,前缀!任何设计师的噩梦:添加它们并保持它们更新确实是一项真正的苦差事。

已经在这些页面中出现的autoprefixer任务有助于减轻负担:它会添加当前的前缀并移除不再需要的任何前缀。这很好……但我们能做得更好!然而,这次的重点更少在于代码,而更多在于我们需要做出的决策:

  • 您需要支持哪些浏览器版本?autoprefixer插件已经使用了来自caniuse.com/的数据,这对于大多数需求来说已经足够了。然而,我们可以调整我们的代码以使用 Browserslist(托管在github.com/ai/browserslist),以确定要支持哪些版本。例如,我们可能添加> 5%来限制支持到全球使用率超过5%的浏览器:

    .pipe(postcss([ rucksack(), autoprefixer({browsers: ['last 2 versions']}) ])
    

    在一个理想的世界里,我会将其提高到 10%,但这可能有点过分了!

  • 从一致性的角度来看,我们应该明确指出autoprefixer在 Rucksack 内部是禁用的——我的偏好是不在那里启用它,因为它可能会让人混淆,不知道哪个插件添加了前缀(考虑到cssnano也可以添加前缀)。为了纠正这一点,我们只需要更改这一行:

    .pipe(postcss([ rucksack({autoprefixer: false  }),
     autoprefixer({browsers: ['last 2 versions']}) ])
    

这不是强制性的,但至少能使其清晰!如果我们想真正成为纯粹主义者,我们会将这项任务分成两个独立的任务,这样我们就能保持一个任务:一个角色咒语:

  • 虽然autoprefixer可以处理冗余前缀的移除,但我们有一个有用的技巧可以使用:添加对postcss-remove-prefixespostcss-unprefix插件的支援。这样做的原因很简单——在运行autoprefixer时,我们可能并不在一个公平的竞争环境中,因为我们的代码中可能缺少一些供应商前缀。添加这两个插件确保我们的代码在运行autoprefixer之前尽可能简洁。

  • 如果我们的代码基于使用 SASS,那么有很大可能性正在使用 Compass 库——检查一下是否正在使用它来添加供应商前缀是有意义的。如果是的话,可能值得切换到使用autoprefixer,因为它报告称在移除代码方面更有效率。别忘了我们可以在运行 PostCSS 插件的同时在 Gulp 文件中编译 SASS 代码——我们将在本书的后面更详细地探讨这一点。

供应商前缀不断变化是不言而喻的;通过仔细规划和正确使用插件,我们可以放心地知道,无论发生什么,我们的代码将在下一次编译时得到更新。

现在,让我们继续前进:我相信您对无处不在的伪选择器,如 hover,已经很熟悉了。这是我们仔细考虑可能纳入基本处理器的内容的一个领域;PostCSS 有几个插件可以帮助我们在代码中处理伪选择器。让我们更详细地看看这一点。

添加对伪选择器的支持

在设计网站时,伪选择器是提供交互的关键部分——它们可以是简单的悬停,一直到更新的元素,如:range 或:placeholder。我们还得注意兼容性——幸运的是,大多数元素在合理近期的浏览器中都能正常工作(是的,甚至 IE8!),但不是所有浏览器在引用 CSS 中的选择器时都使用相同的单冒号或双冒号格式。

为了帮助样式化和提供一致性,PostCSS 提供了一些我们可以使用的选项;我们将在稍后探索使用postcss-pseudo-elements-content插件,但现在,让我们快速看看一些选项,以给你一个可用的感觉:

  • 你经常发现自己需要在代码中添加focus伪选择器吗?如果答案是肯定的,那么postcss-focus插件可能会引起你的兴趣。该插件可在github.com/postcss/postcss-focus找到,当编译代码时,它会自动添加focus伪选择器。样式将与:hover元素相同。

  • 我们刚刚讨论了自动添加focus元素——开发者 Jonathan Neal 有了一个想法,创建一个 polyfill 来添加对:enter伪选择器的支持,这将替换代码中的:hover:focus。当编译时,代码会将任何:enter实例转换为我们的代码中的:hover:active样式。前往github.com/jonathantneal/postcss-pseudo-class-enter了解更多关于此插件的信息。

  • 下一个插件可以被委婉地描述为为那些有比编写链接样式更好的事情要做的人准备的。简单来说,这是一个真正的插件快捷方式!它会自动为所有与链接相关的类添加样式;浏览到github.com/jedmao/postcss-all-link-colors查看如何真正地偷懒的例子...

  • 对于那些经常需要为表单按钮添加样式(说实话,谁不是呢?),那么来自github.com/andrepolischuk/postcss-pseudo-class-any-button的下一个插件可能会引起你的兴趣:它允许我们使用:any-button选择器(这不是一个官方选择器)。当编译时,它会将其转换为四种不同类型——普通按钮和三个输入(重置、提交和按钮)。

这只是 PostCSS 生态系统中目前可用的少量插件中的一小部分,用于处理伪选择器。我们可以讨论如何使用它们,但现实中,了解它们有用性的最好方法是看到它们在实际中的应用!考虑到这一点,让我们看看一个实际应用的例子:postcss-pseudo-elements-content。这个小巧的插件只有一个目的:在我们的代码中,如果适当的选择器没有添加content:属性,就添加这个属性。

更新我们的代码

有几个插件示例可以帮助更好地处理伪选择器;我们的项目将决定我们是否应该根据每个案例使用它们,或者可以将其中一些或全部集成到我们的基本处理器中。

可能适合添加到我们的基本处理器的一个例子是postcss-pseudo-elements-content,它可以从github.com/omgovich/postcss-pseudo-elements-content获取。这个简单的插件解析我们的代码,并在看到适当的伪选择器实例时,将content: ''语句添加到我们的代码中。它不需要任何配置,所以无需多言,让我们开始使用它:

  1. 我们将像往常一样从安装插件开始——为此,打开一个 Node.js 命令提示符窗口,并将工作文件夹更改为我们的项目区域。

  2. 在提示符中,运行此命令:

    npm install postcss-pseudo-elements-content --save-dev
    
    

如果一切顺利,我们应该会看到类似以下内容:

更新我们的代码

  1. 从本书附带的代码副本中,从T51 - 添加前后内容文件夹中提取style.csscontent.html。将样式表保存到src文件夹中,将content.html保存到我们项目区域的根目录。

  2. 打开位于我们项目区域根目录的gulpfile.js副本,然后在第 11 行添加此行:

    var pseudoContent = require('postcss-pseudo-elements-content');
    
  3. 有一点更往下,我们需要更新我们的第一个任务以允许额外的插件;按照指示更改该行:

    gulp.task('styles', function() {
      return gulp.src('src/*.css')
      .pipe(postcss([ autoprefixer(), pseudoContent() ]))
    
  4. 在 Node.js 命令提示符中输入gulp然后按Enter键——如果一切顺利,我们应该会在dest文件夹中看到我们的样式表文件和源映射。

  5. 将此文件夹的内容复制到我们项目区域根目录的css文件夹中;如果我们预览content.html,我们应该会看到我们的菜单出现:更新我们的代码

在某种意义上,这可以被视为一个快捷插件(与我们在本书早期使用的方式类似)。魔法在于在伪选择器后添加–c,如图中代码片段所示:

.underline a:hover::after-c, .underline a:focus::after-c {
  opacity: 1;
  transform: translateY(0px);
}

当编译时,它会添加content: ''属性,如图所示:

更新我们的代码

虽然是否值得为这么小的事情添加插件是有争议的,但它至少确保我们在编译样式表时保持一致的代码库。

真正的决定是,你的代码是否有足够的伪选择器实例,值得将插件作为基本部分安装,或者如果你的项目需要根据具体情况使用。

让我们改变一下方向,我们之前提到的多数改进都与文本相关。没有图像的文本站点可能非常不吸引人——幸运的是,有一些插件可以帮助扩展我们的基本处理器,并更好地管理图像。我感觉一个演示即将到来,所以让我们更详细地探索这一点。

处理图像

如果我们将处理器视为仅用于编译 PostCSS 代码,那么我们可能低估了自己——我们已经介绍了如何使用 Gulp 这样的任务运行器添加额外的任务,如autoprefixercssnano

一个值得考虑添加的功能是压缩我们的图片以获得最佳大小;你愿意手动进行,无论大小节省多少?我想你不会这么做。自动化这个过程意味着我们可以继续进行那些能增加过程价值的任务。我们可以通过来自github.com/sindresorhus/gulp-imagemingulp-imagemin插件来实现这一点——让我们看看在最小化我们的图片时可能涉及的内容:

  1. 启动一个 Node.js 命令提示符窗口,然后更改工作目录到我们的项目区域。

  2. 在提示符中输入两个命令,每个命令后按Enter键:

    npm install gulp-imagemin --save-dev
    npm install imagemin-jpegtran --save-dev
    
    

    保持提示符打开——我们很快还会用到它。

  3. 从本书附带的代码下载中,请从T52 - optimizing images文件夹中提取gulpfile.jspackage.json文件的副本;将这些文件保存到我们项目区域的根目录。

  4. 在我们项目文件夹的根目录下创建一个名为img的文件夹;这将被用作现有dest文件夹的临时替代。

  5. 找一些大图片——它们应该是 JPEG 格式,理想情况下大小为几兆字节;大约四到六张图片就足够了。

  6. 返回 Node.js 提示符,然后输入gulp并按Enter键——下面的截图显示了我在一些图片上执行的一个示例:处理图片

如果一切顺利,我们应该在img文件夹中看到我们新压缩的图片——这是一个在处理器中非常有用的任务,所以让我们更详细地讨论几个要点。

探索过程

图片压缩是高性能网站的关键——如果文本在几秒钟内出现,用户可能会感到厌烦,但图片出现需要更长的时间!然而,这种方法有几个需要考虑的点:

  • 它并不特别快——压缩大约 2MB 大小的十几张图片还不错,但如果你需要压缩大量图片,这不会有所改善。

  • 我们只支持 JPEG 图片——优化 SVG 和 PNG 图片是可能的,但这将需要更改我们的代码。如果我们仔细查看使用的代码,我们可以看到这一点:

    var images = require('gulp-imagemin');
    var jpegtran = require('imagemin-jpegtran');
    

后者插件由gulp-imagemin自动安装,如果处理 SVG 或 PNG 图片,则需要更改:

  • 本地执行的测试似乎表明,尺寸的减少并不像预期的那样好;我怀疑这将在更大的图片上得到改善。尝试改变压缩级别是值得的——确保你的源图片尽可能大!

一旦我们的图片已经针对大小进行了优化,我们就可以探索更多的选项——这里有一些值得考虑的:

  • 在多设备发展的时代,我们需要我们的图片具有响应性;来自github.com/azat-io/postcss-responsive-images的 gulp-responsive 插件可以帮助创建这些图片。

  • 我们可能想使用精灵图代替。幸运的是,在 PostCSS 中添加精灵图支持很容易:查看 postcss-sprites 插件来完成这项任务。

  • 你可能想考虑使用资产管理器来解析 URLs——postcss-assets 插件是这项任务的理想选择。使用方法和详细信息可在github.com/assetsjs/postcss-assets找到。

需要注意的一个重要观点是——尽管有插件可以实现这个目的,但考虑压缩 HTML 并不值得;你不太可能获得任何显著的空间节省,而且代码将变得难以阅读。最好将压缩文件保留给那些必须链接到你的 HTML 页面的外部资源,以便获得最大的好处。

好的,让我们继续:在我们测试我们的处理器之前,还有一个可能值得考虑的想法。将自动重新加载功能添加到我们的代码意味着我们不需要重新加载页面来查看更新内容。这很大程度上依赖于 Chrome 的使用来使其工作,所以它可能不是每个人都需要的:让我们更详细地看看这会涉及哪些内容。

添加重新加载功能

添加重新加载功能可以减少在代码更改后手动重新加载页面所花费的时间;后者可能真的令人头疼,尤其是在处理复杂的 CSS 样式时!

这种方法的缺点是它只能在 Chrome 中工作——如果这不是问题,那么你需要遵循以下步骤来使它工作:

  1. 我们首先从“T53 – 添加 LiveReload 功能”文件夹中提取gulpfile.jspackage.json文件的副本,从本书附带的代码下载中。将这两个副本保存到我们项目区域的根目录下。

  2. 从相同的“T53 – 添加 LiveReload 功能”文件夹中,提取并保存src文件夹内的style.css文件副本,到我们项目区域的根目录下的src文件夹。

  3. 启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  4. 在提示符中输入npm install gulp-livereload –save-dev并按Enter——让这个安装完成。

  5. bit.ly/IKI2MY下载并安装 Chrome LiveReload 小程序。

  6. sourcemap任务末尾添加此行:

    .pipe(plugins.livereload());
    
  7. 删除第 60 行末尾的分号。

  8. 将此行添加到我们的watch任务:

    plugins.livereload.listen();
    
  9. 我们的任务文件将如下所示,其中第 61 行和第 68 行进行了更改:添加重新加载功能

在这一点上,我们可以通过更改样式表来测试它是否工作——如果一切顺利,Gulp 将启动并重新编译我们的代码;如果我们正在浏览器中打开我们正在开发的网站,那么插件将自动重新加载。

注意

对于那些想要更详细地使用此插件的人,请参阅 GitHub 网站上提供的文档,网址为 github.com/vohof/gulp-livereload。示例中使用的完整代码版本位于本书附带的代码下载中的 T53 - 添加 livereload 功能 文件夹内。

好的,继续前进!我们几乎到达了探索可能的技艺的终点;在我们开始测试我们的处理器之前,我想给你留下一些你可能想要考虑在处理器中使用的新想法。所有这些都应该使用我们在这本书中看到的相同过程进行安装。

进一步扩展我们的处理器

在过去的几页中,我们探讨了多种改进现有处理器的方法,以及一些扩展功能性的想法。尽管我们总是可以坚持使用 PostCSS 插件,但我们面临的风险是限制“可能的技艺”,或者我们可用的选择。

有时候,我们可能想要走得更远一些——创建一个处理器不仅仅是关于编译代码的细节,还涉及到我们的工作环境以及支持它的过程(至少部分如此)。为了证明这一点,我们将以安装 postcss-stats 插件为例,展示我们如何扩展我们的插件和工作环境。

此插件有助于在编译项目时提供有关每个项目的有用统计数据——它基于 CSS Stats 系统,并在网上提供,网址为 www.cssstats.com

注意

在整个演示过程中,你可能会看到一些关于弃用警告的问题——在撰写本文时,该插件需要一点打磨/更新。不过不用担心:该插件在我们的演示中仍然可以正常工作。

该插件的源代码可在 GitHub 上找到,网址为 github.com/cssstats/postcss-cssstats,可以使用常规方式安装。让我们深入了解一下:

  1. 我们将首先启动一个 Node.js 命令提示符会话,然后更改工作目录到我们项目区域的根目录。

  2. 我们需要安装该插件,所以在提示符中输入以下命令并按 Enter 键:

    npm install postcss-cssstats --save-dev 
    
    

保持打开状态——我们将在练习的稍后部分需要它。

接下来,我们需要更新我们的 gulpfile.jspackage.json 文件——请从本书附带的代码下载中的 T54 - 使用 cssstats 文件夹中提取这两个文件的副本。将两个文件都保存到我们项目区域的根目录下:

  1. 在我们的文件就绪后,我们现在可以测试它是否工作——请将同一文件夹中的 style.css 文件的副本保存到我们项目区域的 src 文件夹中。

  2. 返回我们之前打开的 Node.js 命令提示符——在提示符中输入 gulp,然后按 Enter 键。

  3. PostCSS 将编译我们的代码——如果一切顺利,我们应该在现在熟悉的 dest 文件夹中看到文件……我们也应该看到类似下面的截图:进一步扩展我们的处理器

如果我是一个赌徒(我不是,但为了这个假设我就是这样)——我会打赌你可能在想“这一切文字究竟是什么意思?”好吧,让我来解释一下这一切的含义。

简而言之,我们安装了一个实际上是一个报告系统——它详细说明了我们代码的一组统计数据。它包含了各种信息的详细信息,包括选择器的数量、颜色、CSS 特异性级别、声明等。这是一种获取我们代码信息、作为记录以供以后使用的方法。它之所以如此容易获取信息,在于它的配置方式——看看 gulpfile.js 文件;我们将在顶部添加对插件的调用:

var reporter = require('postcss-reporter');

然后,我们可以通过在末尾附近添加这一行来修改单个样式任务:

    .pipe(postcss([ cssstats( function(stats) {
      console.log(stats);
    })
  ]))
  .pipe(gulp.dest('dest/'));
})

问题在于,虽然获取信息可能很容易,但存储它却不容易!我们可以绝对地改进它;而不是通过我们的处理器获取信息,我们可以直接从源获取。让我们看看如何实现这一点:

  1. 我们将首先启动一个 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域根目录。

  2. 在提示符下,请继续输入 npm install gulp-stylestats --save-dev,然后按 Enter 键。

  3. 现在,我们需要编辑之前练习中使用的 gulpfile.jspackage.json 文件,所以请在文本编辑器中打开 gulpfile.js 文件,并在 sourcemap 任务的结束括号下方立即添加这些行:

    gulp.task('stylestats', ['minifyCSS'], function () {
      gulp.src('dest/*.css')
        .pipe(stylestats({
          type: 'json',
          outfile: true
        }))
        .pipe(gulp.dest('dest/'));
    });
    
  4. 接下来,我们需要更新默认任务——按照指示进行更改:

    gulp.task('default', ['styles', 'lint', 'rename',
     'minifyCSS', 'sourcemap', 'stylestats']);
    
  5. 返回 Node.js 命令提示符,然后输入 gulp 并按 Enter 键——假设我们仍然在项目区域的 src 文件夹中有相同的 style.css 文件,我们应该在我们的项目区域的根目录下的 dest 文件夹中看到这个文件:进一步扩展我们的处理器

  6. 虽然我们显然需要更改 Gulp 文件的参数以防止它生成压缩的 JSON 文件,但我们至少可以看到(未压缩的)JSON 文件的结果。打开它——它看起来可能像这样:进一步扩展我们的处理器

虽然我们仍然只看到代码,但我们现在可以随意解析内容;例如,我们可以使用 jQuery 来探索内容,并使用适当的样式和格式将其渲染到屏幕上。我相信你也会同意,这确实是一种查看(和存储)信息的方法!插件需要最少的配置即可开始使用。我们可以用它来查看任何标准的 CSS 文件,一旦它通过了编译过程。

小贴士

我们可以使用 gulp-stylestats 插件的一组选项——有关详细信息,请参阅 https://github.com/t32k/stylestats

好的,我们现在有一个完成的处理器;希望这也会包括一个运行中的风格指南,使用我们在上一个练习中讨论过的插件之一。是我们继续前进的时候了——在我们开始下一阶段的旅程之前,我们还需要完成一个任务。是时候对我们的处理器进行测试了…

测试最终的预处理器

在本书中,我们探索了许多不同的插件和概念来构建处理器;在过去的几页中,我们将这些概念整合到了我们处理器的最终版本中——至少是我们可以开始使用的版本。

剩下最后一个关键步骤需要完成——我们已经为简单的练习编译了代码,这效果很好,但并不能真正代表我们作为开发者可能会经历的过程!为此,我们需要构建一个真实世界的例子,并让我们的处理器经受考验。

幸运的是,我们可以从本书附带的代码下载中找到一个示例网页——让我们看看如何将它的样式表代码通过我们的处理器。我们将先运行之前做过的正常任务,但会添加一些插件以使示例更加真实:

  1. 我们将首先从本书附带的代码下载中提取T55 - testing our processor文件夹的副本;将其保存到我们项目区域的根目录。

  2. 将此子文件夹中的gulpfile.jspackage.json文件复制到我们项目区域的根目录。

  3. 启动一个 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  4. 在提示符中,输入以下三行,每行输入后按Enter键:

    npm install postcss-nesting --save-dev
    npm install postcss-short-color --save-dev
    npm install postcss-pixrem
    
    
  5. T55 – testing our processor下的css – completed version文件夹中的site.css文件复制到我们项目区域的根目录下的src文件夹。

  6. 返回 Node.js 会话,然后在提示符中输入gulp并按Enter键——等待它完成编译。

  7. 当编译完成后,将dest文件夹的内容复制到T55 – testing our processor中的css文件夹。

  8. 尝试预览编译文件的成果——如果一切顺利,我们应该看到类似于以下截图的内容:测试最终的预处理器

尝试调整浏览器窗口大小,或者在浏览器中启用响应式设计模式(如果支持的话)——我们应该看到内容会根据你为浏览器窗口设置的大小自动流动或调整大小。总体来说,这是一个成功的成果!

问题是——这里发生了什么?如果我们查看我们的代码,细心的读者应该会注意到增加了三个插件,以及编译版本中大量额外的代码;让我们花点时间来消化我们练习的结果。

消化我们的练习结果

如果我们仔细查看我们的 Gulp 任务文件,其中不应该有太多令人惊讶的内容——许多使用的任务我们在书中已经多次使用过。

然而,关键在于,尽管我们可以运行之前已经使用过的标准处理器,但它可能不太适合所有场合。更有可能的是,我们可以将其作为基础(如前所述),然后根据需要添加任何额外的插件。这个的好处是,大多数配置工作已经完成——它保持了我们对工作的持续方法。剩下的只是安装我们尚未安装的任何插件——我们当然拥有其中大部分,但需要安装三个额外的插件,如这里所示:

gulp.task('styles', function () {
  return gulp.src('src/*.css')
    .pipe(postcss([ rucksack({ fallbacks: true }), autoprefixer(), shortcolor, nesting, pixrem ]))
    .pipe(gulp.dest('dest/'));
});

这些必须在我们的 Gulp 任务文件顶部进行相关调用:

var nesting = require('postcss-nesting');
var shortcolor = require('postcss-short-color');
var pixrem = require('pixrem');

相应地,这些插件如下:

  • 背包:这是为了处理响应式/媒体查询,将 rgba 颜色从十六进制中回退,并实现 @font-face

  • Pixrem:可在 github.com/robwierzbowski/node-pixrem 获取,它负责为我们样式表中的 rem 单位值提供回退机制。

  • 嵌套:可以从 github.com/jonathantneal/postcss-nesting(通过 Node)下载,这涵盖了我们在代码中使用的嵌套实例。

  • 简写颜色:在几个实例中,我们将 background-colorcolor 属性以简写形式结合在一起,该插件随后将其转换。您可以在 github.com/jonathantneal/postcss-short-color 上了解更多关于此插件的信息。

我们当然可以添加更多,并继续转换我们的代码——还有其他一些地方可以应用嵌套,例如在控制我们导航样式的规则中。但关键在于,成功衡量的是我们不得不改变处理器默认设置的多少——在这个例子中,我们根本不需要改变它!我们当然添加了需要更改处理器中一行代码的额外插件,但其他所有任务都不需要任何更改。

正是在这个阶段,我们实际上已经完成了创建我们处理器的旅程——严格来说,我们的旅程应该始终被视为没有尽头;这将有助于确保我们的工具保持最新。尽管如此,还有一些有用的技巧,我们可以在创建处理器时使用,所以让我们花点时间更详细地介绍这些技巧。

从一些提示和技巧开始入门

现在是时候轮到开发者们开始创建自己的处理器了!这可能会在最初看起来是一项艰巨的任务,取决于项目的规模和性质;我列出了一些提示,以帮助您克服规划和创建处理器的初步障碍:

  • 每个处理器都是独特的——不要害怕实验。处理器当然必须满足您的需求,但破解坚果的方法有很多,所以如果您尝试的第一个插件不起作用,那么继续尝试另一个。

  • 不要陷入许多人都会犯的陷阱,将 PostCSS 视为预处理程序或后处理程序;它既不是也不是。库本身不做任何事情;魔法在于你添加的插件,它们决定了它的性能。

  • 从小开始——PostCSS 是设计成模块化的,所以如果你一开始只需要一个添加供应商前缀的功能,那就没问题。随着时间的推移,你可以轻松地向你的处理器添加额外的插件;无论是添加到现有功能,还是替换不再高效或工作的旧流程,都没有关系。

  • 考虑迭代性——不要试图一次性转换像 WordPress 样式表这样大的东西!你很快就会失去耐心和动力,并且可能会在获得 PostCSS 的好处之前放弃项目。

  • 处理器唯一应该被淘汰的时候是,如果你的项目架构发生了根本性的变化,使其与 PostCSS 不兼容。PostCSS 的多功能性使得这种情况不太可能发生——你应该定期审查功能,以确保你从处理器中获得最佳效果。插件会更改、被弃用,或者添加新的插件——检查将确保你的解决方案尽可能高效地工作。

  • 任何处理器都不应该仅限于 PostCSS 插件——尽管这是我们关注的焦点,但还有数千个其他插件可供你选择的任务运行器使用,它们很可能与 PostCSS 兼容。关键在于,如果它有助于自动化一个可以节省你作为开发者时间的日常任务,那么应该考虑是否可以将它包含在你的处理器中。

  • 我个人认为,如果可以可靠地自动化,那么就包括一个任务——我们生活在一个时间宝贵的时代;例如,如果可以自动处理,手动调整图片就没有价值!

  • 尽管我们已经讨论了一些可以使用任务运行器完成的任务,但我们不能忘记文件夹结构。最糟糕的事情莫过于为不同的环境编译文件,例如,如果它们最终落在组织混乱的文件夹中!Gulp 可以自动化大量任务,所以我们需要做的更改越少,或者需要复制的文件越少,就越好。

希望这些建议能帮助你开始!关于 PostCSS 的好处是,没有两个处理器会是相同的;虽然有些人可能认为这是一个缺点,但应该注意的是,有大量的可能性等待探索,你可以根据项目需求将处理器做得尽可能简单或复杂。

在我们结束构建自定义处理器的旅程之前,我们应该考虑一些事情。我们的处理器完全使用 PostCSS 插件构建;实际上,我们的处理器更有可能经历一个过渡阶段,从 SASS 或 less 等转换到使用 PostCSS。

为了帮助这个过程,我们可以始终使用像 CSStyle 这样的库——这个有趣的小宝石可以与 SASS 或 PostCSS 一起工作,并且可能是过渡过程中的一个有用补充。在接下来的两章中,我们将学习如何创建自定义语法,并探索一些我们可以通过相同的过程处理 PostCSS 和 SASS 内容的方法。作为即将到来的内容的预览,让我们快速浏览 CSSStyle,看看它是如何实际工作的。

介绍 CSStyle 库

回想一下第三章,嵌套规则,其中我们探讨了 BEM 背后的概念,或者说是块、元素、修饰符编写 CSS 的方式。使用这种方法的关键好处是帮助减少 CSS 的特定性,或者我们可能否则会使用以下内容来为简单的按钮设置样式:

#maincontent .button .red.large:hover

好吧,这有点牵强,但你应该明白了:特定性的级别使得管理和将来在项目中重用变得尴尬。

我们考虑了 BEM 作为可能的替代方案——它有将样式减少到一到两个类的优点,但可能难以记住使用哪些约定:

.component {
  /* represents a component */
}

.component__element {
  /* represents a small part that is used to make a component */

}

'.component--modifier {
  /* represents a state modifier for the component */
}

好吧,那么我们如何解决这个问题呢?嗯,这里有一个我们可以考虑使用的选项:CSStyle 库。有几个原因可以说明为什么这可以帮助我们——让我们更详细地看看。

探索使用 CSStyle 的好处

CSStyle(可在csstyle.io/获取)的关键在于它由模块化块组成,类似于 BEM 的风格。然而,区别在于,我们不需要记住一组不太直观的约定,而可以使用更简单的集合来创建更干净的代码。

但真正的美在于,我们可以使用 SASS 或 PostCSS 来创建我们的网站——我们可以从 SASS 开始,但也可以开始过渡到使用 PostCSS,而只需进行最小更改。让我们将其付诸实践,并探索一个快速演示,看看进行这些更改有多容易——在我们这样做之前,请查看codepen.io/alibby251/pen/pgmqjJ;这是一个 Pen,展示了我们将要创建的内容:

探索使用 CSStyle 的好处

它不会赢得任何风格奖项,但这个演示的目的是向您展示过程,而不仅仅是产生令人惊叹的东西!考虑到这一点,让我们开始:

  1. 我们将从从本书附带的代码下载中提取T56 - 使用 csstyle 与 sass文件夹的副本开始;将文件夹保存到项目区域的根目录中。

  2. T56 - 使用 csstyle 与 sass文件夹内的src文件夹的内容复制到项目区域的根目录下的src文件夹中。

  3. 将项目区域根目录下的gulpfile.jspackage.json文件替换为T56 - 使用 csstyle 与 sass文件夹内的副本。

  4. 启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们项目区域的根目录。

  5. 在提示符下,输入 gulp 并按 Enter 键——如果一切顺利,我们应该在我们的项目区域 dest 文件夹中看到一个编译后的 style.css 文件。

  6. dest 文件夹的内容复制回 T56 - using csstyle with sass 文件夹内的 css 文件夹。

在这一点上,尝试在浏览器中预览结果。如果一切顺利,我们将看到三个按钮出现,就像我们在本练习开始时提到的笔中显示的那样。

一切看起来都很顺利……我们有一个可以运行的演示,有一个编译后的样式表——但是等等……在 SASS 中?是的,如果你仔细看,演示确实设置为使用 SASS,但这是有原因的:我们将看到在不改变我们的样式表或编译过程的情况下使用 PostCSS 是多么容易。让我们开始吧:

  1. 在我们项目区域根目录下的 gulpfile.js 中,注释掉第 5 行,取消注释第 6 和第 7 行;这会将我们的任务文件从使用 SASS 切换到使用 PostCSS。

  2. 将第 9 行的 [sass] 任务重命名为 [style]

  3. 在第 10 行,gulp.src 调用正在寻找 SASS 文件;将其更改为 src/*.css

  4. 将第 11 行替换为这一行:.pipe(postcss([nested, csstyle]))——这消除了对 SASS 的依赖,并切换到使用 PostCSS。

  5. 在第 15 行,我们的默认任务将调用 [sass] 任务;将 [sass] 更改为 [style]

  6. 将第 17 行的监视任务更改为监视 CSS 文件,而不是 SASS:

    var watcher = gulp.watch('src/*.css', ['style']);
    
  7. 好吧,打开我们项目区域根目录下的 src 文件夹中的 SASS 样式表——将文件重命名为 style.css

  8. style.css 中,请移除样式表顶部的 @import 'csstyle' 行。

  9. 进行搜索和替换 @include——在我们的样式表中移除所有实例。

我们的演示就到这里,很抱歉让您失望,如果您期待更多的话!剩下的只是将项目区域根目录下的 gulpfile.jspackage.json 文件替换为 T57 – using csstyle with postcss 文件夹中的副本,并按正常方式编译。

分析我们的演示

从 SASS 转换到 PostCSS 可以像我们让它变得一样简单或复杂。使用 CSStyle 库可以在很大程度上简化从现有处理器(如 SASS)的转换过程。

尽管我们的演示只是对使用 CSStyle 的快速浏览(我们将在 第十二章,混合预处理器)进行回顾),但它仍然说明了几个重要的要点:

  • 该库使用组件、选项、部分和微调的概念来创建基本组件,传递样式以覆盖基本规则,添加额外元素(如图标),或微调代码。精心设计意味着我们可以减少或消除在转换为使用 PostCSS 过程中修改 HTML 的需要。

  • 完全可以使用标准的 SASS 编译器编译我们演示的 SASS 版本;选择使用任务运行器版本(在这种情况下是 Gulp)的原因是,我们可以将编译过程集中在一个任务文件中,并消除在过程中使用单独编译器的需求。

  • 当计划我们的网站设计或过渡到使用 PostCSS 时,在 PostCSS 中选择插件要小心;这将决定我们在代码和处理器中做出更改的难易程度。

  • 我们的演示重点在于核心编译过程,并且没有包括过去使用的额外任务,例如添加源映射。这纯粹是为了清晰起见——我们没有理由不能在确认我们的编译过程按预期工作后,添加之前使用的剩余任务。

然而,使用这个库的目的是帮助简化过渡到使用 PostCSS 的过程。有不同的方式来处理这个问题——使用 CSSStyle 意味着我们必须完全重新设计我们的 HTML,但可以轻松地以最小的麻烦更改处理器。另一方面,我们可以使用模仿 SASS 编码标准的 PostCSS 插件,或者创建我们自己的自定义语法——我们将在下一两章中探讨这些概念。

摘要

创建我们自己的处理器可以是一种令人满意的经验——我们完全控制应该包含哪些元素,并且可以在任何时候添加或删除元素。在整个本书的过程中,我们探讨了构成可能典型处理器的多个元素;在本章中,我们将所有这些元素结合起来创建我们的最终文章。让我们花点时间回顾一下我们已经学到的内容。

我们首先回顾了我们处理器的一些关键元素,我们之前已经使用过,但并没有真正详细了解它们是如何结合在一起的。考虑到这一点,我们继续检查我们处理器的一些问题,然后找出纠正这些问题和修改我们代码的方法。

在我们的更新处理器到位后,我们研究了通过改变现有功能或包括可能或可能不构成基线处理器或针对特定项目定制的选项来优化我们的输出的方法。然后我们研究了扩展我们的功能,包括我们通常不会考虑的选项,但将完美补充我们的工作流程。

然后,我们在本章的最后,快速测试了我们的处理器在一个示例网站上,然后探索了一些有助于我们创建处理器的提示和技巧。我们旅程的最后一站是快速查看 CSSStyle 库,作为创建 PostCSS 自定义语法的先导,我们将在下一章中探讨。

第十一章。操作自定义语法

虽然许多开发者已经从使用预处理器转向使用 PostCSS,但重要的是要注意,PostCSS 并不是替代品,而是一种预处理 CSS 样式的替代方法。为了帮助过渡,我们不必学习新的语法。通过使用一些插件,我们可以利用 PostCSS 的速度,同时仍然使用我们习惯的语法,如 Less、SASS 或 Stylus。

在本章中,我们将探讨使这成为可能的插件,并工作于一些简单的示例,展示在使用 PostCSS 时,我们仍然可以使用我们所有人都熟悉的自定义语法。

本章将涵盖以下技术主题:

  • 介绍自定义语法

  • 实现自定义语法插件的示例

  • 解析 CSS

  • 使用 API 将内容转换为字符串

  • 为我们的代码添加高亮支持

让我们开始吧!

介绍自定义语法

W3Schools 将 CSS 语法定义为如下:

"一个 CSS 规则集由一个选择器和声明块组成:选择器指向你想要样式的 HTML 元素。声明块包含一个或多个由分号分隔的声明。"

作为开发者,我们花费许多小时来制作网站;这可以是从一个单页联系卡片型网站到大型电子商务网站的各种大小。我们决定使用哪种样式,或者我们如何到达那里,并不重要:关键是最终结果必须使用我们多年来一直喜爱的相同标准语法。

这并不意味着我们的源应该使用标准的 CSS,实际上,如果这是唯一的选择,这将非常受限!我们可以使用 SASS 或 Less 等库,但相反,为什么不直接使用 API 和自定义语法插件来操作我们的样式呢?我们之前在第八章中提到了一些原则,即创建 PostCSS 插件;现在是时候回顾一下,并探索我们如何开始消除这种限制。

你可能会问,我们为什么要这样做?答案很简单——让我们假设一下,你为 WordPress 创建主题。WordPress 的默认主题是使用 SASS(和 PostCSS)创建的;这意味着以某种形式依赖于 SASS。我们可以通过使用gulp-sass插件来稍微减轻这一点,但这仍然使用libsass来编译代码。

如果我们能将其颠倒过来,并使用解析 SASS 代码并将其转换为 PostCSS 等价的 API 和自定义语法插件,会怎样呢?好吧,承认吧,我们可能无法涵盖所有样式;但我们至少可以开始转换一些样式,并减少我们对 SASS 的依赖。让我们开始吧。在我们开始编写代码之前,我们有一个简单的管理任务要执行:我们需要首先安装一个语法高亮器。

准备我们的环境

在本章的整个过程中,我们将直接与 PostCSS API(或插件的个人 API,如果有的话)进行工作。由于我们直接在 CSS 上工作(而不是简单地通过插件配置对象),安装一个与 PostCSS 兼容的语法高亮器是有意义的。

并非每个文本编辑器都有,但如果你恰好使用 Sublime Text,并且安装了 Package Control 功能(并且我假设这本书中的演示就是这样),那么它有一个适用于 PostCSS 的荧光笔可供安装。该插件可在github.com/hudochenkov/Syntax-highlighting-for-PostCSS获取。让我们按照以下步骤安装它:

  1. 打开 Sublime Text,然后按Cmd + Shift + P(OS X)或Ctrl + Shift + P(Linux/Windows)以打开命令面板。

  2. 从出现的列表中,点击Package Control: Install Package

  3. 几分钟后,它将显示一个新的列表;开始键入Syntax Highlighting for PostCSS准备我们的环境

  4. 当它出现时,点击它进行安装;完成需要几分钟。

当然,我们可以简单地使用一个与 JavaScript 兼容的荧光笔;但这不会完全一样:有一个专为 PostCSS 设计的荧光笔将使编辑代码更容易!

我们刚刚安装的语法高亮器自带了一个主题。如果你喜欢自己动手,那么你可以这样做,使用 Base16 网站chriskempson.github.io/base16/

好的,让我们继续前进;是时候深入代码了。让我们先探索一些可用于在 PostCSS 中解析代码的插件。

实现自定义语法插件

根据最后的统计,PostCSS 生态系统包含超过 100 个插件;这个数字还在增加。这些插件将满足不同的需求,但它们有一个共同点:它们用来转换我们代码的过程。

现在,我们应该清楚,这种相似性是在一个非常高的层面上;我们并不是指每个插件的详细技术细节!除此之外,当我们创建自己的自定义语法时,我们必须遵循一个三步过程:

  1. 我们首先通过解析器处理我们的代码。

  2. 然后,我们使用任何一种插件对其进行转换。

  3. 我们最终将其转换为字符串,或者将其转换为字符串格式的有效 CSS。

我们已经有一系列插件,允许我们在 PostCSS 环境中使用其他语法;这些包括如 less 或 JavaScript 等语言:

插件名称 插件用途
sugarss 这个插件是一个基于缩进的语法,类似于 SASS 或 Stylus。插件可在github.com/postcss/sugarss获取。
postcss-less 我们可以使用这个插件将 less 转换为有效的 CSS。注意:它不会编译代码。插件可在github.com/webschik/postcss-less获取。
postcss-js 任何与 JavaScript 一起工作的人都可以使用这个插件用 JS 编写样式或转换 React 内联样式、Radium 或 JSS。插件可以从 github.com/postcss/postcss-js 获取。
postcss-scss 对于使用 SASS 的您,这个插件非常适合处理 SASS 代码;它不会将代码编译为 CSS。插件可以从 github.com/postcss/postcss-scss 获取。
postcss-safe-parser 这个插件非常适合查找和修复 CSS 语法错误。您可以从 github.com/postcss/postcss-safe-parser 下载。
poststylus 我们可以使用这个插件将使用 Stylus 库创建的样式转换为有效的 CSS。注意:它不会编译代码。插件可以从 github.com/seaneking/poststylus 获取。

虽然所有这些插件都服务于不同的目的,但它们都遵循相同的原理:解析代码并对其进行转换,然后再将其转换为可以保存为有效样式表输出的格式。

不论我们使用哪个解析器,但有一个问题:我们为什么要直接操作我们的代码呢?需要直接修改代码的原因有几个;以下是一些:

  • 我们可能想创建一份报告,详细说明有关我们代码的事实和数字,供参考之用;确实,已经有插件或脚本可以做到这一点,但 PostCSS 可以在编译期间为我们提供基本功能,而不是作为一个单独的过程。

  • 这个想法怎么样?如果你恰好使用 Adobe Color CC 这样的应用程序,那么我们可以考虑使用 API 直接将特定颜色转换为有效的 RGB(A) 或 HEX 等价值。我们可以使用一个插件来实现这一点,但直接使用 API 执行此操作可以让我们在颜色选择上保持灵活性。

  • 没有任何阻止我们剖析现有插件,并将它们执行的任务重新基座为我们可以添加到任务运行器文件中的内容,然后根据我们的需求进行适配。我们最终可能会考虑创建一个插件,但如果所需的步骤非常具体,那么插件可能不是一个有用的补充。

  • 有时候错误处理可能会不足。API 包含一些有用的功能,允许我们在进程失败时在屏幕上添加适当格式的消息。

这些只是一些启动想法,除了操作现有的非 PostCSS 样式(例如使用 SASS 创建的样式)之外。

谈得够多了,我感觉一个演示即将到来!我们已经遇到了一些可用的插件,所以是时候将它们用于实际用途了;其中两个特别有趣的是 postcss-scsspostcss-safe-parser 插件。让我们深入探讨并更详细地查看它们,从 postcss-safe-parser 开始。

解析内容和修复错误

在接下来的几页中,我们将简要介绍使用几个解析器插件,以展示如何轻松地转换我们的代码。我们将查看一个插件,它消除了对 SASS 的需求(至少在基本级别上);在这样做之前,让我们首先探索使用 postcss-safe-parser 插件。

可从 github.com/postcss/postcss-safe-parser 获取的 postcss-safe-parser 插件,非常适合查找和修复 CSS 错误。这是一个简单易用的插件,让我们开始使用它:

  1. 我们将首先安装插件,所以请启动一个 Node.js 命令提示符会话,然后更改工作目录到我们的项目区域根目录。

  2. 在提示符下,输入以下命令,然后按 Enter 安装插件:

    npm install postcss-safe-parser --save-dev
    
    
  3. 接下来,请从本书附带的代码下载中提取 T58 – 解析无效内容 文件夹的副本;将其保存到我们的项目区域根目录。

  4. package.jsongulpfile.js 文件从其中复制到我们的项目区域根目录。

  5. 切换回 NodeJS 命令提示符会话,然后在提示符下,输入 gulp 并按 Enter

如果一切顺利,我们应该看到成功的编译:在项目区域根目录中创建一个标记为 output.css 的文件。

好的,打开它。尽管我们的示例只包含一个格式错误的选择器,但文件中包含相同的选择器,但这次添加了缺失的闭合括号。我们还可以在下面的屏幕截图中所见的同时,在控制台日志中看到结果:

解析内容和修复错误

那么,这里发生了什么?嗯,其中一些可能很熟悉。我们在 Gulp 文件中使用标准格式任务,以及一些我们已遇到的插件的引用,例如 autoprefixer

然而,我们感兴趣的内容在默认的 Gulp 任务中,如下面的屏幕截图所示:

解析内容和修复错误

这个任务可能看起来有点复杂,但实际上,我们在操作它之前解析了我们的 CSS。我们首先定义一个 postcss 对象(向其中发送一个运行 autoprefixer 的请求)。然后,使用解析器找到并修复任何问题,将 CSS 处理成 AST,在屏幕上显示,并在我们的项目区域中创建一个名为 output.css 的文件。

注意

抽象语法树AST)是我们 CSS 样式表或代码的语法结构的图形树表示。

好的,我们的示例非常简单,但这是为了向您展示原理。在下一个示例中,使用了相同的原则将标准 SCSS 代码转换为有效的 CSS;请注意,但我们没有调用 SASS(如我们之前所做),而是将 SCSS 代码转换为有效的 CSS 样式。

解析 SCSS 内容

在我们之前的演示中,我们探讨了使用 PostCSS 解析我们的 CSS,并添加了缺失的闭合括号作为修复代码的解决方案。这是一个简单的例子;如果你使用的是标准 CSS,那就再合适不过了,但如果你项目使用的是 SASS 呢?

好吧,作为我们下一个示例的一部分,我们将证明使用编译器已经过时了;我们将使用postcss-scss插件(来自github.com/postcss/postcss-scss)直接转换我们的 SASS 代码,在展开嵌套样式之前使用postcss-nested插件(可在github.com/postcss/postcss-nested):

  1. 我们将首先安装postcss-scss插件。启动一个 NodeJS 命令提示符会话,然后更改工作目录到我们项目区域的根目录。

  2. 在提示符中输入此命令,然后按Enter键:

    npm install postcss-scss --save-dev
    
    

    插件安装完成后,保持会话打开:

    解析 SCSS 内容

  3. 从本书附带的下载代码中,从T59 – 解析 SCSS 内容文件夹中提取package.json文件的副本。将其保存到我们项目区域的根目录。

  4. 从相同的T59 – 解析 SCSS内容文件夹中,将src文件夹的内容复制到我们项目区域根目录的src文件夹。

  5. 在一个新文件中,添加以下代码并将其保存为gulpfile.js,存放在我们项目区域的src文件夹中:

    'use strict';
    var gulp = require('gulp');
    var postcss = require('postcss');
    var fs = require('fs')
    var autoprefixer = require('autoprefixer');
    var nested = require('postcss-nested');
    
    var scss = fs.readFileSync('src/styles.scss', 'utf-8');
    
    gulp.task('default', function () {
      var syntax = require('postcss-scss');
      postcss([ autoprefixer, nested() ]).process(scss, { syntax: syntax }).then(function (result) {
        fs.writeFileSync('dest/styles.css', result.content);
      });
    });
    

眼尖的你们中的一些人会发现对postcss-nested的引用。我们不指定任何内容就不能调用 PostCSS,所以我们将使用此插件来展开我们代码中的嵌套语句:

  1. 返回 NodeJS 命令提示符会话,然后输入以下命令并按Enter键:

    npm install postcss-nested --save-dev
    
    
  2. 一旦 Node 完成了插件的安装,在提示符中输入gulp然后按Enter键:解析 SCSS 内容

  3. 如果一切顺利,我们将在dest文件夹中看到一个编译后的文件:解析 SCSS 内容

但等等:这是一个有效的CSS文件,对吧?绝对正确。但是……到目前为止,我们不得不使用编译器来生成有效的 CSS 代码;为什么现在我们不需要添加一个呢?

探索发生了什么

好吧,答案在于转换过程——传统上我们不得不编译我们的代码,尽管标准 SASS 文件是当前 CSS 的超集。相反,我们只是简单地使用一种语法来重写我们的代码,这种语法可以将标准 SCSS 文件转换为有效的 CSS。

如果我们更详细地查看我们的 Gulp 文件,我们可以看到对标准gulp-postcss插件的引用,以及fsautoprefixerpostcss-nestedpostcss-scss插件的声明实例。这个演示的关键从第 10 行开始,在那里我们声明了scss变量的实例,并使用 Node 的文件系统fs)插件将文件内容读入这个变量。

一旦进入任务,我们创建一个 PostCSS 实例作为对象,然后向它提供autoprefixernested()插件(作为变量)。然后我们使用postcss-scss插件附带的语法处理我们的 SASS 代码,然后将内容作为文件通过管道输出到我们的项目区域的dest文件夹。

看见了吗?简单又容易;周围没有 SASS 编译器!这个简单的更改消除了对编译器的任何依赖,毕竟,SCSS 文件只是标准的 CSS 文本文件,所以为什么还要使用编译器呢?在所有关于解析 CSS(或者 SCSS)的讨论中,花些时间探索我们所说的含义以及它对整个过程的重要性是值得的。

解析 CSS

在编写任何自定义语法的核心是解析内容的能力——这无关紧要这是 CSS、JavaScript 还是其他什么;在我们能够做出更改之前,我们显然需要了解我们正在处理的内容!在基本层面上,当我们使用 PostCSS 处理 CSS 时,我们必须采取以下步骤来转换我们的 CSS:

解析 CSS

我们从源 CSS(可能带有或没有源映射)开始,我们只解析一次,但随后通过任何指定的插件(示例中显示两个,但我们可以轻松使用更多)。然后我们使用字符串化器将输出转换为字符串;在这个时候,我们可以在屏幕上查看内容或将它们保存到磁盘。

让我们暂时看看解析一些示例代码。在这个下一个示例中,我们将使用一个 CSS 规则,并使用postcss-value-parser插件(来自github.com/TrySound/postcss-value-parser)来解析它;原因很快就会变得清楚:

  1. 从本书附带的代码下载中,从“T60 – 解析颜色值”文件夹中提取并保存gulpfile.jspackage.json文件的副本到我们的项目区域根目录;如果您想保存那里现有的任何文件,请先这样做。

  2. 启动一个 NodeJS 命令提示符会话,然后更改工作文件夹到我们的项目区域根目录。

  3. 我们需要安装postcss-value-parser插件,所以在提示符下,输入以下命令然后按Enter

    npm install postcss-value-parser --save-dev 
    
    

    NPM 现在将安装插件;在完成时保持会话打开:

    解析 CSS

  4. 在提示符下,键入gulp然后按Enter;现在 gulp 将消失并显示内容,看起来可能像这样:解析 CSS

哎呀!这一切意味着什么?别担心,看起来比实际情况更糟;这是一个 AST 的例子,我们之前在本章中讨论过。这为我们提供了关于 CSS 内容的深入信息,例如值、值的类型以及它们在树中的位置。

然而,好的一点是,一旦我们有了所有这些内容,我们就可以随意查询和操作内容。一旦我们操作了内容,我们就需要将其转换为字符串格式,以便以更智能的格式在屏幕上显示,或者保存到磁盘。

对于这个演示,我们使用了 postcss-value-parser 插件来创建我们的抽象语法树(AST);我们也可以尝试使用 postcss-safe- parser 插件(来自 github.com/postcss/postcss-safe-parser),或者 postcss-selector-parser 插件(来自 github.com/postcss/postcss-selector-parser),以实现类似的效果。

至于为什么我们在演示中只使用了一行 CSS 代码?嗯,解析 CSS 代码可能会变得非常复杂。我们演示中的例子相对简单;想象一下如果有 2,000+ 行代码会是什么样子!

让我们进一步开发这个主题,并使用它来替换一些示例 RGBA 值为等效的基于十六进制的颜色。我们可以通过使用 postc ss-unrgba 插件(来自 github.com/jonathantneal/postcss-unrgba)轻松做到这一点,但它有近 60 行代码;我们的 Gulp 文件有 43 行,其中很多是注释!

替换 RGBA 颜色

我们接下来的例子是一个相对简单的搜索和替换;这是一个很好的例子,说明了并不总是需要使用插件,我们可以直接解析代码以实现相同的效果。让我们开始吧:

  1. 我们将首先从本书附带的下载代码中提取 T61 – 更改颜色 文件夹的副本;将文件夹保存到我们的项目区域根目录。

  2. gulpfile.jspackage.json 文件从 T61 – 更改颜色 文件夹复制到我们的项目区域根目录。

  3. src 文件夹从 T61 – 更改颜色 文件夹复制到我们的项目区域根目录。

  4. 接下来,启动一个 NodeJS 命令提示符会话,然后更改工作文件夹到我们的项目区域根目录。

  5. 现在,我们需要安装一个额外的插件,color-convert(可在 github.com/qix-/color-convert 获取),我们将使用它从抽象语法树(AST)中提取细节后更改颜色。为此,请启动一个 NodeJS 命令提示符,然后更改工作文件夹到我们的项目区域根目录。

  6. 在提示符下,输入 npm install color-convert --save-dev 并按 Enter

  7. 当插件安装完成后,请输入 gulp 并按 Enter。如果一切顺利,我们应能看到现在熟悉的转换后的样式表出现在目标文件夹中:替换 RGBA 颜色

到目前为止,我们的样式表已经发生了转换。如果我们在一个文本编辑器中预览结果,我们可以确认确实用十六进制等效值替换了原始的 RGBA 颜色,如下面的截图所示:

替换 RGBA 颜色

还不放心?看看源文件中的相同规则;这里显示了原始的 RGBA 值:

替换 RGBA 颜色

看起来很简单?有一点需要注意;如果我们看一下 Gulp 文件,乍一看可能觉得我们还在使用几个插件。关键在于这三个都是 Node 的部分(fspathutil),所以我们没有安装任何新的插件,除了value-parsercolor-convert示例之外。

探索它是如何工作的

值得花时间更详细地考虑这段代码。它包含了一些有用的技术,这些技术将帮助你开始创建自定义语法,从检索我们需要的值开始。

我们首先读取样式表文件的正文内容,然后通过postcss-value-parser插件对其进行解析。我们遍历 AST 中的每个节点,忽略任何包含node.type为函数或node.valuergba的节点。对于剩下的节点,我们收集任何具有单词类型的节点,然后将它们映射到一个单一的数组值中,并将其转换为数字。

然后将其从函数节点转换为单词节点,在我们最终将值从 RGBA 转换为 HEX 颜色之前。内容被转换为字符串,并保存到目标文件夹中的同一文件名。

注意

节点类型代表我们正在处理的选择器类型——例如包括rootstringtagattribute。在我们的例子中,我们使用了node.type来显示选择器类型的字符串表示形式,我们可以在代码中对其进行操作。

好的,让我们继续:使用自定义语法的关键基础是理解我们需要处理的内容;破解这一点,你就已经是在将你的样式转换为有效 CSS 的一部分了。不过,为了帮助这个过程,我们需要将我们的内容转换为可以保存到磁盘的格式。现在是时候看看如何使用 PostCSS API 来实现了。

使用 API 格式化输出

当解析 CSS 时,默认输出将类似于以下截图所示:

使用 API 格式化输出

它看起来真的很乱,但实际上它是 AST 树的标准格式。问题是,如果我们想在代码中使用其中的细节,它并不太有帮助!为了解决这个问题,我们需要将我们的内容转换为字符串格式:最简单的方法是使用.toString()方法,这对于将内容保存到磁盘来说非常合适。

下一个练习的所有代码都包含在这本书附带的代码下载中的T62 – 添加一个字符串化器文件夹里。

在我们的 Gulp 文件中使用它非常简单;让我们在下一个练习中看看:

  1. 我们将首先创建一个新的 Gulp 任务文件。在你的首选文本编辑器中,添加以下代码;涉及的内容相当多,所以我们将分部分进行,从插件声明开始:

    'use strict';
    var gulp = require('gulp');
    var postcss = require('postcss');
    var util = require('util');
    var autoprefixer = require('autoprefixer');
    var fs = require('fs');
    
  2. 我们需要设置一些变量;这些将在编译过程中生成值:

    var newValue = 'white', result, selectors = [], root, decl;
    
  3. 接下来是任务的开头。第一步是解析一些简单的 CSS,作为我们的演示的基础。然后我们获取代码中的第一个子元素,并将其保存到decl变量中:

    gulp.task('default', function () {
      root = postcss.parse('a { color: black }');
      decl = root.first.first;
    
  4. 我们想要的第一条信息是选择器数量;接下来的这个块将使用root.walkRules遍历每个选择器,并将值推入selectors数组:

      // get a selector count
      selectors = [];
      root.walkRules(function (rule) {
        selectors.push(rule.selector);
      });
    
  5. 到目前为止,我们已经准备好输出代码的总结报告——我们使用console.log在屏幕上显示多个不同的值:

      console.log("\nThe declaration type is: " + decl.type);
      console.log("The value of this declaration is: " + decl.toString());
      console.log("Number of nodes in this CSS: " + root.nodes.length);
      console.log("Selectors used in this CSS: " + selectors.toString());
    
  6. 我们几乎完成了——在这个下一个块中,我们执行了 PostCSS 的搜索和替换操作,将我们的颜色从黑色更改为白色:

      // Replace color black with white
      root.walkDecls(function (decl) {
        if ( decl.value.match(/^black/) ) {
          decl.value = 'white';
        }
      });
    
  7. 我们可以在屏幕上显示我们的内容,但一个更有用的步骤是将它保存到磁盘上——为此,我们可以使用 Node.js 的fs插件来创建我们的转换后的 CSS 文件和相关的源映射:

      // display content on screen and save to file
      result = root.toResult({ to: 'all.css', map: { inline: false } });
      console.log("Updated color value: " + decl.value.toString() + "\n");
      fs.writeFileSync('dest/styles.css', result.css);
      fs.writeFileSync('dest/styles.css.map', result.map);
    });
    
  8. 将 Gulp 文件保存到我们的项目区域的根目录,然后启动一个 Node.js 命令提示符,并将工作目录更改为我们的项目区域。

  9. 在提示符下,键入gulp,然后按Enter键,等待编译完成。

    如果一切顺利,我们应该在屏幕上看到第 6 步的结果,如下所示:

    使用 API 格式化输出

    转换后的 CSS 文件和源映射将出现在dest文件夹中:

    使用 API 格式化输出

那么,这一切是如何帮到我们的呢?嗯,直接解析我们的代码的能力打开了一些真正的可能性;让我们暂停一下,看看我们的演示中发生了什么,以及我们如何在未来的项目中利用这个功能。

分析我们的示例代码

在整本书中,我们使用了各种插件来转换我们的代码。这些都会以某种形式使用 PostCSS API。然而,我们不仅限于简单地使用插件;我们还可以直接使用 API 来转换我们的代码。此时,应该注意的是,我们不是在谈论创建一个自定义解析器;实际上,这很容易形成一个足够的内容,足以写一本短书!

看一下 Gulp 文件可能会让一些人望而却步。确实,创建一个自定义语法来解析使用 Stylus 或 less 创建的代码并不容易,并且超出了本书的范围。然而,我们可以利用一些 API 来查询我们的内容。在我们的示例中,有两个代码块值得关注。

第一个块解析每个选择器并保持计数。我们可以使用.walkRules方法遍历每个规则:

分析我们的示例代码

一旦我们有了原始信息,我们就可以将其转换为字符串(或将其转换为字符串),然后再在屏幕上显示内容:

剖析我们的示例代码

值得注意的是,PostCSS API 包含了将我们的 CSS 字符串化和组合在一起的功能。这些被称为 Stringify 和 Builder 命令;这些命令仅用于创建自定义语法时使用。我们只是简单地使用为这个目的设计的现有插件解析了我们的内容,所以使用 toString() 对于我们的需求来说是足够的。

继续前进——我们的示例被故意保持简单,以说明这个过程。我们可以轻松地添加额外的功能。API 参考文档在 github.com/postcss/postcss/blob/master/docs/api.md 是一个很好的起点。比如,添加错误检查怎么样?我们已经从其中添加了一个选项,以源映射的形式;让我们更详细地简要介绍这一点。

添加源映射

在我们的大多数演示中,我们包含了一个创建 CSS 样式源映射的任务。到目前为止,它工作得非常好,但这并不是最终答案——我们可以做得更好!为了了解可能的情况,请再次查看上一个演示中使用的 Gulp 任务文件中的最后一部分代码(应该在 33 到 36 行左右):

// display content on screen and save to file
result = root.toResult({ to: 'all.css', map: { inline: false } });
...
fs.writeFileSync('dest/styles.css', result.css);
fs.writeFileSync('dest/styles.css.map', result.map);

在这里,我们正在创建一个版本的转换后的代码,可以保存到文件。{inline: false} 阻止在代码中直接创建源映射。相反,我们使用 NodeJS 的文件系统根据 result.map 创建源映射;这包含了我们转换后的代码内容。

使用这种方法时有一些需要注意的点;更多细节,请查看主 PostCSS 网站 github.com/postcss/postcss/blob/master/docs/source-maps.md

是时候改变方向了:到目前为止,我们一直专注于编写代码,但关于展示呢?这并不是完全必要的,但设置突出显示有两个好处:它使我们的代码更容易编辑,我们还可以用它来在电子文档中提供一致的主题。毕竟,我相信您至少做了后者……不是吗?

突出显示我们的语法代码

在我们的大多数演示中,我们专注于使用插件,对配置代码以供使用所需的更改很少。这并没有什么问题,但就像往常一样,我们可以做得更好。比如,安装支持突出显示怎么样?

这确实是一种使我们的代码更容易阅读的简单方法,我们确实应该很久以前就安装这样的东西!除此之外,修复起来也很容易;支持广泛的各种编辑器。为了本章节的目的,我将假设您正在使用 Sublime Text;这是一个示例,看看它可能的样子(截图显示了正在使用的 Twilight Light 主题):

突出显示我们的语法代码

让我们深入探讨如何使用 Sublime Text 的包管理器来设置此环境。

安装主题

当使用像 Sublime Text 这样的编辑器时,添加主题支持非常简单。让我们一步步来完成:

  1. 我们首先打开一个命令提示符会话。请继续添加此命令,然后按 Enter 键:

    cd %APPDATA%\Sublime Text 3\Packages\User
    
    
  2. 接下来,输入此命令,然后按 Enter 键:

    git clone git://github.com/chriskempson/base16-textmate.git Base16
    
    
  3. 打开 Sublime Text。如果一切顺利,当我们点击 首选项 | 颜色方案 | 用户 时,我们应该看到一个新菜单项安装主题

这是一个简单的更改,但非常有用;尽管如此,这仅仅是我们能做的事情的一部分!如果我们想(字面上)大干一场,我们可以将类似的样式应用到我们的 CSS 样式中。如果我们想在线记录样式,这将使阅读变得更加容易。毕竟,颜色显然比黑白更容易阅读!让我们花点时间来探索将颜色主题应用到我们的文档代码中所需的内容。

创建 HTML 显示主题

开发代码的一部分是需要对其进行文档记录。这不仅是为了我们的理智,也是为了未来的更改,以防其他人需要修改我们的代码!创建打印文档现在已经过时,更好的方法是创建在线文档,这样我们可以轻松更新它而无需太多麻烦。同时,我们可以在其中添加一些颜色,使其在视觉上更加吸引人,并为我们的努力提供更一致的格式。

我们可以手动创建这段代码,但这是一个资源密集且容易出错的过程!相反,我们可以使用 Midas 库(可在 midasjs.com 获取)来自动化创建我们文档的基础,并且我们可以使用之前练习中提到的 base16 主题之一来对其进行样式化。

让我们开始安装这个支持:

  1. 我们首先启动一个 NodeJS 命令提示符,然后更改工作文件夹到我们的项目区域。

  2. 在提示符中,输入此命令,然后按 Enter 键:

    npm install midas --save-dev 
    
    
  3. 安装完成后,请从 T63 – incorporating midas 文件夹中提取 src 文件夹以及 gulpfile.jspackage.json 文件,然后将它们保存到我们项目区域的根目录。

  4. 回到 NodeJS 命令提示符,然后输入 gulp 并按 Enter 键。

  5. Gulp 将退出并编译我们的代码的基于 HTML 的提取,生成 styles.html 文件,该文件已使用额外的标记进行了适当的格式化。该文件将出现在我们项目区域内的 dest 文件夹中。

在这一点上,如果我们查看该文件的目录,它看起来会很普通;这很容易解决!为此,我们将使用 Timothée Poisot 的 Brewer 主题,可在 github.com/chriskempson/base16-builder/blob/master/schemes/brewer.yml 获取;要实现这一点,涉及几个步骤:

  1. 浏览到github.com/ben-eb/midas/blob/master/templates/template-light.css,然后在我们的项目区域的dest文件夹中将其保存为 CSS 文件。

  2. 在文本编辑器中打开它;它包含了一系列用于可用 base16 主题的占位符。我们通常会使用 Ruby 生成这些主题之一的 CSS,但这并非完全必要;相反,使用您编辑器的搜索和替换功能,将每个占位符与相应的颜色匹配:创建 HTML 显示主题创建 HTML 显示主题

  3. 保存结果;为了使其工作,我们需要调整styles.html文件的内容,以便引用新的样式表,使其具有正确的 HTML 结构。一旦完成,它将看起来像这样:创建 HTML 显示主题

我认为这看起来更有吸引力,我相信你会同意!虽然创建初始主题需要一些工作,但这将是每个主题创建的一次性过程。对 CSS 规则或声明所做的任何更改都可以自动生成,并且 HTML 结果会相应更新。

摘要

我们首先快速介绍了自定义语法,然后为开发代码准备我们的环境。然后,我们介绍了一些可用于解析内容的插件,并在实施两个示例之前,探讨了为什么我们可能需要直接解析自定义语法或样式,以展示如何操作我们的代码。

我们随后深入研究了大多数 PostCSS 插件如何修改我们的代码。然后,我们在考虑我们的更改对代码本身的影响之前,探索了一些直接修改样式的技术。

接下来,我们研究了如何为屏幕或准备保存到磁盘的状态格式化内容(包括创建源映射)。我们注意到,尽管有一些特定的方法可用,但这些方法是为自定义语法开发保留的,而我们使用的选项足以满足对代码的初始操作。

然后,我们通过查看如何为我们的项目添加高亮支持来结束本章——我们探讨了添加此功能的原因,例如使代码编辑更容易,并展示了一个使用 Midas 库创建项目正确布局文档的快速演示。

使用不同的语法是一种很好的方法,可以消除将现有代码重写为有效 CSS 的需求。不过,有一个问题:如果我们混合了标准 CSS 和预处理器代码,比如来自 less 或 Stylus 的代码,会发生什么?我们能否同时处理这两种代码?当然可以!在下一章中,我们将探讨支持这一功能的 Pleeease 库。

第十二章。混合预处理器

在整本书中,我们探讨了使用 PostCSS,并看到了我们如何构建一个更高效的满足我们需求的预处理器,而不需要标准预处理器带来的额外负担。但是等等——构建处理器不是需要时间吗?我们如何管理过渡?

没问题,进入 Pleeease 库!在本章中,我们将利用 Pleeease 的力量将预处理器和后处理器结合成一个过程,混合现有的系统如 SASS、Less 和 Stylus。在接下来的几页中,我们将查看一些示例,并展示如何轻松地利用 Pleeease 的力量。

本章将涵盖以下技术主题:

  • 检查使用 Pleeease 库的好处

  • 安装和配置库

  • 探索 Pleeease 的一些功能

  • 使用 Node 或命令行编译代码

  • 设置配置文件

  • 转换 WordPress 安装并测试结果

让我们开始吧!

开始第一步

在开发网站时,开发者几乎总是有机会从头开始设计和构建它;我们可以做出有关要使用的配色方案、网站结构和外观的一般外观的决定。

有时候,尽管如此,我们可能会想要使用新技术,但不得不使用现有的网站。处于这种位置的大多数人的心中可能会提出一个问题:从哪里开始?当然,这将取决于许多因素,其中之一可能是网站上是否正在使用现有的处理器,如 SASS 或 Less。

PostCSS 的灵活性和强大功能使我们能够轻松地从 Less 或 SASS 等过渡到使用 PostCSS——在本章中,我们将继续探索我们可以用来开始这个转换过程的技巧。我们将探索一些可用的插件选项,然后结束于将 CMS 系统如 WordPress 转换为使用 PostCSS 的转换过程。

注意

有什么原因选择 WordPress 吗?它是用 SASS 编译的,但也使用 PostCSS 来处理供应商前缀,我们将在本章后面通过额外的插件在此基础上进行构建。

让我们开始这段旅程,我们的第一步是探索一些可以帮助我们从使用 SASS 过渡到 PostCSS 的插件。

探索转换过程

暂时回顾一下第十章,构建自定义处理器

那一章的关键主题是将我们在前几章中介绍的一些插件整合在一起,以创建我们将会使用的处理器。到目前为止,我们使用的所有插件都是基于纯 PostCSS 的,因此它们无法编译原始的 SASS 代码。

我们简要地看了看 CSStyle 库,作为绕过这个问题的可能方法——这是一个使用 BEM 原则生成干净代码的出色库,但它要求代码使用特定的格式。通常,这根本没有任何问题——每个开发者的乌托邦应该是生成干净、高效的代码,对吧?

然而,这里有一个小问题,现实!在没有进行大量工作的前提下,重写一个大型、复杂的电子商务网站以使用 CSStyle 是不切实际的;这将需要一个漫长的过渡期来实现这样的变化。虽然不是不可能,但如果你网站上有多个样式表在使用,使用 BEM 风格的注释最好是从头开始,或者至少在定义的块中进行。

所以,如果我们使用 CSStyle 对我们的需求来说不是一个实际的解决方案,我们如何实现这个变化?有一个更实际的解决方案可供我们选择——这可能需要更长的时间,但破坏应该会减少,并允许我们在更可管理的过渡过程中对代码进行更小的更改:

  • 我们首先引入一个任务运行器来编译现有的处理器代码——存在插件可以在 Gulp 或 Broccoli 等运行器中使用库,如 SASS 或 Less,以允许我们编译代码。

  • 一旦我们过渡到使用任务运行器,我们就可以引入插件来处理核心过程,例如管理供应商前缀、创建源映射以及压缩我们的样式表。

  • 然后,我们可以将现有的样式表分解成更小的块,并在编译期间将每个块导入到主文件中。每个块都可以转换为使用 PostCSS 插件来复制现有处理器功能——例如,我们可能会使用 postcss-simple-vars 来创建新的变量来替换现有的基于 SASS 的示例。

在这个过程的最后一步应该是迭代的,至少直到所有内容都已被转换,并且允许我们消除对现有处理器的任何依赖。到目前为止,我们已经使用了相当数量的 Gulp 任务文件,因此我们应该已经对其中一个的基本使用相当熟悉了——如果我们使用 SASS 和 Gulp,一个任务文件可能看起来是这样的:

var gulp = require('gulp');
var postcss = require('gulp-postcss');
var sass = require('gulp-sass');

var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');

gulp.task('css', function () {
  var processors = [ autoprefixer, cssnano ];
  return gulp.src('./src/*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(postcss(processors))
    .pipe(gulp.dest('./dest'));
});

在这个例子中,我们使用的是 Gulp 插件用于 SASS 和 PostCSS——SASS 代码首先被编译,然后由 PostCSS 添加供应商前缀,代码被压缩成最终的文档。

这个过程的优点意味着我们可以控制转换的速度——我们不必一次性转换所有内容,并且可以在过程的每个点上选择性地转换内容。仍然会对外部库有依赖,但这是临时的;当所有内容都转换为使用 PostCSS 时,我们可以消除这个依赖。

选择我们的插件

假设我们已经过渡到使用任务运行器,那么接下来我们该做什么呢?

好吧,是时候选择我们需要的插件了,基于我们网站提供的功能。以下是一些有用的插件,可以帮助你开始:

插件 插件用途
postcss-mixins 如果您的代码包含 SASS 混入(mixins),那么这将变得至关重要——格式非常相似,因此您可以使用编辑器中的搜索和替换来做出更改。该插件可在github.com/postcss/postcss-mixins获取。
postcss-nested 在 SASS 中嵌套代码是一个关键概念——来自github.com/postcss/postcss-nestedpostcss-nested插件是复制这一功能在 SASS 中的好选择。与此相结合,postcss-nested-propspostcss-nested-vars插件可以用来展开嵌套代码中的任何属性或变量。
postcss-sassy-mixins 有时候我们可能会有一些可重用的代码块;我们可以使用混入(mixins)来帮助减少样式表中编写的代码量。这是从 SASS 借用的一个关键概念,这个插件复制了相同的功能,并允许我们轻松地将使用 SASS 转换为 PostCSS。插件源代码可在github.com/andyjansson/postcss-sassy-mixins获取。
postcss-simple-extend 如果我们有共享公共元素的风格,那么我们可以通过扩展现有风格来减少一些这种重复。这是使用 SASS 时的常见做法;来自github.com/davidtheclark/postcss-simple-extend的 postcss-simple-extend 插件非常适合在 PostCSS 中复制这一功能。

其他插件可供选择,具体取决于您的需求。大多数可用的插件都是针对 SASS 的,但这仅仅是因为它的成熟度;随着时间的推移,其他针对 Less 或 Stylus 等处理器的插件无疑也会变得可用。

小贴士

查看来自postcss.parts的 PostCSS 插件目录以获取更多详细信息。

添加单个插件是一个完全可接受的选择,但如果我们添加的不仅仅是几个插件来模拟 SASS 代码呢?这里有两个有用的选项,而且我们还没有在我们的列表中涵盖——使用 PreCSS 或 Pleeease 库。

Pleeease 库被设计用来处理在编译我们的代码时一些必要的繁琐任务。尽管并非所有支持的任务都适用,但至少会有三个任务适用——代码压缩、添加供应商前缀和生成源映射。

与之形成鲜明对比的是,PreCSS 库可能更有用,因为它是一组模拟 SASS 功能的插件。然而,美中不足的是,我们只需要安装一个插件来处理更改;PreCSS 通过单个接口将 PostCSS 风格的手动转换抽象为有效的 CSS。我们将在本章稍后更详细地探讨它的使用,但现在,让我们将注意力转向测试 Pleeease 库。

介绍 Pleeease 库

可从 pleeease.io/ 获取的 Pleeease 库旨在简化预处理器使用,并结合使用多个工具的优点在一个库中。这意味着我们可以将其配置为三个处理器之一,如 SASS、Less 或 Stylus,以及 PostCSS,在编译我们的代码时。编译还可以包括我们可能必须执行的典型任务,例如生成源映射、添加供应商前缀和最小化结果。

图书馆安装简单——在其最简单的格式中,我们可以使用配置文件在命令行中进行编译。或者,我们可以使用几个插件中的任何一个来连接到任务运行器,例如 Gulp 或 Grunt。让我们花点时间更详细地探讨这个问题,从安装和配置插件以供使用开始。

安装和配置 Pleeease 库

Pleeease 库基于 Node.js;它易于安装,无论是手动在命令行使用,还是通过 Gulp 这样的任务运行器。让我们开始设置一切以供使用:

  1. 我们将开始安装 Pleeease 库——为此,请启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  2. 在提示符下,输入 npm install -g pleeease-cli,然后按 Enter 键——等待 Node 完成安装。

到目前为止,Pleeease 库已经安装并配置好,可以从命令行使用。如果 Node 抱怨需要更新的元素,那么可能需要运行 npm update –g n 来更新你的版本。如果你是 Windows 用户,那么有一个方便的 PowerShell 脚本可以在 github.com/felixrieseberg/npm-windows-upgrade 上找到,以帮助完成这个过程。

注意

你可能会收到关于 graceful-fslodash 弃用模块的几个警告:对于演示目的,可以忽略这些警告。

假设我们在安装 Pleeease(除了已经提到的某些弃用警告之外)时没有遇到任何问题,那么我们现在就可以开始使用 Pleeease 了。在接下来的几页中,我们将查看手动编译以及使用 Gulp 作为我们首选的任务运行器。让我们首先探索如何使用 .pleeeaserc 文件在命令行上轻松执行基本编译。

注意

在本章中,我们将专注于使用 SASS;如果你更喜欢使用 Less,可以使用 gulp-less 插件作为替代。同样,如果你希望使用 Stylus,那么 gulp-stylus 插件也可以与 Pleeease 一样很好地工作。

手动编译代码

使用 Pleeease 库编译代码的最简单方法是使用 .pleeeaserc 配置文件。

这类似于一个(简化的)JSON 文件,看起来可能像这样:

{
  "in": ["foo.css", "bar.css"],
  "out": "baz.css",
  "browsers": ["last 3 versions", "Android 2.3"]
}

看起来很简单,不是吗?我们只需要指定我们的源文件(in),以及我们应该得到什么(out)。在这个例子中,我们更进一步,指定所需的浏览器支持级别——这主要是为了确保已经应用了正确的供应商前缀。

此设置使用与 Autoprefixer 相同的配置:我们可以同样传递来自 Browserslist 查询列表的有效查询,该列表可在 github.com/ai/browserslist#queries 找到。

如果我们的需求不需要使用任务运行器,或者我们希望保持我们的流程简单,这是一个有用的编译方法。唯一的缺点是我们不能将任何其他可以自动化的任务绑定进来,例如使用 .min.css 扩展名重命名编译后的样式表——为此,我们需要使用像 Gulp 这样的任务运行器。

如果我们使用任务运行器,这会打开各种可能性,例如自动化调整图像大小、重命名编译后的样式表以及检查代码的一致性。在我们这样做之前,让我们先介绍一个有用的技巧——Pleeease 网站包含一个在线沙盒(可在 pleeease.io/play/ 找到)。我们可以使用这个沙盒来熟悉使用该库,在为我们生产网站编译代码之前:

手动编译代码

好了,闲话不多说:现在是时候实际操作了!对于一些人来说,在命令行中编译可能就足够了,但在这个自动化时代,为什么要把时间花在执行可以轻松自动化的手动流程上?

使用任务运行器编译

如果到现在你还没有猜到,我是一个 Node.js 的忠实粉丝——我选择的任务运行器是 Gulp。以前是 Grunt,但我发现 Gulp 的使用起来更容易——我不确定为什么!无论如何,两者都可以与 Pleeease 一起使用,所以如果你的首选不是 Gulp,那么请随意相应地修改代码。

让我们一步步完成使用 Gulp 运行编译过程的步骤:

  1. 启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  2. 在提示符下,输入以下命令,每输入一个命令后按 Enter

    npm install gulp-pleeease
    
    

    保持 Node.js 命令提示符会话开启——我们很快就会需要它。

  3. 从本书附带的代码下载中提取 T65 – using gulp-pleeease 文件夹的副本到我们的项目区域根目录。

  4. package.jsongulpfile.js 文件复制到我们的项目区域根目录,然后将 src 文件夹下的 example.css 复制到项目区域根目录的 src 文件夹。

  5. 返回 Node.js 命令提示符会话,然后在提示符下输入 gulp 并按 Enter使用任务运行器编译

假设编译成功,Gulp 将在我们项目区域根目录下的dest文件夹中生成现在熟悉的文件。如果我们查看结果,我们应该看到它已经压缩了文件,添加了供应商前缀,并将bluered颜色属性转换为它们的等效十六进制值。

让我们好好利用这项技术,创建一个简单的网页,作为我们如何使用 Pleeease 的示例。当我们检查 Gulp 文件时,我们会看到我们不需要使用之前练习中一半的插件,因为 Pleeease 在其插件内部添加了这些支持。

使用 Pleeease 构建示例

在本书的许多演示中,我们不得不导入一系列插件来管理不同的任务,例如压缩代码或检查其一致性。

这种方法在技术上没有错误,但效率不高——毕竟,为什么需要六种工具,而一种工具就足够了,对吧?我们试图在整个书中保持一个插件对应一个任务的规则,那么为什么我们要打破这个惯例呢?

使用 Pleeease 的好处是它已经包含了对一些任务的内置支持,否则这些任务可能需要单独的插件;这意味着我们可以从 Gulp 任务文件中删除一些插件。Pleeease 只是一个通过一个通用接口抽象支持六个其他插件的层。

让我们将其用于编译简单网页的样式:

使用 Pleeease 构建示例

让我们开始吧:

  1. 我们将像往常一样,从本书附带的代码下载中提取TXX – creating a page using pleeease文件夹的副本;将其保存到我们项目区域的根目录。

  2. css – completed version文件夹中,将styles – pre compile.css文件复制到我们项目区域根目录下的src文件夹;将其重命名为styles.css

  3. gulpfile.jspackage.json文件从tutorial文件夹的根目录复制到项目区域的根目录——这些文件应该替换掉我们项目区域根目录下已有的文件。

  4. 启动一个 Node.js 命令提示符会话,然后更改工作文件夹到项目区域。

  5. 在提示符下,输入gulp然后按Enter——Pleeease 现在将编译我们的代码,并在我们项目区域根目录下的dest文件夹中输出有效的样式表文件。

  6. 完成后,将dest文件夹的内容复制到tutorial文件夹内的css文件夹。

如果我们尝试通过双击webpage.html来预览我们工作的结果,我们应该看到一个网页出现,类似于这个演示开始时的截图。然而,真正的证明是在 Gulp 任务文件中——与我们在早期演示中创建的其他示例相比,我们已经完全删除了一个任务,并且将引用的插件数量减少了超过一半!

使用其他预处理器编译

然而,有一个问题——到目前为止,我们使用 Pleeease 所做的所有工作都是基于 PostCSS 的;如果我们使用 SASS 作为生成代码的基础呢?

不幸的是,这正是 Pleeease 的不足之处——尽管它包括对 SASS、Stylus 和 Less 的支持,但它仍然非常实验性。一个导致问题的例子是在嵌套方面;当配置为使用 SASS 时,Pleeease 尚未支持嵌套。这降低了使用 Pleeease 的吸引力——毕竟,使用 PostCSS 的一个关键原因就是消除对 SASS 等库的任何依赖!

要解决这个问题,意味着使用gulp-sass插件。这是一个libsass库的包装器。为了实现这一点,我们会在我们的 Gulp 任务文件中添加一个这样的任务:

使用其他预处理器编译

当使用这种方法时,我们可以在使用 PostCSS 插件转换之前,预先编译我们的 SASS 代码为有效的 CSS。问题是,这似乎是一种编译代码的低效方式——有一个更好的替代方案,那就是 PreCSS 库。

使用 PreCSS 库

好的……那么为什么它更好,你可能会问?

简单的回答是,我们可以直接编译类似 SASS 的代码,而不需要使用像 libsass 这样的外部库。然而,这里的区别在于它适用于类似 SASS 的代码,而不是直接的 SASS。

虽然这并不是一个问题——我们可以轻松地使用文本编辑器进行搜索和替换,以进行必要的格式更改,使其与 PreCSS 兼容。库源代码可在github.com/jonathantneal/precss找到;开发者甚至为我们提供了一个在线沙盒jonathantneal.github.io/precss/,以便我们在提交代码前尝试更改。

到这一点,我们将打破常规——而不是制作一个简单的网页,让我们大胆一些,使用像 WordPress 这样的内容管理系统。为什么?好吧,有两个原因:WordPress 已经使用了 PostCSS 和 SASS——继续使用相同的工具是完美的!基于这一点,让我们深入探讨并更详细地查看如何使用这两个工具。

转换 WordPress 安装

我们迄今为止的所有示例都基于单页;许多开发者可能会使用像 WordPress 这样的内容管理系统。

幸运的是,我们可以将迄今为止使用的许多相同原则应用于 WordPress 的样式——事实上,有一些插件我们可以使用,它们模仿 SASS,这是用于创建作为每个 WordPress 下载部分的核心样式表的 SASS。我们将使用的主题是 Twenty Sixteen,自 2015 年 12 月以来的 WordPress 当前版本都包含这个主题;它也可以从wordpress.org/themes/twentysixteen/下载。

在本章剩余部分,我们将探讨一些我们可以使用的技巧和窍门,以将 PostCSS 集成到 WordPress 主题中。这里的一个关键点是,我们已经介绍了一些可以使用的任务——通过谨慎规划和设计,一些可以很容易地重用于为 CMS 应用程序创建样式表。我们将探讨一些我们可以使用的技巧和窍门——尽管这些将针对 Twenty Sixteen 主题,但它们同样可以用于为 WordPress 开发的其它主题。

注意

下一个示例假设您对 WordPress 有一定的了解,以及使用 SASS 或 Less 的一些基础知识——如果您是这两个应用程序的新手,我建议您阅读相关主题。

让我们开始吧。我们的第一个任务是设置我们的环境,以便使用。不过,在我们设置环境之前,我强烈建议您手头备有本章代码的副本——将要讨论的许多内容都将参考代码!

设置我们的环境

为了充分利用本章内容,我们需要设置一个 WordPress 的安装环境——对于初学者来说,有两种实现方式:

  • 我们可以使用 Web 服务器(如 WAMPSever www.wampserver.com/en)或 Apache (www.apachefriends.org——如果您是 Linux 或 Mac 用户)将 WordPress 作为本地托管应用程序安装

  • 我们可以使用安装在个人在线空间上的 WordPress 版本

为了本书的目的,我们将使用前者——要获取 WordPress,请访问www.wordpress.org,然后在屏幕右侧(靠近顶部)点击蓝色的下载 WordPress按钮。

我将假设我们的 WordPress 版本已经在本地的C:\wamp\www\wordpress下安装,使用您选择的本地 Web 服务器,按照codex.wordpress.org/Installing_WordPress中提供的说明进行操作。我的首选是使用 WAMPServer(可在www.wampserver.com/en获取),但如果您想使用不同的 Web 服务器或文件夹,请相应调整步骤。

好的,WordPress 安装、配置并准备就绪后,让我们继续前进。下一步是查看我们开始转换过程可用的选项。

注意

本章剩余部分给出的步骤将针对 Windows 平台,因为这是作者通常使用的平台;如果您使用 Linux 或 Mac 设备,请相应调整。

考虑转换过程

当您处理一个普通的 WordPress 样式表时,您可能会问从哪里开始?

好吧,我们首先不应该做的事情就是被其大小所吓倒。是的,我知道这听起来可能有些疯狂(毕竟,Twenty Sixteen 主题有 3920 行!),但只要有些规划,我们就可以轻松地将它分解成更易于管理的部分。

如果我们只通过 PostCSS 完成一个任务,那么这个任务必须是利用postcss-import插件来帮助我们将代码分解成更易于管理的原则。如果你恰好使用过 SASS 或 Less 这样的处理器,那么原理是相同的——在我们的主style.css文件中,我们可以创建一系列导入语句,并将每个块分离到单独的文件中。

一旦我们将样式表分解成更易于管理的块,我们可以在代码中实现相当多的事情;我们应该始终将其视为一个迭代过程,直到我们用尽所有可能的替代方案,网站不再需要,或者我们迁移到不同的解决方案。在接下来的几页中,我们将介绍一些可能出现的想法和考虑因素——这应该有助于你开始对你的主题进行更改。所以,无需多言,我们从哪里开始呢?

好吧,显而易见的一个选择是使用 Autoprefixer;WordPress 很好地利用了 CSS3 样式,其中相当一部分仍然需要供应商前缀。不过,这里有一个考虑因素,那就是我们将从原始样式表反向工作,我们需要移除现有的供应商前缀,并将任务运行器设置为自动添加这些前缀。这是在 WordPress 中处理现有样式表的一个必要之恶,但至少我们只需要做一次!可能会有创建一个 mixin 来管理供应商前缀的诱惑,但这并不是最佳实践——Autoprefixer 会在每次编译时更新样式。

我们已经熟悉了从之前的例子中使用 Autoprefixer——同样,我们还可以考虑压缩我们的代码,这有助于减少带宽使用。添加这样的功能应该轻而易举——我们可以使用之前演示中的相同任务,只要我们设置正确的任务顺序。我们需要将其更改为直接编译style.css(这是 WordPress 样式表的主要文件),但由于我们的处理器将针对 WordPress 使用,这不会成为问题。

另一个我们可以关注的领域是 rem 单位支持,带有像素回退。许多开发者对使用 rem 作为度量单位有自己的看法;有些人说像素值效果一样好,但它的适用性将取决于应用的位置。抛开这一点,Gulp 有一个合适的插件可以帮助我们提供这项功能,如果我们需要的话。

真正对我们代码产生影响的一种方式是使用嵌套——这是 SASS 等预处理器中的一种常见技术,涉及以嵌套格式编写代码。关键好处是移除重复的代码——可以将其视为一种简写(从某种意义上说),它将在编译时转换为有效的 CSS。

另一个值得一看的技术是变量的使用;它们的工作方式与脚本或编程语言非常相似。现在在你跑向山丘之前,别担心:它们很容易使用。我们需要提供一个占位符名称列表,以及它们代表的值;然后我们可以在代码中对每个值进行搜索和替换,并用适当的变量替换它。你可能会问为什么这样做?嗯,很简单:如果你将来更改颜色,你只需要在一个地方更改它;PostCSS 将在编译阶段自动为你更改所有其他实例。

如果你真的想深入 WordPress 的核心代码,那么探索core.trac.wordpress.org/browser/trunk/的代码仓库总是值得的。如果你仔细观察,甚至可以看到 PostCSS 的使用情况!

好了,别再闲聊了:让我们开始写代码吧!我们将在下一个演示中进行的修改只是我们如何将 PostCSS 插件(或者 Gulp)的使用纳入我们流程的一些方法之一。我们将首先探索我们需要进行的修改,然后提供一些使用 PostCSS 的尝试想法。

对我们的代码进行修改

虽然我们只介绍了一些想法,但仍然有许多步骤需要完成;这个问题的关键(以及保持你的理智!)是分块完成每个步骤,而不是一次性完成所有。

我们的大部分更改将使用我们在早期演示中创建的现有任务;我们将添加 PreCSS 库(来自github.com/jonathantneal/precss),以及 postcss-import 和 gulp-pixrem 插件。考虑到这一点,我们将开始——我们的第一个任务是分割代码成更易于管理的样式表。

分割我们的样式表

这个过程的关键部分是将我们的样式表分割开——为此,我们将使用来自github.com/postcss/postcss-importpostcss-import插件:

  1. 我们将像往常一样,启动一个 Node.js 命令提示符,然后更改工作文件夹到项目区域的根目录。

  2. 在提示符下,输入以下命令,然后按Enter

    npm install postcss-import --save-dev
    
    
  3. 等待 Gulp 完成安装过程。

接下来,我们需要将样式表分割成单独的块;最方便的方法是按照style.css顶部的列表将其分割成部分:

  1. 在项目区域的根目录下的src文件夹中,创建一个名为css的新文件夹。

  2. 打开位于C:\wamp\www\wordpress\wp-content\themes\twentysixteen\的 Twenty Sixteen 文件夹中的style.css副本。

  3. 将此保存到项目区域的根目录下的src文件夹中。

  4. 在第 53 行或附近,添加以下行:@import "css/variables.css";。现在不用担心它将用于什么——这将在本章的后面变得清楚。

  5. 找到第 54 至 252 行,然后将它们复制到一个新文件中——将其保存为normalize.css,位于根src文件夹内的css文件夹中。

  6. src文件夹内的style.css文件中,添加这些导入语句,如指示:分割我们的样式表

  7. 重复此过程,直到您已将所有部分提取到各自的文件中(1 至 15)。使用与每个主要部分相同的名称保存它们。

    注意

    注意,在保存文件时,您不需要将第 11 至 15 节分割成子部分——保持这些部分在其各自的文件中。

  8. 我们还需要执行最后一步:我们需要我们的 Gulp 任务文件!从伴随本书的代码下载中,请从T68 - converting a WordPress theme文件夹中提取gulpfile.jspackage.json的副本,然后将它们都保存到项目区域的根目录下。

  9. 快速查看gulpfile.js文件,特别是第 31 至 35 行:分割我们的样式表

注意我们是如何直接编译到style.css的,这与之前的练习不同?这并不理想,但鉴于 WordPress 主题默认使用style.css,这是我们可以在编译过程中容忍的部分。

添加供应商前缀支持

我们接下来的任务是安装添加供应商前缀的支持——从某种意义上说,我们已经在之前的示例中介绍了如何实现这一点。我们之前的多数,如果不是所有,演示已经包括了对供应商前缀的支持,使用了 Autoprefixer 插件。

作为提醒,Autoprefixer 可以从github.com/postcss/autoprefixer获取;我们有一个在线版本可以用来测试更改,在autoprefixer.github.io/

如果我们查看之前示例中下载的 Gulp 任务文件,我们可以看到 Autoprefixer 插件已经被作为触发 PostCSS 的一部分调用:

添加供应商前缀支持

然而,在这个阶段,我们应该注意几个关键点。

当使用 Autoprefixer 插件时,它会使用来自 Can I Use 网站(www.caniuse.com)的数据来更新它找到的任何过时的供应商前缀。花时间检查您的样式表以确保它尚未包含供应商前缀是很值得的——如果包含,这些前缀需要被删除。

我们可以手动删除它们,或者更有效的方法是使用来自github.com/johnotander/postcss-remove-prefixespostcss-remove-prefixes插件。我们可以将其添加到我们的 Gulp 任务文件中,或者直接从命令行运行它。这里的关键是首先完成删除,这样 Autoprefixer 就可以用来管理供应商前缀。

WordPress 已经使用 Autoprefixer 来管理供应商前缀——你可以在core.trac.wordpress.org/browser/trunk/Gruntfile.jsGrunt文件中看到它的证据。当然,它使用的是 Grunt,但对于使用 Gulp 或其他与 PostCSS 兼容的任务运行器的人来说,这个过程非常相似:

添加对供应商前缀的支持

虽然在我们的 Gulp 任务文件中设置 Autoprefixer 非常简单——至少是基础设置——但它只有在花费时间移除任何可以自动在编译时添加的供应商前缀的情况下才会成功。postcss-remove-prefixes插件将移除如下简单示例,这样我们就可以留下不带前缀的版本,Autoprefixer 可以在编译时更新它们:

添加对供应商前缀的支持

我们应该执行的最后一个核心任务是检查代码的一致性——到目前为止,这应该是一个非常熟悉的任务,因为我们已经从早期的演示中设置了一个合适的任务,可以很容易地用于编译 WordPress 主题。现在是时候重新审视这个任务了。为了确保它能够正确工作,我们需要稍微修改一下设置,现在就让我们来谈谈这一点。

检查代码一致性

如果我们查看这些更改开始时保存的 Gulp 任务文件,我们应该看到这个配置对象:

检查代码一致性

有一点更往下是任务,这里有两个更改:我们硬编码了destination文件,并调整了顺序,以便允许pxrem任务的存在。配置对象中的缩进设置也发生了变化——当编译时,样式表使用制表符进行缩进。

这将引发大量警告,我们可以手动更改 3000 多个条目,或者更改检查缩进的方式。希望这很容易就能明白我们更愿意做哪一种,至少在短期内是这样!

除了这两个更改之外,linting 任务的其余部分没有变化:

检查代码一致性

好吧,让我们换个方向:还有一个核心任务我们应该运行,那就是压缩我们的代码。我们已经在多个演示中使用了它,但现在让我们花点时间在编译 WordPress 主题的上下文中重新审视它。

压缩我们的代码

如果我们必须对使用 PostCSS 可以执行的前四项关键任务进行排名,那么接下来的这个任务肯定应该排在最前面。压缩我们的代码对于节省带宽使用至关重要——WordPress 主题可不是轻量级的!

我们的 Gulp 文件已经内置了这个:package.json文件将设置适当的引用。如果我们更详细地查看任务文件,我们应该看到类似以下的内容:

压缩我们的代码

看起来熟悉吗?应该是的——它几乎是我们之前演示中使用的现有任务的直接复制。我们已经关闭了autoprefixer,因为我们在 Gulp 文件的其他地方使用了它。

让我们继续前进。WordPress 使用 SASS 作为其主要预处理器;我们可以使用 Pleeease 库来编译 SASS 和 PostCSS 代码,但一个更干净的选择是使用 PreCSS 库。这个库抽象了对许多模拟 SASS 代码的插件的支持(但可以说没有 SASS 的负担)。让我们深入探讨并更详细地看看这一点。

创建变量

SASS(以及其他处理器)的一个关键特性是能够使用变量作为值的占位符——这些在编译时被转换为有效的 CSS 样式。

你可能会问,为什么使用它们?简单来说,如果你决定更改字体家族或颜色,你希望翻遍数千行代码来更新所有使用过的地方吗?我希望答案是“不”——这是完全正确的:我们还有更重要的事情要做!其中之一就是安装那个将添加变量支持的插件,形式为 PreCSS。

我们已经从之前的演示中安装了 PreCSS,所以剩下的只是确保将其添加到我们的 Gulp 任务文件中相应的地方(它已经在我们的文件中,并且在附带的package.json文件中):

创建变量

真正的工作在于更改我们的样式表——让我们看看需要做什么:

  1. 首先,我们需要创建一个文件来存储我们的变量——请在项目区域的根目录下的src文件夹中的css子文件夹中创建一个空白文件,并将其命名为variables.css

  2. 打开variables.css文件。继续添加这些值:

    /**
     * 0 - Variables
     */
    
     /**
     * 0.1 - Colors
     */
    $lightgray: #d1d1d1;
    $almostblack: #1a1a1a;
    $verydarkgray: #686868;
    $white: #ffffff;
    $verylightgray: #f7f7f7;
    $strongblue: #007acc;
    
    /**
     * 0.2 - Fonts
     */
    $Monserrat: Montserrat, "Helvetica Neue", sans-serif;
    $OpenSans: "Open Sans", sans-serif;
    $Merriweather: Merriweather, Georgia, serif;
    $Inconsolata: Inconsolata, monospace;
    
    /**
     * 0.3 - Font Sizes
     */
    $baseSize: 16px;
    
  3. 保存文件,如果我们回顾一下拆分我们的样式表,你会注意到我们已经从我们的主样式表中包含了对其的链接:创建变量

  4. 来自此文件的值将替换我们代码中的占位符,以生成有效的 CSS。

  5. 下一个任务是必要的恶行,我们必须逐个处理要导入的每个样式表,并将现有值替换为变量等效值。这个截图显示了部分示例——在这里,font-family值已经更新,但border值尚未更改:创建变量

  6. 一旦每个文件都进行了更改,然后保存每个文件,为下一个练习做好准备。

没有简单的方法可以绕过这个问题,但修改 WordPress 主题样式表可能需要大量的耐心!最好的办法是使用你编辑器的搜索和替换功能——例如 Sublime Text 3(作者的偏好编辑器)有一个非常有用的功能,可以在多个文件中替换文本;利用这个功能将有助于减少手动更新每个文件所需的工作量。

添加对 rem 单位的支持

下一个任务可能会引起争议——将我们的代码更改为使用 rem 单位,并自动添加像素回退。

一些开发者声称像素值效果一样好;其他人说,这完全取决于你需要在哪里指定值,以及使用哪种度量单位。无论如何,我们可以使用 PostCSS 自动添加像素回退支持。此插件的源代码可在https://github.com/gummesson/gulp-pixrem找到。让我们探索添加对 rem 单位支持所需的内容:

  1. 打开一个 Node.js 命令提示符会话,或者如果上一个会话的命令提示符仍然可用,则切换到它。

  2. 确保工作文件夹设置为我们的项目文件夹的根目录,然后在提示符中输入此命令并按Enter

    npm install gulp-pixrem --save-dev
    
    
  3. Gulp 将移除并安装插件——等待它完成后再继续。

  4. 我们已经有了我们的 Gulp 任务文件——如果我们仔细查看,我们可以看到它在第 39 行被调用:添加对 rem 单位的支持

到目前为止,我们已经准备好了所有东西——下一个任务是逐一处理我们创建的各种样式表,并将任何像素值替换为 rem 等效值。

这是一项不受欢迎但必要的任务——插件通过在我们的代码中找到的任何 rem 单位的实例添加像素回退值来工作。至于改变值,我们想改变多少就改变多少——确保代码可以编译是值得做一些改变的,但剩余的改变可以逐步进行。

在编译代码时,我们最终会得到类似这个示例的代码——这个摘录来自小部件部分(第十部分):

添加对 rem 单位的支持

将我们的样式表简单地转换为使用像素回退支持很容易——关键是我们需要决定在哪里想要使用 rem 单位支持,以及在哪里使用像素或 em 单位等现有值会更合适。

接下来,我们可以在编译过程中设置两个更多任务——模仿 SASS 嵌套样式的功能,或者创建迷你循环来自动生成某些样式怎么样?如果你不熟悉这些,不要担心——让我们深入看看这些在实际操作中的意义。

我们样式表中的嵌套规则

使用处理器如 SASS 时,样式的嵌套是一个常见功能——如果我们有一堆具有非常相似选择器的样式,那么它就会产生一定程度的无用的重复。

我们可以继续这种重复,但更合理的选项是取选择器的核心部分,然后在那个块内嵌套后代——这个例子是从typography.css文件中取的:

我们的样式表中嵌套规则

想法是避免不得不写相同的重复父级样式——我们可以专注于子级!虽然代码看起来更长,但确实更容易阅读;我们可以将具有共同父级的样式组合在一起。

这个技术很容易掌握,但可能具有欺骗性的难以正确应用;如果你不熟悉它,那么我建议你看看我的两本关于 SASS 的书《SASS Essentials》和《SASS CSS How-to》,可在 www.packtpub.com/ 购买。

编译完成后,代码将显示为正常的 CSS。但有一点需要注意:抵制嵌套一切的诱惑;嵌套最好保留在你能看到代码使用量真正有差异的地方!

循环样式

我们还可以对我们的代码进行一项更改——看看我们在本章早期创建的 media.css 文件,特别是从第 158 行到末尾:

循环样式

我已经能听到接下来可能的问题了,这是什么意思,为什么我们在代码中使用看起来像编程循环的东西?在这个例子中,我们借鉴了 SASS 的一个原则,即创建循环;结合字符串插值,这使我们能够自动创建规则。

小贴士

字符串插值是在我们的代码中创建一个占位符,该占位符将在编译时用值替换。

当代码编译时,代码将呈现如下:

循环样式

这是一个更高级的概念,但确实值得花时间去掌握——如果应用得当,它可以在创建样式时节省大量时间!

让我们改变一下方向——我们已经介绍了一些帮助你入门的概念;还有一些想法我们可以在基础知识建立后,稍后再跟进。让我们花点时间更详细地考虑这些。

考虑未来的可能想法

在过去的几页中,我们介绍了一些可以使用 PostCSS 来帮助更好地管理我们的 WordPress 样式表的领域。重要的是要记住,并没有一成不变的规则来决定应该使用什么,但每个样式表都会有它自己的需求。

我们所讨论的选项只是触及了可能性的表面——对于那些熟悉 SASS 的你,你可能想知道为什么我们没有使用 SASS 混合,例如。简单的原因是,没有任何阻止我们这样做:一切都是关于提供在转换阶段快速且容易获胜的选项。

让我们暂停一下,考虑一些其他可以帮助我们开始更新样式表的思路:

  • 添加混合:这是一个显而易见的选择,但需要规划;这将涉及到创建可以在我们代码中随意重用的代码块。

  • 颜色回退:尽管这并不是我们迄今为止使用的 PreCSS 包的一部分,但颜色回退是另一个可以考虑的选项。核心 WordPress 主题使用标准的 HEX 表示法;我们可以将其更新为使用 RGB 等效值,并使用 PostCSS 插件添加 HEX 值。如果我们愿意,甚至可以使用如 postcss-rgba-hex 之类的插件将 RGBA 颜色转换为纯 HEX——你可能更喜欢使用前者,或者有需要使用该格式的流程。

  • 字体支持:我们是否可以整合 PostCSS 的字体魔法师插件?如果我们查看之前创建的variables文件,它将包含一些非标准字体(至少对 Windows 来说是这样);这意味着我们的 WordPress 主题看起来可能有点单调,至少可以说!幸运的是,我们可以使用字体魔法师插件为非标准字体提供 font-face 支持;大多数,如 Inconsolata、Open Sans 和 Merriweather,都可以在 Font Squirrel 网站上找到,网址为www.fontsquirrel.com

我们只挑选了三种方法来帮助扩展你的主题——使用像 Gulp 这样的任务运行器,我们实际上只受 Gulp 插件可用性的限制。我们不必仅限于 PostCSS 插件;这将限制我们能做什么,意味着我们错过了有用的功能。这里的关键是考虑你想要做出哪些更改,并计划如何以及何时进行这些更改——这个过程应该是迭代的,这将有助于管理这些更改!

编译和测试更改

在过去的几页中,我们介绍了一些非常适合编译 WordPress 主题的关键任务。

在一个理想的世界里,我们会尽可能地自动化,或者至少是那些有意义的任务——例如,移除手动执行时价值不大的任务。然而,关键是要充分考虑这些任务应该按照什么顺序执行;正确的顺序可能是得到一个有效样式表文件用于使用,和得到……好吧,只能称之为垃圾!是的,这听起来可能有些极端,但如果任务顺序不正确,那么你可能会得到破坏你主题的编译文件。

把这个放在一边,让我们来看看如何编译我们的代码——为了这个练习,我们将使用这本书附带的代码下载中可用的预编辑文件副本。

注意

在继续之前,你可能想保存你创建在项目区域根目录下的src文件夹中的源文件副本,以备安全之需。

为了让大家对我们将要讨论的内容有一个感性的认识,这是 Twenty Sixteen 主题实际应用的截图摘录:

编译和测试更改

让我们开始吧:

  1. 我们将首先下载并解压src文件夹的副本,该文件夹位于代码下载中的T68 - 转换 WordPress 主题下;将其保存到我们的项目区域根目录。首先确保这个文件夹中没有任何其他文件!

  2. 接下来,请确保我们之前下载的gulpfile.jspackage.json文件仍然存在——在编译过程中我们需要这两个文件。

  3. 请继续启动一个 Node.js 命令提示符会话,然后将工作文件夹更改为我们的项目区域根目录。

  4. 在提示符下,输入gulp然后按Enter键;Gulp 将运行每个任务,并在我们的项目区域根目录的dest文件夹中输出一个编译后的主题文件。

  5. 将此复制到Twenty Sixteen文件夹的根目录,该文件夹位于C:\wamp\www\wordpress\wp-content\themes\twentysixteen——如果你使用 Linux 或 OSX,请相应地更改路径。

现在我们已经完全编译了样式表!但也有一些需要注意的点——例如,我们在这部分前面提到的任务顺序与gulpfile.js文件中显示的顺序不匹配。现在让我们来讨论这些点:

  • 你可以包含的任务可以分为两组——我将其描述为核心任务,例如压缩文件或提供源映射;这些任务可以对任何网站执行。第二组是自定义任务——这些任务将针对每个网站特定,可能包括编译变量或嵌套代码的请求。如何规划这些任务的创建取决于你,这样你就可以在未来的项目中重复使用它们。

  • 关于一个 Gulp 任务文件应该包含什么,没有正确或错误答案;你想要包含的任务以及它们的调用顺序最终将由你的需求决定。在我们的例子中,我们使用了以下顺序,从上到下:

    任务名称 目的
    styles 这个任务编译了原始代码——将规则合并到一个文件中,通过 PreCSS 处理代码,并在需要的地方更新供应商前缀。
    pxrem 在设置好基本代码后,我们现在可以运行它,并为 rem 单位添加适当的像素回退。
    lint 在这个任务中,我们正在检查编译后的代码的一致性。
    rename 然后,我们将我们的基本编译文件重命名为具有.min.css扩展名;这是为了准备压缩我们的代码。
    sourcemap 在这个阶段,我们想要生成一个源映射,因此这个任务会启动以为我们创建一个合适的映射文件。
    minifyCSS 最后的任务是压缩 CSS 样式表文件——它已经具有正确的扩展名。

我们已经完成了艰苦的工作——现在是时候看到我们劳动成果的实际效果了。样式表现在位于我们 WordPress 安装的theme文件夹中——剩下的只是测试它!我们可以使用自动化测试工具,如 Quixote(可在www.quixote-css.com/找到),但这将超出本书的范围。相反,我们可以简单地启动我们的本地 WordPress 安装,查看它看起来如何——这是一种有用的方法来评估我们的主题是否有任何问题。

为了我们的演示,我们使用了 Twenty Sixteen 主题。这个主题默认情况下非常简洁。虽然这看起来可能不多,但我们已经完成了在 WordPress 中实现 PostCSS 工作流程所需的大部分艰苦工作。成功的关键衡量标准是基础主题应该与 WordPress 附带的原版完全相同。如果是这样,那么这意味着我们现在可以集中精力在一段时间内操纵我们的样式表,以进一步开发这个(或任何其他)主题中的样式。

摘要

使用 PostCSS 的成功将部分取决于你的代码从现有的处理器(如 SASS)转换得有多好——这需要规划和采用迭代的方法来转换代码。我们已经介绍了很多技巧和想法来帮助这个过程,所以让我们花点时间回顾一下我们已经学到的内容。

我们从探讨转换过程开始,并介绍了一些可能用到的插件来帮助这个过程。然后我们转向介绍 Pleeease 库,在快速演示中使用它之前,我们查看如何安装和配置它。

接下来,我们简要讨论了为什么 Pleeease 可能不像我们最初认为的那么有用;然后我们介绍了使用 PreCSS 库作为过渡到 PostCSS 的更好替代方案。

然后,我们深入探讨了使用 PreCSS,通过为 WordPress 的标准主题进行修改来工作——我们发现了我们可以在初始更改时使用的一些技巧和窍门来快速取得成效。然后,我们通过查看编译我们的代码,并在标准 WordPress 安装中检查它以确保它仍然按预期工作来结束这一章。

呼吸,我们已经覆盖了很多内容!通过仔细规划和采用迭代的方法,我们可以从使用处理器如 SASS 过渡到使用 PostCSS。然而,有时我们的代码可能不会按预期工作——我们的代码可能在几个地方出现问题,所以我们将在下一章中介绍它们。

第十三章. 解决 PostCSS 问题

对于许多读者来说,PostCSS 可能会有些令人困惑——毕竟,它仍然是一个相对较新的库,与其他处理器不同!在整个书中,我们一直在进行探索之旅——我们将以查看一些最佳实践的技巧和窍门以及一些故障排除的提示结束,以防我们发现自己陷入了困境。

在本章的整个过程中,我们将查看一些在创建 PostCSS 处理器时可能会遇到的一些常见问题。我们将看到解决它们是多么容易,并发现如果问题不像我们预期的那样容易解决,我们接下来应该做什么…

在本章中,我们将涵盖多个主题,包括以下内容:

  • 解决一些常见问题

  • 详细探索一些常见问题

  • 寻求他人的帮助

让我们开始吧!

解决一些常见问题

在一个理想的世界里,我们构建的任何处理器都将完美无瑕地工作,无论我们使用哪种任务运行器或方法来处理每个 PostCSS 任务。我们的目标是最终得到一个或多个成功编译的文件,如下例所示:

解决一些常见问题

但正如我们所知,并非所有事情都能 100%顺利;如果真的如此,我们可能要么非常幸运,要么事情开始变得平凡无奇!

作为实用主义者,我怀疑大多数人都会在掌握 PostCSS 的过程中遇到某种形式的问题;在本章的整个过程中,我们将探讨一些更常见的问题,如何解决它们,以及汇总一些我们可以使用的技巧和窍门,以帮助我们在使用 PostCSS 时生活更加轻松。让我们先详细看看我们可能会遇到的一些更常见的问题。

探索一些常见问题

PostCSS 作为一个处理系统,设置和使用都很简单,但在开发过程中偶尔我们可能会遇到一些问题。当然,这些问题会有所不同,但为了帮助您,我们可以探讨一些我们在开发过程中可能会遇到的一些更常见的问题。

为了本章的目的,我们将假设已经使用了 Gulp 任务运行器——你可能会在其他任务运行器(如 Grunt 或 Broccoli)上看到类似的问题。是时候更详细地探索这些问题了。

不兼容您的操作系统…

PostCSS 的美丽之处在于我们可以安装成百上千个插件之一——如果我们使用 Gulp 这样的任务运行器,那么我们可以轻松地将其扩展到大量可用的插件。

在大多数情况下,插件将无故障安装;你可能会偶尔看到这个出现:

不兼容您的操作系统…

对我们来说最重要的警告信息不是ENOENT消息,而是这个:

notsup: Not compatible with your operating system or architecture: fsevents@1.0.6

应该注意的是,这只是一个警告,而不是错误(在这种情况下)——这是由于使用了 fsevents,它仅适用于 MacOSX,在 Windows 或 Linux 环境中无法工作。在大多数情况下,这可以忽略,尽管明智的做法是测试您的处理器,以确保它没有对您的代码产生任何不利影响。

注意

这个错误不仅限于 fsevents——它可以应用于任何不支持的环境中的包。

任务 '' 不在您的 gulpfile 中

我们创建了一个包含多个任务的 Gulp 任务文件,并运行它来编译我们的样式表。结果,我们没有得到处理后的文件,而是得到了这条消息:

任务 '' 不在您的 gulpfile 中

这是由 Gulp 任务在 gulp 文件中不存在引起的——在这个声明示例中,我们正在调用 rename 任务:

任务 '' 不在您的 gulpfile 中

但查看 gulp 文件后,我们发现其中没有 rename 任务:

任务 '' 不在您的 gulpfile 中

这是一个简单的修复方法——只需重命名任务,使名称匹配,然后重试编译。注意,尽管如此——如果多个任务名称错误,则过程将失败,但只会显示第一个出错的任务名称。确保在文件中正确输入每个任务名称,以确保成功编译。

找不到模块 ''

如果任何错误可能会让我们措手不及,那么就是这一个——让我来解释:

当你开始掌握安装 PostCSS 插件时,你会看到许多使用命名约定 postcss-<name of plugin> 的插件。这似乎是一个合理的建议,但请注意——并非每个 PostCSS 插件都使用这种命名约定!

一个很好的例子是 Rucksack ——人们会预期使用 postcss-rucksack(是的,包括我!),但如果我们这样做,我们会得到这个错误:

找不到模块 ''

结果表明,Rucksack 是那些不使用许多人预期使用命名约定的例子之一。相反,它使用 rucksack-css,因为我们预期使用的名称已经被占用。

这就是那些值得阅读文档的实例之一,即使只是为了避免以后造成很多尴尬:

找不到模块 ''

清楚地说明了在安装插件时应该使用什么名称!是的,这种情况可能发生在我们所有人身上……

ReferenceError: '' 未定义

假设我们在 gulp 文件中有一系列任务,但出于某种未知原因,我们在编译代码时遇到了这个错误:

ReferenceError: '' 未定义

什么可能导致这种情况?嗯,至少有两个可能的原因:

  • 我们在任务本身中包含了插件,但忘记在文件顶部的声明中包含对其的引用

  • 我们确实在任务中包含了插件及其相关的声明,但不知何故没有使用相同的声明名称

修复方法是确保在 Gulp 任务文件顶部包含声明时,在文件稍后调用任务时使用相同的名称。

请提供 postcss 处理器数组!

下一个错误可能会让任何人感到困惑,但修复起来很容易——随着时间的推移,你将开发出自己的处理器;你可能会达到一个点,开始将一些 PostCSS 处理器任务从主 PostCSS 调用中移出,放入它们自己的任务中。

想象一下,你有一个类似于以下的 PostCSS 任务:

请提供 postcss 处理器数组!

有一种自然的倾向将这个任务拆分成单独的任务;毕竟,我赞扬了保持任务名称(即样式)与其在任务中所做事情之间 1:1 关系的优点!然而,如果你走得太远,认为不需要在任务中包含 PostCSS 处理器,那么你可能会遇到类似以下截图的错误:

请提供 postcss 处理器数组!

在你的 Gulp 任务文件中快速检查可能会显示类似以下内容:

请提供 postcss 处理器数组!

我们有一个 postcss 任务,但没有包含任何处理器!虽然重新工作处理器以确保我们保持 1:1 关系很有吸引力,但我们必须始终在 postcss() 任务中留下一个处理器,以确保其正确运行。

注意

你会听到关于术语处理器以几种不同的方式被提及——它们可以同样适用于我们 Gulp 任务文件的通用术语。它也可以适用于应该添加到任何 postcss() 任务中的处理器任务。

package.json 文件中未出现的条目

下一个错误稍微有点难以捕捉,但修复起来很容易——随着时间的推移,你可能会修改你的 Gulp 任务文件;这当然意味着需要从另一个 package.json 文件(如果已经安装)中添加新插件,或者如果尚未安装,则重新添加。

由于 Gulp 任务文件只是一个纯文本文件,我们可以在任何文本编辑器中编辑它——我个人的最爱是 Sublime Text 3 (www.sublimetext.com/3),但任何编辑器都足够用。记事本不是一个好选择,因为它无法正确处理行结束符!

除了这个之外,如果我们编辑 package.json 文件以删除一个条目,然后添加一个新的条目,我们可能会遇到以下警告:

package.json 文件中未出现的条目

这个小问题的原因真的很令人烦恼——真令人惊讶,一个单独的字符竟然能引起所有这些警告!罪魁祸首是行尾多余的逗号,在该行后面没有立即列出另一个插件,如截图中的第 27 行所示:

package.json 文件中未出现条目

如果我们去掉逗号并重新尝试安装,我们很快就会看到错误已经消失了。

编译结果的输出不符合预期

这个最后的错误可能会让人误解——严格来说,甚至都不是一个错误!想象一下,我们使用 Node 和 Gulp 创建了一个杀手级处理器应用程序;它包含了一些与我们之前创建的类似的任务,并且自那以后一直在本书中使用。

我们输入相关命令并按Enter。PostCSS 开始编译:到目前为止一切顺利。查看dest文件夹,你会发现这个——我们的压缩文件和源映射在哪里?

编译结果的输出不符合预期

但等等——错误在哪里?检查编译过程的输出显示没有错误,那么问题出在哪里呢?

编译结果的输出不符合预期

这个小异常是由于错误地计算了任务应该运行的顺序造成的——尽管截图清楚地显示了一个错误,但这并不是真正的错误。让我来解释一下。

成功编译过程(以及默认情况下,一个正常工作的处理器)的关键在于确保我们运行的每个任务都是按照正确的顺序触发的。为了生成这个错误,我在T45 – 转换为使用 Rucksack演示中的 Gulp 文件中移除了对重命名任务的约束,因此:

gulp.task('rename', function () {
  return gulp.src('dest/*.css')
    .pipe(postcss([ cssnano() ]))
    .pipe(rename('style.min.css'))
    .pipe(gulp.dest("dest/"));
});

任务看起来完全没问题,但引入了一个问题——现在我们有两个起始任务了!

这种情况的结果是,样式任务首先运行(因为它在默认任务中首先被调用,从第 36 行开始)。很快,重命名任务(没有约束)接着运行,然后是sourcemaplint-styles(遵循对每个任务设置的约束)。

我们最终在dest文件夹中只得到一个编译后的文件——重命名和样式任务同时运行;由于后者没有完成,前者无法产生任何内容!

让我们继续。如果所有其他方法都失败了,你发现了一些无法修复的问题,那么是时候寻求帮助了……这里为你作为一个 PostCSS 初学者提供的可用选项快速概述。

向他人寻求帮助

在这个阶段,你已经尝试修复了一个问题,但失败了——你并不确定下一步该怎么做…

别担心,有很多经历过这种情况并需要帮助的人!一个好的开始是查看主要文档,它可在github.com/postcss/postcss/tree/master/docs找到。如果这没有解决问题,那么谷歌是一个不错的选择;此外,还有一些其他可能有助于解决问题的选项。

在 Stack Overflow 记录问题

如果你花费时间在谷歌上搜索,但没有找到之前遇到过这个问题的人,那么你可以在 Stack Overflow 上记录一个问题:

在 Stack Overflow 记录问题

网址是 stackoverflow.com/questions/tagged/postcss—这列出了所有带有名称 postcss 标签的问题,任何人都可以提出行动方案或希望有帮助的技巧,让你重新开始工作。如果你确实遇到了问题,请尽可能提供你使用的系统详情(Windows、Mac 或 Linux),任何截图或错误详情,以及你采取的导致错误的步骤。

使用 PostCSS 查找错误

进一步来说,你也可以记录一个开发请求,或者需要更改代码的问题——为此,请浏览到 GitHub 网站上的问题日志,在 github.com/postcss/postcss/issues

使用 PostCSS 查找错误

有几点需要注意:

  • 如果你被建议在插件的网站上而不是在 PostCSS 的 GitHub 上记录问题,请不要感到惊讶——许多问题并不是由于 PostCSS 的问题,而是由于插件本身的问题。

  • 展示你的任务运行器的任务文件和 package.json 的副本可能会有所帮助——一些人们遇到的问题是由于(已知或未知)插件之间的兼容性问题,或者必须遵循特定顺序以确保插件可以使用。

  • PostCSS 插件开发者通常在 Apple Mac 或 Linux 平台上进行开发——这意味着你可能看到的某些错误是由于使用不支持插件中一个或多个命令的平台造成的。

  • 如果你使用一个或多个插件时遇到问题,尝试将处理器任务简化到包含引起问题的插件的那些。即使这不能消除问题,它也会帮助缩小原因,并在故障排除过程中避免其他人进行不必要的检查。

  • PostCSS 使用的许可证是 MIT 许可证——这意味着只要版权声明与库一起保留,你可以随意使用该软件。

  • 值得注意的是,CodePen 支持使用 PostCSS——你可能发现尝试在线代码(并自动编译)很有用,而不是手动运行处理器。CodePen 只支持有限范围的插件,所以它并不适用于所有场合——但希望对某些场合有用!

    小贴士

    更多详情,请查看 CodePen 网站上的这篇博客文章:blog.codepen.io/2015/07/14/postcss-now-supported-on-codepen/

  • 开源软件(以及为 PostCSS 创建的插件)的缺点是插件开发者提供的支持水平参差不齐——虽然对核心系统的支持非常好,但你可能会发现对个别插件的支持并不那么活跃!如果你遇到一个关键问题,不要指望插件开发者能迅速响应——请在 Stack Overflow 上先记录下来,然后再在插件网站上提出。如果问题被确认为需要进一步开发,后者应该被使用。

考虑到这些,祝你好运!PostCSS 正在迅速获得认可——一些知名公司正在使用它,例如 WordPress 和 Google,它肯定将成为非常流行的工具。如果你在使用插件时遇到问题,放弃它将是一件遗憾的事情!

记住,如果一个插件不起作用,那么四处看看——可能还有其他插件可以替代。这是一个“适者生存”的世界,那些不受支持的插件很可能会被淘汰,留下那些支持活跃、问题得到高效和及时解决的地方。

摘要

在学习新主题时,我们很可能会有疑问——在本章的整个过程中,我们查看了一些你可能遇到的常见错误,并列出了解决方案以帮助你重新回到正轨。然后我们探讨了如果你遇到无法轻易通过其他开发者的帮助解决的问题会发生什么。

好的,让我们继续前进:在这本书中,我们已经覆盖了很多内容,现在是时候展望未来,看看 CSS 可能会带来什么。PostCSS 的伟大之处在于,已经存在一些插件,允许我们今天使用明天的功能——我们将在下一章中介绍这一点以及更多内容。

第十四章. 为未来做准备

精通 CSS 是一项基本技能——这项技术不断在发展,因此为了成功,我们必须跟上变化的步伐。PostCSS 是一款强大的工具,它不仅允许我们使用今天的 CSS 规则,还可以处理未来的规则。在本章中,我们将探讨一些构成通常所说的 CSS4 的 CSS 语法,以及我们如何可以使用当前的 CSS3 类提供等效支持。

在本章中,我们将涵盖多个主题,包括以下内容:

  • 了解支持当前未来 CSS 标准的一些风险

  • 探索使用 cssnext 来提供对未来 CSS 语法的支持

  • 与一些现有的插件合作,将 CSS4 标准转换为当前的 CSS3 代码

  • 检查我们如何可以修改现有的插件以增加对新 CSS4 选择器的支持

让我们开始吧!

支持当前的 CSS4 样式

样式表已经存在了超过 35 年,我们今天所知道的 CSS 的原始版本可以追溯到 20 世纪 80 年代的 SGML 时代。

自从 1996 年最初 CSS 标准发布以来,我们已经走了很长的路——在过去的几年里,CSS4 的标准得到了发展,新增了诸如 :not:matches 伪类等新特性,以更好地定位元素,自定义属性(或变量),以及基于位置的链接,如 local-link。在接下来的几页中,我们将探讨这些 CSS4 特性,并看看我们如何可以使用当前的 CSS3 相当代码来引入对这些特性的支持。

首先,我们需要澄清一个小问题——CSS4…并不存在。什么?你可能会说。当然,它必须存在,我在网上看到过很多关于它的信息!是的,这是真的:CSS4 作为一项标准确实存在,但它并不是一个单一的独立实体。让我来解释。

之前的 CSS 版本都是围绕创建一个单一的全局标准来进行的,无论浏览器制造商如何决定支持构成标准的元素。这就是为什么我们不得不在多年里严重依赖供应商前缀,我们仍然在这样做,但大多数供应商已经从许多更常见的属性中移除了前缀,例如 border-radiusbox-shadow

然而,这里的关键区别在于,我们决定将 CSS4 作为一系列模块来交付——CSS 作为一项标准已经变得非常庞大,导致开发所需的时间增加变得不可持续。这就是为什么我们会听到有关 CSS 模块(如自定义属性或选择器)的讨论——这些可以作为独立的标准发展,到一定程度,我们可能不再将 CSS 称为版本 X,而只是 CSS。

注意

本章中提到的任何关于 CSS4 的内容纯粹是为了识别我们可以使用 PostCSS 插件和 CSS3 当前标准实现的下一代样式。

好的,抛开这一点,现在是时候深入研究了:PostCSS 对我们共同称之为 CSS4 的某些更常见元素提供了良好的支持。让我们来看看有哪些可用。

转换 CSS4 样式以使用

将 CSS4 基于一系列模块的想法是为了使其更容易(并且最终更快)更新每个标准;这确实意味着模块将处于不断变化的状态,至少在目前是这样!

话虽如此,我们可以使用当前的 CSS3 样式重新创建一些明确的样式——CSS4 选择器就是一个例子。尽管在撰写本文时它仍在草案阶段,但有一个名为 postcss-selector-not 的 PostCSS 插件可用(来自 github.com/postcss/postcss-selector-not)。另一个也作为 PostCSS 插件提供的类似样式是 postcss-selector-matches(可在 github.com/postcss/postcss-selector-matches 获取)——这两个插件旨在复制作为新 CSS4 标准一部分的 :not 否定和 :matches 伪选择器。

注意

要了解更多关于个别 CSS4 选择器的信息,请查看在 css4-selectors.com/selectors/ 提供的完整列表。这也会给你一个关于可用浏览器支持的指示——随着选择器的批准使用,此列表将随着变化而更新。

从更远的角度来看,有一些插件提供了对即将到来的 CSS4 标准的支持——除了 :matches:not 插件之外,我们还可以使用以下任何一种:

插件名称 插件用途
mq4-hover-shim 目前处于测试版,此插件为 Media Queries Level 4 hover 媒体功能提供有限支持——可在 github.com/twbs/mq4-hover-shim 获取。
host 如果你在使用 Shadow DOM,并且需要让 :host 选择器与伪类正常工作,那么这个插件就是为你准备的——源代码可在 github.com/vitkarpov/postcss-host 获取。
pseudo-class-any-link 你有多少次需要在代码中添加伪选择器,如 :link:visited?这个 PostCSS 插件解决了这个问题——我们现在可以在 CSS 中使用提议的 :any-link 伪类。详情请访问 github.com/jonathantneal/postcss-pseudo-class-any-link
postcss-initial PostCSS Initial 插件将指定属性的值重置为其在代码中最初设置的值(而不是浏览器设置的)。更多详情请访问 github.com/maximkoretskiy/postcss-initial
font-variant 此插件将 font-variant 设置转换为等效的 font-feature-settings 值——它用于特殊案例,当无法使用常规方式复制时,例如,用斜杠区分 0 和 O——前者等同于零。此插件的源代码可在 github.com/postcss/postcss-font-variant 获取。
postcss-input-range 此插件允许我们样式化输入范围元素。我们需要提供无前缀的 CSS 样式,该插件将自动处理所有必要的各种前缀,以允许在不同浏览器中对该元素进行样式化。此插件的源代码可以从 github.com/jonathantneal/postcss-input-range 下载。

好的,让我们继续:我感觉要来一个演示了!让我们看看如何使用 postcss-selector-matches 属性,看看我们如何在实际操作中使用它。

验证电子邮件地址

你有多少次遇到过来自你网站的电子邮件提交,你并不完全确定你的访客是否留下了一个有效的电子邮件地址?

如果我们不考虑确切数字,那么不言而喻,任何来自任何网站的请求都必须有一个有效的电子邮件地址;现在可用的顶级域名如此之多,它们的有效性就更加重要了!

为了帮助做到这一点,我们可以使用 :invalid:valid 属性来样式化 <input> 字段——虽然它们可能无法判断 .design 是否是一个有效的顶级域名(是的,它是),但它们至少可以处理确保你有一个顶级域名、一个 @ 符号和一个收件人名称的基本要求。

小贴士

你可以在浏览器中执行测试,以评估对这些和其他 CSS4 选择器的支持——查看 CSS4 选择器网站,在 css4-selectors.com/

让我们用一个简单的例子来演示如何使用 PostCSS 的 postcss-selectors-matches 插件,看看我们如何对这些字段进行样式化:

  1. 我们将像往常一样启动一个 Node.js 命令提示符会话,然后更改工作目录到我们的项目区域。

  2. 在提示符中,输入以下命令,然后按 Enter 键:

    npm install postcss-selector-matches --save-dev
    
    

    保持窗口打开,我们很快就会用到它。如果一切顺利,我们应该会看到以下内容出现:

    验证电子邮件地址

  3. 接下来,从本书附带的代码下载中提取 T69 - matches 伪选择器 文件夹的副本,并将其保存到项目区域的根目录。

  4. T69 - matches 伪选择器 文件夹中的 css—completed 版本文件夹复制 matches – pre-compile.css,并将其保存到项目区域的根目录下的 src 文件夹中。

  5. T69 - matches 伪选择器 文件夹中的 gulpfile.jspackage.json 文件复制到项目区域的根目录,并将其重命名为 matches.css

  6. 返回到之前使用的 Node.js 命令提示符会话,然后在提示符中输入 gulp 并按 Enter 键。

  7. 如果一切顺利,PostCSS 将消失并编译我们的代码为有效的 CSS——我们应该在 dest 文件夹中看到现在熟悉的文件。

  8. 将此 dest 文件夹的内容复制到 T69 - matches 伪选择器 下面的 css 文件夹中,然后在浏览器中尝试预览 matches.html 文件。如果一切顺利,我们应该看到类似这样的内容:验证电子邮件地址

这是一个简单的演示,是的,有些人为设计。在这个层面上,使用 :matches 并不是必要的,因为它最终生成的代码比必要的还要多!但它确实展示了应用这项技术是多么容易,并且能够提供有效的 CSS,就像我们例子中的那样:

验证电子邮件地址

好的,让我们改变一下方向:在下一个演示中,我们将继续使用范围主题,但这次我们将看看一个更时尚的例子,我们可以真正地大显身手,改变我们选择元素的外观。

范围输入元素是传统上难以样式的元素,很多时候,我们可能不得不求助于 jQuery UI 来改变其外观!但在 CSS4 中不是这样——我们可以使用一系列新的 CSS 属性来应用样式,而不需要使用任何额外的库。让我们更详细地看看这一点。

支持新的范围输入

一个快速的问题,你有多少次需要创建一个网站,其中需要选择一个值,比如说从 1 到 100?或者选择特定颜色的不透明度,从几乎透明到完全不透明?

好的,也许这些问题问得有些奇怪,但细心的读者应该能注意到我当然是在指使用滑块,我们可以调整它们来选择特定的值。有几种方法可以将这些添加到页面中,但主要还是需要一些帮助来设置样式,最容易被识别的工具可能是 jQuery UI!

如果我们需要在页面中使用 jQuery UI 来提供其他功能,这当然是可以的,但如果我们只需要它来使用滑块呢?这有点大材小用——幸运的是,我们可以通过使用 postcss-input-range 插件来解决这个问题,该插件可以从 github.com/jonathantneal/postcss-input-range 获取。现在是时候进行演示了,让我们快速举一个例子,看看如何将一个滑块样式化以表示进度条:

  1. 我们将首先安装 postcss-input-range 插件,为此,请启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。

  2. 在提示符下,输入以下命令,然后按 Enter

    npm install postcss-input-range --save-dev
    
    

    如果一切顺利,我们应该看到类似这样的截图:

    支持新的范围输入

  3. 接下来,从本书附带的代码下载中提取 T70 - 使用范围输入 文件夹的副本。然后将其保存到我们的项目区域中。

  4. T70 - 使用范围输入css 文件夹中,将 range – pre-compile.css 文件复制到我们的项目区域的 src 文件夹中,并将其重命名为 range.css

  5. T70 - 使用范围输入 文件夹中的 gulpfile.jspackage.json 文件复制到我们项目区域的根目录。

  6. 返回 Node.js 命令提示符窗口,然后在提示符下输入 gulp 并按 Enter 键。

  7. 如果一切顺利,Gulp 将运行我们的编译过程,现在熟悉的样式表文件应该会出现在 dest 文件夹中,同时 maps 子文件夹中也会有一个映射文件。将这些文件复制到 T70 - 使用范围输入 下的 css 文件夹中。

  8. 如果一切顺利,我们在浏览器中预览结果时应看到类似下面的截图:支持新的范围输入

尽管我们只是触及了为范围元素添加样式的可能性的表面,但这个演示并没有什么复杂的地方——例如,我们可以添加 datalists 来定义可以滚动浏览的特定值,比如一天中的特定时间。

浏览器支持仍然有限,但为了了解可能实现的功能,请查看 demo.agektmr.com/datalist/;CSS 大师 Chris Coyier 在他的网站上也有几篇关于范围输入使用的文章,网址为 www.css-tricks.com

我们演示中的真正魔法出现在我们查看编译后的代码时——为范围输入元素添加样式的关键元素之一是 range-thumb 属性,这是我们用来在滑块上选择值的旋钮。以下是我们的代码现在看起来像样的两个示例,增加了对 Firefox 的支持:

支持新的范围输入

这支持了 Internet Explorer(已被 Edge 取代):

支持新的范围输入

当使用此插件时,我们不必担心添加供应商前缀——我们的原始代码只包含未加前缀的版本;相关的供应商前缀将在编译时添加,直到它们不再需要用于样式化我们的输入元素。

小贴士

如果你需要帮助为新范围输入元素添加样式,请查看 danielstern.ca/range.css/——这是一个有用的工具!

在过去的两个演示过程中,我们简要地触及了使用 PostCSS 时可能实现的功能——我们使用了相同的 Gulp 任务文件格式来集成每个插件的支持,运行后为每个演示生成了必要的 CSS 样式表。这里并没有什么过分的,对吧?毕竟,它遵循了我们之前提到的相同原则,比如一个插件对应一个任务……或者我们能做得更好吗?

是的,到现在你应该知道,如果可能的话,我总是喜欢做得更好。记得我曾经说过,创建任务时最好是一对一的吗?我们(Gulp)文件中的每个任务是否都与特定的插件相关?嗯,正如有人曾经说过,规则是为了被打破的——是时候扔掉规则手册,考虑一种不同的方法了,至少对于未来的语法来说是这样……

使用 cssnext 支持未来语法

支持 CSS4 的一个关键部分是我们必须处理的不断变化的状态,直到模块被标准化。如果我们保持当前的方法,它将涉及不断更新我们决定使用的任何与 CSS4 属性相关的插件——这显然是不可持续的!

相反,我们可以使用单个插件包,在这种情况下是cssnext,来管理对一系列新特性的支持。然而,美中不足的是,所有特性默认都是启用的,并且只有在代码需要时才会激活。当然,将来会有新特性被原生支持的时候,到那时,我们只需简单地丢弃编译过程,而不会影响最终结果。

花时间了解cssnext是值得的——让我们深入探讨并更详细地查看。

使用 cssnext 创建一个站点模板

cssnext插件是我们指南中每个任务一个插件的例外之一;我们通过插件名称调用它,但实际上,它将同时执行多个转换。

该插件可在cssnext.io/获取。值得注意的是,存在一个较旧的版本;我们在本演示中使用的是较新版本。cssnext插件最初是一个完整的系统,在 PostCSS 变得像现在这样流行之前。

该插件包含了一些实际上并不属于专注于 CSS 未来的插件的选项,因此开发者重新编写了它,使cssnext更简单。同时,它被设计成可以集成到 PostCSS 中,在那里我们可以在处理器中的其他插件的同时使用它。

注意

该插件甚至有自己的游乐场,我们可以用它来测试更改是否会产生预期的效果——在cssnext.io/playground/查看。

让我们更详细地探索这个插件——我们将从安装它开始,然后再为我们的下一个演示设置代码。

设置我们的演示

在接下来的演示中,我们将设置一个基本的模板,它可以用于网站——它不会在风格上赢得任何奖项,但这里的目的是探索进行更改有多容易,而不是在下一个颁奖典礼上成为焦点!让我们深入探讨我们需要做什么:

  1. 我们将首先启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们项目区域的根目录。

  2. 在提示符下,输入以下命令,然后按Enter键:

    npm install postcss-cssnext --save-dev
    
    

    如果一切顺利,我们应该会看到类似下面的截图:

    设置我们的演示

  3. 接下来,请从本书附带的代码下载中提取T71 - working with cssnext文件夹的副本——将其保存到我们项目区域的根目录。

  4. T71 - working with cssnext文件夹中,将styles.css文件的副本保存到我们项目区域根目录下的src文件夹,然后将package.jsongulpfile.js保存到我们项目区域的根目录。

  5. 返回我们打开的提示符,然后输入gulp并按Enter键。

  6. 当 Gulp 完成编译后,我们将看到现在熟悉的文件出现在我们项目区域根目录下的dest文件夹中。

  7. 将我们项目区域根目录下dest文件夹的内容复制到T71 - working with cssnext文件夹根目录下的css文件夹中。

  8. 在浏览器中预览T71 - working with cssnext文件夹内的sitepage.html——如果一切顺利,我们应该看到类似于这个截图的内容:设置我们的演示

我们现在看到了我们网站的模板——有几个地方我们进行了修改,以利用cssnext的强大功能。为此,请打开我们保存到项目区域的T71 - working with cssnext文件夹中的css文件夹内的stylescss文件的副本——让我们看看都做了哪些修改:

  • Autoprefixer:该插件涵盖了为任何未来样式提供供应商前缀的支持;在基础文件中的任何前缀都已移除,将在编译期间添加。

  • 媒体查询范围/自定义查询:我们不再使用现有的格式,该格式并不总是清晰,我们可以开始使用>=<=来更好地表达我们查询中应用的范围。同时,我们可以使用自定义查询在文件顶部定义预设范围,这些范围可以在整个样式表中使用。

  • 嵌套:熟悉现有预处理器(如 SASS 或 Less)的粉丝会对嵌套原则很熟悉;简单来说,这允许我们将样式嵌套在彼此内部。然后我们可以调整这些规则所使用的选择器,以便在编译时,每个规则都转换为有效的 CSS。

  • 自定义选择器:继续讨论预处理器主题,我们可以在代码顶部创建自定义值,并适当地应用它们。

  • #rrggbbaa 颜色:传统上,十六进制颜色以三位或六位数字值表示;使用cssnext,我们可以将它们扩展到四位或八位数字值。cssnext插件将它们转换为标准的 RGBA 值,并提供等效的 HEX 值作为后备机制。

  • rem 单位:传统上,开发者使用像素值来应用元素或字体的大小。像素值不尊重浏览器中的用户设置,因此可能不会按预期缩放。为了解决这个问题,引入了em单位;随着rem单位的引入,计算em值的数学变得简单。今天,一些开发者认为像素值应该占主导地位;cssnext插件提供了像素和 rem 单位,可以在浏览器支持的地方使用。

初步思考,你可能认为需要包含多个插件或详细的配置对象;并非如此!相反,我们只需要在我们的 Gulp 任务文件中包含这个:

设置我们的演示

我一直热衷于保持事情简单——cssnext 插件就是完美的例子!虽然我们可能需要定期更新插件以跟上变化,但我们不需要更改我们的 Gulp 文件。

插件将简单地转换它找到的受插件支持的样式,并保留任何未受插件覆盖的样式。这种美在于我们可以让它按原样运行,或者如果我们想禁用不再需要的功能,我们只需在配置对象中禁用它:

cssnext(input, { 
  features: { customProperties: false }
})

为了证明我们做出的更改是有效的,我们将我们的(非响应式)模板从这样:

设置我们的演示

…到这个视图,我们的内容显然更适合较小的屏幕:

设置我们的演示

尽管这只是所做的更改的一小部分,但它直接负责使我们的模板响应式,这也表明将 cssnext 纳入我们的流程比我们想象的要容易得多!

好吧,让我们继续:我们将探讨几个额外的插件,但会有一些变化。我们首先将查看使用一个插件来支持 CSS4 中引入的新颜色,然后再用它作为修复一个本应很久以前就应解决的问题的基础!

创建插件以提供额外的 CSS4 支持

在本章的整个过程中,我们介绍了许多处理即将推出的 CSS4 标准支持的插件,并探讨了如何使用 cssnext 插件包来管理过渡到使用这些新样式。

我们可以做更多的事情——PostCSS 的好处在于我们可以绝对编写自己的插件来帮助增强对 CSS4 属性的支持。为了证明这一点,在接下来的几页中,我们将解决一些 CSS 中的问题,并修改现有的 CSS4 颜色插件以添加对另一种颜色的支持;如果允许我打趣一下,我们将从给下一个演示添加一点颜色开始!

向 CSS 添加未来颜色支持

“她活到了六岁。在将近十二个小时里,她六岁了……”

下一个演示有一个相当感人的转折——这个 rebeccapurple 颜色,是下一个练习的基础,它是为了纪念埃里克·迈耶的女儿丽贝卡,她在 2014 年 6 月六岁生日那天因癌症去世。

任何花时间开发 CSS 样式的人都知道,埃里克一直是网络标准的坚定倡导者,特别是 CSS——经常有人说,在他们的开发过程中,很难找到一本不是埃里克写的关于 CSS 的书!社区提议添加 rebeccapurple 作为一种颜色以纪念丽贝卡(这是她最喜欢的颜色),并为了表彰埃里克的贡献。CSS 工作组宣布这一变更已被批准,并将作为即将推出的 CSS4 标准的一部分添加。

为了帮助支持尚未跟上变化的浏览器,开发者 Maxime Thirouin 创建了一个 PostCSS 插件,将 rebeccapurple 颜色转换为更兼容的格式 rgb(102, 51, 153)——该插件的源代码可在 github.com/postcss/postcss-color-rebeccapurple 获取。

让我们更详细地看看这个插件:

  1. 我们将首先安装 postcss-color-rebeccapurple 插件,所以请打开 Node.js 命令提示符,并将工作文件夹更改为我们的项目根目录。

  2. 在提示符中,输入以下命令:

    npm install postcss-color-rebeccapurple –save-dev 
    
    

    然后按 Enter 键——如果一切顺利,我们应该能看到类似下面的截图:

    添加 CSS 的未来颜色支持

    保持窗口打开——我们很快就会用到它。

  3. 接下来,从本书附带的代码下载中提取 T72 - adding rebeccapurple color 文件夹的副本,并将其保存到我们项目区域的根目录。

  4. T72 - adding rebeccapurple color 文件夹中的 styles – pre-compile.css 复制到我们项目区域的根 src 文件夹中,并将其重命名为 styles.css

  5. T72 - adding rebeccapurple color 文件夹中的 gulpfile.jspackage.json 文件复制到我们项目区域的根目录。

  6. 返回 Node.js 命令提示符会话,然后在提示符中输入 gulp 并按 Enter 键。

  7. 编译完成后,将项目区域根目录下的 dest 文件夹的内容复制到 T72 - adding rebeccapurple color 下的 css 文件夹。

  8. 尝试预览结果——如果一切顺利,我们应该能看到这个简单的盒子,它已经被填充了颜色 rebeccapurple添加 CSS 的未来颜色支持

为了证明它的工作,查看 T72 - adding rebeccapurple color 下的 css 文件夹中的 .css 文件——我们应该能看到其中的编译 RGB 值。

添加 CSS 的未来颜色支持

我们也可以通过使用像 ColorHexa.com 这样的网站进行快速检查来验证这一点——查看 www.colorhexa.com/663399;搜索 rebecca purple 将显示相同的页面。

这里有一个完美的机会——我相信,从悲剧中总会产生一些好的东西。抛开这个插件的 raison d'être,我们可以将其作为添加 CSS4 颜色模块标准中其他颜色支持的基础。

我们需要做出的更改相对简单,可以轻松地成为新插件的基础。让我们看看涉及的内容;对于这个演示,我们将使用 burlywood,这是一种浅棕色:

  1. 我们将首先编辑我们的 CSS 文件——打开项目区域根目录下的 src 文件夹中的 styles.css,并按指示更改高亮行:

          #box {
            background-color: burlywood;
            height: 10rem;
    
  2. 接下来,我们需要更新rebeccapurple插件文件,将现有的颜色引用更改为使用新颜色——我们将从初始声明开始:

          var postcss = require("postcss")
          var color = require("color")("burlywood").rgbString()
    

    注意

    index.js插件文件可以在node_modules\postcss-color-rebeccapurple\文件夹中找到。

  3. 我们接下来需要更改代码中更改颜色实例的检查:

          if (value && value.indexOf("burlywood") !== -1) {
            decl.value = value.replace(/(burlywood)\b/gi, color)
          }
        })
    
  4. 目前,请继续保存文件——是的,插件名称并不代表颜色,但这只是一个测试,所以这不会有什么影响。

  5. 启动 Node.js 命令提示符,然后更改工作文件夹到我们的项目区域。在提示符中输入gulp然后按Enter

  6. 将根目录dest文件夹的内容复制到“T73 - 添加对新颜色支持”文件夹中的css文件夹。

  7. 运行testpage.html——如果一切顺利,我们可以在其中看到之前编译的 RGB 值:添加对 CSS 未来颜色的支持

  8. 尝试从“T73 - 添加对新颜色支持”文件夹中重新运行testpage.html文件;我们应该看到颜色的变化(而且,它绝对不是紫色!)添加对 CSS 未来颜色的支持

我们可以使用与之前相同的原则来验证显示的颜色确实是burleywood。这次查看www.colorhexa.com/deb887,它清楚地显示了 HEX 和 RGB 值:

添加对 CSS 未来颜色的支持

现在剩下要做的就是将其转换为插件。这应该很容易做到:尝试复制现有的rebeccapurple插件,然后使用您选择的颜色进行搜索和替换。它并不完美,但会给你一个良好的开端——下一部分将是使用我们在第八章中学到的知识,将其转变为一个可在 NPM 存储库中使用的完整插件。

小贴士

要获取 CSS4 模块颜色的列表,请在 Google 上查看——有很多链接;从这里开始尝试:github.com/segundofdez/css4-colors/blob/master/less/colors.less

好吧,是时候改变焦点了:尽管本章旨在展望未来,但我们将改变方向,暂时回顾一下。

原因是什么?CSS 设计中的许多错误已被 CSS 工作组认可。乔纳森·尼尔(Jonathan Neal)的一个 PostCSS 插件为这些问题的短期修复提供了解决方案——其中一些问题可能在 CSS 的将来版本中得到正确修复!

回顾过去

在开发代码时,我非常喜欢只针对现代浏览器进行开发,作为一条规则——理想情况下是n-1,即当前版本加上上一个版本。在主要方面,这对 Firefox 或 Chrome 来说不是问题,但对于 IE 来说则不同。在可能的情况下,旧版本的 IE 可能会被遗弃……但这又是另一个故事了!

我们可以做出的一个例外不是关于浏览器支持,而是尝试纠正一些属性命名的问题。CSS 工作组已经承认,在发布时一些属性名称定义不正确——您可以在wiki.csswg.org/ideas/mistakes上看到完整的列表,该列表会定期更新。

为了帮助解决这个问题,并且作为本节第二个演示的先导,我们将安装 PostCSS 的 Time Machine 插件(因此本节的标题是“Time Machine!”)。这为 CSS 中的一些错误提供了一种短期解决方案。然后我们将以此为契机,设计一个快速且实用的插件来修复 CSSWG 网站上列出的另一个错误,但这个错误并没有被 Time Machine 插件修复。

注意

此插件的源代码可在github.com/jonathantneal/postcss-time-machine获取。

对于这个演示,我们将重用为显示rebeccapurple颜色而创建的简单演示。该演示的编译结果使用 RGBA 函数——CSSWG 已经声明 RGBA 不应该存在,而 alpha 通道应该作为RGB()函数的第四通道添加。我们将作为演示的一部分修复这个问题——插件将允许我们编写预期的代码,同时将其编译成任何浏览器都能理解的代码。

让我们开始安装用于演示的插件:

  1. 我们将首先安装插件,所以请启动一个 Node.js 命令提示符会话,然后更改工作文件夹到我们的项目区域。

  2. 在提示符下输入以下命令并按Enter键:

    npm install postcss-time-machine --save-dev
    
    

    如果一切顺利,我们应该看到类似于这个截图的内容——保持会话开启,因为我们很快就会需要它:

    回到过去

  3. 接下来,从本书附带的代码下载中提取T74 - going back in time文件夹的副本——将文件夹保存到我们项目区域的根目录。

  4. T74 - going back in time文件夹内的styles – pre-compile.css复制到我们项目区域的根src文件夹,并将其重命名为styles.css

  5. gulpfile.jspackage.json文件从同一T74 - going back in time文件夹复制到我们项目区域的根文件夹。

  6. 返回 Node.js 命令提示符,然后在提示符下输入gulp并按Enter键。

  7. 如果一切顺利,我们应该在dest文件夹中看到我们通常的编译文件——将这些文件复制到T74 - going back in time下的css文件夹。回到过去

到目前为止,尝试预览我们演示的结果,如果一切顺利,我们应该看到之前相同的盒子,但这次颜色稍微浅一点的rebecca purple

如果我们查看 CSS 样式,细心的读者可能会注意到——为什么我们使用 194 作为我们的 alpha 值,而代码中显示的是 0.8?答案是简单的:这个插件被设计为使用 0255 的每个值,包括 alpha 通道。只需将 194 除以 255

回到过去

答案是 0.8。好吧,你实际上会得到类似 0.76078 的东西,但如果我们四舍五入到一位小数,这将变成 0.8

回到过去

现在,在我们继续之前,我们应该考虑一些事情:这个插件的实际应用。这并不是要浇灭一个美好的想法,但采用可能需要一点时间——团队中的开发者已经习惯了编写包含 rgba()background-blend-modehsla() 等函数的样式,因此改变思维模式可能需要一些时间!

话虽如此,如果我们想遵守预期的标准,并且能够管理改变思维模式,不输入我们可能已经做了很长时间的函数名,这是一个有用的修复。我们甚至可以更进一步,创建我们自己的插件——时间机器并没有为所有属性提供修复,所以让我们看看如何创建一个来管理围绕 CSS 中 background-blend-mode 属性的 bug。

创建我们自己的插件

时间机器插件没有固定的一个属性是 background-blend-mode 函数——这个函数通常用于计算当图层重叠时最终的像素颜色。

引入的 bug 与名称有关——CSS 工作组CSSWG)已经承认所有混合模式变体应该在不包含 -mode 的名称中书写,因此在我们的情况下,background-blend-mode 应该写成 background-blend

这是一个简单的修复,这是一个完美的机会让你尝试创建你自己的插件!现在我们将采取不同的路线——是时候一些观众参与,换句话说!

为了帮助你,值得再次查看第八章,创建 PostCSS 插件;在创建插件时,我们可以使用以下代码:

var postcss = require('postcss');

module.exports = postcss.plugin('backgroundblend', function backgroundblend(options) {
  return function (css) {
    options = options || {};

    // Processing code will be added here
    css.eachDecl('background-blend', function (decl) {
      if (decl.prop.indexOf('background-blend') !== -1) {
        decl.prop  = 'background-blend-mode';
      }
    });
  };
});

这段代码的大部分是样板代码,但使它工作关键的是以下摘录:

    css.eachDecl('background-blend', function (decl) {
      if (decl.prop.indexOf('background-blend') !== -1) {
        decl.prop = 'background-blend-mode';
      }
    });

简而言之,我们依次解析每个 CSS 规则——如果其中包含 background-blend,我们就简单地将其每个实例替换为 background-blend-mode

为了证明它的工作,我们可以使用以下代码来测试我们的插件是否工作:

<!DOCType html>
<head>
  <meta charset="utf-8">
  <title>Demo: Creating new plugin to change blend-mode</title>
  <link rel="stylesheet" type="text/css" href="css/styles.css">
</head>
<body>
  <div id="div"></div>
</body>
</html>

如果一切顺利,我们应该得到类似以下截图的结果:

创建我们自己的插件

如果你遇到了困难,那么这本书附带的代码下载中有一个完整的版本;快速查看 T75 - back in time change 文件夹中的 styles – pre-compile.css 文件将显示这一点:

创建我们自己的插件

当编译时,我们可以清楚地看到它显示了我们将习惯看到的版本:

创建我们自己的插件

这是一个快速且简单的插件,在投入生产使用之前可能需要更多的开发——例如,它仅针对 background-blend-mode,而我们应该支持任何名称中包含 blend-mode 的属性!

摘要

创建 CSS 样式打开了一个充满可能性的世界:我们受限于我们必须支持的内容!在本章的整个过程中,我们探索了一些在使用较新的 CSS4 样式时我们可以使用的可能选项——让我们花点时间回顾一下我们已经学到的内容。

我们从对 CSS4 的快速介绍开始——然后转向探索一些插件,这些插件通过提供更兼容的 CSS 等效代码来支持更新的样式。

接下来是一个简单的演示,探索了一些新的 CSS4 伪类,以及我们如何在(理论上的)环境中使用它们。然后我们转向更实际的内容,仅使用 CSS 来样式化新的范围输入。

快速前进,我们接下来查看 cssnext 插件包,它为支持 CSS4 提供了一个抽象层;我们看到了实现它是多么容易,并且我们可以轻松地关闭在项目中不再需要的功能。

我们接下来的演示是关于处理颜色——我们使用了 rebeccapurple 插件,首先展示了在 CSS4 中添加对新颜色支持是多么容易,然后修改它以提供在我们项目中需要支持的其他颜色的支持。然后我们通过回顾 CSS 发布时的一些错误来结束本章,以及(通过一点想象力),我们可能提供临时的解决方案,直到这些错误在未来版本的 CSS 中得到修复。

我们现在已经到达了本书的结尾——我希望你喜欢我们穿越 PostCSS 世界的旅程,并且希望这本书能帮助你作为一个未来的开发者或 PostCSS 的用户提高技能。

posted @ 2025-10-26 09:00  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报