使用-CSS3-设计下一代-Web-项目-全-

使用 CSS3 设计下一代 Web 项目(全)

原文:zh.annas-archive.org/md5/F3C9A89111033834E71A833FAB58B7E3

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

你会感到惊讶,但 CSS3 并不存在。实际上,这个术语用来将许多不同的规范(请参见www.w3.org/Style/CSS/current-work上的列表)分组,每个规范都有自己的工作团队和完成状态。有些仍然是工作草案,而其他一些已经是候选推荐。

本书试图向您展示今天可以使用这项技术做些什么。它分为 10 个项目,每个项目都严重依赖一些新的 CSS 功能,如背景渐变、弹性盒布局或 CSS 滤镜。

所有项目都经过开发和测试,可以在最新的 Chrome 和 Firefox 浏览器上运行良好。其中绝大多数即使在 Internet Explorer 10 上也能良好地呈现和表现。

在可能的情况下,提供了使事情即使在旧浏览器上也能正常工作的解决方法。通过这种方式,引入了不同的技术和工具,如使用 Modernizr 进行特性检测,优雅降级,通过条件注释触发回退属性,以及一堆高质量的 polyfill 库。

本书还专注于不同类型的工具,旨在帮助我们开发相当复杂的 CSS 文档。我说的是 Sass 和 Compass,它们为我们提供了一种更好地组织项目的新语法,以及一堆我们将在本书后面看到的有用函数。

处理供应商实验性前缀很烦人。在本书中,我们将发现如何使用一些库来为我们完成这项任务,无论是客户端还是服务器端。

嗯,这里没有更多要说的了,我希望你会发现这些项目至少和我一样有趣和有趣,从中你会学到新的技术、属性和工具,帮助你在日常工作中。

本书涵盖的内容

第一章,没有注册?没有派对!,将向您展示如何为即将到来的派对创建一个订阅表单。我们利用这一章来发现 CSS3 功能,如一些新的伪选择器,如何通过为必填字段或有效/无效字段添加特定样式来增强表单。

第二章,闪亮按钮,将向您展示如何通过使用圆角、多重背景、渐变和阴影等技术来创建一些 CSS3 增强的按钮。然后我们使用经典的:hover伪选择器和 CSS3 过渡来对其进行动画处理。

第三章,全能菜单,专注于开发一个根据我们用来查看它的设备而表现不同的菜单。我们使用媒体查询和一个很好的特性检测库来实现这一目标。

第四章,缩放用户界面,使用 CSS3 过渡混合 SVG 图形和新的:target伪选择器来创建一个完全功能的缩放用户界面,显示一个很酷的信息图表。

第五章,图像库,将向您展示如何使用纯 CSS3 图像幻灯片显示不同的过渡效果,如淡入淡出、滑动和 3D 旋转,以及多种导航模式。使用新的:checked伪选择器可以实现在不同效果之间切换。本章还介绍了 Sass,这是 CSS3 的扩展,我们可以使用它来编写更干净、更可读、更小的 CSS 文件。

第六章,视差滚动,专注于在页面滚动时触发真正的视差效果。这是通过使用 3D 变换属性,如transform-styleperspective来实现的。

第七章, 视频杀死了电台之星,通过 CSS3 实验了一些酷炫的视频效果,包括静态和动画遮罩、模糊、黑白等。本章还涉及一些有趣的向后和跨浏览器兼容性问题。

第八章, 转动表盘,展示了如何通过创建一个可以作为网页小部件使用的动画表盘来充分利用新的 CSS3 属性。该项目还介绍了 Compass:一个 Sass 插件,负责处理实验性前缀、重置样式表等。

第九章, 创建介绍,通过创建一个使用摄像机在 3D 场景中移动的 3D 动画,将 CSS3 动画提升到另一个水平。

第十章, CSS 图表,将向您展示如何使用 CSS3 创建条形图和饼图,而无需使用除 CSS 和 HTML 之外的任何东西。通过正确的 polyfills,我们甚至可以使这些图表在旧版浏览器上表现良好。

本书所需内容

要开发本书提供的项目,您需要一个文本编辑器(例如 Sublime Text 2、Notepad++等)和一个 Web 服务器来运行代码。如果您从未安装过 Web 服务器,您可能希望使用预打包的解决方案,如 MAMP for Mac (www.mamp.info/en/mamp/index.html)或 WampServer for Windows (www.wampserver.com/)。这些软件包还会安装 PHP 和 MySQL,这些对于运行本书的项目并不需要,因此您可以简单地忽略它们。

一旦您下载、安装并启动了 Web 服务器,您就可以在 Web 服务器的文档根目录中创建项目。

本书适合对象

本书专为前端网页开发人员设计。它需要对 CSS 语法和最常见的 CSS2 属性和选择器有扎实的了解。

约定

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

文本中的代码单词显示如下:"我们使用:after伪选择器来访问具有label类的元素后面的位置。"

代码块设置如下:

html{
  height: 100%;
  background: black;
  background-image: url('../img/background.jpg');
  background-repeat: no-repeat;
  background-size: cover;
  background-position: top left;
  font-family: sans-serif;
  color: #051a00;
}

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

#old_panel{
 background: rgb(150,130,90);
  padding: 9px 0px 20px 0px;
}

任何命令行输入或输出都以以下方式编写:

sass scss/application.scss:css/application.css

新术语重要单词以粗体显示。例如,屏幕上看到的单词,如菜单或对话框中的单词,会在文本中显示为这样:"让我们标记border-radiusbox-shadowCSS Gradientsmultiple backgrounds。"

注意

警告或重要提示会以这样的方式出现在一个框中。

提示

提示和技巧会以这种方式出现。

第一章:不注册?不派对!

CSS3 对于表单来说是一个重大的飞跃。不仅有新的样式可能性可用,而且还可以使用新的强大的伪选择器来修改我们页面的外观,这取决于表单或其字段的状态。在本章中,我们将使用一个派对注册表单作为测试案例,展示如何通过新的 CSS 规范来增强这个组件。我们还将注意如何保持旧浏览器的正确行为。我们将涵盖以下主题:

  • HTML 结构

  • 表单

  • 基本样式

  • 标记必填字段

  • 选中的单选按钮技巧

  • 计算无效字段

  • 气球样式

HTML 结构

让我们从一些 HTML5 代码开始,来塑造我们项目网页的结构。为此,在一个名为no_signup_no_party的新文件夹中创建一个名为index.html的文件,其中包含以下标记:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title>No signup? No party!</title>
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.7.3/build/cssreset/cssreset-
min.css">
  <link rel='stylesheet' type='text/css' 
href='http://fonts.googleapis.com/css?family=Port+Lligat+Sans'>
  <link rel='stylesheet' type='text/css' 
href='css/application.css'>
  <script 
src="img/html5.js">
</script>
</head>
<body>
  <article>
    <header>
      <h1>No signup? No party!</h1>
      <p>
        Would you like to join the most amazing party of the 
planet? Fill out this form with your info but.. hurry up! only a 
few tickets are still available!
      </p>
    </header>
    <form name="subscription">
      <!-- FORM FIELDS -->
      <input type="submit" value="Yep! Count me in!">
    </form>
    <footer>
      Party will be held at Nottingham Arena next sunday, for info 
call 555-192-132 or drop us a line at info@nottinghamparties.fun
    </footer>
  </article>
</body>
</html>

提示

下载示例代码

您可以通过您在www.packtpub.com账户中购买的所有 Packt 图书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便直接通过电子邮件接收文件。

从标记中可以看出,我们正在利用 HTML5 提供的新结构。诸如<article><header><footer>之类的标签通过为内容添加语义含义来丰富页面。这些标签在语义上比<div>更好,因为它们解释了它们的内容。

注意

有关更多信息,建议您查看以下文章:html5doctor.com/lets-talk-about-semantics

除了口味文本之外,唯一需要详细解释的部分是<head>部分。在这个标签内,我们要求浏览器包含一些外部资产,这些资产将帮助我们前进。

重置样式表和自定义字体

首先,有一个重置样式表,这对于确保浏览器默认应用于 HTML 元素的所有 CSS 属性被移除非常有用。在这个项目中,我们使用了雅虎免费提供的样式表,基本上将所有属性设置为none或等效的值。

接下来,我们要求另一个样式表。这个来自谷歌服务的样式表叫做谷歌网络字体(www.google.com/webfonts),它分发可以嵌入和在网页中使用的字体。自定义网络字体是用特殊的@font-face属性定义的,其中包含了浏览器必须实现的字体文件的链接。

@font-face{
  font-family: YourFontName;
  src: url('yourfonturl.eot');
}

不幸的是,为了在浏览器之间达到最大可能的兼容性,需要更多的字体文件格式,因此需要更复杂的语句。以下语句有助于实现这种兼容性:

@font-face{
  font-family: YourFontName;
  src: url('yourfonturl.eot');
  src: 
    url('yourfonturl.woff') format('woff'), 
    url('yourfonturl.ttf') format('truetype'), 
    url('yourfonturl.svg') format('svg');
  font-weight: normal;
  font-style: normal;
}

谷歌网络字体为我们提供了一个包含我们选择的字体语句的样式表,为我们节省了所有与字体转换相关的麻烦。

接下来,让我们在项目的css文件夹下创建一个空的样式表文件。

最后但同样重要的是,我们需要确保即使是较旧的 Internet Explorer 浏览器也能正确处理新的 HTML5 标签。html5shivhtml5shiv.googlecode.com)是一个小的 JavaScript 文件,正是完成了这个任务。

创建表单

现在让我们通过在<!--FORM FIELDS-->标记下面添加以下代码来编写表单的 HTML 代码:

<fieldset>
  <legend> 
    Some info about you:
  </legend>
  <input type="text" name="name" id="name" placeholder="e.g. 
Sandro" title="Your name, required" required>
  <label class="label" for="name"> Name: </label>
  <input type="text" name="surname" id="surname" placeholder="e.g. 
Paganotti" title="Your surname, required" required>
  <label class="label" for="surname"> Surname: </label>
  <input type="email" name="email" id="email" placeholder="e.g. 
sandro.paganotti@gmail.com" title="Your email address, a valid 
email is required" required>
  <label class="label" for="email"> E-mail: </label>
  <input type="text" name="twitter" id="twitter" placeholder="e.g. 
@sandropaganotti" title="Your twitter username, starting with @" 
pattern="@[a-zA-Z0-9]+">
  <label class="label" for="twitter"> Twitter:</label>
  <footer></footer>
</fieldset>

HTML5 提供了一些新属性,我们将简要探讨如下:

  • placeholder:这用于指定在字段为空时显示的一些帮助文本。

  • required:这用于将字段标记为必填项。这是一个布尔属性,告诉浏览器在提交表单之前确保字段不为空。该属性是新表单验证功能的一部分,基本上提供了一种在客户端指定一些输入约束的方式。不幸的是,每个浏览器以不同的方式处理title属性中包含的错误消息的显示,但我们将在本章稍后进行检查。

  • pattern:这是一种指定验证模式的强大且有时复杂的方式。它需要一个正则表达式作为值。然后该表达式将与用户插入的数据进行检查。如果失败,将显示title属性中包含的消息。

在给定的示例中,模式值为@[a-zA-Z0-9]+,表示“一个或多个出现(+符号)在a-z(所有小写字母)、A-Z(所有大写字母)和0-9(所有数字)范围内的字形”。

注意

更多可直接使用的模式可以在html5pattern.com/找到。

就像 HTML5 引入的大多数功能一样,甚至在代码中看到的新表单属性在完全浏览器兼容性方面也存在问题。

注意

要了解当前浏览器对这些属性以及许多其他 HTML5 和 CSS3 功能的支持情况,建议访问caniuse.com/

放错位置的标签

这段代码中还有另一个奇怪之处:标签放置在它们所链接的字段之后。尽管这种标记不常见,但仍然有效,并为我们提供了一些新的有趣选项来拦截表单元素的用户交互。这可能听起来神秘,但我们将在几页后详细分析这种技术。

让我们在刚刚编写的fieldset元素下面添加另一个fieldset元素:

<fieldset class="preferences">
  <legend> Your party preferences: </legend>
  <input type="radio" name="beers" id="4_beers" value="4">
  <label class="beers" for="4_beers">4 beers</label>
  <input type="radio" name="beers" id="3_beers" value="3">
  <label class="beers" for="3_beers">3 beers</label>
  <input type="radio" name="beers" id="2_beers" value="2">
  <label class="beers" for="2_beers">2 beers</label>
  <input type="radio" name="beers" id="1_beers" value="1">
  <label class="beers" for="1_beers">1 beers</label>
  <input type="radio" name="beers" id="0_beers" value="0" 
required>
  <label class="beers" for="0_beers">0 beers</label>
  <span  class="label"> How many beers?: </span>
  <input type="radio" name="chips" id="4_chips" value="4">
  <label class="chips" for="4_chips">4 chips</label>
  <input type="radio" name="chips" id="3_chips" value="3">
  <label class="chips" for="3_chips">3 chips</label>
  <input type="radio" name="chips" id="2_chips" value="2">
  <label class="chips" for="2_chips">2 chips</label>
  <input type="radio" name="chips" id="1_chips" value="1">
  <label class="chips" for="1_chips">1 chips</label>
  <input type="radio" name="chips" id="0_chips" value="0" 
required>
  <label class="chips" for="0_chips">0 chips</label>
  <span class="label"> How many chips?: </span>
  <footer></footer>
</fieldset>

这里没有什么需要强调的;我们只是添加了两个单选按钮组。现在,如果我们尝试在浏览器中运行到目前为止所做的事情,我们将会感到失望,因为默认的浏览器样式已经被重置样式表移除了。

放错位置的标签

是时候添加一些基本样式了!

基本样式

我们需要做的是将表单居中,为文本选择合适的大小,选择一个背景,并调整标签和字段的位移。

让我们从背景开始。我们想要实现的是将一张尽可能大的图像放置在页面上,同时保持其比例。在“CSS2 时代”,这个简单的任务可能涉及一些 JavaScript 的使用,比如众所周知的 Redux jQuery 插件(bavotasan.com/2011/full-sizebackground-image-jquery-plugin/)。使用 CSS3 只需要几个语句:

html{
  height: 100%;
  background: black;
  background-image: url('../img/background.jpg');
  background-repeat: no-repeat;
  background-size: cover;
  background-position: top left;
  font-family: sans-serif;
  color: #051a00;
}

基本样式

这里的关键是background-size属性,它接受以下值:

  • length:使用此值,我们可以使用任何测量单位来表示背景的大小,例如background-size: 10px 10px;

  • percentage:使用此值,我们可以指定随元素大小变化的背景大小,例如background-size: 10% 10%;

  • cover:此值将图像按比例缩放(而不是拉伸),以覆盖整个元素的整个区域。这意味着图像的一部分可能不可见,因为它可能比容器更大。

  • contain:此值将图像按比例缩放(而不是拉伸),使其在容器内保持整个图像的最大尺寸。显然,这可能会导致元素的某些区域未被覆盖。

因此,通过使用cover,我们确保整个页面将被我们的图像覆盖,但我们可以做得更多!如果我们在浏览器中运行到目前为止所做的所有工作,我们会发现如果我们将窗口放大太多,背景图像的像素会变得可见。为了避免这种情况,我们可以在这个图像的顶部使用另一个背景图像。我们可以使用小黑点来隐藏底层图像的像素,从而获得更好的效果。

好消息是,我们可以在不使用另一个元素的情况下做到这一点,因为 CSS3 允许在同一元素上使用多个背景。我们可以使用逗号(,)来分隔背景,要记住的是我们首先声明的将覆盖其他背景。因此,让我们稍微改变前面的代码:

html{
  height: 100%;
  background: black;
  background-image: 
    url('../img/dots.png'), 
    url('../img/background.jpg');
  background-repeat: repeat, no-repeat;
  background-size: auto, cover;
  background-position: center center, top left;
  font-family: sans-serif;
  color: #051a00;
}

基本样式

此外,所有其他与背景相关的属性都以相同的方式起作用。如果我们省略一个值,那么将使用前一个值,因此,如果声明了两个背景图像,则写background-repeat: repeat与写background-repeat: repeat, repeat是相同的。

定义属性

让我们继续定义其余的必需属性,以完成项目的第一阶段:

/* the main container */
article{
  width: 600px;
  margin: 0 auto;
  background: #6cbf00;
  border: 10px solid white;
  margin-top: 80px;
  position: relative;
  padding: 30px;
  border-radius: 20px;
}
/* move the title over the main container */
article h1{
  width: 600px;
  text-align: center;
  position: absolute;
  top: -62px;
/* using the custom font family provided by google */
  font-family: 'Port Lligat Sans', cursive;
  color: white;
  font-size: 60px;
  text-transform: uppercase;
}

/* the small text paragraphs */
article p, 
article > footer{
  padding-bottom: 1em;
  line-height: 1.4em;
}

/* the fieldsets' legends */
article legend{
  font-family: 'Port Lligat Sans', cursive;
  display: block;
  color: white;
  font-size: 25px;
  padding-bottom: 10px;
}

.label{
  display: block;
  float: left;
  clear: left;
}

/* positioning the submit button */
input[type=submit]{
  display:block;
  width: 200px;
  margin: 20px auto;
}

/* align texts input on the right */
input[type=text], input[type=email]{
  float: right;
  clear: right;
  width: 350px;
  border: none;
  padding-left: 5px;
}
input[type=text], 
input[type=email], 
.label{
  margin: 2px 0px 2px 20px;
  line-height: 30px;
  height: 30px;
}

span + input[type=radio], legend + input[type=radio]{
  clear: right
}

/* size of the small labels linked to each radio */
.preferences label.chips,
.preferences label.beers{
  width: 60px;
  background-image: none;
}

input[type="radio"]{
  padding-right: 4px;
}

input[type="radio"], 
.preferences label{
  float: right;
  line-height: 30px;
  height: 30px;
}

这里只有几件事情需要强调。首先,通过使用一些浮动,我们将所有字段移到右侧,标签移到左侧。接下来,我们定义了一些元素之间的距离。也许最神秘的陈述是以下陈述:

span + input[type=radio], legend + input[type=radio]{
  clear: right
}

由于我们刚刚谈到的浮动,每组单选按钮的第一个元素变成了最右边的元素。因此,我们使用selector1 + selector2选择器来标识这个元素,该选择器表示指定的元素必须是兄弟元素。这被称为相邻兄弟选择器,并选择直接跟在匹配selector1选择器的元素后面的所有匹配selector2选择器的元素。最后,使用clear:right,我们简单地声明右侧不能有其他浮动元素。

让我们在浏览器中重新加载项目,以欣赏我们工作的结果:

定义属性

标记必填字段

让我们来看一个简单的技巧,自动在必填字段的标签旁边显示一个星号(*)。HTML5 表单验证模型引入了一些新的和非常有趣的伪选择器:

  • :valid:它仅匹配处于有效状态的字段。

  • :invalid:它的工作方式相反,仅匹配具有错误的字段。这包括将required属性设置为true的空字段。

  • :required:它仅匹配带有required标志的字段,无论它们是否已填写。

  • :optional:它适用于所有没有required标志的字段。

在我们的情况下,我们需要匹配所有跟随具有required属性的字段的标签。现在我们之前实现的 HTML5 结构派上了用场,因为我们可以利用+选择器来实现这一点。

input:required + .label:after, input:required + * + .label:after{
  content: '*';
}

我们添加了一个小变化(input:required + * + .label:after)以拦截单选按钮的结构。

在继续之前,让我们分析一下这个句子。我们使用:after伪选择器来访问具有label类的元素后面的位置。然后,使用content属性,我们在该位置内注入了星号。

如果我们重新加载页面,我们可以验证现在所有属于带有required标志字段的标签都以星号结尾。有人可能指出屏幕阅读器无法识别这种技术。为了避免这种情况,我们可以利用 WAI-ARIA 规范的aria-required属性(www.w3.org/TR/WCAG20-TECHS/ARIA2)。

标记必填字段

选中的单选按钮技巧

现在我们可以专注于单选按钮,但是如何以更好的方式呈现它们呢?有一个很酷的技巧;它利用了一个事实,即即使通过单击其链接的标签,也可以选中单选按钮。我们可以隐藏输入元素并样式化相应的标签,也许使用代表薯条和啤酒的图标。

让我们从单选按钮标签中删除文本并在鼠标悬停在它们上方时改变光标外观开始:

.preferences label{
  float: right;
  text-indent: -100px;
  width: 40px !important;
  line-height: normal;
  height: 30px;
  overflow: hidden;
   cursor: pointer;
}

干得好!现在我们必须隐藏单选按钮。我们可以通过在单选按钮上放置与背景相同颜色的补丁来实现这一点。让我们这样做:

input[type=radio]{
  position: absolute;
  right: 30px;
  margin-top: 10px;
}

input[type=radio][name=chips]{
  margin-top: 35px;
}

span + input[type=radio] + label, 
legend + input[type=radio] + label{
  clear: right;
  margin-right: 80px;
  counter-reset: checkbox;
}

.preferences input[type="radio"]:required + label:after{
  content: '';
  position: absolute;
  right: 25px;
  min-height: 10px;
  margin-top: -22px;
  text-align: right;
  background: #6cbf00;
  padding: 10px 10px;
  display: block;
}

如果我们现在尝试使用基于 WebKit 的浏览器或 Firefox 提交表单,我们会发现与单选按钮相关的验证气泡在两者上都正确显示。

在单选按钮标签中显示图标

让我们继续处理目前完全为空的单选按钮标签,因为我们使用text-indent属性将文本移开。我们现在要做的是在每个标签中放置一个微小的占位图像,并利用 CSS3 的~选择器,创建一个带有漂亮鼠标悬停效果的伪星级评分系统。

由于我们必须使用不同的图像(用于啤酒和薯条),所以我们必须复制一些语句。让我们从.beers标签开始:

.preferences label.beers{
  background: transparent url('../img/beer_not_selected.png') 
no-repeat center center;
}

.preferences label.beers:hover ~ label.beers, 
.preferences label.beers:hover, 
.preferences input[type=radio][name=beers]:checked ~ label.beers{
  background-image: url('../img/beer.png');
  counter-increment: checkbox;
}

elem1 ~ elem2选择器适用于所有elem2标签,它们是elem1标签的兄弟标签,并且跟在它后面(尽管elem2标签不必是相邻的)。这样,我们可以使用选择器.preferences label.beers:hover ~ label.beers来定位跟随处于悬停状态的标签的所有标签。

使用 CSS3 的:checked伪类选择器,我们可以识别已选中的单选按钮,并通过应用刚讨论的相同技巧,我们可以使用.preferences input[type=radio][name=beers]:checked ~ label.beers来定位所有跟随已选中单选按钮的标签。通过组合这两个选择器和经典的.preferences label.beers:hover选择器,我们现在能够根据用户与单选按钮的交互改变占位图像。现在让我们添加一个最后的很酷的功能。我们已经使用counter-increment属性来跟踪所选标签的数量,因此我们可以利用这个计数器并显示它。

.preferences input[type=radio][name=beers]:required + 
label.beers:after{
  content: counter(checkbox) " beers!";
}

让我们在浏览器中尝试一下结果:

在单选按钮标签中显示图标

现在,我们也必须为.chips标签复制相同的语句:

.preferences label.chips{
  background: transparent 
url('../img/frenchfries_not_selected.png') 
no-repeat center center;
}

.preferences label.chips:hover ~ label.chips, 
.preferences label.chips:hover, 
.preferences input[type=radio][name=chips]:checked ~ label.chips {
 background-image: url('../img/frenchfries.png');
  counter-increment: checkbox;
}

.preferences input[type=radio][name=chips]:required + 
label.chips:after {
  content: counter(checkbox) " chips!";
}

在本章中我们所做的所有样式都有一个大问题;如果浏览器不支持 CSS3,它会成功隐藏单选按钮和文本标签,但无法添加它们的图像替换,使一切都无法使用。有几种方法可以防止这种情况发生。这里介绍的方法是使用媒体查询

媒体查询将在以后的项目中详细介绍,基本上是由描述应用某些样式所需的一些条件的语句组成。让我们考虑以下例子:

@media all and (max-width: 1000px){
  body{
    background: red;
  }
}

在这个例子中,只有当浏览器窗口的大小不超过1000px时,才会将 body 背景变成红色。媒体查询非常有用,可以将特定样式应用于目标设备(智能手机、平板电脑等),但它们还有另一个有趣的特性;如果浏览器支持它们,它也支持我们使用的 CSS3 规则,因此我们可以将在本节和上一节中编写的所有 CSS 都放在媒体查询语句中:

@media all and (min-device-width: 1024px){

/* --- all of this and previous sections' statements --- */

}

通过这个技巧,我们解决了另一个微妙的问题。在 iPad 上尝试该项目,如果没有这个媒体查询语句,可能会在单选按钮上点击时出现问题。这是因为 iOS 上的标签不会响应点击。通过实现这个媒体查询,我们强制 iOS 设备回退到常规单选按钮。

计算和显示无效字段

在前一节中,我们使用了一些属性而没有解释它们;它们是counter-resetcounter-increment。此外,我们使用了一个类似函数的命令叫做counter()。在本节中,我们将通过创建一个机制来显示无效字段的数量来解释这些属性。计数器基本上是一个我们可以命名的变量,其值可以使用counter-increment来递增。接下来,这个计数器可以通过在content属性中使用counter(变量名)声明来显示。

让我们看一个小例子:

<ul>
  <li>element</li>
  <li>element</li>
  <li>element</li>
</ul>
<p></p>

<style>

ul{
  counter-reset: elements;
}

li{
  counter-increment: elements;
}

p:after{
  content: counter(elements) ' elements';
}

</style>

尝试这小段代码会得到一个包含句子3 个元素p元素。

计数和显示无效字段

我们可以将这些强大的属性与新的表单伪选择器结合起来,以获得一种显示有效和无效字段的方法。

实现计数器

让我们从创建两个计数器invalidfields开始,并在每个fieldset元素中重置它们,因为我们想要为每个fieldset元素显示无效字段。然后,当我们找到一个无效字段时,我们递增两个计数器,当我们找到一个有效字段时,只递增fields计数器。

fieldset{
  counter-reset: invalid fields;
}

input:not([type=submit]):not([type=radio]):invalid, 
input[type=radio]:required:invalid{
  counter-increment: invalid fields;
  border-left: 5px solid #ff4900;
}

input:not([type=submit]):not([type=radio]):valid, 
input[type=radio]:required{
  counter-increment: fields;
  border-left: 5px solid #116300;
}

:not伪选择器非常直观。它从括号内匹配的元素中减去左侧选择器匹配的元素。如果这看起来有点混乱,让我们试着阅读最后一个选择器:匹配所有input元素,其type不是submit不是radio,并响应:valid伪选择器。

我们快到了!现在我们有了计数器,让我们使用footer元素来显示它们:

fieldset footer{
  clear: both;
  position: relative;
}

fieldset:not([fake]) footer:after{
  content: 'yay, section completed, move on!';
  text-align: right;
  display: block;
  font-size: 13px;
  padding-top: 10px;
}

/* the value of the content property must be on one single line */ 
fieldset > input:invalid ~ footer:after{
  content: counter(invalid) '/' counter(fields) " fields with 
problems; move the mouse over the fields with red marks to see 
details.\a Fields with * are required.";
  white-space: pre;
}

:not([fake])选择器像之前显示的媒体查询一样使用。我们只想确保只有支持:valid:invalid伪选择器的浏览器才能解释这个选择器。

然而,这最后的添加有一些缺点;通常最好避免将演示与内容混合在一起。

气球样式

每个浏览器实际上以自己的方式显示表单错误,我们无法对此可视化做太多影响。唯一的例外是基于 WebKit 的浏览器,它们让我们改变这些消息的外观。以下代码显示了在这些浏览器中如何构建错误气球:

<div>::-webkit-validation-bubble
  <div>::-webkit-validation-bubble-arrow-clipper
    <div>::-webkit-validation-bubble-arrow
    </div>
  </div>::-webkit-validation-bubble-message
  <div>
    <b>Browser validation message</b>
    element's title attribute
  </div>
</div>

我们可以通过使用前面代码中列出的特殊伪类来访问组成错误消息的所有元素。所以,让我们开始吧!

::-webkit-validation-bubble{
  margin-left: 380px;
  margin-top: -50px;
  width: 200px;
}

input[type=radio]::-webkit-validation-bubble{
  margin-left: 50px;
  margin-top: -50px;
}

::-webkit-validation-bubble-arrow-clipper{
  -webkit-transform: rotate(270deg) translateY(-104px) 
translateX(40px);
}

::-webkit-validation-bubble-arrow{
  background: #000;
  border: none;
  box-shadow: 0px 0px 10px rgba(33,33,33,0.8);
}

::-webkit-validation-bubble-message{
  border: 5px solid black;
  background-image: none;
  box-shadow: 0px 0px 10px rgba(33,33,33,0.8);
}

通过-webkit-transform,我们对匹配的元素应用了一些变换。在这种情况下,我们将箭头从气球底部移动到左侧。

以下是我们完成的项目的一瞥:

气球样式

优雅降级

正如我们所预期的,这个项目并不完全支持所有浏览器,因为它实现了 HTML5 和 CSS3 的特性,当然,这些特性不包括在旧浏览器中。存在许多技术来解决这个问题;我们现在要看的是优雅降级。它基本上侧重于使项目的核心功能尽可能得到广泛支持,同时接受其他一切可能不受支持,因此不会显示。

我们的项目是优雅降级的一个很好的例子:当浏览器不支持特定属性时,其效果会被简单地忽略,而不会影响表单的基本功能。

为了证明这一点,让我们在 IE8 上尝试该项目,因为它基本上不支持 CSS3:

优雅降级

为了实现最佳的浏览器支持,我们可能还需要在 IE9 上隐藏页脚元素和单选按钮,否则它们会被显示,但行为不如预期。为此,我们需要在index.html文件的head部分结束前添加一个条件注释。我们将在后面的章节中看到条件注释是如何工作的,但现在让我们说它们允许我们指定一些标记,只有选择的浏览器才能解释。

<!--[if IE 9]>
  <style>
    footer, input[name=beers], input[name=chips]{
      display: none;
    }
  </style>
<![endif]-->

摘要

在这个第一个项目中,我们探讨了 CSS3 如何通过从标记和字段状态中获取的有用信息来增强我们的表单。在下一章中,我们将把注意力集中在按钮上,探讨如何利用渐变和其他 CSS3 属性充分模拟真实世界的形状和行为,而不使用图像。

第二章:闪亮按钮

自从它们首次出现在尖端浏览器的夜间构建以来,CSS3 按钮一直被认为是一个热门话题。按钮是大多数用户界面中重要且广为人知的元素。对于 Web 开发人员而言,使它们成为热门话题的原因是 CSS3 按钮易于通过简单更改文本或样式表声明来修改。

在本章中,我们将仅使用 CSS3 创建模仿现实世界对应物的按钮。在这样做的同时,我们将探索新的 CSS 属性和技巧来实现我们的目标。我们将涵盖以下主题:

  • 一个投币式按钮

  • :before:after伪选择器

  • 渐变

  • 避免实验性前缀

  • 阴影

  • 添加标签

  • 处理鼠标点击

  • CSS 中的小改变,大结果

  • 一个开关

  • 活动状态

  • 选中状态

  • 添加颜色

  • 支持旧版浏览器

  • 关于 CSS 渐变语法的最后说明

创建一个投币式按钮

在本章的第一部分,我们专注于创建一个逼真的投币式按钮。我们希望尽可能多地使用 CSS,并利用新功能而不使用图像。以下截图是结果的一瞥:

Creating a coin-operated push button

首先,让我们创建一个名为shiny_buttons的文件夹,我们将在其中存储所有项目文件。然后,我们需要一个填充了非常少标记的文件index.html

<!doctype html>
<html>
<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <meta charset="utf-8">
  <title>Shiny Buttons: from the reality to the web!</title>
  <link 
href='http://fonts.googleapis.com/css?family=Chango|Frijole|
Alegreya+SC:700' rel='stylesheet' type='text/css'>
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.4.1/build/cssreset/cssreset-min.css">
  <link rel="stylesheet" type="text/css" 
href="css/application.css">
  <script 
src="img/html5.js"></script>
</head>
<body>
  <section>
    <article id="arcade_game">
 <a href="#" role="button" class="punch">punch</a>
 <a href="#" role="button" class="jump">jump</a>
    </article>

  </section>
</body>
</html>

正如标记所示,我们使用单个<a>元素来声明我们的按钮。锚标记可能看起来不够复杂,无法产生复杂的按钮,并且让我们相信我们需要更多的 HTML,但事实并非如此。我们可以仅使用此标记以及我们的 CSS3 声明来实现惊人的结果。

:before:after伪选择器

正如我们在上一章中发现的,伪选择器可以被视为元素并且可以在不需要向 HTML 页面添加额外标记的情况下进行样式化。如果我们将<a>元素设置为position:relative,并且将:after:before都设置为position:absolute,我们可以使用相对于<a>位置的坐标来放置它们。让我们尝试通过在项目中的css文件夹中创建一个application.css文件来实现这一点:

/* link */
#arcade_game a{
 display: block;
 position: relative;
  text-transform: uppercase;
  line-height: 100px;
  text-decoration: none;
  font-family: 'Frijole', cursive;
  font-size: 40px;
  width: 300px;
  padding: 10px 0px 10px 120px;
  margin: 0px auto;
  color: rgb(123,26,55);
}

/* :before and :after setup */
#arcade_game a:before, 
#arcade_game a:after{
  content: "";
 display: block;
 position: absolute;
  left: 0px;
  top: 50%;
}

/* :before */
#arcade_game a:before{
  z-index: 2;
  width: 70px;
  height: 70px;
  line-height: 70px;
  left: 15px;
  margin-top: -35px;
  border-radius: 35px;
  background-color: red; /* to be removed */
}

/* :after */
#arcade_game a:after{
  z-index: 1;
  width: 100px;
  height: 100px;
  border-radius: 50px;
  margin-top: -50px;
  background-color: green; /* to be removed */
}

如果我们在浏览器中加载到目前为止所做的工作,我们开始注意到一个投币式按钮的形状。两个圆,一个在另一个内部,位于标签的左侧,如下截图所示:

The :before and :after pseudo-selectors

我们所做的一切是创建圆形形状,就是施加一个边框半径等于盒子尺寸的一半。干得好!现在我们可以移除绿色和红色的圆形背景,然后继续探索渐变。

渐变

在使用 CSS 渐变时,我们指示浏览器的布局引擎根据我们的 CSS 方向绘制图案。渐变对应于运行时生成的、大小独立的图像,因此可以在允许url()表示法的任何地方使用。有四种类型的渐变:linear-gradientrepeating-linear-gradientradial-gradientrepeating-radial-gradient。以下渐变代码示例提供了对它们的简要概述:

<!doctype html>
<html>
<head>
  <meta charset="utf8">
  <title>Explore gradients</title>

  <style>
    .box{
      width: 400px;
      height: 80px;
      border: 3px solid rgb(60,60,60);
      margin: 10px auto;
      border-radius: 5px;
      font-size: 30px;
      text-shadow: 2px 2px white;
    }

    #linear{
 background-image: linear-gradient(top left, red, white, green);
    }

    #repeating_linear{
 background-image: repeating-linear-gradient(top left, red, white, red 30%);
    }

    #radial{
 background-image: radial-gradient(center center, ellipse cover, white, blue);
    }

    #repeating_radial{
 background-image: repeating-radial-gradient(center center, ellipse cover, white, blue, white 30px);
    }

    #collapsed_linear{
 background-image: linear-gradient(left, red, red 33%, white 33%, white 66%, green 66%);
    }

    #collapsed_radial{
 background-image: radial-gradient(center center, ellipse contain, white, white 55%, blue 55%);
    }

  </style>

</head>
<body>
  <section>

    <div id="linear" class="box">linear</div>
    <div id="repeating_linear" class="box">repeating_linear</div>
    <div id="radial" class="box">radial</div>
    <div id="repeating_radial" class="box">repeating_radial</div>
    <div id="collapsed_linear" class="box">collapsed_linear</div>
    <div id="collapsed_radial" class="box">collapsed_radial</div>

  </section>
</body>
</html>

渐变语法

在前面的渐变代码示例中,很明显每个语句都包含位置信息(例如top left45deg)和颜色步骤,这些颜色步骤可以选择性地具有指示颜色停止的值。如果两种颜色停在完全相同的位置,我们将获得一个锐利的颜色变化而不是渐变。

径向渐变允许额外的参数。特别是,我们可以选择渐变的形状,圆形和椭圆形之间,以及渐变如何填充元素的区域。具体来说,我们可以在以下选项中进行选择:

  • closest-side:使用此参数,渐变会扩展直到与包含元素的最近一侧相遇

  • closest-corner:使用此参数,渐变会扩展到达包含元素的最近一角

  • farthest-side:使用此参数,渐变会扩展到达包含元素的最远一侧

  • farthest-corner:使用此参数,渐变会扩展到达包含元素的最远一角

  • 包含:这是closest-side的别名

  • cover:这是farthest-corner的别名

以下截图显示了在浏览器中执行前面代码的结果:

渐变语法

不幸的是,之前的截图并没有说明我们在 web 浏览器中运行示例代码时看到的情况。事实上,如果我们在支持 CSS3 渐变的浏览器(例如 Google Chrome)中执行之前的代码,我们得到的是一列带有黑色边框的白色框。这是因为渐变被视为实验性质的,因此需要为每个我们想要支持的浏览器添加特定的前缀(例如,-webkit--ms--o--moz-)。这意味着我们必须为每个想要支持的浏览器重复声明。例如,在之前代码中的#linear选择器中,为了实现最大的兼容性,我们应该写成:

#linear{
  background-image: -webkit-linear-gradient(top left, red, white, 
green);
  background-image: -ms-linear-gradient(top left, red, white, 
green);
  background-image: -o-linear-gradient(top left, red, white, 
green);
  background-image: -moz-linear-gradient(top left, red, white, 
green);
  background-image: linear-gradient(top left, red, white, green);
}

避免实验性前缀

我们需要找到一种方法来避免编写大量重复的 CSS 代码,以实现所有现有的浏览器实验性前缀。一个很好的解决方案是由 Lea Verou 创建的 Prefix Free(leaverou.github.com/prefixfree/),这是一个小型的 JavaScript 库,它可以检测用户的浏览器并动态添加所需的前缀。要安装它,我们只需要在项目中的js文件夹中下载.js文件,命名为prefixfree.js,并在index.html中的css请求之后添加相应的脚本标签。

<script src="img/prefixfree.js"></script>

从现在开始,我们不再需要担心前缀,因为这个库会为我们完成繁重的工作。然而,也有一些小的缺点;有些属性不会自动检测和添加前缀(例如,radial-gradientrepeating-radial-gradient不会添加-moz-前缀),并且我们需要忍受一个短暂的延迟,大致等于脚本下载时间,才能正确地添加前缀。

因此,让我们继续向我们的按钮添加一些渐变:

#arcade_game a:before, #arcade_game a:after{
  background: gray;  /* to be removed */
}

#arcade_game a:before{
  background-image: 
    -moz-radial-gradient(7px 7px, ellipse farthest-side, 
    rgba(255,255,255,0.8), rgba(255,255,255,0.6) 3px, 
    rgba(200,200,200,0.0) 20px);
 background-image: 
 radial-gradient(7px 7px, ellipse farthest-side, 
 rgba(255,255,255,0.8), rgba(255,255,255,0.6) 3px, 
 rgba(200,200,200,0.0) 20px);
}

#arcade_game a:after{
  background-image: 
    -moz-radial-gradient(7px 7px, ellipse farthest-side, 
    rgba(255,255,255,0.8), rgba(255,255,255,0.6) 3px, 
    rgba(200,200,200,0.0) 20px), 
    -moz-radial-gradient(50px 50px, rgba(255,255,255,0), 
    rgba(255,255,255,0) 40px, rgba(200,200,200,0.1) 43px, 
    rgba(255,255,255,0.0) 50px);
 background-image: 
 radial-gradient(7px 7px, ellipse farthest-side, 
 rgba(255,255,255,0.8), rgba(255,255,255,0.6) 3px, 
 rgba(200,200,200,0.0) 20px), 
 radial-gradient(50px 50px, rgba(255,255,255,0), 
 rgba(255,255,255,0) 40px, rgba(200,200,200,0.1) 43px, 
 rgba(255,255,255,0.0) 50px);
}

为了专注于向我们的按钮添加新功能的主题,前面的代码没有重复现有的application.css中的 CSS 声明。无论我们如何应用新的指令,我们都可以追加之前的声明或合并每个选择器的属性。无论如何,结果都是一样的。

使用上述代码,我们使用径向渐变创建了两个光点,模拟了我们按钮的形状和反射。CSS3 允许我们通过支持rgba()符号来创建这种效果,该符号接受0(透明)到1(不透明)之间的 alpha 值。

让我们在浏览器中尝试一下结果:

避免实验性前缀

CSS3 渐变的即将到来的语法更改

关于 CSS3 渐变的最新编辑草案(www.w3.org/TR/2012/CR-css3-images-20120417/)在提供关键字定义位置信息时引入了一个小的语法更改。因此,我们不再需要写:

linear-gradient(bottom, blue, red);

现在我们需要写成:

linear-gradient(to top, blue, red);

对于径向渐变的语法,还有一些更改;因此,我们之前写的:

radial-gradient(center center, ellipse cover, white, blue);

被更改为:

radial-gradient(cover ellipse at center center, white, blue);

不幸的是,这种新的语法在撰写本书时在各种浏览器中的支持并不好。因此,我们将继续使用旧的语法,因为它有很好的支持。

阴影

CSS3 中实现阴影有两个不同的属性,具有相似的语法,box-shadowtext-shadow。让我们创建另一个示例来展示它们的工作原理:

<!doctype html>
<html>
<head>
  <meta charset="utf8">
  <title>Explore Shadows!</title>

  <style>
    .box{
      width: 400px;
      height: 80px;
      border: 3px solid rgb(60,60,60);
      margin: 30px auto;
      border-radius: 5px;
      line-height: 80px;
      text-align: center;
    }  
    #outset{
      box-shadow: 10px 10px 3px rgb(0,0,0);
    }
    #inset{
      box-shadow: 10px 10px 3px rgb(0,0,0) inset;
    }
    #offset{
      box-shadow: 0px 0px 0px 10px rgb(0,0,0);
    }
    #text{
      text-shadow: 10px 10px 3px rgb(0,0,0);
    }

  </style>

  <script src="img/prefixfree.js"></script>
</head>
<body>
  <section>

    <div id="outset" class="box"></div>
    <div id="inset" class="box"></div>
    <div id="offset" class="box"></div>
    <div id="text" class="box">Some text</div>

  </section>
</body>
</html>

实质上,box-shadowtext-shadow 是相似的。这两个属性都有阴影偏移(前两个参数)和模糊(第三个参数)。只有 box-shadow 有一个可选的第四个参数,用于控制阴影的扩散或模糊的距离。

接下来是颜色,然后,仅对于 box-shadow 属性,还有一个额外的关键字 inset,它导致阴影落在元素内部而不是外部。最后,可以用逗号(,)分隔定义更多阴影。

以下截图显示了在浏览器中执行上述代码的结果:

阴影

有了这些新知识,我们现在可以向我们的按钮添加更多效果。让我们在 application.css 中添加一些属性:

/* shadows */
#arcade_game a:before{
  box-shadow: 
    0px 0px 10px rgba(0,0,80,0.7), 
    0px 0px 4px rgba(0,0,0,0.4), 3px 3px 6px rgba(0,0,0, 0.5), 
    2px 2px 1px  rgba(255,255,255,0.3) inset, 
    10px 10px 20px rgba(0,0,0,0.1) inset;
}
#arcade_game a:after{
  box-shadow: 
    1px 0px 1px rgba(0,0,0, 0.7), 
    6px 0px 4px rgba(0,0,0, 0.6), 
    0px 1px 0px rgba(200,200,200,0.7) inset, 
    2px 2px 1px  rgba(255,255,255,0.3) inset;
}

然后,在浏览器中重新加载项目。

阴影

添加标签

每个按钮必须有自己的符号。为了获得这个结果,我们可以使用 HTML5 的 data-* 属性,比如 data-symbol。HTML5 认为所有 data-* 属性都是有效的,并且可以自由地由开发人员用来保存一些特定于应用程序的信息,就像在这种情况下一样。然后,我们可以使用 content 属性将自定义属性的值插入到按钮中。让我们看看如何做,但首先我们需要更新我们的 <a> 元素。所以让我们编辑 index.html

<a href="#" class="punch" data-symbol="!">PUNCH</a>
<a href="#" class="jump" data-symbol="★">JUMP</a>

注意

要输入黑星(★)(Unicode 字符:U+2605),我们可以从www.fileformat.info/info/unicode/char/2605/index.htm复制粘贴,或者我们可以使用 Windows 中包含的字符映射。

接下来,我们需要在 application.css 中添加适当的说明:

/* text */
#arcade_game a:before{
  font-family: 'Chango', cursive;
  text-align: center;
  color: rgba(255,255,255, 0.4);
  text-shadow: -1px -1px 2px rgba(10,10,10, 0.3);
  content: attr(data-symbol);
}

以下截图显示了浏览器中的结果:

添加标签

事实上,我们可以通过修改 data-symbol 属性的值来简单地改变按钮的符号。

处理鼠标点击

几乎完成了!现在我们需要使按钮更具响应性。为了实现这一点,我们可以利用 :active 伪选择器来修改一些阴影。让我们在 application.css 中添加以下行:

/* active */
#arcade_game a:active:before{
  background-image: none;
  box-shadow: 
    0px 0px 7px rgba(0,0,80,0.7), 
    0px 0px 4px rgba(0,0,0,0.4), 
    10px 10px 20px rgba(0,0,0,0.3) inset;
 line-height: 65px;
}

#arcade_game a:active:after{
  background-image: 
    -moz-radial-gradient(7px 7px, ellipse farthest-side, 
    rgba(255,255,255,0.8), rgba(255,255,255,0.6) 3px, 
    rgba(200,200,200,0.0) 20px),
    -moz-radial-gradient(53px 53px, rgba(255,255,255,0), 
    rgba(255,255,255,0) 33px, rgba(255,255,255,0.3) 36px, 
    rgba(255,255,255,0.3) 36px, rgba(255,255,255,0) 36px);
  background-image: 
    radial-gradient(7px 7px, ellipse farthest-side, 
    rgba(255,255,255,0.8), rgba(255,255,255,0.6) 3px, 
    rgba(200,200,200,0.0) 20px),
    radial-gradient(53px 53px, rgba(255,255,255,0), 
    rgba(255,255,255,0) 33px, rgba(255,255,255,0.3) 36px, 
    rgba(255,255,255,0.3) 36px, rgba(255,255,255,0) 36px);
}

通过增加 line-height 属性的值,我们将符号向下移动了一点,给人一种它已经被按钮按下的错觉。让我们重新加载浏览器中的项目并检查结果:

处理鼠标点击

CSS 中的小改变,大结果

我们现在已经完成了第一种按钮。在继续下一个之前,我们最好停顿一下,意识到我们编写的所有阴影和渐变基本上都是无色的;它们只是为底色添加了白色或黑色。这意味着我们可以为每个按钮选择不同的背景颜色。所以让我们在 application.css 中添加以下代码:

 /* puch */
#arcade_game .punch:after, #arcade_game .punch:before{
  background-color: rgb(123,26,55);
}

#arcade_game .punch{
  color: rgb(123,26,55);
}

/* jump */
#arcade_game .jump:after, #arcade_game .jump:before{
  background-color: rgb(107,140,86);
}

#arcade_game .jump{
  color:  rgb(107,140,86);
}

以下截图显示了结果:

CSS 中的小改变,大结果

创建一个开关

好的,现在我们将样式一些复选框按钮,尝试匹配一些录音室按钮("REC")的外观。以下是最终结果的截图:

创建一个开关

首先,让我们在 index.html 中添加复选框,就在之前的 article 元素后面:

<article id="old_panel">
  <form>
    <input type="checkbox" id="rec">
    <label class="rec" for="rec">RECORD</label>
    <input type="checkbox" id="at_field">
    <label class="at_field" for="at_field">AT FIELD</label>
  </form>
</article>

就像在上一章中所做的那样,我们现在想隐藏 input 元素。让我们通过在 application.css 中添加几行来实现这一点:

#old_panel input{
  visibility: hidden;
  position: absolute;
  top: -999px;
  clip: 'rect(0,0,0,0)';
}

#old_panel label{
  display: block;
  position: relative;
  width: 300px;
  padding-left: 125px;
 cursor: pointer;
  line-height: 140px;
  height: 130px;
  font-family: 'Alegreya SC', serif;
  font-size: 40px;
  margin: 0px auto;
  text-shadow: 1px 1px 1px rgba(255,255,255, 0.3), -1px -1px 1px 
rgba(10,10,10, 0.3);
}

很好!我们希望这个元素像某种按钮一样工作,所以我们使用 cursor 属性强制光标采用指针图标。

创建一个遮罩

现在我们为 article 元素设置了背景颜色。这对我们即将构建的内容非常重要。

#old_panel{
 background: rgb(150,130,90);
  padding: 9px 0px 20px 0px;
}

接下来,我们关注 :before:after 伪选择器:

#old_panel label:before{
  content: '';
  z-index: 1;
  display: block;
  position: absolute;
  bottom: 0px;
  left: 0px;
  width: 126px;
  height: 131px;
  background-image: 
    -moz-radial-gradient(50% 50%, circle, 
    rgba(0,0,0,0.0), 
    rgba(0,0,0,0.0) 50px, 
    rgb(150,130,90) 50px);
 background-image: 
 radial-gradient(50% 50%, circle, 
 rgba(0,0,0,0.0), 
 rgba(0,0,0,0.0) 50px, 
 rgb(150,130,90) 50px);
}

我们现在所做的是使用渐变作为一种遮罩。实质上,我们创建了一个半径为 50px 的透明圆,然后我们使用背景颜色来覆盖剩余的区域。

好的,现在是棘手的部分。为了模拟按钮的形状,我们创建一个带有圆角的框,然后使用box-shadow属性来产生高度的错觉。

#old_panel label:after{
  content: 'OFF';
  display: block;
  position: absolute;
  font-size: 20px;
  text-align: center;
  line-height: 60px;
  z-index: 2;
  bottom: 30px;
  left: 30px;
  width: 60px;
  height: 65px;
  border-radius: 7px;
  background-image: 
    -moz-radial-gradient(30px -15px, circle, 
    rgba(255,255,255,0.1), rgba(255,255,255,0.1) 60px, 
    rgba(255,255,255,0.0) 63px);
  background-image: 
    radial-gradient(30px -15px, circle, 
    rgba(255,255,255,0.1), rgba(255,255,255,0.1) 60px, 
    rgba(255,255,255,0.0) 63px);
  box-shadow: 
    0px 1px 0px rgba(255,255,255,0.3) inset, 
    0px -11px 0px rgba(0,0,0,0.4) inset, 
    -3px 9px 0px 0px black, 
    3px 9px 0px 0px black, 
    0px 10px 0px 0px rgba(255,255,255,0.3), 
    -4px 9px 0px 0px rgba(255,255,255,0.3), 
    4px 9px 0px 0px rgba(255,255,255,0.3), 
 0px 0px 0px 30px rgb(150,130,90);

  border: 
    3px solid rgba(0,0,0,0.2);
    border-bottom: 3px solid rgba(0,0,0,0.4);
  background-clip: padding-box;
}

阴影的最后一个声明(高亮显示的声明)也被用作遮罩。它与背景颜色相同,并且在我们刚刚创建的框周围扩展了30px,覆盖了我们之前用前一个渐变声明的透明区域。

这到底是什么?让我们试着用一个方案来解释一下:

创建遮罩

前面的图显示了我们使用的三个形状,一个在另一个上面。如果我们关闭box-shadow,那么在label:before上设置的每个颜色都将在label:beforebackground-image属性创建的遮罩内可见。

为了查看我们到目前为止所做的工作,让我们在浏览器中加载项目:

创建遮罩

活动状态

现在我们需要像以前一样处理活动状态。为了模拟压力,我们降低了元素的高度,并改变了一些阴影的偏移。

  #old_panel label:active:after{
    height: 54px;
    box-shadow: 
      0px 0px 0px 3px black,
      -3px 9px 0px 0px black,
      3px 9px 0px 0px black,
      0px 0px 0px 4px rgba(255,255,255,0.3),
      0px 10px 0px 0px rgba(255,255,255,0.3),
      -4px 9px 0px 0px rgba(255,255,255,0.1),
      4px 9px 0px 0px rgba(255,255,255,0.1),
      0px 0px 0px 30px rgb(150,130,90);
  }

让我们在浏览器中试一试:

活动状态

添加已选状态

现在,我们基本上要做的是将标签的文本从 OFF 更改为 ON,并删除box-shadow遮罩,以暴露我们将用来模拟从按钮传播光的背景颜色。

#old_panel input:checked + label:not(:active):after{
  content: 'ON';
  background-clip: border-box;
  box-shadow: 
    0px 1px 0px rgba(255,255,255,0.3) inset, 
    0px -11px 0px rgba(0,0,0,0.4) inset, 
    -3px 9px 0px 0px black, 
    3px 9px 0px 0px black, 
    0px 10px 0px 0px rgba(255,255,255,0.3), 
    -4px 9px 0px 0px rgba(255,255,255,0.3), 
    4px 9px 0px 0px rgba(255,255,255,0.3);
}

#old_panel input:checked + label:not(:active):before{
  background-image: 
    -moz-radial-gradient(50% 57%, circle, 
    rgba(150,130,90,0.0), 
    rgba(150,130,90,0.3) 40px, 
    rgb(150,130,90) 55px);
  background-image: 
    radial-gradient(50% 57%, circle, rgba(150,130,90,0.0), 
    rgba(150,130,90,0.3) 40px, 
    rgb(150,130,90) 55px);
}

我们不希望在按钮仍然被按下时激活这种效果,因此我们添加了:not(:active)伪选择器。

添加颜色

让我们为每个按钮设置不同的颜色。这一次,我们需要为关闭状态和打开状态分别指定一种颜色:

 /* -- record -- */
#old_panel input:checked + label.rec:not(:active):before, #old_panel input:checked + label.rec:not(:active):after{
  background-color: rgb(248,36,21);
}

#old_panel label.rec:before{
  background-color: rgb(145,67,62);
}
/* -- at field -- */
#old_panel input:checked + label.at_field:not(:active):before, #old_panel input:checked + label.at_field:not(:active):after{
  background-color: rgb(61,218,216);
}

#old_panel label.at_field:before{
  background-color: rgb(29,51,200);
}

以下的截图显示了结果:

添加颜色

支持旧版浏览器

这个项目不打算在旧版浏览器上优雅地降级,因此我们需要应用一种不同的技术来检测这个项目所需的功能是否缺失,并提供一种替代的 CSS2 样式表。

为此,我们依赖于一个名为Modernizr.js的 JavaScript 库(modernizr.com/),它显示了每个 HTML5/CSS3 功能的方法。这些方法根据所需功能的存在与否简单地返回truefalse。然后,我们将使用Modernizr.js中包含的一个小库,称为 yepnope.js(yepnopejs.com),动态选择我们想要加载的样式表。

首先,我们需要下载这个库。为此,我们必须在下载页面modernizr.com/download/上标记对应于我们想要测试的功能的复选框,然后点击Generate按钮,然后点击Download按钮,将文件保存为modernizr.js,保存在我们项目的js文件夹下。

好的,现在我们需要在我们的index.html文件的<head>标签中做一些更改,以使这个新技巧起作用。新的<head>部分如下所示:

<head>
  <meta charset="utf8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>Shiny Buttons: from the reality to the web!</title>
  <link 
href='http://fonts.googleapis.com/css?family=Chango|Frijole|Alegre
ya+SC:700' rel='stylesheet' type='text/css'>
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.4.1/build/cssreset/cssreset-
min.css">
  <script 
src="img/html5.js"></script>
  <script src="img/modernizr.js"></script>
 <script>
 yepnope({
 test : Modernizr.borderradius && Modernizr.boxshadow && 
Modernizr.multiplebgs && Modernizr.cssgradients, 
 yep  : ['css/application.css','js/prefixfree.js'], 
 nope : 'css/olderbrowsers.css'
 });
 </script>

</head>

我们只需要记住创建一个css/olderbrowsers.css文件,其中包含一些 CSS2 指令,用于为旧版浏览器样式化这些元素,例如以下指令:

#arcade_game a{
  display: block;
  margin: 20px auto;
  width: 200px;
  text-align: center;
  font-family: 'Frijole', cursive;
  font-size: 40px;
  color: white;
  text-decoration: none;
}

/* puch */
#arcade_game .punch{
  background-color: rgb(123,26,55);
}

/* jump */
#arcade_game .jump{
  background-color: rgb(107,140,86);
}

#old_panel{
  text-align: center;
}

#old_panel label{
  font-family: 'Alegreya SC', serif;
  font-size: 40px;
}

我们还必须考虑,仅依赖 JavaScript 有时可能是一个危险的选择,因为我们没有提供非 JavaScript 的替代方案。一个简单的解决方法可能是将olderbrowsers.css设置为默认样式表,然后仅在所需的 CSS3 属性得到支持时动态加载application.css

然而,为了这样做,我们必须在application.css中添加一些行来避免olderbrowsers.css的属性:

/* === [BEGIN] VOIDING BASE CSS2 === */

#arcade_game a{
  background-color: transparent !important;
  width: 300px !important;
  text-align: left !important;
}

#old_panel{
  text-align: left !important;
}

/* === [END] VOIDING BASE CSS2 === */

最后,我们可以按照以下方式更改我们之前的 HTML 代码:

<link rel="stylesheet" type="text/css" 
href="css/olderbrowsers.css">
<script>
  yepnope({
    test : Modernizr.borderradius && Modernizr.boxshadow && 
Modernizr.multiplebgs && Modernizr.cssgradients, 
    yep  : ['css/application.css','js/prefixfree.js']
  });
</script>

支持 IE10

Internet Explorer 10 支持该项目中展示的所有 CSS 特性。然而,我们不得不面对一个事实,即 Prefix Free 在radial-gradient符号上没有添加-ms-实验性前缀。这并不是一个大问题,因为我们的按钮即使没有渐变也能正常工作,除了我们在 ON/OFF 开关中用作蒙版的radial-gradient符号。为了解决这个问题,我们可以在application.css中添加以下行:

#old_panel label:before{
  background-image: 
  -ms-radial-gradient(50% 50%, circle, 
  rgba(0,0,0,0.0), 
  rgba(0,0,0,0.0) 50px, 
  rgb(150,130,90) 50px);
}

总结

这个项目详细介绍了渐变和阴影,演示了如何利用一小组 HTML 元素来实现惊人的效果。

在进入下一章之前,了解一下在线渐变生成器可能会很有用,它们可以让我们使用友好的 UI 来组合渐变,然后提供正确的 CSS 语法以包含在我们的样式表中。它们可以在www.colorzilla.com/gradient-editor/www.cssbuttongenerator.com/css3generator.com/找到。

在下一章中,我们将学习如何通过创建一个在桌面和智能手机上都能工作的菜单来处理多个设备的可视化。

第三章:Omni 菜单

使用媒体查询,我们可以在满足某些设备或视口要求时激活或停用 CSS 指令。当我们需要处理需要根据用户设备具有不同表示的元素时,这是非常有用的。菜单通常就是这样的一个元素。在本章中,我们将开发一个主菜单系统,可以在桌面浏览器和移动设备上完美显示;我们可以称之为 Omni 菜单。我们将涵盖以下主题:

  • 设置操作

  • 第一级

  • 第二级

  • 移动部件

  • 基本过渡

  • 介绍动画

  • 添加一些颜色

  • 媒体查询

  • 移动版本

  • 提高速度

在下一节中,我们将开始创建一个基本的 HTML 菜单结构。通常情况下,我们可以将项目的所有文件存储在一个以项目名称(在本例中为omni_menu)命名的文件夹中。在开始之前,让我们先看一下最终结果的截图:

Omni 菜单

设置操作

要为菜单设置样式,我们需要先定义标记。让我们编写一个小的 HTML 文件index.html,在其中我们将使用liul项目定义一个经典的两级菜单结构。接下来,我们将在转到本章的核心部分之前添加一些基本的 CSS。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />

  <title> Omnimenu: good for desktop, good for mobile </title>

 <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.7.3/build/cssreset/
cssreset-min.css" data-noprefix>

  <link rel="stylesheet" type="text/css" 
href="css/application.css">

  <script src="img/prefixfree.js"></script>
</head>
<body>
  <nav>
    <ul>
      <li data-section="about-me">
        <a href="#" class="item"> About me </a>
        <ul>
          <li><a href="#" class="item">Early years</a></li>
          <li><a href="#" class="item">First works</a></li>
          <li><a href="#" class="item">Today and tomorrow</a></li>
          <li class="cursor"><a href="#" class="item"> back </a>
          </li> 
        </ul>
      </li>
      <li data-section="portfolio">
        <a href="#" class="item"> Portfolio </a>
        <ul>
          <li> <a href="#" class="item"> Design </a> </li>
          <li> <a href="#" class="item"> Articles </a> </li>
          <li class="cursor"> <a href="#" class="item"> back </a>
          </li>
        </ul>
      </li>
      <li data-section="interests">
        <a href="#" class="item"> Interests </a>
        <ul>
          <li> <a href="#" class="item"> Skying </a> </li>
          <li> <a href="#" class="item"> Snowboarding </a> </li>
          <li> <a href="#" class="item"> Wakeboarding </a> </li>
          <li class="cursor"> <a href="#" class="item"> back </a>
          </li>
        </ul>
      </li>
      <li class="cursor"></li>
    </ul>
  </nav>

</body>
</html>

我们利用新的data-*属性来语义化地增强菜单第一级中的项目。我们稍后还将看到这些属性如何帮助我们更好地样式化这个结构。

现在让我们打开application.css,定义一个基本的 CSS 结构来居中这个菜单并添加一个漂亮的背景。对于项目的这一部分,我们不关注移动布局,所以我们可以使用经典的 960 像素方法:

/* === [BEGIN] Style === */
html{
  height: 100%;
}

body{
 background-image: repeating-linear-gradient(315deg, #ddd, #ddd 
40px, #aaa 40px, #aaa 80px);
  padding: 20px;
  height: 100%;
}

nav{
  margin: 0 auto;
  width: 960px;
  font-family: sans-serif;
  font-size: 0.6em;
  background-color: rgb(86,86,86);
  background-image: linear-gradient(bottom, rgb(75,75,75), 
rgb(86,86,86));
  border-radius: 4px;
  box-shadow: 0 0 10px rgba(0,0,0,0.1), 0 -1.5em 0 rgba(0,0,0,0.1) 
inset, 0 1px 1px 1px rgba(0,0,0,0.1) inset;
}

nav > ul{
  padding: 0 10px;
}

/* === [END] Style === */

上述代码中的高亮部分定义了一个折叠的渐变,以获得条纹背景。接下来我们将nav元素的大小定义为960px,并在其上添加一些漂亮的渐变、阴影和边框半径。

如果我们在支持 CSS3 的浏览器中加载项目,我们可以查看我们的第一个样式效果:

设置操作

样式化第一级项目

许多两级菜单的典型格式是在同一行上水平显示第一级项目,然后隐藏第二级项目。我们将添加一些 CSS 代码到application.css中来实现这一点,如下所示:

nav > ul > li{
 display: inline-block;
  vertical-align: top;
  line-height: 3em;
  width: 100px;
  z-index: 2;
  position: relative;
  border-left: 1px solid #313131;
  box-shadow: 1px 0 1px rgba(255,255,255,0.1) inset, -1px 0 1px 
rgba(255,255,255,0.1) inset;
}

nav > ul > li:nth-last-child(2){
  border-right: 1px solid #313131;
}

nav > ul > li > ul{
  position: absolute;
  left: -1px;
  top: 3em;
  clip: rect(0,0,0,0);
  opacity: 0;
}

使用 inline-block 显示

在上述代码中,我们使用了display: inline-block而不是通常使用的浮动元素。这两个属性通常用于将元素内联对齐,但不同之处在于display: inline-block不会破坏页面流,并且可以节省我们使用clearfix。然而,使用display: inline-block属性也有一个缺点。让我们在一个小的演示中看看:

<!doctype html>
<html>
  <head>
    <title>inline-block demo</title>
    <style>
      div{
        display: inline-block;
        width: 100px;
        border: 1px solid black;
        height: 30px;
        line-height: 30px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div> ONE </div>
    <div> TWO </div>
    <div> THREE </div><div> FOUR </div>
  </body>
</html>

如果我们在浏览器中加载我们的演示页面,结果如下:

使用 inline-block 显示

您会注意到THREEFOUR之间没有空格,但ONETWOTHREE之间有空格。为什么会这样?这是因为display: inline-block考虑了 HTML 标记中元素之间的空格。为了避免这个问题,我们将确保在每个元素之间有一致的空格或换行。

使用新的伪选择器

现在,让我们转到下一个有趣的指令:nth-last-child(2)。这是 CSS3 引入的许多新伪选择器之一。使用nth-last-child(n),我们可以定位从最后开始计数的第n个元素,而使用nth-child(n),我们可以从顶部开始做同样的事情。这两个伪选择器也可以用来通过某种模式选择元素。例如,假设我们只想突出显示以下列表的偶数元素:

<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
</ul>

我们可以通过以下简单的 CSS 代码实现这一点:

li:nth-child(2n){
  background: yellow;
}

如果我们想要只针对索引大于三的元素进行目标定位,我们可以使用以下 CSS:

li:nth-child(n+4){
  background: yellow;
}

以下截图显示了上一个示例的结果:

使用新的伪选择器

完成第一级

我们仍然需要添加一些 CSS 属性来完成我们第一级元素的样式:

nav .item{
  color: #fff;
  text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
  text-decoration: none;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  padding-left: 10px;
  white-space: nowrap;
  display: block;
  cursor: pointer;
}

干得好!现在让我们在支持 CSS3 的浏览器中运行项目,以欣赏结果:

完成第一级

样式化子菜单

现在我们必须为第二级项目设置样式。好吧,说实话,我们在上一节中已经隐藏了它们,以获得漂亮的一级样式,但现在我们可以用更多的属性丰富第二级元素,并确保当用户将鼠标悬停在它们的一级父级上时它们会显示出来。

让我们从刚讨论的最后一部分开始。为了显示第二级元素,我们必须使用:hover伪选择器:

nav > ul > li > .item:hover + ul, 
nav > ul > li > ul:hover{
  clip: auto;
  /* temporary property, to be removed */
  opacity: 1;
}

我们拦截了父级和所有子级的悬停,以便即使鼠标移动到它们上面,第二级菜单仍然显示。完成后,我们可以开始一些基本的样式:

nav > ul > li > ul{
  padding: 0.7em 0px;
  border-bottom-left-radius: 5px;
  border-bottom-right-radius: 5px;
  border-top: none;
 background-color: rgb(117,189,70);
 background-color: rgba(119,172,48, 0.8);
  background-image: linear-gradient(left, rgba(117,189,70,1), 
rgba(117,189,70, 0.0));
}

nav > ul > li > ul > li > .item{
  text-align: left;
  min-width: 100px;
  padding: 0px 10px;
  line-height: 2.5em;
}

nav > ul > li > ul > li{
  display: block;
  position: relative;
  z-index: 4;
}

这里只有一件小事要强调。在上一段代码的突出部分中,有一个简单的回退机制,用于不支持 CSS3 的浏览器。如果我们首先声明一个rgb 背景颜色值,然后是一个rgba值,我们可以确保不支持 CSS3 的浏览器应用rgb指令并跳过rgba指令,而支持 CSS3 的浏览器则用rgba指令覆盖rgb指令。

好了,是时候在我们首选的 CSS3 浏览器中重新加载项目并测试结果了:

样式化子菜单

在接下来的部分,我们将添加一些基本的 CSS 以响应鼠标移动。例如,当鼠标放在其父级一级菜单上时激活特定的子菜单。

移动部分

我们在第一级和第二级末尾添加了一个(尚未使用的)<li class="cursor">元素。我们想要创建的是一个能够在鼠标悬停在其上时移动到元素下方的块。这是一个很好的效果,为了实现它,我们将使用 CSS3 过渡。但首先让我们创建相同的效果,但没有动画:

nav > ul{
  position: relative;
}

nav li.cursor{
  position: absolute;
  background-color: #75BD46;
  text-indent: 900px;
  border: none;
  height: 3em;
  z-index: 1;
  left: 11px;
  clip: rect(0,0,0,0);
  box-shadow: 
    0px 0px 10px rgba(0,0,0,0.1), 
    0px -1.5em 0px rgba(0,0,0,0.1) inset, 
    0px 1px 1px 1px rgba(0,0,0,0.1) inset;
}
nav li.cursor a{
  display: none;
}

nav > ul > li > ul > li.cursor{
  height: 2.5em;
  left: 0px;
  width: 100%;
  bottom: 0.7em;
  box-shadow: none;
  background-image: none;
  background-color: rgb(165,204,60);
  background-color: rgba(165,204,60,0.7);
  z-index: 3;
}

nav > ul li:hover ~ li.cursor{
 clip: auto;
}

nav > ul > li:hover + li + li + li.cursor{
  left: 11px;
}

nav > ul > li:hover + li + li.cursor{
  left: 112px;
}

nav > ul > li:hover + li.cursor{
  left: 213px;
}

nav > ul > li > ul > li:hover + li + li + li.cursor{
  bottom: 5.7em;
}

nav > ul > li > ul > li:hover + li + li.cursor{
  bottom: 3.2em;
}

nav > ul > li > ul > li:hover + li.cursor{
  bottom: 0.7em;
}

nav li.cursor .item{
  display: none;
}

突出显示的代码显示了我们用来切换.cursor元素可见性的特殊选择器。基本上,如果鼠标悬停在前面的li元素之一上,我们就显示它。

接下来,我们必须定义.cursor元素的绝对位置,这显然取决于我们正在悬停的li元素。为了实现这种行为,我们使用+选择器来精确地将光标移动到元素下方。对于第二级元素也是如此。

如果我们在浏览器中运行项目,可能会感到失望。效果与仅使用:hover伪选择器改变li背景的效果完全相同。

移动部分

好了,是时候添加我们隐藏的成分:过渡效果了。

添加过渡效果

过渡背后的逻辑简单而强大。我们可以指示浏览器在两个不同的属性值之间创建动画。就是这样!我们可以使用transition属性来指定当另一个 CSS 属性发生变化时(例如width),元素不应立即从一个值切换到另一个值,而是花费一定的时间,从而在两个值之间创建动画。以下示例说明了这种效果:

<!doctype html>
<html>
  <head>
    <title>basic animation</title>
    <style>
      a{
        display: block;
        width: 300px;
        line-height: 100px;
        height: 100px;
        text-align: center;
        font-size: 50px;
        font-family: sans-serif;
        font-weight: bold;
        color: black;
        border: 10px solid black;
        text-decoration: none;
 transition: all 1s;
 -ms-transition: all 1s;
      }
      a:hover{
        color: red;
      }
    </style>
 <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <a href="#"> HOVER ME </a>
  </body>
</html>

all关键字告诉浏览器在所有支持过渡的属性上从一个属性变化到另一个属性时花费一秒钟。在这种情况下,当我们悬停在a元素上时,color属性从black变为red,但不是立即变化;相反,它在一秒钟内覆盖了从黑色到红色的所有颜色,产生了一个非常酷的效果。

添加过渡效果

我们可以用很多其他属性和很多其他方式来做这个技巧,正如我们将在本书的后面章节中看到的那样。目前,我们可以利用我们所学到的知识来增强我们的项目。让我们在application.css中添加一个transition语句:

nav li.cursor{
  -ms-transition: all 1s;
  transition: all 1s;
}

通过这个简单的属性,我们获得了一个全新的结果。现在每当我们悬停在一个元素上时,光标都会以非常平滑的动画移动到该元素下方。

添加转换

注意

在撰写本书时,prefixfree.js不支持 Internet Explorer 10 中的转换和动画。因此,我们必须记住添加带有-ms-实验性前缀的转换属性的副本。这很可能会在将来发生变化,这既是因为微软将删除实验性供应商前缀的需要,也是因为这个 JavaScript 库的新版本。

现在我们必须处理另一个问题。二级菜单出现得太早,效果不佳。我们如何延迟其出现,直到.cursor元素在li元素下方达到正确位置?我们将在下一节中看到这一点。

引入动画

动画是一步向前的转换。通过它们,我们可以详细控制一个或多个属性之间的过渡。动画由一组关键帧组成,其中每个关键帧基本上是声明我们选择的属性在动画的特定进度百分比时必须具有哪些值的一种方式。让我们通过以下示例来探索这个特性:

<!doctype html>
<html>
  <head>
    <title>basic animation</title>
    <style>
      div{
        position: absolute;
        top: 0px;
        left: 0px;
        width: 100px;
        height: 100px;
        border: 10px solid black;
        background-color: red;
        text-decoration: none;
        -ms-animation: fouredges 5s linear 2s infinite alternate;
 animation: fouredges 5s linear 2s infinite alternate;
      }

      @-ms-keyframes fouredges{
        0%   { top: 0px; left: 0px;}
        25%  { top: 0px; left: 100px;}
        50%  { top: 100px; left: 100px;}
        75%  { top: 100px; left: 0px;}
        100% { top: 0px; left: 0px;}
      }

 @keyframes fouredges{
 0%   { top: 0px; left: 0px;}
 25%  { top: 0px; left: 100px;}
 50%  { top: 100px; left: 100px;}
 75%  { top: 100px; left: 0px;}
 100% { top: 0px; left: 0px;}
 }

    </style>
    <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <div></div>
  </body>
</html>

通过@keyframes语句,我们定义了在从0%100%的过程中我们选择的一些属性的值。一旦这样做了,我们就可以使用animation属性以一些参数来定义,如下所示:

  • 第一个参数:它指定我们要在元素上执行的动画的名称(例如,在上面的代码中是fouredges)。

  • 第二个参数:它指定我们希望动画在单个循环中进行的总时间。

  • 第三个参数:它指定加速函数。基本上,我们可以决定元素是否应该以恒定的速度移动(使用关键字linear)或者在动画的开始或结束阶段加速(使用ease-inease-outease)。

  • 第四个参数:它指定我们希望应用于动画开始的延迟。

  • 第五个参数:它指定我们希望动画重复的次数。infinite是这个参数的有效值,以及正数。

  • 第六个参数:使用关键字alternate,我们可以要求浏览器切换动画的方向。换句话说,动画将首先从0%100%,然后从100%0%,再次循环。

如果我们在浏览器中尝试刚刚写的例子,我们会看到一个正方形沿着四个顶点路径移动:

引入动画

嗯,听起来很有趣,但这如何帮助我们的项目呢?简单!我们可以使用延迟动画(带有一些延迟的动画)来创建二级菜单的淡入效果。所以让我们移除之前添加的opacity: 1临时属性,并在application.css中添加一些 CSS:

nav > ul > li > .item:hover + ul, 
nav > ul > li > ul:hover{
 animation: fadein 0.1s linear 0.9s;
  -ms-animation: fadein 0.1s linear 0.9s;
 animation-fill-mode: forwards;
  -ms-animation-fill-mode: forwards;
}

@keyframes fadein{
  1% {
    opacity: 0.0;
  }

  100% {
    opacity: 1.0;
  }
}

@-ms-keyframes fadein{
  1% {
    opacity: 0.0;
  }

  100% {
    opacity: 1.0;
  }
}

animation-fill-mode: forwards属性告诉浏览器在动画结束时不要恢复到0%,而是保持100%位置。

有了这些新的附加功能,我们现在可以在浏览器中尝试一个几乎完整的桌面版本。享受光标动画和二级菜单的淡入效果。

然而,通过上面的代码,我们已经移除了不支持 CSS3 动画的浏览器的支持,特别是 IE9 及以下版本。为了解决这个问题,有很多技术,其中大部分将在本书的过程中揭示。我们将实现的第一种技术是通过用稍微复杂一些的东西替换<html>标签,如下所示:

<!--[if lte IE 9]> <html class="lteie9"> <![endif]-->
<!--[if !IE]> --> <html> <!-- <![endif]-->

通过使用条件注释,我们现在可以确定用户是否使用 IE9 或更低版本浏览我们的网站,因为新的.lteie9类被添加到html元素中。

因此,我们可以向我们的 CSS 文件添加一小段代码,只有在.lteie9存在时才会触发:

.lteie9 nav > ul > li > .item:hover + ul, 
.lteie9 nav > ul > li > ul:hover{
  opacity: 1;
}

添加颜色

我们可以根据鼠标悬停在哪个元素上轻松更改.cursor元素的颜色。由于我们的transition: all 1s属性,我们还将观察颜色如何逐渐变化,从而创建一个非常好的效果。

让我们向application.css添加一些属性,以更改.cursor元素的颜色,并向二级菜单添加一些颜色:

/* portfolio */
li[data-section=portfolio]:hover ~ li.cursor {
  background-color: #468DBD;
}

nav > ul > li[data-section=portfolio] > ul{
  background-color: rgb(70, 141, 189);
  background-color: rgba(60, 194, 204, 0.8);
  background-image: linear-gradient(left, rgba(70, 141, 189,1), 
rgba(70, 141, 189, 0.0));
}

nav > ul > li[data-section=portfolio] > ul > li.cursor{
  background-color: rgb(60, 194, 204);
  background-color: rgba(60, 194, 204, 0.7);
}

/* interests */
li[data-section=interests]:hover ~ li.cursor {
  background-color: #9E5CD0;
}

nav > ul > li[data-section=interests] > ul{
  background-color: rgb(158, 92, 208);
  background-color: rgba(186, 99, 195, 0.8);
  background-image: linear-gradient(left, rgba(158, 92, 208, 1), 
rgba(158, 92, 208, 0.0));
}

nav > ul > li[data-section=interests] > ul > li.cursor{
  background-color: rgb(186, 99, 195);
  background-color: rgba(186, 99, 195, 0.7);
}

在上面的代码中,我们针对三个不同的元素。首先是.cursor元素,当具有属性data-section-portfolioli元素处于状态:hover时,接下来是对应于具有属性data-section-portfolioli元素的二级菜单,最后是此二级菜单的.cursor元素。在这种情况下,利用data-*属性来语义化地标记菜单的每个项目特别有用。

让我们在浏览器中重新加载项目以查看和体验效果:

添加颜色

媒体查询

媒体查询是一种简单但非常强大的工具,可以根据某些浏览器和设备特性(如浏览器的视口大小、设备的宽度和高度以及方向)激活一些 CSS 属性。在深入细节之前,让我们编写一个小脚本来尝试这个功能:

<!doctype html>
<html>
  <head>
    <title>media queries</title>
    <style>
    ul{
      margin: 0;
      padding: 0;
    }
    li{
      list-style-type: none;
      border: 2px solid black;
      margin: 5px;
      padding: 0px 10px;
      display: inline-block;
    }

 @media screen and (max-width: 400px){
      li{
        line-height: 20px;
        text-align: center;
        display: block;
      }

    }
    </style>
  </head>
  <body>
    <ul>
      <li>one</li>
      <li>two</li>
      <li>three</li>
      <li>four</li>
      <li>five</li>
      <li>six</li>
      <li>seven</li>
      <li>eight</li>
      <li>nine</li>
      <li>ten</li>
      <li>eleven</li>
      <li>twelve</li>
    </ul>
  </body>
</html>

在这个例子中,我们指示浏览器仅在满足所表达的条件时应用@media大括号之间的属性。让我们来看看它们:

  • screen:此关键字是可用媒体类型之一,用于指示必须实现封闭语句的媒体类型。在专门的 W3C 规范中描述了许多媒体类型(www.w3.org/TR/CSS2/media.html#media-types),但只有少数几种(screenprintprojection)实际上受到今天浏览器的支持。

  • max-width:这是我们可以链接的许多条件关键字之一,以列出必须存在于设备中才能激活封闭语句的特征。max-width关键字可以解读为“最大为”,因此此条件在浏览器的视口大小超过给定值之前得到验证。

如果我们在兼容 CSS3 的浏览器中运行上面的代码,我们可以看到类似以下截图的内容:

媒体查询

但是,如果我们调整窗口大小到400px以下,媒体查询中的语句将被激活,结果将类似于以下截图:

媒体查询

很酷,不是吗?当然,除了max-width之外,还有其他条件关键字。让我们来看看它们:

  • min-width:此关键字可以解读为“视口宽度最小为 x”,其中 x 是分配给min-width属性的值。

  • max-heightmin-height:这些关键词的工作方式与*-width相同,但它们适用于浏览器的视口高度。

  • min-device-widthmax-device-widthmin-device-heightmax-device-height:这些关键字标识设备的实际尺寸;因此,如果我们只想针对大于 1900 x 1200 的屏幕进行定位,我们必须编写诸如(min-device-width: 1900px)(min-device-height: 1200px)的规则。

  • orientation:此属性的值可以是portraitlandscape。它标识设备的当前方向。

甚至还有更多这样的条件关键字,但是在上一个列表中不存在的关键字并不那么有用,而且目前还没有任何浏览器支持。无论如何,完整列表可以在www.w3.org/TR/css3-mediaqueries/#media1上查看。

我们还可以在<link>声明中使用media属性来定义媒体查询,如下所示:

<link rel="stylesheet" type="text/css" media="screen and 
(max-device-width: 480px)" href="css/small.css" />

在这种情况下,我们必须考虑不理解媒体查询语句的浏览器,因为它们将始终加载链接的 CSS,而不考虑条件。为了防止这种行为,至少在较旧版本的 Internet Explorer 中,我们可以使用条件注释将<link>元素包装起来:

<!-- [if gte IE 9]> -->
<link rel="stylesheet" type="text/css" media="screen and 
(max-width: 480px)" href="css/small.css" />
<!-- <![endif]-->

好的,现在我们知道媒体查询是如何工作的,但是我们如何使用此功能来针对移动设备呢?我们可以使用max-device-widthmax-width来做到这一点。

max-device-width属性检查设备的大小,这在桌面 Web 浏览器或笔记本电脑上模拟是困难的。使用此属性的另一个缺点是,我们不希望根据屏幕大小来更改布局;我们希望根据浏览器窗口的大小来更改布局。因此,首选属性是max-width,这是将为我们的菜单系统提供最大灵活性的行为。

现在我们已经选择了针对移动设备的行为,我们有另一个问题要解决。为了表示页面的桌面版本,然后让用户放大和缩小,移动设备会伪造它们的实际分辨率。为了强制移动浏览器暴露其真实尺寸并禁用缩放,我们可以使用<meta>标签。该标签基本上表示最大和最小缩放因子必须等于 1。在index.html文件中的<head>标签后面添加以下行:

<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1">

干得好!现在我们只需要找到要用作触发器以启用我们的“移动”CSS 的大小。我们将使用320px,这是 iPhone 在纵向模式下的大小。因此,让我们在css文件夹下创建一个新的application_mobile.css文件,并在index.html文件中的上一个link元素下面添加以下link元素:

<!-- [if gte IE 9]> -->
<link rel="stylesheet" type="text/css" media="screen and 
(max-width: 320px)" href="css/application_mobile.css"/>
<!-- <![endif]-->

为移动版本设置样式

现在我们准备开始为该项目的移动版本设置样式。为了实现这一点,我们将把菜单从水平形状转换为垂直形状。我们将不再使用二级菜单,而是创建一些卡片,并在单击相应的一级菜单项时使它们滑入。

因此,首先让我们编写必要的 CSS 来改变我们菜单的形状(在application_mobile.css中):

nav {
  width: 290px;
  height: 100%;
  font-size: 1em;
  text-align: center;
  border-radius: 0;
  box-shadow: 0 0 5px rgba(0,0,0,0.4);
  position: relative;
  overflow: hidden;
}

nav > ul{
  width: 290px;
  padding: 0;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
}

nav > ul > li{
  width: 100%;
  display: block;
  position: static;
  border-bottom: 1px solid #313131;
  box-shadow: 
    0 1px 1px rgba(255,255,255,0.1) inset, 
    0 -1px 1px rgba(255,255,255,0.1) inset, 
    0 -1.5em 0 rgba(0,0,0,0.1) inset;
}

nav > ul > li > .item {
  padding-right: 15px;
  position: relative;
 box-sizing: border-box;
  z-index: 1;
}

nav > ul > li > ul {
  display: block;
  padding: 0;
  padding-top: 3em;
  top: 0;
  left: 290px;
  height: 610px;
  width: 290px;
  clip: auto;
  opacity: 1;
 transition: left 1s;
  z-index: 2;
}

第一个突出显示的指令显示了我们如何利用一个非常有用的属性,称为box-sizing,它基本上表示在设置元素宽度时受影响的部分。选项如下:

  • content-box:在此选项中,宽度仅指围绕元素内容的框。填充、边距和边框宽度被排除。

  • padding-box:此选项与前一个选项相同,但这次宽度包括填充。边框和边距宽度仍然被排除。

  • border-box:此选项与前两个选项相同,但这次只有边距宽度被排除。

因此,当我们编写以下内容时,我们要求浏览器在100%宽度内处理 30px 的填充:

box-sizing: border-box;
width: 100%;
padding: 0px 15px;

如果我们现在尝试在移动浏览器中加载项目(例如,从 iPhone 模拟器中),或者如果我们将桌面浏览器窗口调整到 320px 以下,我们可以尝试这种布局:

为移动版本设置样式

我们已经在 CSS 代码的移动版本(application_mobile.css)中添加了属性transition: left 1s,所以我们需要做的就是在相应的一级菜单被点击时将二级菜单移动到left: 0px,以使其重叠在一级菜单上。为了实现这一点,我们可以利用:hover伪选择器,在移动环境中,当用户触摸元素时会触发。因此我们可以写如下:

nav > ul > li > .item:hover + ul{
 left: 0px;
 animation: none;
}

nav li.cursor{
  display:none;
  transition: none;
}

nav > ul > li > ul > li.cursor{
  display: block;
  top: 0px;
  text-indent: 0;
  left: 0px;
  line-height: 3em;
  height: 3em;
  clip: auto;
}

nav > ul > li > ul > li.cursor .item{
  display: block;
}

nav > ul > li > ul > li:first-child{
  border-top: 1px solid rgba(0,0,0,0.1);
}

nav > ul > li > ul > li{
  height: 3em;
  border-bottom: 1px solid rgba(0,0,0,0.1);
}

nav > ul > li > ul > li > .item{
  line-height: 3em;
  text-align: center;
}

最重要的语句是突出显示的那个;其他的只是为了调整一些细微的视觉细节。现在我们可以重新加载项目,欣赏我们刚刚编写的代码的效果:

为移动版本添加样式

在桌面浏览器上处理新布局

我们在最后一部分代码中所做的对移动设备有效,但在桌面浏览器上失败,因为:hover伪选择器的行为不同。尽管几乎不太可能有人会从宽度小于 320 像素的桌面计算机浏览器中探索这个项目,但我们可以使用一点 JavaScript 来解决这个问题。以下是要添加到index.html中的代码,放在</head>标签之前:

<script 
src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.
js"></script>
<script 
src="http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.5.3/
modernizr.min.js"></script>
<script>
  $(document).ready(function(){
  if(!Modernizr.touch){
    $('ul > li > .item').on('click', function(ev){
      $('ul > li').attr('data-status',null);
      $(ev.target).parent().attr('data-status','selected');
    });
    $('ul > li > ul > li > .item').on('click', function(ev){
      $(ev.target).parents('li[data-section]').
attr('data-status',null);
    });
  }
  });
</script>

通过这段代码,我们检查浏览器是否不支持触摸事件(因此不支持我们需要的:hover行为),然后,如果为真,我们为用户点击的一级菜单元素添加data-status='selected'属性。

为了实现这个结果,我们使用了一个非常有趣的库,我们将在下一章中详细介绍:Modernizr (modernizr.com/)。这个库包含一些方法,用于检查大多数 HTML5 和 CSS3 特性的存在(例如,Modernizr.touch),返回truefalse

此外,每个特性也以附加到html元素的类的形式表示。例如,如果支持触摸事件,html元素会接收touch类;否则,它会接收no-touch类。

完成这一步,我们需要做的就是将使用:hover的选择器限制为仅触摸设备,并处理新的data-status="selected"属性。为此,我们需要在application_mobile.css中稍微更改nav > ul > li > .item:hover + ul选择器,如下所示:

nav > ul > li[data-status="selected"] > .item + ul,
.touch nav > ul > li > .item:hover + ul

最终调整

现在我们可以通过:after:before伪选择器为这个项目添加一些更多的增强。所以让我们在application_mobile.css中添加这个最后的修饰:

nav > ul > li > .item:after, 
nav > ul > li > ul > li.cursor:before{
  content: '>';
  display: block;
  font-size: 1em;
  line-height: 3em;
  position: absolute;
  top: 0px;
  text-shadow: 1px 1px 0px rgba(0,0,0,0.5);
  font-weight: bold;
  color: #fff;
}

nav > ul > li > ul > li.cursor:before{
  content: '<';
  left: 15px;
}

nav > ul > li > .item:after{
  right: 15px;
}

每当我们使用 CSS 生成的内容时,我们必须记住,我们注入的内容不会被屏幕阅读器处理;因此,我们必须通过注入仅非必要内容(例如在这种情况下)或提供备用机制来处理。

好的,让我们最后一次在移动浏览器模拟器中重新加载项目,看最终结果:

最终调整

提高速度

如果我们想要改进“滑入”动画的速度,我们可以实施的最有效的变化之一是从背景中去除透明度。为此,我们必须在application_mobile.css中添加一些 CSS,以覆盖从桌面版本继承的设置:

nav > ul > li > ul{
  background-color: rgb(117,189,70);
  background-image: none;
}

nav > ul > li[data-section=interests] > ul{
  background-color: rgb(186, 99, 195);
}

nav > ul > li[data-section=portfolio] > ul{
  background-color: rgb(70, 141, 189);
}

在旧版浏览器中实现

我们在开发这个项目时非常小心,所以即使旧版浏览器不支持动画和渐变,基本结构仍然完美运行。以下是从 Internet Explorer 8 中截取的屏幕截图:

在旧版浏览器中实现

总结

在这一章中,我们尝试了媒体查询的强大功能,并开始探索动画和过渡效果。我们还发现了display:inline-block和浮动元素之间的区别,并开始收集一些关于移动性能的小贴士。当然,在接下来的章节中,我们会有时间深入了解这些新特性,发现许多其他有趣的 CSS3 属性。

然而,现在是时候翻开新的一页,开始着手处理一个涉及信息图表的新项目了!

第四章:缩放用户界面

在本章中,我们将学习如何创建一个简单的ZUI。这个缩放用户界面的首字母缩写代表缩放用户界面;一个用户可以改变所看区域的比例以查看更多或更少细节的图形环境。对于这个项目,我们将创建一个 ZUI,让用户移动和探索一个信息图表,这是数据、信息或知识的视觉图形表示。我们将要构建的项目结合了许多 CSS3 特性,如过渡、变换和灵活的盒子布局。它还介绍了 SVG 以及我们可以用来在 HTML 页面中嵌入它们的各种方法。此外,作为额外的挑战,我们还将使我们的页面在旧版浏览器上运行,并探索完成这项任务的巧妙方法。

以下是本章讨论的主题的预览:

  • 信息图表

  • 灵活的盒子布局

  • Polyfills

  • 嵌入 SVG

  • Modernizr

  • :target伪选择器

  • CSS3 变换

  • 用 CSS 定位 SVG

  • 优雅降级

信息图表

信息图表正在迅速改变我们消费信息的方式,通过创建图形表示来聚合数据或显示流程,并能够以非常直观和易于使用的方式显示大量知识。关于这个主题的一个很好的信息来源是 FlowingData 博客(flowingdata.com/)。

对于这个项目,我们将使用意大利公司 Oxigenio 创建的以下令人惊叹的信息图表(www.officinastrategia.it):

信息图表

我们希望为这个惊人的信息图表保留大部分浏览器视口区域,除了一个宽度为 200 像素的侧边栏,其中包含一些我们马上会看到的命令。首先让我们在一个index.html文件中定义一些基本的 HTML:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title> A ZUI for an infographic</title>
    <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.5.0/build/cssreset/
cssreset-min.css" data-noprefix>
    <link rel="stylesheet" type="text/css" 
href="css/application.css">

    <script src="img/modernizr.js"></script>
    <script src="img/prefixfree.js"></script>

  </head>
  <body>
    <section id="infographic">
      <header>
        <h1>a cool infographic</h1>
      </header>
      <article>

      </article>
    </section>
  </body>
</html>

对于这个项目,我们使用modernizr.jsprefixfree.js文件。因此,让我们在项目的根文件夹下创建一个js目录,并从它们各自的网站(modernizr.com/downloads/modernizr-latest.jsleaverou.github.com/prefixfree/)下载它们到那里。

接下来,我们需要准备一个css文件夹,并在其中创建一个空的application.css文件。

到目前为止,我们定义的 HTML 结构非常简单和极简:一个header元素和一个被section元素包围的article元素。现在我们想把header元素放在左侧,宽度固定为 200 像素,并告诉article元素覆盖屏幕的剩余部分。

我们可以通过各种技术实现这种元素布置。对于本书的目的,我们将使用 CSS3 灵活的盒子布局。

实现灵活的盒子布局

CSS2.1 定义了四种布局模式:块状、内联、表格和定位。CSS3 添加了一些新的布局模式,其中之一是灵活的盒子布局。这种新模式是通过我们可以给display语句的一个新值来激活的,并且可以通过一整套新的属性进行配置。

这种新布局模式背后的基本思想是,在容器元素(例如,我们的section元素)中,我们可以指定我们希望内部元素显示的方向。因此,如果我们说horizontal,那么元素将从左到右流动,如果我们说vertical,它们将依次从上到下排列。

然后我们可以通过使用固定尺寸或定义增长因子来决定每个元素的大小。

注意

当容器内有新空间可用时,元素会按照它们的增长因子成比例地增加宽度。

够说了!让我们创建一个小型演示来测试一下:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title> A ZUI for an infographic</title>
    <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.5.0/build/cssreset/
cssreset-min.css">

    <style>
      ul{
        width: 500px;
        height: 200px;
 display: box;
        counter-reset: anchors;
 box-orient: horizontal;
        border: 1px solid black;
      }
      li{
        text-align: center;
        line-height: 200px;
        display: block;
 box-flex: 1;
        counter-increment: anchors;
      }
      li:hover{
 box-flex: 2;
      }
      li:nth-child(2n){
        background: #ddd;
      }
      li:before{
        content: counter(anchors);
      }
    </style>

    <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <ul>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
      <li></li>
    </ul>
  </body>
</html>

我们可以看到ul元素内的li元素以相同的宽度开始,这恰好是包含元素宽度的五分之一。这是因为它们都具有相同的增长因子,由属性box-flex指定,这使它们平均分配可用空间。当我们将鼠标悬停在li元素上时,我们改变了元素的box-flex值;我们将鼠标悬停在2上,使其宽度是其他元素的两倍。以下是刚加载页面的屏幕截图:

实现弹性盒布局

以下是悬停在元素上时的屏幕截图:

实现弹性盒布局

通过将box-orient属性从horizontal更改为vertical,我们可以观察到在相反轴上的相同行为。由于这个特定示例的结构,我们还必须修改line-height以去除我们设置的200px高度:

ul{
  box-orient: vertical;
}
li{
  line-height: normal;
}

以下是显示结果的屏幕截图:

实现弹性盒布局

定义基本结构

现在我们已经有了创建项目结构的基础,我们需要在section元素内定义水平方向,然后将header元素的宽度设置为固定值。

我们已经在本章的第一部分创建了index.html HTML。现在让我们为了清晰起见再次重印body部分:

<body>
  <section id="infographic">
    <header>
      <h1>a cool infographic</h1>
    </header>
    <article>

    </article>
  </section>
</body>

我们可以开始将以下指令添加到application.css

html, body{
  height: 100%;
}
body{
  overflow: hidden;
font-family: sans-serif;
}
section{
 display: box;
 box-orient: horizontal;
  height: 100%;
  width: 100%;
  overflow: hidden;
}
header{
 width: 200px;
  background: rgb(181, 65, 71);
}
article{
  background-color: rgb(204, 204, 204);
  background-image: 
    repeating-linear-gradient(bottom left, rgb(204, 204, 204) 0px, 
    rgb(204, 204, 204) 20px, 
    rgb(210, 210, 210) 20px, rgb(210, 210, 210) 40px);
 box-flex: 1;
  overflow: hidden;
  position: relative;
}

我们在上一个示例中添加了更多的说明,因为我们还希望section元素覆盖整个浏览器视口。此外,我们应该防止显示垂直滚动条,因为唯一的导航机制必须是 ZUI 提供的。因此,我们在sectionarticle中都添加了overflow: hidden属性。

如果我们现在在支持 CSS3 的浏览器中加载项目,我们可以欣赏结果:

定义基本结构

注意

弹性盒布局模块规范正在迅速发展,目前没有一个 web 浏览器支持所有规范。我们的实现对应于 2009 年 7 月 23 日发布的以下文档:

www.w3.org/TR/2009/WD-css3-flexbox-20090723/

添加 Polyfills

自本书开始以来,我们首次使用 CSS3 来定义页面的结构。这意味着我们不能简单地依赖优雅降级来支持旧版浏览器,因为这会损害项目的整体结构。相反,我们将寻找一些能够模拟我们已实现行为的 JavaScript 库。当然,如果用户的浏览器缺少 JavaScript 支持和弹性盒布局,这可能会导致一些问题,但至少我们可以希望这样的用户数量非常少。

有不同类型的 JavaScript 库,根据需要多少额外工作来获得与原生实现相同的结果进行分类:

  • 通用库:通用库不允许开发人员获得完全相同的结果,但给他/她一些工具来编写解决方案的替代实现。

  • Shims:Shims 允许开发人员完美地模仿原生实现,但实现它需要额外的工作成本。

  • Polyfills:Polyfills 是最好的。这些库读取我们的代码,检测不支持的功能,并实现所需的 JavaScript 解决方法,而无需添加额外的代码。

我们需要找到一个模拟弹性盒布局模块的 polyfill。我们可以从以下页面开始搜索,这个页面是由 Modernizr 的作者创建和维护的,列出了他们测试过并发现有效的所有 polyfills:

github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills

在页面向下滚动后,我们找到了 Flexie,它声称为旧版浏览器(最多到 IE6)添加了对弹性盒布局的支持。我们所要做的就是将库flexie.js下载到我们的js文件夹中(它也可以从 GitHub 上获取,网址为github.com/doctyper/flexie,在src文件夹中)。

让我们通过在</body>标签之前添加以下行来修改我们的index.html文件:

<!-- Adding older browser's support -->
<script 
src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/
jquery.min.js"></script>
<script src="img/flexie.js"></script>

现在我们可以测试一下,看看是否一切顺利,加载我们的项目到不支持 CSS3 弹性盒布局的浏览器中。以下是从 IE8 中获取的屏幕截图:

添加 Polyfills

从输出中可以看出,没有条纹背景,但整体结构得到了很好的保留。

注意

向项目添加 Polyfill 不可避免地增加了其复杂性。Polyfills 几乎总是能够模拟它们为之构建的 CSS3 功能,但显然与原生实现有所不同。可能需要 Polyfill 向我们的页面注入额外的元素,或者添加 CSS 属性。因此,在开发页面时尽早添加这些库,并经常测试,以便捕捉开发页面和库之间的冲突,这是一个很好的经验法则。

嵌入 SVG

我们想要在支持的情况下使用可缩放矢量图形SVG)而不是光栅图像。我们正在构建一个 ZUI,因此我们的信息图表需要进行缩放,使用矢量图形可以保持对象的质量。事实上,矢量图像是大小独立的,因此在缩放时不会出现像素化。

注意

有关矢量图像和 SVG 的更多信息可以在维基百科上找到en.wikipedia.org/wiki/Vector_graphics

有三种嵌入 SVG 的方式:

  • 作为<object>元素。这是添加 SVG 的最受支持的方式。然而,它在某种程度上受到限制,因为 SVG 被视为外部元素,因此不能通过 JavaScript 进行操作(除了一些明显的属性,如widthheight)。

  • 作为 CSS 的值,需要图像的地方。

  • 直接在我们的 HTML 代码中。这种方法提供了 SVG 和页面之间最多的交互。正如我们将在本章后面看到的,我们可以直接从 CSS 或甚至从 JavaScript 与矢量图形进行交互。

让我们选择第三种方式,因为我们希望我们的 CSS 能够影响 SVG 图形的一部分。首先,让我们创建一个div元素,它将作为我们在本章前面创建的<article>中的 SVG 元素的容器:

<article>
<div class="panel">

  <!-- place here the svg content -->

</div>
</article>

接下来,我们可以使用 jQuery 从img文件夹直接加载 SVG 文件到我们刚刚创建的容器中,只需在我们之前编写的index.html文件的script标签后添加几行:

  <script>
    $(document).ready(function(){
      $('div.panel').load('img/infographic.svg' );
    });
  </script>
</body>

在这些行中,我们首先要求 jQuery 等待 DOM 准备就绪,然后将我们的 SVG 文件的内容加载到具有.panel类的div元素中。

现在我们可以添加一些 CSS 来使div元素在包含的article中垂直和水平居中。

这可能会很奇怪,因为只有 Webkit 浏览器和 IE9+似乎接受大小为100%的容器,所以我们必须区分这些浏览器和其他浏览器。因此,让我们在application.css中添加以下指令:

div.panel{
  width: 572px;
  height: 547px;
}

.-webkit- div.panel, 
.-ms- div.panel {
  width: 100%;
  height: 100%;
}

img.panel{
  display: block;
  position: absolute;
  top: 50%; left: 50%;
  margin-top: -282px;
  margin-left: -273px;
}

html:not(.-webkit-):not(.-ms-) div.panel{
  display: block;
  position: absolute;
  top: 50%; left: 50%;
  margin-top: -282px;
  margin-left: -273px;
}

我们现在已经涵盖了所有可能的情况:

  • 我们使用了 Prefix Free 的能力,向<html>元素添加了一个额外的类,以检测 Webkit 和 Microsoft 浏览器,并为这些浏览器设置容器大小为100%,以便获得尽可能大的 SVG 容器。

  • 如果浏览器不是前一项讨论中的浏览器之一,我们将 SVG 居中对齐并设置固定大小

  • 如果有图像而不是 SVG(我们马上会看到我们如何处理这个),我们基本上做与前一项相同的事情。

如果我们现在在浏览器中重新加载项目,我们可以看到 SVG 的显示:

嵌入 SVG

注意

由于我们使用了 AJAX,我们需要一个合适的 Web 服务器来尝试这个项目。只需双击index.html文件,不会生成预期的结果。请参考本书的前言部分,以获取有关如何安装 Web 服务器的更多信息。

当然,有些浏览器不支持 SVG。IE8 就是其中之一,因此我们需要找到一个解决方案,以便在这些浏览器上也能保持我们的项目愉快。

利用 Modernizr

在上一章中,我们已经对 Modernizr 有所了解,它是一个库,可以做很多事情,其中一些列在下面:

  • 它为旧浏览器添加了对新 HTML5 标签的支持。

  • 它在 JavaScript 中公开了一些方法,允许我们测试某个 CSS3/HTML5 功能。例如,Modernizr.multiplebg根据对多个背景的支持返回truefalse

  • 它向<html>元素添加了一些类,反映了对某些 CSS3/HTML5 功能的支持。例如,根据对多个背景的支持,是<html class="multiplebg">还是<html class="no-multiplebg">

我们已经将这个库添加到我们的项目中。但是,如果没有正确调整,Modernizr 会执行所有测试来检测支持的功能,即使我们不打算使用它们。为了增强库的性能,我们可以选择要执行哪些测试。

为此,我们必须单击 Modernizr 的下载页面(modernizr.com/download/),并仅检查我们将使用此库的功能。

对于这个项目,我们需要测试对内联 SVG 的支持。以下是屏幕截图,右侧的复选框已被选中:

利用 Modernizr

接下来,我们单击生成!按钮,然后单击下载按钮,以下载并覆盖我们项目中的modernizr.js文件。

我们现在可以检查我们项目的生成 HTML 代码,看看如果浏览器支持内联 SVG,则html元素如何被inlinesvg类丰富,否则是no-inlinesvg类。

提示

您可以使用浏览器的开发控制台检查生成的 HTML 代码。例如,如果使用 Google Chrome,按下Ctrl + Shift + I(在 Windows 和 Linux 上),或按下Command + Option + I(在 Mac 上)。

利用 Modernizr

我们现在要实现一个替代 SVG 图形的方法,使用普通图像;然后,通过利用 Modernizr 提供给我们的类,根据浏览器的支持切换其中一个。因此,让我们首先在index.html</article>标签之前添加一个小的 HTML 片段:

<img class="panel" src="img/infographic.png">

然后我们需要修改我们的application.css

.no-inlinesvg div.panel{
  display: none;
}

.inlinesvg img.panel{
  display: none;
}

如果我们现在在 IE8 中重新加载项目,我们可以看到一切都被正确处理了:

利用 Modernizr

:target 伪选择器

现在我们可以开始为我们的项目添加一些交互。我们希望在<header>侧边栏中公开一些控件,当单击时,可以缩放到信息图表的指定区域。

为了实现这一点,我们将利用一个新的 CSS3 伪选择器::target。当锚点成为当前 URL 的目标时,它会被激活。让我们创建一个小例子来尝试一下:

<!doctype html>
<html>
  <head>
    <meta charset="utf8">
    <title> :target test</title>
    <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.5.0/build/cssreset/
cssreset-min.css">

    <style>
    a[id]{
      display: block;
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      margin: 10px;
      background: gray;
    }
 a:target{
 background: yellow;
 }
    </style>

    <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <a href="#one"> light 1 </a>
    <a href="#two"> light 2 </a>
    <a id="one" name="one"> one </a>
    <a id="two" name="two"> two </a>
  </body>
</html>

在上面的例子中,我们基本上说当a元素成为当前 URL 的目标时,它的背景颜色必须变成黄色。下面的屏幕截图显示了结果(注意 URL):

:target 伪选择器

现在我们需要一组包含用户可以执行的命令的a元素。所以让我们在我们的index.html文件的header元素中添加一个nav元素:

<nav>
  <ul>
    <li><a href="#italy">Italy</a></li>
    <li><a href="#montreal">Montreal</a></li>
    <li><a href="#sanfrancisco">San Francisco</a></li>
    <li><a href="#">Whole view</a></li>
  </ul>
</nav>

接下来,我们可以在我们的application.css文件中使用一些 CSS 指令来为这些命令设置样式:

nav, ul, li{
  width: 100%;
}

h1{
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: -1px;
  font-weight: bold;
  line-height: 30px;
  text-align: center;
  padding: 10px 0 10px 0;
  color: rgb(255,255,255);
  background: rgb(85, 85, 85);
  margin-bottom: 10px;
}

li, li a{
  display: block;
  height: 30px;
  line-height: 30px;
}

li a{
  color: rgb(255,255,255);
  text-decoration: none;
  font-weight: bold;
  padding-left: 20px;
}

li a:hover{
  text-decoration: underline;
}

如果我们重新加载项目,我们可以看到结果:

伪选择器

添加一些锚点

现在我们需要放置a元素,这些元素是我们刚刚实现的命令的目标。这里有一个小技巧:如果我们将这些元素放在页面顶部然后隐藏它们,我们可以使用邻接选择器(+~)来匹配它们后面的元素,并能够虚拟地到达页面中的每个其他元素。

所以,让我们从在我们的index.html文件的body元素下方为我们指定的每个命令添加一个a元素开始:

<a id="italy" name="italy"></a>
<a id="montreal" name="montreal"></a>
<a id="sanfrancisco" name="sanfrancisco"></a>

好了!现在,如果我们想在单击Italy命令后更改header背景颜色,我们可以在我们的 CSS 中添加一行简单的代码:

a[id="italy"]:target ~ section header{
  background: green;
}

当然我们不想这样做,但是通过使用相同的原理,我们可以触发信息图的一些变化。首先我们必须学习有关变换的知识。

CSS3 变换

我们将探索一整套新的属性,目标是能够使用 CSS 任意缩放元素。这是我们需要学习的最后一个核心技术,涉及的属性被称为CSS3 变换

使用 CSS3 变换,我们可以对页面上的元素应用一些修饰符,即:

  • translateX(x), translateY(y), 和 translate(x,y): 这些修饰符通过由xy变量指定的距离沿一个或两个轴移动元素(以 px 为单位)

  • rotate(deg): 它通过由deg变量指定的值旋转元素,该值必须以度数表示(从 0 到 360 度)

  • scaleX(s), scaleY(s), 和 scale(s,[s]): 它通过比例因子s缩放元素,其中比例为1表示保持元素大小不变

  • skewX(k)skewY(k): 它通过给定的k角度(以度数表示,从 0 到 360 度)应用倾斜变换

还有一个接受六个参数并让我们定义一个变换矩阵的matrix修饰符。有关matrix修饰符的更多信息可以在www.w3.org/TR/SVG/coords.html#TransformMatrixDefined找到。

让我们在一个小演示中尝试这些修饰符:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>transform test</title>
    <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.5.0/build/cssreset/
cssreset-min.css">

    <style>
    div{
      width: 100px;
      height: 100px;
      background: green;
      margin: 30px auto;
    }
 div:first-child{
 transform: translateX(100px);
 }
 div:nth-child(2){
 transform: rotate(45deg);
 }
 div:nth-child(3){
 transform: scale(2);
 background: red;
 }
 div:nth-child(4){
 transform: skewX(45deg);
 }
 div:last-child{
 transform: skewY(45deg) scale(1.2) rotate(45deg);
 }
    </style>

    <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <div>translate</div>
    <div>rotate</div>
    <div>scale</div>
    <div>skew</div>
    <div>mixed</div>
  </body>
</html>

正如你所看到的,变换可以组合在一起以获得一些有趣的结果。以下是在符合 CSS3 标准的浏览器中运行此演示的屏幕截图:

CSS3 变换

另一个要注意的好功能是元素位置在应用变换之前进行计算。这一点的证明是缩放的div元素不会使其他元素向下移动,而只是重叠。

应用变换

现在我们只需要将我们刚学到的东西放在一起,并在点击其中一个命令时转换信息图。为了实现平滑的变换,让我们在application.css中的所有变换属性上指定一个1秒的过渡:

.panel{
    transition: transform 1s;
}

/*
Now we can add these few instructions to trigger the transform when corresponding anchor became target of the current URL:
*/

a[id='italy']:target ~ section div.panel { 
  transform: scale(2) translateY(15%);
  -ms-transform: scale(2) translateY(15%);
}

a[id='montreal']:target ~ section div.panel{
  transform: scale(1.8) translate(24%, -21%);
  -ms-transform: scale(1.8) translate(24%, -21%);
}

a[id='sanfrancisco']:target ~ section div.panel{
  transform: scale(1.8) translate(-24%, -21%);
  -ms-transform: scale(1.8) translate(-24%, -21%);
}

好了!让我们在浏览器中重新加载项目:

应用变换

闪烁问题

在撰写本文时,所有最新版本的 Chrome 浏览器(截至 18 版本)在应用某些 CSS 属性时会在 CPU 和 GPU 加速图形之间切换(其中包括过渡)。如果计算机处理不够快,这可能会在屏幕上产生闪烁。一种解决方法是在页面加载时强制 Chrome 应用 GPU 加速属性。在这种解决方案中,我们将在接下来的几章中看到的 3D 变换属性非常有用,因此我们可以向body元素添加一个空的translateZ属性,如下所示:

body{
  -webkit-transform: translateZ(0);
}

然而,我们必须记住,这种解决方案降低了 SVG 的质量,因为 Chrome 似乎不会在加速后对图形进行细化。此外,像我们刚刚使用的这样的 3D 变换属性在移动环境中应该谨慎对待,因为它们占用内存。

添加蒙版

我们可能想为每个可用的缩放区域添加一个小的描述蒙版。在蒙版中,我们还希望用户能够使用小箭头在缩放区域之间移动。

首先让我们定义所需的 HTML:将有四个蒙版,一个用于三个命令中的每一个,一个用于中心区域。我们可以在</section>标签之后添加所需的标记:

<div id="mask"> 
  <div data-detail="italy">
    <span>Help text. Click the arrows to explore more.</span>
    <menu>
      <a role="button" aria-label="move down" 
href="#italy2">&#x25BC;</a>
    </menu>
  </div>
  <div data-detail="italy2">
    <span>Help text. Click the arrows to explore more.</span>
    <menu>
      <a role="button" aria-label="move left" 
href="#montreal">>&#x25C4;</a>
      <a role="button" aria-label="move up" 
href="#italy">&#x25B2;</a><a role="button" aria-label="move right" href="#sanfrancisco">&#x25BA;</a>
    </menu>
  </div>
  <div data-detail="montreal">
    <span>Help text. Click the arrows to explore more.</span>
    <menu>
      <a role="button" aria-label="move right" 
href="#italy2">&#x25BA;</a>
    </menu>
  </div>
  <div data-detail="sanfrancisco">
    <span>Help text. Click the arrows to explore more.</span>
    <menu>
      <a role="button" aria-label="move left" 
href="#italy2">>&#x25C4;</a>
    </menu>
  </div>
</div>

现在我们必须将#mask元素放置在视口底线的下方,并在触发其中一个命令时激活它。因此,让我们在application.css中写入以下指令:

#mask{
  position: absolute;
 padding-top: 5px;
  font-size: 18px; 
  font-weight: bold;
 height: 50px;
  color: rgb(255,255,255);
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.8);
  text-align: center;
 bottom: -55px;
  left: 201px;
  right: 0;
}

#mask menu{
  position: absolute;
  padding: 0; margin: 0;
  bottom: 4px;
  left: 0;
  right: 0;
  text-align: center;
}

#mask div{
 display: none;
}

#mask a{
  text-decoration: none;
  color: rgb(255,255,255);
  padding: 0 10px;
}

a[id='montreal']:target ~ #mask div[data-detail="montreal"],
a[id='italy2']:target ~ #mask div[data-detail="italy2"],
a[id='italy']:target ~ #mask div[data-detail="italy"],
a[id='sanfrancisco']:target ~ #mask 
div[data-detail="sanfrancisco"]{
 display: block;
}

a[id='italy']:target ~ #mask,
a[id='italy2']:target ~ #mask,
a[id='montreal']:target ~ #mask,
a[id='sanfrancisco']:target ~ #mask{
 transition: bottom 1s;
 bottom: 0;
}

在代码的突出部分,我们指示浏览器:

  • 隐藏#mask元素在浏览器底线以下

  • 隐藏#mask元素内的所有div元素

  • 仅显示与目标a元素对应的#mask元素内的div元素

  • a元素是:target时,显示#mask元素

现在我们需要处理italy2锚点。因此,让我们在index.html中的<section>之前再添加一个a元素:

<a id="italy2" name="italy2"></a>

以及在application.css中对应的 CSS:

a[id='italy2']:target ~ section div.panel{
  transform: scale(2) translateY(-15%);
  -ms-transform: scale(2) translateY(-15%);
}

干得好!现在让我们在浏览器中重新加载项目:

添加蒙版

用 CSS 定位 SVG

好的,是时候做最后的修饰了。现在我们想要的是提供一个机制来切换信息图表标签的可见性。由于我们的 SVG 是内联的,我们可以通过简单地向它们的id选择器添加opacity: 0来关闭它们,就像我们对普通 HTML 元素所做的那样。因此,让我们在application.css中添加以下行:

#Layer_2{ /* this id is present within the SVG */
  opacity: 0;
  transition: opacity 1s;
}

下一步是找到一种让用户切换opacity值的方法。我们可以使用复选框来实现这个结果,并利用:checked伪选择器,就像我们使用:target一样。

因此,首先让我们在index.html文件中的<section>标签之前添加一个复选框:

<input type="checkbox" id="show_labels" name="show_labels">

然后,在nav命令的</ul>标签之前添加相应的标签:

<li><label for="show_labels"></label></li>

现在在application.css中添加以下行:

#show_labels{
 display: none;
}

nav label:before{
 content: 'Click to show labels';
}

#show_labels:checked ~ section label:before{
 content: 'Click to hide labels';
}

#show_labels:checked ~ section #Layer_2{
 opacity: 1;
}

label{
  text-align: left;
  font-family: sans-serif;
  padding-left: 20px;
  font-size: 13px;
  cursor: pointer;
}

nav label{
  display: block;
  height: 30px;
  line-height: 30px;
}

li:not(:nth-last-child(2)) a:after{
  content: " \00BB";
}

li:nth-last-child(2) a:before{
  content: "\00AB";
}

以下是我们项目的最终截图:

用 CSS 定位 SVG

优雅降级

因为我们添加了 CSS 变换,旧版浏览器开始出现问题。事实上,旧版浏览器不支持变换和:target伪选择器,因此我们必须找到一个有效的替代方案。一个解决方案可以是通过 JavaScript 监听 URL 哈希变化,并使用hashchange事件将当前哈希值反映到section#mask元素的类中。然后可以使用这个类来触发一些 CSS 属性。

为了能够在旧版浏览器上监听hashchange事件,我们需要一个小的 JavaScript 库。我们可以从benalman.com/code/projects/jquery-hashchange/docs/files/jquery-ba-hashchange-js.html下载它,将其重命名为jquery.hashchange.js,并放置在我们的js文件夹中。接下来,我们必须用一个包含多重背景测试的新版本替换我们的 Modernizr 副本(js/modernizr.js)。为了实现这一点,我们可以使用与之前讨论过的相同的过程。

现在我们需要插入这个库,然后在</body>标签之前添加一些小的 JavaScript 代码:

<script src="img/jquery.hashchange.js"></script>
<script>
  $(document).ready(function(){
/* we check for multiblegbs support because browsers who do support multiple backgrounds surely support also the features we need */
    if(!Modernizr.multiplebgs){
      if(window.location.hash.substring(1) != "")
        window.location.href = window.location.href.replace(window.location.hash,'');
      jQuery(window).hashchange(function(e){
 $('section, #mask').removeClass().addClass(window.location.hash.substring(1));
      });
    }
  });
</script>

好了!现在我们可以通过改变img.panel元素的宽度、高度和位置来模拟transform属性。此外,我们还可以使用 JavaScript 动态添加的类来显示和隐藏#mask元素。

.no-inlinesvg #mask{
  left: 0px;
}

.no-inlinesvg label{
  display: none;
}

#mask.montreal, #mask.sanfrancisco, #mask.italy, #mask.italy2{
  bottom: 0px;
}

#mask.montreal div[data-detail="montreal"], 
#mask.italy2 div[data-detail="italy2"], 
#mask.italy div[data-detail="italy"], 
#mask.sanfrancisco div[data-detail="sanfrancisco"]{
 display: block;
}

section.italy img.panel{
 top: 60%; left: 25%;
 width: 1000px;
 height: 1000px;
}

section.italy2 img.panel{
 top: 0%; left: 25%;
 width: 1000px;
 height: 1000px;
}

section.montreal img.panel{
 top: -10%; left: 50%;
 width: 1000px;
 height: 1000px;
}

section.sanfrancisco img.panel{
 top: -10%; left: 0%;
 width: 1000px;
 height: 1000px;
}

以下屏幕截图显示了最终结果:

优雅降级

总结

在本章中,我们学习了如何处理对我们页面结构产生影响的 CSS3 属性。我们还发现了转换,以及一些与 SVG 互动的酷炫方式。在下一章中,我们将讨论如何增强图库。

第五章:图库

图库现在是网站的常见组件。在本章中,我们将发现如何使用CSS 属性实现一系列过渡效果和几种导航模式。我们将首先实现一个基本的过渡效果,使用一系列图像,然后我们将开发一个纯 CSS 结构,让用户选择他喜欢的导航模式和过渡效果,最后,我们将添加更复杂的过渡效果。以下是本章将涵盖的主题列表:

  • 基本图库 HTML 结构

  • 实现不透明度过渡

  • 实现幻灯片过渡

  • 3D 变换

  • 添加幻灯片模式

  • 创建上一个和下一个箭头

  • CSS 预处理器

准备结构

与前几章一样,我们首先定义一个基本的 HTML 结构,然后在此基础上构建我们的项目。所以让我们为这个项目创建一个新的文件夹,其中包含一个名为index.html的文件,其中包含以下代码:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>A 3D Gallery</title>
    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.7.3/build/cssreset/cssreset-min.css">
    <link rel="stylesheet" type="text/css" href="css/application.css">
    <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <div>
      choose effect:
      <input type="radio" name="mode" id="opacity" checked >
      <label for="opacity">opacity</label>
      <input type="radio" name="mode" id="slidein">
      <label for="slidein">slidein</label>
      <input type="radio" name="mode" id="cube" >
      <label for="cube">cube</label>
      <br>
      choose mode:
      <input type="radio" name="controls" id="animate">
      <label for="animate">animate</label>
      <input type="radio" name="controls" id="bullets" checked>
      <label for="bullets">bullets</label>
      <input type="radio" name="controls" id="arrows">
      <label for="arrows">arrows</label>

      <a id="picture1" name="picture1"></a>
      <a id="picture2" name="picture2"></a>
      <a id="picture3" name="picture3"></a>
      <a id="picture4" name="picture4"></a>
      <a id="picture5" name="picture5"></a>
      <section>
        <ul>
          <li>
            <figure id="shot1"></figure>
          </li>
          <li>
            <figure id="shot2"></figure>
          </li>
          <li>
            <figure id="shot3"></figure>
          </li>
          <li>
            <figure id="shot4"></figure>
          </li>
          <li>
            <figure id="shot5"></figure>
          </li>
        </ul>
        <span>
          <a href="#picture1" ></a>
          <a href="#picture2" ></a>
          <a href="#picture3" ></a>
          <a href="#picture4" ></a>
          <a href="#picture5" ></a>
        </span>
      </section>
    </div>
  </body>
</hthiml>

与前几章一样,我们使用 Yahoo!重置 CSS 样式表以及 Lea Verou 的 Prefix Free 库。您可以从上一章的示例中复制prefixfree.js,或者从leaverou.github.com/prefixfree/下载它。

我们定义的结构包含一些单选按钮,分为modecontrols两组。在这个项目中,我们将学习如何改变我们的图库的行为,以反映我们的用户所做的选择。首先要实现的默认设置涉及不透明度过渡和基于项目符号的导航系统。

接下来有与我们想要显示的图像数量相等的锚点。然后,在一个section元素内,我们为每个图像有一个figure元素,并且有一个指向先前定义的锚点的a元素。

我们要实现的内容是在按下相应的a元素时激活特定图像。为此,我们将使用已经介绍的:target伪选择器与其他一些小技巧结合使用,但首先我们必须花一点时间定义基本的 CSS 结构。

应用基本 CSS

首先,我们必须将我们的项目居中在浏览器的视口中,然后稍微设计一下单选按钮。为此,我们在application.css中写入几行 CSS,如下所示:

/* == [BEGIN] General == */

body,html{
  height: 100%;
  background-image: radial-gradient(center center, white, gray);
}
body > div{
  position: absolute;
  width: 500px;
  height: 400px;
  top: 50%; left: 50%;
  margin-left: -250px;
  margin-top: -200px;
  text-align: center;
  font-family: sans-serif;
  font-size: 13px;
  color: #444;
  line-height: 1.5;
}

section{
  margin-top: 20px;
  width: 500px;
  height: 390px;
  position: relative;
}

section > ul{
  width: 500px;
  height: 390px;
  position: relative;
}

input{
  width: 20px;
}

/* == [END] General == */

好了!现在让我们为每个figure元素分配相应的图像:

/* == [BEGIN] Pics == */

section figure {
  position: absolute;
  top: 0px; left: 0px;
  width: 500px; height: 370px;
  padding: 0px; margin: 0px;
  background-position: center center;
}

#shot1{
  background-image: url('../img/picture1.jpg');
}
#shot2{
  background-image: url('../img/picture2.jpg');
}

#shot3{
  background-image: url('../img/picture3.jpg');
}

#shot4{
  background-image: url('../img/picture4.jpg');
}

#shot5{
  background-image: url('../img/picture5.jpg');
}

/* == [END] Pics == */

注意

请注意,在实际示例中,我们可能会通过style属性动态插入这些图像。

现在我们可以使用符合 CSS3 标准的浏览器测试此设置阶段的成功。在这一点上,我们还没有为单选按钮添加任何行为,所以我们只期望在#shot5中看到图像,而没有任何交互或动画。

应用基本 CSS

样式化项目符号

让我们开始为a元素应用一些样式。我们首先创建了项目符号,因为它们是默认表示。我们的项目符号将显示为一组空心的可点击圆圈,就像在线幻灯片中经常发现的那样。我们可以为这些圆圈使用一些圆角边框,并在元素被点击时应用background规则。为了拦截这种状态,我们将在页面顶部插入的相应a元素上使用:target伪选择器。

/* == [BEGIN] Span == */

section > span > a{
 display: inline-block;
  text-decoration: none;
  color: black;
  font-size: 1px;
  padding: 3px;
  border: 1px solid black;
  border-radius: 4px;
  font-weight: bold;
}

section > span{
  position: absolute;
  bottom: 0px;
  left: 0px;
  right: 0px;
  text-align: center;
}

a[name=picture1]:target ~ section a[href="#picture1"],
a[name=picture2]:target ~ section a[href="#picture2"],
a[name=picture3]:target ~ section a[href="#picture3"],
a[name=picture4]:target ~ section a[href="#picture4"],
a[name=picture5]:target ~ section a[href="#picture5"]{
 background: #111;
}

/* == [END] Span == */

我们决定将项目符号设置为display:inline-block,以便从此属性在元素标签之间留下一些空间时注入的空间中受益,就像我们在第三章中看到的那样,Omni 菜单

接下来,我们使用:target伪选择器与相邻选择器~结合使用,定义一个规则,匹配指向当前锚点的项目符号。

现在一切准备就绪,我们可以开始处理我们的第一个过渡效果:不透明度。

实现不透明度过渡

透明度效果是最简单的,我们只需要通过opacity:0属性隐藏所有元素,除了对应于点击的子弹的元素。为了获得一个漂亮的淡出效果,我们可以使用transition属性指定两种状态之间的过渡期。

我们必须在这里实现的一个技巧是,只有在我们的设置面板中选择了opacity单选按钮时,才能附加这种行为。为了实现这一点,我们可以在规则之前放置另一个选择器#opacity:checked

/* == [BEGIN] Opacity == */

#opacity:checked ~ section figure{
  opacity: 0;
  transition: opacity 0.4s;
}

#opacity:checked ~ a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) ~ section #shot1,
#opacity:checked ~ a[name=picture1]:target ~ section #shot1,
#opacity:checked ~ a[name=picture2]:target ~ section #shot2,
#opacity:checked ~ a[name=picture3]:target ~ section #shot3,
#opacity:checked ~ a[name=picture4]:target ~ section #shot4,
#opacity:checked ~ a[name=picture5]:target ~ section #shot5{
  opacity: 1;
}

/* == [END] Opacity == */

我们基本上使用了与之前相同的技巧,再加上一个规则,如果没有选择任何子弹,则将opacity:1设置为第一张图像。为了实现这一点,我们使用+选择器来具体匹配五个连续的不是:targeta元素。

干得好!如果我们在浏览器中运行项目,我们可以测试效果,并注意到这只有在对应的单选按钮被选中时才会起作用。

实现透明度过渡

在继续之前的最后一点,我们为这个项目创建的选择器非常复杂,如果在大型应用程序中广泛使用,可能会引入性能问题。

是时候实现一个新的效果了:滑动!

实现滑动过渡

滑动效果基本上是一个过渡,其中一个元素在用户视图之外移动,向一个方向滑动,而另一个元素在移动。为了实现这种效果,我们必须处理两种不同的动画:滑入和滑出。使这种效果起作用的基本思想与之前的类似,尽管稍微复杂一些。为了实现滑入效果,我们必须将所有图片移出部分视口,比如left:-500px,然后,当对应的子弹被点击时,取出选定的图片,并使用一个动画将其移动到相反的一侧(left:500px),然后将其移动到正确的位置(left:0)。

为了实现滑动效果,我们可以使用另一个动画,从left:0pxleft:-500px开始。以下是完整的 CSS 片段:

/* == [BEGIN] Slide In == */

#slidein:checked ~ section > ul{
 overflow:hidden;
}

#slidein:checked ~ section figure{
  left: -500px;
  animation-name: slideout;
  animation-duration: 1.5s;
}

#slidein:checked ~ a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) ~ section #shot1,
#slidein:checked ~ a[name=picture1]:target ~ section #shot1,
#slidein:checked ~ a[name=picture2]:target ~ section #shot2,
#slidein:checked ~ a[name=picture3]:target ~ section #shot3,
#slidein:checked ~ a[name=picture4]:target ~ section #shot4,
#slidein:checked ~ a[name=picture5]:target ~ section #shot5{
  animation-name: slidein; 
  animation-duration: 1.5s;
  left: 0px;
}

@keyframes slidein{
 0% { left: 500px; }
 100% { left: 0px; }
}

@keyframes slideout{
 0% { left: 0px; }
 100% { left: -500px; }
}

/* == [END] Slide In == */

我们使用overflow:hidden来隐藏部分视口外的图像。slideout动画被添加到除选定元素之外的所有元素,因此当一个元素退出选定状态时,动画被激活并将元素平滑地移动到left:-500px

以下是从支持 CSS3 的浏览器(例如 Chrome,Firefox,IE10 等)中截取的屏幕截图:

实现滑动过渡

现在我们准备编写第三个过渡效果的代码:立方体!但首先,为了更好地理解下一步,让我们花一些时间介绍 3D 变换的基础知识。

3D 变换

3D 变换在设计网站方面引入了一个重大飞跃。我们现在可以尝试在 3D 空间中移动和动画化元素,如divimg,甚至video,这些都受益于 GPU 加速(对于大多数浏览器)。一旦我们决定引入 3D 效果,我们必须处理的第一件事是透视

我们为perspective属性设置的值告诉浏览器如何渲染在 z 轴上位置为 0(或未设置)的元素。例如,perspective:300px意味着 z=0(或未设置)的元素被绘制得好像它离视口有 300 像素远。当然,这会影响元素在旋转时的渲染方式。

接下来是一个有用的属性,其目的是告诉浏览器应用 3D 变换。这个属性叫做transform-style,它的值可以是flatpreserve-3d。当值为flat时,具有影响 x 或 y 轴旋转的变换的元素没有透视,但当值为preserve-3d时,它们实际上表现得像真正的 3D 表面。这个属性也适用于所有元素的子元素。

最后是变换。这里要使用的属性与 2D 变换相同,是transform,但有一些新的关键字可以作为值选择。

变换原点默认设置为 z = 0 的元素中心,但可以使用transform-origin属性进行调整。

有了这些概念,我们可以开始定义立方体效果,它基本上与滑动效果相同,但当然要利用 3D 变换机制。

/* == [BEGIN] Cube == */

#cube:checked ~ section{
 perspective: 500px;
}

#cube:checked ~ section > ul{
 transform-style: preserve-3d;
}

#cube:checked ~ section figure{
 transform-origin: 250px 185px -250px;
 backface-visibility: hidden;
  transform: rotateY(-90deg);
  animation-name: cubeout;
  animation-duration: 1.5s;

}

#cube:checked ~ a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) ~ section #shot1,
#cube:checked ~ a[name=picture1]:target ~ section #shot1,
#cube:checked ~ a[name=picture2]:target ~ section #shot2,
#cube:checked ~ a[name=picture3]:target ~ section #shot3,
#cube:checked ~ a[name=picture4]:target ~ section #shot4,
#cube:checked ~ a[name=picture5]:target ~ section #shot5{
  animation-name: cubein;
  animation-duration: 1.5s;
  transform: rotateY(0deg);
}

@keyframes cubein{
  0%   { transform: rotateY(90deg); }
  100% { transform: rotateY(0deg); }
}

@keyframes cubeout{
  0%   { transform: rotateY(0deg); }
  100% { transform: rotateY(-90deg); }
}

/* == [END] Cube == */

我们将perspectivetransform-style设置为要进行变换的父元素。然后我们定义一个原点,它位于figure元素的中心,但从视口中偏移了 250 像素。

然后我们应用绕 y 轴旋转的变换,使用与我们之前使用slidein动画相同的机制。

最后,我们告诉浏览器在图片旋转到用户视角的相反方向时不显示图片。这是通过backface-visibility: hidden语句实现的。

在浏览器中快速刷新,结果如下:

3D 变换

注意

如果运行浏览器的 PC 硬件没有 GPU,Chrome 会自动禁用 3D 效果。要检查是否触发了这种行为,可以在地址栏中输入about:GPU

添加幻灯片放映模式

现在我们准备实现剩下的两种模式:幻灯片放映和箭头。让我们从幻灯片放映开始。在这里,我们只需为每种效果(不透明度滑入立方体)定义一个动画,并触发它,注意为每个figure元素指定不同的延迟(使用animation-delay属性)。

让我们从最后一部分开始,为每个figure元素定义不同的延迟:

/* == [BEGIN] Animation == */

#animate:checked ~ section #shot1{
  animation-delay: 0s;
}

#animate:checked ~ section #shot2{
  animation-delay: 2.5s;
}

#animate:checked ~ section #shot3{
  animation-delay: 5s;
}

#animate:checked ~ section #shot4{
  animation-delay: 7.5s;
}

#animate:checked ~ section #shot5{
  animation-delay: 10s;
}

#animate:checked ~ section span{
 display: none;
}

如果每个动画持续 4 秒(1.5 秒用于动画进入,1 秒保持不动,1.5 秒用于动画退出),我们需要第二个figure元素在 2.5 秒后开始,正好在第一个元素开始退出动画时。在本章后面,我们将学习如何使此 CSS 代码适应不同数量的图片。

然后我们为剩下的figure元素重复这一步骤,并得到之前的代码。

高亮部分用于隐藏子弹,因为在幻灯片放映期间它们是不必要的。

好了!现在我们要写动画。让我们从不透明度动画开始:

/* opacity animation */
#opacity:checked ~ #animate:checked ~ section #shot1,
#opacity:checked ~ #animate:checked ~ section #shot2,
#opacity:checked ~ #animate:checked ~ section #shot3,
#opacity:checked ~ #animate:checked ~ section #shot4,
#opacity:checked ~ #animate:checked ~ section #shot5{
  opacity: 0;
 animation-name: opacity;
 animation-duration: 12.5s;
 animation-iteration-count: infinite;
}

@keyframes opacity{
  0%    { opacity: 0; }
  12%   { opacity: 1; }
  20%   { opacity: 1; }
  32%   { opacity: 0; }
  100%  { opacity: 0; }
}

我们必须检查不透明度动画单选按钮是否都被选中。在这种状态下,我们可以将动画设置为opacity,并选择持续时间为最后一个figure元素#shot5animation-delay属性的值(10 秒)加上其动画时间(4 秒),减去此动画与上一个动画重叠的时间(1.5 秒)。

接下来,我们定义一些关键帧,将时间转换为百分比(例如,12.5 秒的 12% = 1.5 秒)。

我们也可以轻松地将此行为扩展到剩下的两个动画,如下所示:

  • 对于滑动效果,我们从可见区域外开始,然后将其移动直到完全可见。最后,一段时间后,我们再次将其移出可见区域,但是从另一侧。
/* slide animation */
#slidein:checked ~ #animate:checked ~ section #shot1,
#slidein:checked ~ #animate:checked ~ section #shot2,
#slidein:checked ~ #animate:checked ~ section #shot3,
#slidein:checked ~ #animate:checked ~ section #shot4,
#slidein:checked ~ #animate:checked ~ section #shot5{
  left: -500px;
  animation-name: slide;
  animation-duration: 12.5s;
  animation-iteration-count: infinite;
}

@keyframes slide{
  0%    { left: 500px; }
  12%   { left: 0px;   }
  20%   { left: 0px;   }
  32%  { left: -500px;}
  100%  { left: -500px;}
}
  • 对于旋转立方体效果,我们基本上做同样的事情,但是不使用left属性,而是使用transform: rotate(),而不是将图片滑入(-500 像素,然后 0 像素,最后 500 像素),我们旋转立方体(90 度,然后 0 度,最后-90 度)。
/* cube animation */
#cube:checked ~ #animate:checked ~ section #shot1,
#cube:checked ~ #animate:checked ~ section #shot2,
#cube:checked ~ #animate:checked ~ section #shot3,
#cube:checked ~ #animate:checked ~ section #shot4,
#cube:checked ~ #animate:checked ~ section #shot5{
  transform: rotateY(-90deg);
  transition: none;
  animation-name: cube;
  animation-duration: 12.5s;
  animation-iteration-count: infinite;
}

@keyframes cube{
  0%    { transform: rotateY(90deg); }
  12%   { transform: rotateY(0deg);  }
  20%   { transform: rotateY(0deg);  }
  32%  { transform: rotateY(-90deg);}
  100%  { transform: rotateY(-90deg);}
}

/* == [END] Animation == */

上一页和下一页箭头

好的,接下来是最棘手的部分:创建箭头。为了完成这个任务,我们要做的是:

  1. 使用 CSS 将每个子弹转换为箭头符号,改变其形状并使用漂亮的背景图像。

  2. 将所有箭头移动到图片的左侧,依次排列。这样,唯一可见的箭头将是与最高索引图片对应的箭头。

  3. 隐藏与所选图片对应的箭头。

  4. 将所有跟随所选图片对应的箭头移动到右侧,一个在另一个上面。这样,左侧将只保留那些对应于所选图片索引低于所选图片的箭头(例如,如果我们选择第三张图片,只有第一张和第二张图片的箭头会留在左侧,第二张图片的箭头会位于堆栈的顶部)。

  5. 选择跟随所选图片的箭头,并更改其z-index值,以将其放在右侧堆栈的顶部。

以下是相应的 CSS 代码:

/* == [BEGIN] Arrows == */

#arrows:checked ~ section span{
  position: static;
}

/* step 1 and 2: transform each bullet in an arrow sign and move all the arrows to the left of the picture */
#arrows:checked ~ section a{
  display: block;
  width: 50px; height: 50px;
  background-image: url('../img/freccie.png');
  background-repeat: no-repeat;
  background-color: #000;
  background-position: -50px 0px;
  position: absolute;
  padding: 0;
  top: 50%;
  margin-top: -25px;
  margin-left: -70px;
  left: 0;
}

#arrows:checked ~ section a:hover{
  background-color: #333;
}

/* step 3: hide the arrow corresponding to the selected image */
#arrows:checked ~ a[name=picture1]:target ~ section a[href="#picture1"],
#arrows:checked ~ a[name=picture2]:target ~ section a[href="#picture2"],
#arrows:checked ~ a[name=picture3]:target ~ section a[href="#picture3"],
#arrows:checked ~ a[name=picture4]:target ~ section a[href="#picture4"],
#arrows:checked ~ a[name=picture5]:target ~ section a[href="#picture5"]{
 display: none;
}

/* step 4: Move all the arrows that follow the one corresponding to the selected image to the right, one above another */
#arrows:checked ~ a[name=picture1]:target ~ section a[href="#picture1"] ~ a,
#arrows:checked ~ a[name=picture2]:target ~ section a[href="#picture2"] ~ a,
#arrows:checked ~ a[name=picture3]:target ~ section a[href="#picture3"] ~ a,
#arrows:checked ~ a[name=picture4]:target ~ section a[href="#picture4"] ~ a,
#arrows:checked ~ a[name=picture5]:target ~ section a[href="#picture5"] ~ a{
 display: block;
 position: absolute;
 margin-right: -70px;
 right: 0;
 left: auto;
}
/* step 5: Pick the arrow that follows the one corresponding to the selected image and change its z-index in order to put it on top of the right stack */
#arrows:checked ~ a[name=picture1]:target ~ section a[href="#picture1"] + a,
#arrows:checked ~ a[name=picture2]:target ~ section a[href="#picture2"] + a,
#arrows:checked ~ a[name=picture3]:target ~ section a[href="#picture3"] + a,
#arrows:checked ~ a[name=picture4]:target ~ section a[href="#picture4"] + a,
#arrows:checked ~ a[name=picture5]:target ~ section a[href="#picture5"] + a{
 background-position: 0px 0px;
 z-index: 20;
}

/* == [END] Arrows == */

以下截图显示了结果:

上一个和下一个箭头

CSS 预处理器

在这一部分,我们将尝试解决这个项目的最大问题:整个样式表严重依赖于画廊中显示的图片数量。每个效果都围绕着这个数字定制,因此添加新图片可能会在我们的 CSS 中引起大量工作。

为了解决这个问题,我们可以使用CSS 预处理器,它可以让我们创建一个文件,使用一种包含一些便利设施的语言,比如循环和变量,并且可以编译成 CSS 样式表。

我们将在这个项目中使用 Sass。要安装它,您需要首先安装 Ruby(www.ruby-lang.sorg/en/downloads/),然后在项目目录中的终端模拟器中键入gem install sass(根据您的操作系统,您可能需要使用sudo gem install sass)。

安装完成后,由于 SCSS 是 CSS3 的超集,我们可以通过复制css/application.css的内容创建一个scss/application.scss文件。

接下来,我们可以在整个代码前面添加一个变量,以包含我们的画廊当前拥有的图片数量:

/* == [BEGIN] Variables == */

$number_of_images: 5;

/* == [END] Variables == */

/* ... rest of CSS ... */

现在,每当在 CSS 中遇到类似以下结构的情况时:

a[name=picture1]:target ~ section a[href="#picture1"],
a[name=picture2]:target ~ section a[href="#picture2"],
a[name=picture3]:target ~ section a[href="#picture3"],
a[name=picture4]:target ~ section a[href="#picture4"],
a[name=picture5]:target ~ section a[href="#picture5"]{
  background: #111;
}

我们可以改变代码,使其根据$number_of_images生成正确数量的选择器:

@for $i from 1 through $number_of_images {
 a[name=picture#{$i}]:target ~ section a[href="#picture#{$i}"]{
 background: #111;
 }
}

处理特殊情况

不过,还有一些特殊情况,其中之一是当我们遇到一个包含一个字符串令牌重复次数等于图片数量的 CSS 选择器时。例如,以下一行 CSS:

#opacity:checked ~ a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) + a:not(:target) ~ section #shot1,

要将上述代码转换为其变量驱动的版本,我们必须创建一个函数,一个返回字符串的小段代码。我们可以将其写在变量声明的上方,如下所示:

/* == [BEGIN] Function == */

@function negate-a-times($n) {
 $negate: unquote("");
 @for $i from 1 through $n - 1 {
 $negate: append($negate, unquote("a:not(:target) + "), space);
 }
 @return $negate + unquote("a:not(:target)")
}

/* == [END] Function == */

现在我们可以定义一个新变量,其中包含字符串a:not(:target)重复的次数等于我们画廊中的图片数量。因此,.scss文件中的新变量部分将如下所示的 CSS 片段:

* == [BEGIN] Variables == */

$number_of_images: 5;
$negate_images: negate-a-times($number_of_images);

/* == [END] Variables == */

最后,之前的 CSS 片段可以转换为:

#opacity:checked ~ #{$negate_images} ~ section #shot1,

我们还需要注意的一件事是我们动画的时机。我们必须动态计算动画的总持续时间以及三个关键帧的百分比(进入动画,静止和退出动画),从我们画廊中的图片数量开始。为此,我们必须在application.scss文件的Variables部分结束之前定义一些额外的变量:

$animation_duration: 2.5 * $number_of_images;
$enter_animation: 0% + (1.5 / $animation_duration) * 100;
$still: 0% + (2.5 / $animation_duration) * 100; 
$exit_animation: 0% + (4 / $animation_duration) * 100;
$animation_duration: $animation_duration + 0s;

/* == [END] Variables == */

在前面的几行中,我们定义了动画的总持续时间,然后将动画的时间(1.5 秒动画进入,1 秒静止,1.5 秒动画退出)转换为百分比。

最后但同样重要的是,我们必须遍历我们的.scss代码,并将每个animation-duration: 12.5s;转换为animation-duration: $animation_duration;。我们还必须将@keyframes opacity@keyframes slide@keyframes cube更改为以下内容:

@keyframes opacity{
  0%    { opacity: 0; }
 #{$enter_animation}   { opacity: 1; }
 #{$still}             { opacity: 1; }
 #{$exit_animation}    { opacity: 0; }
  100%  { opacity: 0; }
}
@keyframes slide{
  0%    { left: 500px; }
 #{$enter_animation} { left: 0px; }
 #{$still}           { left: 0px; }
 #{$exit_animation}   { left: -500px; }
  100%  { left: -500px; }
}
@keyframes cube{
  0%    { transform: rotateY(90deg); }
 #{$enter_animation}   { transform: rotateY(0deg); }
 #{$still}              { transform: rotateY(0deg); }
 #{$exit_animation}    { transform: rotateY(-90deg); }
  100%  { transform: rotateY(-90deg); }
}

注意

application.scss文件的完整版本可在项目的源代码中找到。

要将我们的application.scss文件编译成application.css,我们可以在项目的根目录中使用终端模拟器调用以下命令:

sass scss/application.scss:css/application.css

通过使用这些简单的翻译规则,我们可以将我们的 CSS 转换成非常灵活的 SCSS。为了证明这一点,我们可以尝试从 HTML 中删除一个figure元素(及其相应的a元素),将$number_of_images:更改为4,重新编译application.scss,然后注意整个项目如何保持平稳运行。

对于旧版浏览器的支持

Internet Explorer 9 或更低版本不支持 CSS3 过渡,也不支持 CSS3 3D 变换,因此这个项目几乎无法在这些浏览器上模拟。然而,我们可以实现基本的图片导航,同时隐藏所有其他选项。为了实现这一点,让我们再次利用条件注释,并用以下行替换<html>

<!--[if lte IE 9]> <html class="lteie9"> <![endif]-->
<!--[if !IE]> --> <html> <!-- <![endif]-->

接下来,我们需要为项目中使用的一些 CSS3 选择器添加对 Internet Explorer 8 的支持。为此,我们必须添加一个名为 Selectivizr(selectivizr.com/)的库,该库使用 JavaScript 来支持大多数新的 CSS3 选择器。Selectivizr 依赖于 jQuery,所以我们也需要添加它。最后,我们需要使用一个 polyfill 来使 Internet Explorer 8 支持新的 HTML5 元素。以下是插入这三个库所需的代码片段,我们需要在index.htmlhead部分的末尾之前添加它:

<!--[if lte IE 8]> 
<script src="img/html5.js"></script>
<script src="img/jquery.min.js"></script>
<script src="img/selectivizr-min.js"></script>
<![endif]-->

最后,我们可以添加一些 CSS 行来隐藏除第一个figure元素之外的所有内容,当.lteie9类存在时。此外,我们可以利用 Sass 来触发所选项目对应的figure元素上的display:block

/* == [BEGIN] Old Browser == */

.lteie9 body > div > span, 
.lteie9 body > div > input, 
.lteie9 body > div > br, 
.lteie9 body > div > label, 
.lteie9 figure{
  display: none;
}

.lteie9 #{$negate_images} ~ section #shot1{
  display: block;
}

@for $i from 1 through $number_of_images {
  .lteie9 a[name=picture#{$i}]:target ~ section #shot#{$i}{
    display: block;
  }
}

/* == [END] Old Browser == */

总结

CSS3 提供了新的简化方法来创建令人惊叹的画廊,而无需使用 JavaScript。可以理解的是,这些技术不适用于旧的非 CSS3 兼容浏览器,但我们可以检测这些浏览器并创建备用解决方案。

在本章中,我们看到了如何仅使用 CSS3 就可以创建出色的交互机制。此外,我们还发现了一种从更灵活的语言静态生成 CSS 的好方法。

最后但并非最不重要的是,我们尝试了三种很酷的动画效果。这些效果可以很容易地混合使用,或者可以通过例如将rotateX更改为rotateY,或者将left更改为top来创建新的效果。在下一章中,我们将探讨如何获得有趣的视差效果。

第六章:视差滚动

什么是视差滚动?视差滚动是一种视觉效果技术,试图通过移动场景中具有不同速度的元素来实现深度感,以响应用户的操作,比如网页的滚动。这种技术自 80 年代以来在 2D 视频游戏行业被广泛使用。

在本章中,我们将发现如何通过视差滚动和其他对页面滚动响应的酷炫效果来增强我们的网站。为了实现这一点,我们将深入一些高级的——有时是实验性的——CSS 3D 技术,并学习如何有效处理透视。

由于一些实现差异,我们将重点关注如何在不同的布局引擎(如 WebKit 和 Gecko)上获得类似的效果。

如果您正在使用 Windows 操作系统并且使用 Chrome,如果由于缺少或不支持的 GPU 而导致 CSS 3D 效果不如预期,您可能需要切换到 Firefox(或 IE10)。为了检查这一点,我们可以从 Chrome 浏览器中导航到about:gpu,并检查3D CSS复选框是否已启用。

本章涵盖的主题如下:

  • 发现透视

  • 创建一个立方体

  • 透视原点

  • CSS 3D 视差

  • 布局引擎之间的差异

  • 在页面滚动时改变视差

  • 创建一个支持视差的图库

发现透视

正如我们在上一章中开始探索的那样,CSS3 引入了在三维空间中移动我们的 HTML 元素的可能性。我们现在可以沿着 x、y 和 z 三个轴移动和旋转它们。虽然处理围绕 x 和 y 轴的运动相当容易理解,但当 z 轴出现时,情况就变得有些混乱。

沿着 z 轴移动一个元素意味着使其离我们的视点更近或更远,但这个动作有一些隐藏的问题,例如,接下来看下面的陈述:

#element{
  transform: translateZ(100px);
}

我们如何想象将一个以像素为单位测量的距离的对象向我们移动?为了解决这个困境,W3C 引入了一个称为perspective的属性,基本上告诉浏览器我们从什么距离观察页面。

因此,如果我们将500px设置为透视属性,放置在 z 轴上距离为250像素的对象将看起来是原来的两倍大,而放置在 z 轴上距离为500像素的对象将看起来是原来的一半大。

让我们通过一个小例子来尝试一下:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>experimenting with perspective</title>

  <style>

  body{
 perspective: 500px;
    transform-style: 'preserve-3d';
  }

  #red-square{
    margin: auto;
    width: 500px;
    height: 500px;
    background: red;
    transform: rotateX(40deg);
  }

  </style>

  <script src="img/prefixfree.js"></script>

</head>
<body>

  <div id="red-square"></div>

</body>
</html>

如果我们在支持 CSS 3D 特性的浏览器(如 Chrome、Firefox 或 IE10)中运行此代码,我们将注意到与以下截图中显示的结果类似的结果:

发现透视

增加perspective属性的值,结果看起来会更扁平,另一方面,如果减少这个属性,红色的框看起来会被拉伸到地平线上。这里有一个perspective: 250px的例子:

发现透视

创建一个立方体

为了更好地理解一些perspective属性,我们可以利用我们到目前为止学到的知识,仅使用 CSS 创建一个真正的 3D 立方体。我们需要六个div标签,每个面一个,再加上一个作为其他面的容器:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>A cube</title>

    <style>

    body, html{
      height: 100%;
      width: 100%;
    }

    </style>

    <script src="img/prefixfree.js"></script>
  </head>
  <body>
    <div id="container">
      <div class="square back"></div>
      <div class="square bottom"></div>
      <div class="square right"></div>
      <div class="square left"></div>
      <div class="square top"></div>
      <div class="square front"></div>
    </div>
  </body>
</html>

首先,我们必须将一些属性应用于#container选择器;让我们在已经定义的style标签中插入以下一段 CSS 代码:

    #container{
      perspective: 500px;
      backface-visibility: visible;
      transform-style: 'preserve-3d';	
      position: relative;
      height: 100%;
      width: 100%;
    }

在这里,我们告诉浏览器,容器内的内容必须考虑到 z 轴上的位置进行渲染,并且我们为#container选择器和容器内的元素设置了perspective属性为500px。最后但同样重要的是,我们要求浏览器也渲染我们将用来创建立方体的div标签的后面。

好的,现在让我们创建面。我们可以从.square的一些基本属性开始:

.square{
  transform-style: 'preserve-3d';	
  position: absolute;
  margin: -100px 0px 0px -100px;
  top: 50%;
  left: 50%;
  height: 200px;
  width: 200px;;
}

好的,现在每个正方形都放在另一个上面,我们可以开始逐个调整它们。让我们从.back开始,我们必须将其从相机移开到一半大小,所以将transform属性设置为-100px

    .back{
      background: red;
      transform: translateZ(-100px);
    }

接下来我们看.left。在这里,我们首先必须对其 y 轴应用旋转,然后将其向左移动一半大小。这是因为除非另有说明,否则每个转换都是以元素中心为原点;另外,我们必须记住转换是按顺序应用的,所以元素必须沿其 z 轴进行平移,以获得正确的结果:

    .left{
      background: blue;
      transform: rotateY(90deg) translateZ(-100px);
    }

这是一个提醒我们迄今为止取得的进展的屏幕截图:

创建一个立方体

我们可以用相同的策略处理所有剩余的面:

    .right{
      background: yellow;
      transform: rotateY(-90deg) translateZ(-100px);
    }

    .front{
      background: green;
      transform: translateZ(100px);
    }

    .top{
      background: orange;
      transform: rotateX(-90deg) translateZ(-100px);
    }

    .bottom{
      background: purple;
      transform: rotateX(90deg) translateZ(-100px);
    }

如果我们现在尝试对这个实验进行截图(如图所示),我们可能会遇到一点小失望:

创建一个立方体

.front选择器的div标签覆盖了所有其他div标签。这个小实验向我们展示了一个场景的消失点默认设置为持有perspective属性的元素的中心。

透视原点属性

幸运的是,我们可以使用perspective-origin属性轻松改变消失点,该属性接受两个值,可以用所有常见的 CSS 测量单位或使用文字表达,就像background-position一样。

所以我们将把以下内容添加到#container

perspective-origin: top left;

并获得类似于这里显示的结果:

透视原点属性

如果我们调整浏览器窗口大小,我们还会注意到消失点会改变,因为它与#container选择器相关联,该选择器的widthheight属性设置为与浏览器视口相等。

这种行为是我们将在下一章中用来构建视差项目的技巧的根源。

CSS 3D 视差

好了,现在我们有了开始构建项目所需的工具。我们要创建的基本想法是,如果我们将元素放置在不同的高度并在保持消失点在可见区域中心的情况下滚动,那么我们就可以获得一个很酷的视差滚动效果。

像往常一样,我们首先需要一个 HTML 结构,所以让我们从这里开始。让我们创建带有以下代码的index.html文件:

<!doctype html>
<html>
  <head>
  <meta charset="utf-8">	
<link href='http://fonts.googleapis.com/css?family=Bowlby+One+SC' rel='stylesheet' type='text/css' data-noprefix>
    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.5.1/build/cssreset/cssreset-min.css" data-noprefix>
    <link rel="stylesheet" type="text/css" href="css/application.css">

    <script src="img/prefixfree.js"></script>
    <script src="img/jquery.min.js"></script>

  </head>
  <body>

    <div id="body">
      <div id="container">

      </div>
    </div>

  </body>
</html>

除了这个页面,我们还必须创建一个名为css/application.css的文件,其中将保存我们的 CSS 属性和选择器。就像我们在之前的例子中所做的那样,我们将#body拉伸到适合浏览器视口的大小,所以我们可以在application.css中添加几行 CSS 代码:

body,html{
  height: 100%;
}

#body{
  height: 100%;
 overflow-y: auto;
 overflow-x: hidden;
}

我们还向元素添加了overflow-y: autooverflow-x: hidden,我们将在一会儿讨论这些将如何有用。

在 WebKit 中实现视差滚动

好的,在继续之前,我们现在必须一次专注于一个布局引擎;这是因为在 WebKit 和 Firefox 之间关于实现 CSS 3D 属性的一些差异,所以我们必须分别处理这两种情况。让我们从 WebKit 开始。

我们可以利用 Lea Verou 的 Prefix Free 自动放在插入页面的html元素上的一个整洁的类。这个类的名称与浏览器所需的实验性前缀相同;所以如果我们从 Internet Explorer 查看页面,类是-ms-,如果从 Firefox 查看,它是-moz-

所以我们可以开始向#body添加perspectivetransform-style属性,就像我们在之前的例子中所做的那样:

.-webkit- #body{
  perspective: 500px;
  transform-style: preserve-3d;
}

现在我们必须处理#container选择器;这必须比视口更长——和往常一样,用于此项目的所有图像都位于 Packt Publishing 网站上(www.packtpub.com):

#container{
  background-image: url('../img/grass.png');
  text-align: center;
  padding-bottom: 300px;
  /* to be removed when we'll have content */
min-height: 1000px;
}

由于我们已经将overflow属性应用于#body,我们在浏览器中看到的滚动条并不属于整个 HTML 文档,而是属于#body

#body也有一个perspective属性;这意味着包含元素的消失点始终位于浏览器屏幕的中心,因此我们已经实现了我们在本章开头希望实现的结构。

为了测试我们的代码,我们可以在容器内添加一些元素并分配它们不同的高度:

<div id="body">
  <div id="container">

  <!-- EXPERIMENT -->
    <img class="experiment1" src="img/pic1.jpg">
    <img class="experiment2" src="img/pic2.jpg">

  </div>
</div>

我们可以使用transform: translateZ();来设置高度:

.experiment1{
  transform: translateZ(10px);
}

.experiment2{
  transform: translateZ(150px);
}

好了,现在我们可以在符合 WebKit 标准的浏览器中测试我们到目前为止所做的事情:

在 WebKit 中实现视差滚动

在滚动时,我们可以注意到第二张图片——离我们视点最近的图片——移动得比第一张图片快。我们刚刚在 WebKit 上实现了视差!

在 Gecko 中实现视差滚动

Gecko 和 WebKit 之间存在一些微妙的实现差异,以及一些错误。

首先,在 Gecko 中的transform-style: preserve-3d属性不会传播到匹配元素的所有后代,而只会传播到一级子元素。perspectiveperspective-origin属性也是如此。

幸运的是,我们可以找到解决这个问题的方法。例如,可以通过将perspective表达为一个转换来实现:

transform: perspective(500px);

当我们使用这种方法时,perspective-origin就不再有用了,应该用transform-origin代替。在 Gecko 内核的浏览器上这样强加perspective会导致与在 WebKit 内核的浏览器上使用 perspective 时相同的行为。

所以我们可以添加几行 CSS 代码,使用与我们在 WebKit 中所做的相同策略:

.-moz- #container{
  transform: perspective(500px);
  transform-style: preserve-3d;	
}

如果我们现在打开 Firefox 并测试我们的项目,我们会看到类似这样的东西:

在 Gecko 中实现视差滚动

尽管结果看起来与 WebKit 获得的结果相似,但在这种情况下滚动页面不会产生任何视差效果。经过快速分析,我们可能会认为这种行为是由于将transform: perspective属性放在了错误的元素(#container而不是#body)上导致的,但事实是我们有意选择这样做是因为一个微妙的错误(bugzilla.mozilla.org/show_bug.cgi?id=704469)会从具有overflow属性的元素中移除transform: perspective属性。

所以现在使 Gecko 内核的浏览器表现如预期的唯一方法是实现一小段 JavaScript 代码,可以动态修改我们的消失点,使其保持在浏览器窗口的中心。

这个脚本必须根据滚动事件调整transform-origin属性:

<script>
  $(document).ready(function(){
    if($.browser.mozilla){
      $('#body').scroll(function(event){
        var viewport_height = $(window).height(),
          body_scrolltop = $('#body').scrollTop(),
          perspective_y = body_scrolltop + Math.round( viewport_height / 2 );

        $('#container').css({
          'transform-origin': 'center ' + perspective_y + "px",
          '-moz-transform-origin': 'center ' + perspective_y + "px",
        });
      })
    }
  });
</script>

完美!现在 Gecko 内核的浏览器也会表现如预期。

在 Internet Explorer 中实现视差滚动

Internet Explorer 9 不支持 CSS 3D 变换,但 IE10 支持,所以我们也可以尝试在该浏览器上运行这个项目。为了在 IE10 上实现正确的行为,我们必须应用一些自定义属性;这是因为 IE10 的行为与其他两个浏览器的行为略有不同。

基本上 IE10 支持perspectivetransform: perspective属性,但前者只对具有此属性的元素的直接后代产生影响,后者只对具有该属性的元素起作用。

所以我们必须采用一种更接近 Gecko 内核的行为,但使用perspective代替transform: perspective。这里是:

.-ms- #container{
  perspective: 500px;
}

现在我们还需要稍微改变我们的 JavaScript 代码,以便在浏览器是 Internet Explorer 并支持 3D 变换时影响perspective-origin。以下是可以用来代替先前代码的代码:

// == for Firefox and MSIE users ==
$(document).ready(function(){
  if($.browser.mozilla || ( $.browser.msie&& Modernizr.csstransforms3d )){
    $('#body').scroll(function(event){
      var viewport_height = $(window).height(),
        body_scrolltop = $('#body').scrollTop(),
        perspective_y = body_scrolltop + Math.round( viewport_height / 2 );

      if($.browser.mozilla){              
        $('#container').css({
          'transform-origin': 'center ' + perspective_y + "px",
          '-moz-transform-origin': 'center ' + perspective_y + "px",
        });
      }else{
        $('#container').css({
          'perspective-origin': 'center ' + perspective_y + "px",
          '-ms-perspective-origin': 'center ' + perspective_y + "px",
        });
      }
    })
  }
});

为了使这个工作,我们必须下载 Modernizr 以检查 CSS 3D 支持,我们可以像在上一章中那样创建一个自定义构建,但这次我们只在配置面板中检查CSS 3D Transforms复选框(modernizr.com/download/)。接下来,我们必须在页面中包含下载的文件(js/modernizr.js)在其他script标签之后:

<script src="img/modernizr.js"></script>

这是 IE10 的屏幕截图:

在 Internet Explorer 中实现视差滚动

向画廊添加一些随机性

现在我们已经解决了浏览器兼容性问题,我们可以安全地删除我们之前附加到图像的实验性注释和类。

为了营造一种随机感,我们可以定义一些类的组,每个组有同一属性的更多变体,然后我们可以为每个图像的每个组选择一个类来显示。这是一个例子;让我们将以下内容添加到application.css

/* sizes */
.size-a{
  width: 30%;
}

.size-b{
  width: 35%;
}

.size-c{
  width: 50%;
}

/* z-indexes */
.depth-a{
  transform: translateZ(10px);
  z-index: 1;
}

.depth-b{
  transform: translateZ(50px);
  z-index: 2;
}

.depth-c{
  transform: translateZ(100px);
  z-index: 3;
}

.depth-d{
  transform: translateZ(150px);
  z-index: 4;
}

.depth-e{
  transform: translateZ(200px);
  z-index: 5;
}

现在我们可以用这个列表替换上一节中使用的图像,其中每个图像都有一个depth-*和一个size-*属性(其中*表示在前面的代码中定义的随机选择的类):

<img class="basic_parallax depth-a size-a" src="img/picture1.jpg">
<img class="basic_parallax depth-b size-c" src="img/picture2.jpg">
<img class="basic_parallax depth-c size-b" src="img/picture3.jpg">
<img class="basic_parallax depth-b size-a" src="img/picture4.jpg">
<img class="basic_parallax depth-d size-c" src="img/picture5.jpg">
<img class="basic_parallax depth-e size-b" src="img/picture6.jpg">
<img class="basic_parallax depth-a size-c" src="img/picture7.jpg">
<img class="basic_parallax depth-c size-a" src="img/picture8.jpg">
<img class="basic_parallax depth-d size-c" src="img/picture9.jpg">
<img class="basic_parallax depth-a size-b" src="img/picture10.jpg">
<img class="basic_parallax depth-e size-b" src="img/picture11.jpg">
<img class="basic_parallax depth-a size-a" src="img/picture12.jpg">
<img class="basic_parallax depth-b size-c" src="img/picture13.jpg">
<img class="basic_parallax depth-c size-a" src="img/picture14.jpg">

最后但并非最不重要的,让我们为每个图像定义基本的 CSS:

img.basic_parallax{
  background: rgb(255,255,255);
  padding: 10px;
  box-shadow: 10px 10px10pxrgba(0,0,0,0.6);
  position: relative;
  margin: 10px;
}

好了,现在让我们重新加载浏览器并测试一下:

向画廊添加一些随机性

旋转图像

由于我们正在处理一个真正的 3D 环境,我们可以尝试使用相同的基本思想开发更有趣的效果。例如,如果我们旋转一个元素而不是简单地将它向我们移动会怎么样?让我们试试!首先,我们需要向我们的画廊添加一些更多的图像;这次我们还决定添加一些装饰性文本,如下:

<!-- DECKS -->
<img class="rotatextop" src="img/picture15.jpg">
<p>
  Keremma Dunes
  <small>Bretagne, Finist&eacute;re</small>
</p>
<img class="rotatexbottom" src="img/picture16.jpg">
<p class="depth-e">
  Rennes
  <small>Bretagne</small>
</p>
<img src="img/picture17.jpg">

然后我们可以对图像使用rotateX变换方法:

.rotatextop{
  transform-origin: top center;
  transform: rotateX(15deg);
}

.rotatexbottom{
  transform-origin: bottom center;
  transform: rotateX(-15deg);		
}

还有一些 CSS 属性来稍微样式化段落,然后我们就完成了:

p{
  text-align: center;
  font-family: 'Bowlby One SC', cursive;
  font-size: 6em;
  color: #e4ddc2;
}

p small{
  display: block;
  font-size: 0.4em;
  margin-top: -1em;
}

这是结果画廊的屏幕截图:

旋转图像

一个 3D 全景

让我们也尝试使用rotateY方法来完成这个项目。这次我们将使用这个属性以及perspective-origin属性技巧来创建一个很酷的全景效果。

首先我们需要一个全景图像,然后我们可以使用图像编辑器将其切成三部分,其中中央图像的大小大约是其他两个的两倍(例如,800 x 800 像素和 500 x 800 像素)。完成后,我们可以将这些图像添加到#container选择器的末尾之前:

<p>
  Ortigia
  <small>Italy</small>
</p>
<img class="panorama left" src="img/panorama_left.jpg">
<img class="panorama center" src="img/panorama.jpg">
<img class="panorama right" src="img/panorama_right.jpg">

现在我们可以对.left.right都使用rotateY方法,如下:

.panorama.left{
  transform-origin: center right;
  transform: rotateY(43deg);  
}

.panorama.right{
  transform-origin: center left;
  transform: rotateY(-43deg);	
}

.panorama.left, .panorama.right{
  width: 27%;
}

.panorama.center{
  width: 43.2%;
}

这就是结果:

一个 3D 全景

处理旧版本浏览器

尽管这个项目的核心效果利用了一些 CSS 3D 属性,这些属性在旧版本的浏览器中无法模拟,但整个结构只使用了兼容 CSS 2 的属性和选择器,因此几乎可以在任何浏览器中查看:

处理旧版本浏览器

摘要

处理第三维可能会导致与许多小的实现差异的斗争,但一旦我们驯服了它们,结果就会令人惊叹并且非常愉快。

到目前为止,我们在本章讨论了以下内容:

  • CSS 可以用来转换元素并将它们移动到 3D 空间中

  • 我们可以使用一些属性来定义 3D 场景中的消失点

  • 通过使用 CSS 3D 属性,可以模拟出很酷的视差滚动效果

  • 需要一些 JavaScript 编码来处理浏览器实现差异

在下一章中,我们将学习如何使用 CSS 增强 HTML5 的video元素。

第七章:视频杀死了广播明星

在撰写本文时,使用 CSS 和 HTMLvideo元素仍然有点像黑魔法。主要问题是每个浏览器都利用其特定的视频实现技术;其中一些使用 GPU,而其他一些使用用于页面其余部分的相同渲染引擎。在本章中,我们将探讨如何通过利用 SVG 和 CSS 的功能在运行视频上创建面具和效果。以下是我们将涵盖的主题列表:

  • HTML5video元素

  • 使用 SVG 进行面具处理

  • SVG 动画

  • 基于 WebKit 的特定面具属性

  • CSS 滤镜

HTML5 视频元素

HTML5 规范引入了新的多媒体元素,允许更好地将视频和音频整合到网页中,而无需嵌入外部插件,如 Flash。现在嵌入视频就像写这样简单:

<video src="img/video">

但是需要考虑一些注意事项;首先,每个浏览器只支持视频编解码器的一小部分,因此如果我们希望我们的元素能够播放,我们需要至少将我们的视频编码为mp4webm,然后使用另一种语法来包含这两种格式,如下所示:

<video>
  <source src="img/video.mp4" type="video/mp4">
  <source src="img/video.webm" type="video/webm">
</video>

Mirowww.mirovideoconverter.com/)是一款很好的免费视频转换软件,适用于 Mac 和 Windows 操作系统。它非常易于使用 - 只需选择所需的输出格式,然后将文件拖放到应用程序窗口中开始转换过程。

一旦设置了我们的video元素,我们很快就会发现大多数常见的 CSS3 属性在所有浏览器上对这个元素的形状的影响并不相同。例如,border-radius属性;在下面的屏幕截图中,显示了这个属性在各种浏览器中的不同行为(请注意这个属性在不同浏览器中的不同行为):

HTML5 视频元素

基于 WebKit 的浏览器似乎忽略了这个属性,而 Firefox 和 IE9 正确实现了它。这可能是因为 Chrome 和 Safari 使用 GPU 播放视频,因此无法很好地对此内容应用 CSS 修改。

布局引擎之间的这些差异在处理视频和 CSS 时需要谨慎对待。

在这个项目中,我们将使用 CSS 开发一小部分可以在运行时应用于视频的修改。让我们从一些基本的面具开始。

面具

面具是在我们需要隐藏部分内容时非常有用的工具;它们在视频中更加有用,因为我们可以应用有趣的效果,否则需要一些专门的软件。我们可以使用 HTML5/CSS3 创建面具的几种技术;然而,跨浏览器的支持是不一致的。为了解决这些不一致性,我们将在系列中结合几种技术。

在某种程度上,我们可以使用border-radius来遮罩我们的视频,如下所示:

<!doctype html>
<html>

  <head>
    <meta charset="utf-8">
    <title>Masking</title>

    <style>
      video{
        border-radius: 300px;
      }
    </style>

  </head>

  <body>

    <video autoplay muted loop>
      <source src="img/sintel-trailer.mp4">
      <source src="img/sintel-trailer.webm">
    </video>

  </body>

</html>

正如你所看到的,这种方法适用于 Firefox 和 IE,但对于基于 WebKit 的浏览器,我们需要使用不同的方法。

如果我们使用 Web 服务器(如 Apache 或 IIS)进行工作,可能需要配置它以使用适当的内容类型提供视频文件。为此,我们可以在项目的根目录(如果使用 Apache)中创建一个.htaccess文件,内容如下:

AddType video/ogg .ogv 
AddType video/mp4 .mp4 
AddType video/webm .webm

如果我们使用 IIS,还有另一个程序需要遵循。这在blog.j6consultants.com.au/2011/01/10/cross-browser-html5-video-running-under-iis-7-5/的指南中有详细说明。

自 2008 年以来,WebKit 支持一组管理面具的 CSS 属性。我们将使用webkit-mask-box-image选择器将图像面具应用到我们的电影示例中。为此,我们需要一个类似于以下图像中的300px黑色圆:

面具

然后,我们将使用之前介绍的属性将这个黑色圆设置为video元素的蒙版。应用后,这个图像的黑色部分将让底层内容可见,而白色部分将完全隐藏内容。当然,灰色可以用来部分隐藏/显示内容。

video{
  border-radius: 300px;
  -webkit-mask-box-image: url(img/circle-mask.png) stretch;
}

这是结果:

蒙版

更高级的蒙版

目前,我们只能处理基本类型的蒙版,也就是一切可以用border-radius属性模拟的东西。但是,如果我们尝试简单地创建一个中心有小圆的蒙版,我们会发现这种组合在以前的技术中是不可行的,因为圆角只能位于元素的一侧。幸运的是,我们可以转向一个更复杂但更强大的方法,涉及 SVG 格式。

Gecko 和 WebKit 都支持 SVG 蒙版,通过不同的 CSS 属性——基于 Gecko 的浏览器使用mask属性,而 WebKit 使用-webkit-mask-image

这些属性不仅名称不同,它们的行为也不同:

  • mask属性需要链接到一个名为<mask>的 SVG 元素,基本上是我们将用来蒙版html元素的所有形状的容器

  • 另一方面,-webkit-mask-image属性需要指向一个包含我们想要用来覆盖视频的所有形状的 SVG 元素

例如,这是我们如何正确实现mask属性的:

<!doctype html>
<html>

  <head>
    <meta charset="utf-8">
    <title>svg mask</title>

  </head>

  <body>

    <video autoplay muted loop>
      <source src="img/sintel-trailer.mp4">
      <source src="img/sintel-trailer.webm">
    </video>

 <style>
 video{
 mask: url('#circle');
 }
 </style>

 <svg>
 <defs>
 <mask id="circle">
 <circle cx="427" cy="240" r="100" fill="white"/>
 </mask>
 </defs>
 </svg>

  </body>

</html>

这是我们如何处理-webkit-mask-image属性的:

<!doctype html>
<html>

  <head>
    <meta charset="utf8">
    <title>svg mask</title>

  </head>

  <body>

    <video autoplay muted loop>
      <source src="img/sintel-trailer.mp4">
      <source src="img/sintel-trailer.webm">
    </video>

 <style>
 video{
 -webkit-mask-image: url('svg/mask-circle.svg');
 }
 </style>

  </body>

</html>

在这里,SVG 文件svg/mask-circle.svg的定义如下:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg version="1.1"  >
 <circle cx="427" cy="240" r="100" fill="white"/>
</svg>

在这两种情况下,最终结果是相同的,如下所示:

更高级的蒙版

这种方法的缺点是我们必须创建两个不同的 SVG 片段来适应两种布局引擎。这里有一个小的改进,可以让我们走向更好的解决方案;通过利用<use>元素,我们可以在单个 SVG 文件svg/mask.svg中满足两种属性的需求,如下所示:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg version="1.1"  >
  <defs>
 <mask id="circle">
 <circle id="circle-element" cx="427" cy="240" r="100" fill="white"/>
 </mask>
  </defs>
 <use xlink:href="#circle-element"/>
</svg>

通过使用这种方法,我们可以在两个浏览器上获得与之前图像相同的结果,并且只需一个 CSS 语句:

<!doctype html>
<html>

  <head>
    <meta charset="utf-8">
    <title>svg mask</title>

    <style>
 video{
 mask: url('svg/mask.svg#circle');
 -webkit-mask-image: url('svg/mask.svg');
 }
    </style>

  </head>

  <body>

    <video autoplay muted loop>
      <source src="img/sintel-trailer.mp4">
      <source src="img/sintel-trailer.webm">
    </video>

  </body>

</html>

干得好!现在我们准备在项目中实现一些蒙版。

实现项目

在这个项目中,我们将使用 Sintel 的精美预告片(www.sintel.org/about/),这是根据知识共享许可发布的电影。

和往常一样,我们需要一个基本的项目结构,包括一些文件夹(cssimgsvgjsvideo)。在这个项目中使用的视频要么可以在 Sintel 网站上找到,要么可以从 Packt 的网站(www.packtpub.com)下载,以及完成的项目。我们还将使用Prefix Freeleaverou.github.com/prefixfree/),所以让我们下载它并放到js文件夹中。

让我们创建一个index.html文件开始:

<!doctype html>
<html>

  <head>
    <meta charset="utf8">
    <title>Video killed the radio star</title>

    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.5.1/build/cssreset/cssreset-min.css" data-noprefix>
    <link rel="stylesheet" type="text/css" href="css/application.css">

    <script src="img/prefixfree.min.js"></script>

  </head>

  <body>

    <a id="mask" name="mask"></a>
    <a id="mask-stretch" name="mask-stretch"></a>
    <a id="mask-animate" name="mask-animate"></a>
    <a id="mask-animate-webkit" name="mask-animate-webkit"></a>
    <a id="mask-text" name="mask-text"></a>
    <a id="blur-filter" name="blur-filter"></a>
    <a id="grayscale-filter" name="grayscale-filter"></a>

    <video autoplay muted loop>
      <source src="img/sintel-trailer.mp4">
      <source src="img/sintel-trailer.webm">
    </video>

    <ul>
      <li>
        <a href="#">reset</a>
      </li>
 <li>
 <a href="#mask">mask</a>
 </li>
      <li>
        <a href="#mask-animate">animated mask</a>
      </li>
      <li>
        <a href="#mask-animate-webkit">animated mask (webkit)</a>
      </li>
      <li>
        <a href="#mask-text">text mask</a>
      </li>
      <li>
        <a href="#blur-filter">blur filter</a>
      </li>
      <li>
        <a href="#grayscale-filter">grayscale filter</a>
      </li>
    </ul>
  </body>

</html>

然后,在application.css中,让我们进行一些基本的 CSS 样式以及刚刚介绍的蒙版技术:

html{
  min-height: 100%;
  background-image: linear-gradient(top, black, black 500px, white);
  background-size: cover;
  background-repeat: no-repeat;
}

video{
  display: block;
  margin: 0 auto;
}

ul{
  text-align: center;
  position: absolute;
  bottom : 100px;
  width: 100%;
}

li{
  display: inline;
}

li > a{
  display: inline-block;
  padding: 5px;
  background: #FFF;
  border: 3px solid black;
  text-decoration: none;
  font-family: sans-serif;
  color: black;
  font-size: 10px;
}

/* ==[BEGIN] Masking == */

a[name="mask"]:target ~ video{
 mask: url('../svg/mask.svg#circle');
 -webkit-mask-image: url('../svg/mask.svg');
}

一旦按下mask按钮,这就是结果:

实现项目

动画蒙版

SVG 支持通过一些特殊元素进行动画。在本章中,我们将使用最通用的一个<animate>

这是一个例子:

<circle ... >
<animate attributeType="CSS" attributeName="opacity" from="1" to="0" dur="5s" repeatCount="indefinite" />
</circle>

包含<animate>的元素会根据标签属性中指定的选项描述进行属性动画。在上面的代码中,我们要求浏览器在五秒内将圆的不透明度从完全可见变为隐藏。

因此,如果我们创建一个新的 SVG 文件,命名为svg/mask-animate.svg,并使用以下代码,我们将能够在 Gecko 和 WebKit 浏览器上获得一个动画效果:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg version="1.1"  >
  <defs>
    <mask id="circle">
      <circle id="circle-element" cx="427" cy="240" r="100" fill="white">
 <animate attributeName="r" values="100;200;100" dur="5s" repeatCount="indefinite" />
      </circle>
    </mask>
  </defs>
  <use xlink:href="#circle-element"/>
</svg>

这是我们需要添加到css/application.css的 CSS:

a[name="mask-animate"]:target ~ video{
  mask: url('../svg/mask-animate.svg#circle');
  -webkit-mask-image: url('../svg/mask-animate.svg');
}

并且这是蒙版在 5 秒动画中增长和缩小的结果:

动画蒙版

WebKit 特定属性

还有一些与蒙版相关的额外属性,只适用于 WebKit 浏览器;它们的工作方式与它们的background属性对应项完全相同,因此以下是原始 WebKit 博客文章中列出的列表:

  • -webkit-maskbackground):这是所有其他属性的快捷方式

  • -webkit-mask-attachmentbackground-attachment):这定义了蒙版是否应在内容中滚动

  • -webkit-mask-clipbackground-clip):这指定了蒙版的裁剪区域

  • -webkit-mask-positionbackground-position):此属性指定元素内蒙版的位置

  • -webkit-mask-originbackground-origin):这指定了坐标 0,0 应该放置在元素内的位置(例如,在使用padding-box作为值的填充区域的开始处)

  • -webkit-mask-imagebackground-image):这指向一个或多个图像或渐变,用作蒙版

  • -webkit-mask-repeatbackground-repeat):这定义了蒙版是否应重复,以及是在一个方向还是两个方向

  • -webkit-mask-compositebackground-composite):这指定了两个蒙版在重叠时应该如何合并

  • -webkit-mask-box-imageborder-image):这指向一个或多个图像或渐变,用作具有相同属性和行为的蒙版来定义边框图像

有了这些新属性,我们可以通过利用 CSS 过渡创建一些额外的效果,例如,我们可以用渐变蒙版我们的电影,然后使用:hover,改变它的蒙版位置;以下是 CSS 代码:

a[name="mask-animate-webkit"]:target ~ video{
  -webkit-mask-position: 0 100%;
  -webkit-mask-size: 100% 200%;
  -webkit-mask-image: -webkit-gradient(linear, center top, center bottom, 
      color-stop(0.00,  rgba(0,0,0,1)),
      color-stop(1.00,  rgba(0,0,0,0))
    );
  -webkit-transition: -webkit-mask-position 1s;
}

a[name="mask-animate-webkit"]:target ~ video:hover{
  -webkit-mask-position: 0 0;
}

由于这些 WebKit 蒙版属性是在 2008 年创建的,可能自那时以来从未更新过,我们必须使用旧的 WebKit 渐变语法;除此之外,其他一切都很简单,如下图所示:

WebKit 特定属性

使用文本蒙版

我们可以使用文本来蒙版video元素;该过程与我们之前看到的类似,但当然,我们需要制作另一个特定的 SVG 文件,命名为svg/mask-text.svg

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg   width="1000" height="280" version="1.1">
  <defs>
    <mask id="sintel-mask">
 <text x="0" y="300" id="sintel" fill="white" style="color: black;font-size:210px;
 font-family: Blue Highway, Arial Black, sans-serif;">SINTEL</text>
    </mask>
  </defs>
 <text x="0" y="80%" id="sintel" fill="white" style="color: black;font-size:240px;
 font-family: Blue Highway, Arial Black, sans-serif;">SINTEL</text>
</svg>

在这里,我们无法利用<use>元素,因为蒙版定位和蒙版大小的确定方式之间存在另一个差异。

基于 Gecko 的浏览器只能承受固定坐标,而基于 WebKit 的浏览器可以拉伸蒙版以适应屏幕,如果我们使用-webkit-mask-box-image(如本章中最初的示例中所示)而不是-webkit-mask-image

以下是所需的 CSS:

a[name="mask-text"]:target ~ video{
  mask: url('../svg/mask-text.svg#sintel-mask');
  -webkit-mask-box-image: url('../svg/mask-text.svg');
}

这是结果的屏幕截图:

使用文本蒙版

滤镜

除了蒙版,滤镜是其他强大的修改器,可以应用于元素,以获得各种效果,如模糊、灰度等。当然,也有缺点;在撰写本文时,滤镜支持是不均匀的。以下是一些缺点:

  • IE9 支持使用众所周知的progid滤镜的一些效果

  • Firefox 支持在 SVG 片段中声明滤镜

  • Chrome、Safari 和其他基于 WebKit 的浏览器支持最后的 CSS 滤镜规范

  • IE10 尚未确认对这些属性的支持,而且它将放弃对progid滤镜的支持

因此,让我们尽可能广泛地实现模糊滤镜。首先,我们将处理非常容易的 WebKit:

-webkit-filter: blur(3px);

传递给blur函数的参数是效果的像素半径。接下来是 Gecko 支持;为此,我们必须在一个正确完成的 SVG 文件中使用feGaussianBlur元素,命名为svg/filters.svg

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg   version="1.1">
  <defs>
    <filter id="blur">
    <feGaussianBlur stdDeviation="3" />
  </filter>
  </defs>
</svg>

然后,我们可以使用 Gecko 支持的filter属性来引用这个效果:

filter: url('../svg/filters.svg#blur');

接下来,我们还可以通过使用progid滤镜在 IE9 上实现这种效果:

filter:progid:DXImageTransform.Microsoft.Blur(pixelradius=3);

以下是最终的 CSS。请注意,我们添加了一个:hover选择器技巧来在鼠标悬停时改变模糊;这实际上只在基于 WebKit 的浏览器上有效,但可以通过遵循先前规则轻松扩展支持:

a[name="blur-filter"]:target ~ video{
  -webkit-filter: blur(3px);
  -webkit-transition: -webkit-filter 1s;      
  filter: url('../svg/filters.svg#blur');
}

.-ms- a[name="blur-filter"]:target ~ video{
  filter:progid:DXImageTransform.Microsoft.Blur(pixelradius=3);
}

a[name="blur-filter"]:target ~ video:hover{
  -webkit-filter: blur(0px);
}

我们还必须处理 Gecko 和 IE9 引用相同的filter属性但具有非常不同的值。为了解决这个问题,我们可以使用 Lea Verou 的 prefixfree 库在顶级html元素上添加的特殊-ms-类。

以下是结果:

滤镜

在当前稳定的 Chrome 版本中,filter属性似乎无法直接使用。这是因为我们需要在加速元素上启用滤镜。因此,让我们打开一个新标签页,然后在地址栏中输入about:flags并启用GPU 加速的 SVG 和 CSS 滤镜实验功能。

灰度滤镜

让我们再看一个滤镜,灰度!灰度滤镜基本上将目标图像或视频的所有颜色转换为相应的灰度值。

这是完整的 CSS:

/* == [BEGIN] Grayscale filter == */

a[name="grayscale-filter"]:target ~ video{
  -webkit-filter: grayscale(1);
  filter: url('../svg/filters.svg#grayscale');
}

.-ms- a[name="grayscale-filter"]:target ~ video{
  filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
}

这是 SVG 片段:

  <filter id="grayscale">
          <feColorMatrix values="0.3333 0.3333 0.3333 0 0
                                 0.3333 0.3333 0.3333 0 0
                                 0.3333 0.3333 0.3333 0 0
                                 0      0      0      1 0"/>
  </filter>

最后,这是从 IE9 中截取的屏幕截图:

灰度滤镜

我们的元素可以应用许多其他滤镜;要获取完整列表,我们可以查看:

总结

在本章中,我们发现了如何使用 CSS 处理 HTML5 video元素;我们了解到浏览器的行为非常不同,因此我们必须实施各种技术以实现兼容性。

我们找出了如何动态添加蒙版 - 静态或动画 - 以及如何创建滤镜,无论是使用 SVG 还是新的 W3C 规范。

在下一章中,我们将学习如何处理复杂的动画。

第八章:仪表

在 Web 应用程序开发中,仪表可以用于以视觉或直观的方式显示复杂或动态数据。在本章中,我们将学习如何创建一个完全可定制的动画仪表,可以对实时变化做出响应。我们还将讨论将此类小部件移植到旧的 Web 浏览器中的技术。我们将首先学习一个名为Compass的很酷的 SASS 增强功能;这是处理 CSS3 实验前缀的另一种方法。以下是我们将讨论的主题列表:

  • 基本仪表结构

  • 使用 Compass

  • 使用 rem

  • 移动箭头

  • 动画化箭头

  • 处理旧的浏览器

基本仪表结构

让我们从一个新项目开始;像往常一样,我们需要创建一个index.html文件。这次所涉及的标记非常小而紧凑,我们现在可以立即添加它:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />

  <title>Go Go Gauges</title>

  <link rel="stylesheet" type="text/css" href="css/application.css">
</head>
<body>

 <div data-gauge data-min="0" data-max="100" data-percent="50">
 <div data-arrow></div>
 </div>

</body>
</html>

仪表小部件由data-gauge属性标识,并使用其他三个自定义数据属性进行定义;即data-mindata-maxdata-percent,它们表示范围的最小和最大值以及以百分比值表示的当前箭头位置。

在标有data-gauge属性的元素内,我们定义了一个将成为仪表箭头的div标记。

要开始样式化阶段,我们首先需要装备一个易于使用并且可以为我们提供生成 CSS 代码的框架。我们决定使用 SASS,与我们在第五章中使用的相同,图库,因此我们首先需要安装 Ruby(www.ruby-lang.org/en/downloads/),然后从命令行终端输入以下内容:

gem install sass

注意

如果您在 Unix/Linux 环境中工作,可能需要执行以下命令:

sudo gem install sass

安装 Compass

对于这个项目,我们还将使用 Compass,这是一个 SASS 扩展,能够为我们的 SASS 样式表添加一些有趣的功能。

要安装 Compass,我们只需在终端窗口中输入gem install compass(或sudo gem install compass)。安装过程结束后,我们必须在项目的根文件夹中创建一个名为config.rb的小文件,其中包含以下代码:

# Require any additional compass plugins here.

# Set this to the root of your project when deployed:
http_path = YOUR-HTTP-PROJECT-PATH
css_dir = "css"
sass_dir = "scss"
images_dir = "img"
javascripts_dir = "js"

# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed

# To enable relative paths to assets via compass helper functions. Uncomment:
relative_assets = true

# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false

preferred_syntax = :sass 

config.rb文件帮助 Compass 了解项目的各种资产的位置;让我们详细了解这些选项:

  • http_path:这必须设置为与项目根文件夹相关的 HTTP URL

  • css_dir:这包含了生成的 CSS 文件应保存的文件夹的相对路径

  • sass_dir:这包含了包含我们的.scss文件的文件夹的相对路径

  • images_dir:这包含了项目所有图像的文件夹的相对路径

  • javascripts_dir:与images_dir类似,但用于 JavaScript 文件

还有其他可用的选项;我们可以决定输出的 CSS 是否应该被压缩,或者我们可以要求 Compass 使用相对路径而不是绝对路径。有关所有可用选项的完整列表,请参阅compass-style.org/help/tutorials/configuration-reference/上的文档。

接下来,我们可以创建刚才描述的文件夹结构,为我们的项目提供cssimgjsscss文件夹。最后,我们可以创建一个空的scss/application.scss文件,并开始发现 Compass 的美丽。

CSS 重置和供应商前缀

我们可以要求 Compass 在对其 SCSS 对应文件进行每次更新后重新生成 CSS 文件。为此,我们需要在项目的根目录中使用终端执行以下命令:

compass watch .

Compass 提供了一个替代我们在上一个项目中使用的 Yahoo!重置样式表。要包含此样式表,我们只需在application.scss文件中添加一个 SASS include指令:

@import "compass/reset";

如果我们检查css/application.css,结果如下(已修剪):

/* line 17, ../../../../.rvm/gems/ruby-1.9.3-p194/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  font-size: 100%;
  vertical-align: baseline;
}

/* line 22, ../../../../.rvm/gems/ruby-1.9.3-p194/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */
html {
  line-height: 1;
}

... 

还要注意生成的 CSS 如何保留对原始 SCSS 的引用;当需要调试页面中一些意外行为时,这非常方便。

接下来的@import指令将处理 CSS3 实验性供应商前缀。通过在application.scss文件顶部添加@import "compass/css3",我们要求 Compass 自动为我们提供许多强大的方法来添加实验性前缀;例如,以下代码片段:

.round {
    @include border-radius(4px);
}

编译成以下内容:

.round {
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    -o-border-radius: 4px;
    -ms-border-radius: 4px;
    -khtml-border-radius: 4px;
    border-radius: 4px;
}

装备了这些新知识,我们现在可以开始部署项目了。

使用 rem

对于这个项目,我们想引入rem,这是一个几乎等同于em的测量单位,但始终相对于页面的根元素。因此,基本上我们可以在html元素上定义一个字体大小,然后所有的大小都将与之相关:

html{
  font-size: 20px;
}

现在,1rem对应20px;这种测量的问题在于一些浏览器,比如 IE8 或更低版本,实际上不支持它。为了解决这个问题,我们可以使用以下两种不同的备用测量单位:

  • em:好消息是,em如果完全调整,与rem完全相同;坏消息是,这种测量单位是相对于元素的font-size属性而不是相对于html。因此,如果我们决定采用这种方法,那么每次处理font-size时,我们都必须特别小心。

  • px:我们可以使用固定单位像素大小。这种选择的缺点是在旧版浏览器中,我们使得动态改变小部件比例的能力变得更加复杂。

在这个项目中,我们将使用像素作为我们的测量单位。我们之所以决定这样做,是因为rem的一个好处是我们可以通过媒体查询改变字体大小属性来轻松改变仪表的大小。这仅在支持媒体查询和rem的情况下才可能。

现在,我们必须找到一种方法来解决大部分重复的问题,即必须两次插入包含空格测量单位的语句(rempx)。我们可以通过在我们的application.scss文件中创建一个 SASS mixin 来轻松解决这个问题(有关 SASS mixin 的更多信息,我们可以参考sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#mixins的规范页面):

@mixin px_and_rem($property, $value, $mux){
  #{$property}: 0px + ($value * $mux);
  #{$property}: 0rem + $value;
}

因此,下次我们可以写成:

#my_style{
width: 10rem;
}

我们可以写成:

#my_style{
@include px_and_rem(width, 10, 20);
}

除此之外,我们还可以将pxrem之间的“乘数”系数保存在一个变量中,并在每次调用此函数和html声明中使用它;让我们也将这个添加到application.scss中:

$multiplier: 20px;

html{
  font-size: $multiplier;
}

当然,仍然有一些情况下我们刚刚创建的@mixin指令不起作用,这种情况下我们将不得不手动处理这种二重性。

仪表的基本结构

现在我们准备至少开发我们仪表的基本结构,包括圆角边框和最小和最大范围标签。以下代码是我们需要添加到application.scss中的:

div[data-gauge]{
  position: absolute;

  /* width, height and rounded corners */
  @include px_and_rem(width, 10, $multiplier);
  @include px_and_rem(height, 5, $multiplier);
  @include px_and_rem(border-top-left-radius, 5, $multiplier);
  @include px_and_rem(border-top-right-radius, 5, $multiplier);

  /* centering */
  @include px_and_rem(margin-top, -2.5, $multiplier);
  @include px_and_rem(margin-left, -5,  $multiplier);
  top: 50%;
  left: 50%;

  /* inset shadows, both in px and rem */
box-shadow: 0 0 #{0.1 * $multiplier} rgba(99,99,99,0.8), 0 0 #{0.1 * $multiplier} rgba(99,99,99,0.8) inset;
  box-shadow: 0 0 0.1rem rgba(99,99,99,0.8), 0 0 0.1rem rgba(99,99,99,0.8) inset;

  /* border, font size, family and color */
  border: #{0.05 * $multiplier} solid rgb(99,99,99);	
  border: 0.05rem solid rgb(99,99,99);

  color: rgb(33,33,33);
  @include px_and_rem(font-size, 0.7, $multiplier);
  font-family: verdana, arial, sans-serif;

  /* min label */
  &:before{
    content: attr(data-min);
    position: absolute;
    @include px_and_rem(bottom, 0.2, $multiplier);
    @include px_and_rem(left, 0.4, $multiplier);
  }

  /* max label */
  &:after{
    content: attr(data-max);
    position: absolute;
    @include px_and_rem(bottom, 0.2, $multiplier);
    @include px_and_rem(right, 0.4, $multiplier);
  }
}

使用box-shadowborder,我们无法使用px_and_rem混合,因此我们首先使用px,然后使用rem复制这些属性。

以下截图显示了结果:

仪表的基本结构

刻度标记

如何处理刻度标记?一种方法是使用图像,但另一个有趣的选择是利用多重背景支持,并用渐变创建这些刻度标记。例如,要创建一个垂直标记,我们可以在div[data-gauge]选择器中使用以下内容:

linear-gradient(0deg, transparent 46%, rgba(99, 99, 99, 0.5) 47%, rgba(99, 99, 99, 0.5) 53%, transparent 54%)

基本上,我们定义了一个非常小的透明渐变和另一种颜色之间的渐变,以获得刻度线。这是第一步,但我们还没有处理每个刻度线必须用不同的角度来定义这个事实。我们可以通过引入一个 SASS 函数来解决这个问题,该函数接受要打印的刻度数,并在达到该数字时迭代,同时调整每个标记的角度。当然,我们还必须处理实验性的供应商前缀,但我们可以依靠 Compass 来处理。

以下是这个功能。我们可以为此和其他与仪表相关的功能创建一个名为scss/_gauge.scss的新文件;前导下划线是告诉 SASS 不要将这个.scss文件创建为.css文件,因为它将被包含在一个单独的文件中。

@function gauge-tick-marks($n, $rest){
  $linear: null;
  @for $i from 1 through $n {
 $p: -90deg + 180 / ($n+1) * $i;
    $linear: append($linear, linear-gradient( $p, transparent 46%, rgba(99,99,99,0.5) 47%, rgba(99,99,99,0.5) 53%, transparent 54%), comma);
  }
  @return append($linear, $rest);  
}

我们从一个空字符串开始,添加调用linear-gradient Compass 函数的结果,该函数处理基于当前刻度线索引的角度变化。

为了测试这个功能,我们首先需要在application.scss中包含_gauge.scss

@import "gauge.scss";

接下来,我们可以在application.scss中的div[data-gauge]选择器中插入函数调用,指定所需的刻度数:

@include background(gauge-tick-marks(11,null));

background函数也是由 Compass 提供的,它只是处理实验性前缀的另一种机制。不幸的是,如果我们重新加载项目,结果与预期相去甚远:

刻度线

虽然我们可以看到总共有 11 条条纹,但它们的大小和位置都是错误的。为了解决这个问题,我们将创建一些函数来设置background-sizebackground-position的正确值。

处理背景大小和位置

让我们从background-size开始,这是最简单的。由于我们希望每个刻度线的大小恰好为1rem,我们可以通过创建一个函数,根据传递的参数的数量打印1rem 1rem,来继续进行;因此,让我们将以下代码添加到_gauge.scss中:

@function gauge-tick-marks-size($n, $rest){
  $sizes: null;
  @for $i from 1 through $n {
 $sizes: append($sizes, 1rem 1rem, comma);
  }
  @return append($sizes, $rest, comma);
}

我们已经注意到了append函数;关于它的一个有趣的事情是,这个函数的最后一个参数让我们决定是否使用某个字母来连接正在创建的字符串。其中一个可用的选项是逗号,这非常适合我们的需求。

现在,我们可以在div[data-gauge]选择器内添加对这个函数的调用:

background-size: gauge-tick-marks-size(11, null);

以下是结果:

处理背景大小和位置

现在刻度线的大小是正确的,但它们是一个接一个地显示,并且在整个元素上重复。为了避免这种行为,我们可以在上一条指令的下面简单地添加background-repeat: no-repeat

background-repeat: no-repeat;

另一方面,为了处理刻度线的位置,我们需要另一个 SASS 函数;这次它更复杂一些,涉及一点三角学。每个渐变必须放在其角度的函数中——x 是该角度的余弦,y 是正弦。sincos函数由 Compass 提供,我们只需要处理一下偏移,因为它们是相对于圆的中心,而我们的 css 属性的原点是在左上角。

@function gauge-tick-marks-position($n, $rest){
  $positions: null;
  @for $i from 1 through $n {
 $angle: 0deg + 180 / ($n+1) * $i;
 $px: 100% * ( cos($angle) / 2 + 0.5 );
 $py: 100% * (1 - sin($angle));
    $positions: append($positions, $px $py, comma);
  }
  @return append($positions, $rest, comma);
}

现在我们可以继续在div[data-gauge]选择器内添加一行新代码:

background-position: gauge-tick-marks-position(11, null);

这就是期待已久的结果:

处理背景大小和位置

下一步是创建一个@mixin指令来将这三个函数放在一起,这样我们可以将以下内容添加到_gauge.scss中:

@mixin gauge-background($ticks, $rest_gradient, $rest_size, $rest_position) {

  @include background-image(
    gauge-tick-marks($ticks, $rest_gradient) 
  );

  background-size: gauge-tick-marks-size($ticks, $rest_size);
  background-position: gauge-tick-marks-position($ticks, $rest_position);
  background-repeat: no-repeat;
}

并用一个单独的调用替换我们在本章中放置在div[data-gauge]内的内容:

@include gauge-background(11, null, null, null );

我们还留下了三个额外的参数来定义backgroundbackground-sizebackground-position的额外值,因此我们可以很容易地添加一个渐变背景:

@include gauge-background(11,
  radial-gradient(50% 100%, circle, rgb(255,255,255), rgb(230,230,230)),
  cover,
  center center
);

以下是屏幕截图:

处理背景大小和位置

创建箭头

要创建一个箭头,我们可以从定义仪表中心的圆形元素开始,该元素容纳箭头。这很容易,也没有真正引入任何新东西;以下是需要嵌套在div[data-gauge]选择器内的代码:

  div[data-arrow]{
    position: absolute;
    @include px_and_rem(width, 2, $multiplier);
    @include px_and_rem(height, 2, $multiplier);
    @include px_and_rem(border-radius, 5, $multiplier);
    @include px_and_rem(bottom, -1, $multiplier);
    left: 50%;
    @include px_and_rem(margin-left, -1, $multiplier);
   box-sizing: border-box;

    border: #{0.05 * $multiplier} solid rgb(99,99,99);  
    border: 0.05rem solid rgb(99,99,99);
    background: #fcfcfc;
  }

箭头本身是一个更严肃的事情;基本思想是使用线性渐变,只在元素的一半开始添加颜色,然后我们可以旋转元素,以便将指向末端移动到其中心。以下是需要放在div[data-arrow]内的代码:

    &:before{
      position: absolute;
      display: block;
      content: '';
      @include px_and_rem(width, 4, $multiplier);
      @include px_and_rem(height, 0.5, $multiplier);
      @include px_and_rem(bottom, 0.65, $multiplier);
      @include px_and_rem(left, -3, $multiplier);
 background-image: linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange); 
 background-image: -webkit-linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange); 
 background-image: -moz-linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange);
 background-image: -o-linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange); 

 @include apply-origin(100%, 100%);
 @include transform2d( rotate(-3.45deg));
 box-shadow: 0px #{-0.05 * $multiplier} 0 rgba(0,0,0,0.2);
 box-shadow: 0px -0.05rem 0 rgba(0,0,0,0.2);			@include px_and_rem(border-top-right-radius, 0.25, $multiplier);
 @include px_and_rem(border-bottom-right-radius, 0.35, $multiplier);
    }

为了更好地理解这个实现背后的技巧,我们可以暂时在结果中的&:before选择器内添加border: 1px solid red,然后放大一点:

创建箭头

移动箭头

现在我们想要根据data-percent属性值将箭头定位到正确的角度。为了做到这一点,我们必须利用 SASS 的强大功能。理论上,CSS3 规范允许我们使用从属性中获取的值来赋值给一些属性,但实际上,这只有在处理content属性时才可能,就像我们在本书中之前看到的那样。

所以我们要做的是创建一个@for循环,从0100,并在每次迭代中打印一个选择器,该选择器匹配data-percent属性的定义值。然后我们将为每个 CSS 规则设置不同的rotate()属性。

以下是代码;这次它必须放在div[data-gauge]选择器内:

@for $i from 0 through 100 {
  $v: $i;
  @if $i < 10 { 
    $v: '0' + $i;
  }

  &[data-percent='#{$v}'] > div[data-arrow]{
      @include transform2d(rotate(#{180deg * $i/100}));
  }
}

如果你对生成的 CSS 数量感到害怕,那么你可以决定调整仪表的增量,例如,调整为10

  @for $i from 0 through 10 {
    &[data-percent='#{$i*10}'] > div[data-arrow]{
      @include transform2d(rotate(#{180deg * $i/10}));
    }
  }

以下是结果:

移动箭头

动画仪表

现在我们可以使用 CSS 过渡来使箭头动画化。基本上,我们必须告诉浏览器需要对transform属性进行动画处理;必要的 SASS 代码比预期的要长一些,因为 Compass 尚不能为transition属性及其值添加前缀(github.com/chriseppstein/compass/issues/289),所以我们必须手动完成:

  -webkit-transition: -webkit-transform 0.5s;
  -moz-transition: -moz-transform 0.5s;
  -ms-transition: -ms-transform 0.5s;
  -o-transition: -o-transform 0.5s;
  transition: transform 0.5s;

当我们将这些 CSS 指令放在div[data-arrow]选择器内时,我们会注意到,如果我们改变data-percentage属性,例如,使用 Chrome 和它的开发控制台,箭头会以平滑的动画做出响应。

整体指示器

一些仪表具有颜色指示器,通常从绿色到红色,与箭头的位置相关联;我们可以得出类似的结果。首先,我们需要定义两个新的自定义数据属性,一个指示指示器从绿色切换到橙色的百分比,另一个指示指示器从橙色切换到红色的百分比。在这里:

<div data-gauge data-min="0" data-max="100" data-percent="50" data-orange-from="60" data-red-from="90">
  <div data-arrow></div>
</div>

然后我们需要在div[data-gauge]中指定一个默认的背景颜色,比如说green

background-color: green;

接下来,我们重新定义背景渐变,使圆周的前 25%透明;这样我们就可以显示(和控制)底层颜色,所以让我们重新编写gauge-background调用:

@include gauge-background(11,
 radial-gradient(50% 100%, circle, rgba(255,255,255,0), rgba(255,255,255,0) 25%, rgb(255,255,255) 25%, rgb(230,230,230)),
  cover,
  center center
);

现在我们可以使用另一个 Sass 循环来改变background-color属性,以符合属性中定义的值。由于我们将在前一个循环中嵌套实现一个循环,我们必须小心,不要使生成的 CSS 的大小增加太多。

为了实现这一点,让我们只考虑data-orange-fromdata-red-from数据属性的十位数。我们需要做的基本上是编写一个 CSS 规则,如果data-percentage属性大于或等于data-orange-fromdata-red-from,则激活红色或橙色背景颜色。

以下是完整的循环,包括我们之前用来移动箭头的循环:

@for $i from 0 through 100 {
  $v: $i;
  @if $i < 10 { 
    $v: '0' + $i;
  } 

  &[data-percent='#{$v}'] > div[data-arrow]{
    @include transform2d(rotate(#{180deg * $i/100}));
  }

 @for $k from 0 through 10 {
 @if $i >= $k * 10 {
 &[data-percent='#{$v}'][data-orange-from^='#{$k}']{
 background-color: orange;
 }
 &[data-percent='#{$v}'][data-red-from^='#{$k}']{
 background-color: red;
 }
 }
 }
}

以下是结果:

整体指示器

减小 CSS 的大小

通过要求 Compass 不在每个规则之前添加指向相应 SASS 规则的注释,可以减少生成的 CSS 的大小。如果我们想要这样做,只需在config.rb文件中添加line_comments = false,然后在项目的根文件夹中停止并重新启动compass watch

添加一些颤动

作为一个额外的功能,我们可以添加一个选项,让箭头在接近 100%时颤动一点。如果存在额外的data-trembling属性,我们可以通过添加一个小动画来实现这种行为:

<div data-gauge data-min="0" data-max="100" data-percent="50" data-orange-from="60" data-red-from="90" data-trembling>

不幸的是,Compass 没有默认提供 CSS3 动画 mixin,因此我们必须安装一个可以帮助我们的 Compass 插件。在这种情况下,插件称为compass-animationgithub.com/ericam/compass-animation),由 Eric Meyer 创建(eric.andmeyer.com/)。安装方法如下:

gem install animation –pre

或者如下:

sudo gem install animation –-pre

然后在调用compass watch时必须同时包含插件:

compass watch . –r animation

application.scss的头部添加:

@import "animation";

干得好!现在我们准备定义一个非常简单的动画,修改箭头的旋转角度,引起我们寻找的颤动效果。让我们在application.scss的末尾添加几行代码:

@include keyframes(trembling) {
  0% {
      @include transform2d( rotate(-5.17deg));
  }
  100% {
      @include transform2d( rotate(-1.725deg));
  }
}

然后,我们需要在div[data-gauge]内添加一个新规则,如果data-trembling存在,并且data-percentage89开头,或者等于100,则激活此动画:

&[data-trembling][data-percent^='8'] > div[data-arrow]:before,
&[data-trembling][data-percent^='9'] > div[data-arrow]:before,
&[data-trembling][data-percent='100'] > div[data-arrow]:before{
 @include animation(trembling 0.2s infinite linear alternate);
}

不幸的是,由于 WebKit 浏览器中一些尚未解决的错误,阻止动画应用于beforeafter伪选择器,目前只有 Firefox 正确实现了这种行为:

添加一些颤动

显示仪表值

如果我们对 HTML 代码进行小修改,就可以轻松显示当前的仪表值:

<div data-gauge data-min="0" data-max="100" data-percent="50" data-orange-from="60" data-red-from="90" data-trembling>
<span>50</span>
  <div data-arrow></div>
</div>

以下是要添加到div[data-gauge]选择器内的代码:

span{
  display: block;
  color: #DDD;
  @include px_and_rem(font-size, 1.5, $multiplier);
  text-align: center;
  @include px_and_rem(width, 10, $multiplier);
  @include px_and_rem(height, 5, $multiplier);
  @include px_and_rem(line-height, 5, $multiplier);
}

结果:

显示仪表值

优雅降级

为了使这个小部件对那些不支持背景渐变的浏览器也有意义,我们必须处理箭头的不同表示。为了检测缺少此功能的位置,我们可以使用 Modernizr 创建一个自定义构建(modernizr.com/download/),就像我们在前几章中只检查渐变支持一样:

<script src="img/modernizr.js"></script>

然后我们可以选择一个纯色背景;箭头当然会变成一个矩形,但我们会保留小部件的含义;让我们在application.scss的底部添加这条规则:

.no-cssgradients div[data-gauge]{ 

 div[data-arrow]:before{
 background-color: orange;
 @include transform2d( rotate(0deg));
 box-shadow: none;
 border-radius: 0;
 }
}

以下是结果:

优雅降级

我们可以进一步使用 Compass 将渐变转换为Base64编码的 SVG,并在本机不支持渐变的情况下将它们用作回退背景图像。不幸的是,这在使用数值表达角度的渐变(如23deg)时不起作用,因此我们将无法重现刻度线。但是,我们可以要求 Compass 转换我们用于背景的radial-gradient属性。以下是我们需要在.no-cssgradients div[data-gauge]规则内添加的属性:

background-image: -svg(radial-gradient(50% 100%, circle, rgba(255,255,255,0), rgba(255,255,255,0) 35%, rgb(255,255,255) 35%, rgb(230,230,230)));
background-size: cover;
background-position: auto;

以下是结果,更接近原始仪表:

优雅降级

在 Internet Explorer 8 中实现仪表

如果我们想支持 Internet Explorer 8,那么我们需要解决border-radiustransform属性的缺失。

对于border-radius,我们可以使用基于 JavaScript 的 polyfill,比如 CSS3 Pie,我们可以从它的网站css3pie.com/下载这个 polyfill,然后将PIE.js复制到项目的js文件夹中。接下来,我们可以在index.html中包含这个 JavaScript 文件,以及最新版本的 jQuery 和js/application.js,这是一个我们一会儿要用到的空文件:

<!--[if IE 8]>
  <script src="img/jquery-1.8.0.min.js"></script>
  <script src="img/PIE.js"></script>
  <script src="img/application.js"></script>
<![endif]-->

通常情况下,CSS3 Pie 会自动检测如何增强给定元素,通过识别要模拟的 CSS3 属性。然而,在这种情况下,我们使用了border-top-left-radiusborder-top-right-radius,而 CSS3 Pie 只支持通用的border-radius。我们可以通过在div[data-gauge]规则中添加一个带有-pie前缀的特殊border-radius属性来解决这个问题:

-pie-border-radius: #{5 * $multiplier} #{5 * $multiplier} 0px 0px;

接下来,我们需要通过在js/application.js中插入几行 JavaScript 代码来激活 CSS3 Pie:

$(function() {
    if (window.PIE) {
 $('div[data-gauge]').each(function() {
 PIE.attach(this);
 });
    }
});

以下是结果:

在 Internet Explorer 8 中实现仪表

现在,如果我们想要激活箭头旋转,我们需要模拟transform属性。为了实现这种行为,我们可以使用 Louis-Rémi Babé的jquery.transform.jsgithub.com/louisremi/jquery.transform.js);Louis-Rémi Babé(twitter.com/louis_remi)。

下载完库之后,我们需要将jquery.transform2d.js复制到项目的js文件夹中。然后在index.html中添加必要的script元素。为了在 Internet Explorer 8 浏览器中为html元素添加不同的类,我们将使用IE条件注释来为html元素添加不同的类。结果如下:

<!doctype html>
<!--[if IE 8]> <html class="ie8" > <![endif]-->
<!--[if !IE]> --> <html> <!-- <![endif]-->
<head>
  <title>Go Go Gauges</title>
  <script src="img/modernizr.js"></script>
  <link rel="stylesheet" type="text/css" href="css/application.css">
  <!--[if IE 8]>
    <script src="img/jquery-1.8.0.min.js"></script>
 <script src="img/jquery.transform2d.js"></script>
    <script src="img/PIE.js"></script>
    <script src="img/application.js"></script>
  <![endif]-->
</head>
<!-- ...rest of index.html ... -->

jquery.transform2d.js使得即使在 Internet Explorer 8 浏览器上也能触发transform属性,从而增强了 jQuery 提供的css功能;以下是一个例子:

$(elem).css('transform', 'translate(50px, 30px) rotate(25deg) scale(2,.5) skewX(-35deg)');

因此,我们可以尝试通过调用前述函数添加一些 JavaScript 代码行;这将使js/application.js变成如下形式:

$(function() {
    if (window.PIE) {
        $('div[data-gauge]').each(function() {
            PIE.attach(this);

 var angle = Math.round(180 * parseInt($(this).attr('data-percent'),10)/100);
 $('div[data-arrow]',$(this)).css({
 'transform': 'rotate(' + angle + 'deg)'
 });
        });
    }
});

不幸的是,结果并不如预期那样好:

在 Internet Explorer 8 中实现仪表

问题在于div[data-arrow]:before元素被裁剪在其父元素内。这可以通过在箭头下方绘制一个白色圆盘(现在是一个正方形),并将div[data-arrow]调整大小为整个小部件,并且背景透明且没有边框,以便容纳箭头。

为此,我们可以使用.ie8类,仅在浏览器为 Internet Explorer 8 时添加一些属性。让我们在application.scss中添加几行代码。

.ie8 div[data-gauge]{
  div[data-arrow]{
    width: #{10 * $multiplier};
    height: #{10 * $multiplier};
    margin-top: #{-5 * $multiplier};
    margin-left: #{-5 * $multiplier};
    top: 50%;
    left: 50%;
    background: transparent;
    border: none;
    &:before{
      bottom: 50%;
      margin-bottom: #{-0.25 * $multiplier};
      left: #{1 * $multiplier};
    }
  }
}

最后,以下是工作结果:

在 Internet Explorer 8 中实现仪表

Compass 和 Internet Explorer 10

在撰写本文时,Compass 的最新版本(0.12.0)没有为linear-gradientradial-gradient添加-ms-实验性前缀。为了解决这个问题并使仪表在 IE10 上顺利工作,我们必须对我们的.scss代码进行一些修改。特别是,我们需要按照以下方式修改_gauge.scss中的gauge-tick-marks函数:

@function gauge-tick-marks($n, $rest, $ms){
  $linear: null;
  @for $i from 1 through $n {
    $p: -90deg + 180 / ($n+1) * $i;
 $gradient: null;
 @if $ms == true {
 $gradient: -ms-linear-gradient( $p, transparent 46%, rgba(99,99,99,0.5) 47%, rgba(99,99,99,0.5) 53%, transparent 54%);
 } @else{
 $gradient: linear-gradient( $p, transparent 46%, rgba(99,99,99,0.5) 47%, rgba(99,99,99,0.5) 53%, transparent 54%);
 }
    $linear: append($linear, $gradient, comma);
  }
 @if $ms == true {
 @return append($linear, #{'-ms-' + $rest} ); 
 } @else{
 @return append($linear, $rest); 
 }
}

我们还需要在_gauge.scss中修改gauge-background mixin:

@mixin gauge-background($ticks, $rest_gradient, $rest_size, $rest_position) {

 @include background-image(
 gauge-tick-marks($ticks, $rest_gradient, false) 
 );

 background-image: gauge-tick-marks($ticks, $rest_gradient, true);

  background-size: gauge-tick-marks-size($ticks, $rest_size);
  background-position: gauge-tick-marks-position($ticks, $rest_position);
  background-repeat: no-repeat;
}

最后,我们还需要在application.scss中的:before中的div[data-arrow]中添加额外的 CSS 行:

background-image: -ms-linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange);

在进行这些小修改之后,我们也可以在 Internet Explorer 10 中欣赏到这个小部件:

Compass 和 Internet Explorer 10

总结

绘制仪表可能比预期更困难;如果我们还要考虑支持旧版浏览器,情况就更加复杂。在本章中,我们学习了如何安装和使用 Compass,利用 SASS 语法的强大功能创建复杂的 CSS,并处理优雅降级和填充技术。在下一章中,我们将利用 CSS 动画和 3D 变换的功能创建一个电影预告片。

第九章:创建介绍

这个项目的目标是创建一个介绍,一个非交互式的动画,使用文本和视觉效果来呈现产品、概念或其他内容。这个项目让我们有机会探索一些高级动画和 3D 主题,并在创建一些特定函数来处理这种复杂性的同时扩展我们对 Compass 的了解。

本章将涵盖以下主题:

  • 新的 flexbox 模型

  • 创建关键帧动画

  • 连接动画

  • CSS 3D 属性的动画

项目描述

我们想要在 3D 场景中放置一些元素,然后穿过它们。为此,我们首先必须创建一个 HTML 结构来容纳这些元素,然后我们必须找到一种聪明的方法来获得所需的效果。但是,在做任何其他事情之前,我们必须定义文件夹结构并初始化项目的基本文件。

与之前的项目一样,我们将使用 SASS 和 Compass,因此我们需要安装 Ruby (www.ruby-lang.org/en/downloads/),然后在终端窗口中输入gem install compass(或sudo gem install compass)。之后,我们需要在项目的根文件夹中创建一个config.rb文件,其中包含 Compass 配置:

# Require any additional compass plugins here.

# Set this to the root of your project when deployed:
http_path = "YOUR-HTTP-PROJECT-PATH"
css_dir = "css"
sass_dir = "scss"
images_dir = "img"
javascripts_dir = "js"

# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed

# To enable relative paths to assets via compass helper functions. Uncomment:
relative_assets = true

# To disable debugging comments that display the original location of your selectors. Uncomment:
line_comments = false

preferred_syntax = :sass

干得好!下一步是创建项目所需的文件夹,即cssscssimgjs,并定义一个空的scss/application.scss文件。然后我们需要从项目的根文件夹启动compass watch .,最后创建主 HTML 文档index.html

创建 HTML 结构

我们要创建的基本上是一个幻灯片放置在 3D 空间中,动画从一张幻灯片移动到另一张。一个基本的幻灯片结构可以是这样的:

<div data-sequence="1">
  <div data-slide>
    Hello,
  </div>
</div>

我们需要两个嵌套的div标签来定义这个结构;第一个将覆盖窗口区域的 100%,第二个div标签将具有必要的属性来将其内容放在屏幕中央。此外,我们需要设置每个幻灯片,使它们在开始在 3D 空间中移动之前堆叠在彼此上方。

我们可以使用flexbox CSS 属性来实现这个结果。事实上,flexbox 具有定义垂直和水平对齐的属性。

让我们根据我们迄今为止所见的内容定义一个基本的 HTML 结构:

<!doctype html>
<html>
<head>
  <title>Movie Trailer</title>
  <link href='http://fonts.googleapis.com/css?family=Meie+Script' rel='stylesheet' type='text/css'>
  <link rel="stylesheet" type="text/css" href="css/application.css">
</head>
<body>
 <div id="viewport">
 <div id="container">

 <div data-sequence="1">
 <div data-slide>
 Hello,
 </div>
 </div>

 <div data-sequence="2">
 <div data-slide>
 this is a demo
 </div>
 </div>

 <div data-sequence="3">
 <div data-slide>
 about the power
 </div>
 </div>

 <div data-sequence="4">
 <div data-slide>
 of CSS 3D 
 </div>
 </div>

 <div data-sequence="5">
 <div data-slide>
 and animations
 </div>
 </div>

 <div data-sequence="6">
 <div data-slide>
 :D
 </div>
 </div>

 </div>
 </div>
</body>
</html>

没有任何 CSS 的幻灯片将是这样的:

创建 HTML 结构

创建幻灯片

首先,让我们将每个幻灯片的position属性设置为absolute,并通过在scss/application.scss中编写几行代码将widthheight设置为100%

@import "compass/reset";
@import "compass/css3/box";
@import "compass/css3/transform";

html,body, #viewport, #container{
  height: 100%;
  font-size: 150px;
}

#container{

  & > div{
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;    
  } 

  div[data-slide]{
    width: 100%;
    height: 100%;
    text-align: center;

 @include display-box;
 @include box-align(center);
 @include box-pack(center);
  }

}

Flexbox 非常方便,由于box-packbox-align属性,它基本上在主 Flexbox 方向(默认为水平,但可以通过box-orient属性更改)和其垂直方向上设置对齐。

由于这个项目目前只在 Chrome 和 Firefox 上运行(IE10 似乎在使用嵌套的 3D 变换时存在一些问题),我们对这些属性感到满意;否则,我们应该记住,旧的 Flexbox 语法(我们正在使用的 2009 年的语法)不受 Internet Explorer 10 的支持。

微软的最新浏览器只包括对最新的 Flexbox 实现的支持,它有一个相当不同的语法,不幸的是,它目前还不能在基于 Gecko 的浏览器上工作。

在第四章缩放用户界面中,我们开发了一个项目,尽管使用了不受支持的 Flexbox 语法,但在 IE10 中也运行得很好。这是因为在那种情况下,我们包含了 Flexie,一个模拟 Flexbox 行为的 polyfill,当旧的 Flexbox 语法不受支持时。

让我们深入了解这种新的 Flexbox 语法的细节,并为了完整起见,让我们将两种语法都添加到这个项目中。

新的弹性盒模型

新的灵活布局模型(从这里开始,以及在本章的整个过程中,被称为 Flexbox)旨在像其以前的版本一样,为开发人员提供一种在页面上对齐元素的新方法。

使用这种新的盒模型的元素可以垂直或水平放置,并可以动态交换它们的顺序,还可以根据可用空间“伸缩”它们的大小和位置。

这里有一个例子(在 Internet Explorer 10 上测试):

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>

        <style>
            html,body,ul{
                height: 100%;
                margin: 0;
                padding: 0;
            }
 ul{
 display: -ms-flexbox;
 -ms-flex-direction: row-reverse;
 -ms-flex-pack: center;
 -ms-flex-align: center;
 -ms-flex-wrap: wrap;
 }
 li {
 font-size: 70px;
 line-height: 100px;
 text-align: center;
 list-style-type: none;
 -ms-flex: 1 0 200px;
 }

        </style>

    </head>
    <body>

        <ul>
            <li style="background-color: #f9f0f0">A</li>
            <li style="background-color: #b08b8b">B</li>
            <li style="background-color: #efe195">C</li>
            <li style="background-color: #ccdfc4">D</li>
        </ul>

    </body>
</html>

这是生成的页面:

新的弹性盒模型

通过之前定义的属性,我们使用了display: -ms-flexbox来定义了一个 Flexbox(W3C 的值是flex,但每个浏览器都会稍微改变这个值,要么通过添加自定义前缀,要么稍微改变它)。我们使用了-ms-flex-direction: row-reverse来反转可视化顺序;这个属性也用于指定我们想要水平还是垂直排列。可用的值有:rowcolumnrow-reversecolumn-reverse-ms-flex-pack-ms-flex-align属性确定了 Flexbox 子元素在它们的主轴和垂直轴上的对齐方式(如-ms-flex-direction所指定的)。

这些属性仍然是 Flexbox IE10 实现的一部分,但最近已被align-itemsjustify-content替换,因此在整合时我们也需要注意这一点。

我们使用了-ms-flex-wrap: wrap来要求浏览器在主轴上的空间不足以容纳所有元素时将元素放置在多行上。

最后,我们在每个元素上使用了-ms-flex: 1 0 200px来指示每个子元素具有正的 flex 因子1,因此它们将以相同的速度覆盖空白空间,保持它们的大小相等,负的 flex 因子0,和一个首选大小200px

这与我们之前指定的-ms-flex-wrap属性一起,创建了一个有趣的响应效果,当浏览器窗口太小无法容纳它们在一行时,元素会移动到新的行:

新的弹性盒模型

创建一个示例布局

我们可以利用这个属性来创建一个三列布局,其中两个侧列在没有足够空间的情况下移动到中央列的上方和下方,比如在移动设备上。以下是创建这种布局的代码:

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>

        <style>
 section {
 min-height: 300px; 
 }

 div {
 display: -ms-flexbox;
 -ms-flex-direction: row;
 -ms-flex-pack: center;
 -ms-flex-wrap: wrap;
 }

 aside, nav {
 -ms-flex: 1 3 180px;
 min-height: 100px;
 }

 nav {
 -ms-flex-order: 1;
 background-color:  #ffa6a6;
 }

 aside {
 -ms-flex-order: 3;
 background-color:  #81bca1;
 }

 section {
 -ms-flex: 3 1 600px;
 -ms-flex-order: 2;
 background-color: #72c776;
 }
        </style>
    </head>
    <body>

        <div>
            <section></section>
            <nav></nav>
            <aside></aside>
        </div>

    </body>
</html>

这是结果:

创建一个示例布局

如果我们现在调整浏览器窗口大小,我们会注意到navaside元素如何在主内容上下移动,为移动设备创建了一个漂亮的布局。

创建一个示例布局

让我们回到我们的项目;我们可以很容易地通过几行 CSS 来支持 Flexbox 的新版本,如下所示:

  div[data-slide]{
    width: 100%;
    height: 100%;
    text-align: center;

    @include display-box;
    @include box-align(center);
    @include box-pack(center);

 display: -ms-flexbox;
 display: -moz-flex;
 display: -webkit-flex;
 display: flex;
 -ms-flex-pack: center;
 -moz-align-items: center;
 -webkit-align-items: center;
 align-items: center;
 -ms-flex-align: center;
 -moz-justify-content: center;
 -webkit-justify-content: center;
 justify-content: center;
  }

这是期待已久的结果:

创建一个示例布局

处理幻灯片

现在我们可以使用一些 3Dtransform属性来移动和旋转 3D 场景中的每个幻灯片。这些变换是绝对任意的,可以根据电影预告片的整体效果进行选择;这里有一个例子:

div{
  &[data-sequence="1"]{
    @include transform(rotateX(45deg));
  }

  &[data-sequence="2"]{
    @include transform(rotateY(45deg) translateY(300px) scale(0.5));
  }

  &[data-sequence="3"]{
    @include transform(rotateX(90deg) translateY(300px) scale(0.5));
  }

  &[data-sequence="4"]{
    @include transform(rotateX(90deg) translateY(300px) translateX(600px) scale(0.5));
  }

  &[data-sequence="5"]{
    @include transform(rotateX(90deg) translateZ(300px) translateY(350px) translateX(600px) scale(0.5));
  }

  &[data-sequence="6"]{
    @include transform(rotateZ(30deg) translateY(500px) translateZ(300px));
  }
}

现在,我们需要在幻灯片的父元素上设置一些 3D 标准属性,如transform-styleperspective

#viewport{
  @include transform-style(preserve-3d);
  @include perspective(500px);
  overflow: hidden;
  width: 100%;
}

#container{
    @include transform-style(preserve-3d);
}

如果我们现在在 Chrome 中运行项目,我们会注意到幻灯片不像之前的截图中堆叠在一起;相反,它们现在都放置在 3D 场景的各个位置(大部分在变换后不可见):

处理幻灯片

移动摄像机

现在,我们将学习如何创建一个摄像机穿过幻灯片的效果;由于我们无法移动用户的视口,我们需要通过移动场景中的元素来模拟这种感觉;这可以通过对#container应用一些变换来实现。

要将摄像机移近幻灯片,我们需要应用我们在该幻灯片上使用的确切变换,但使用相反的值并以相反的顺序。因此,例如,如果我们想查看data-sequence属性为3的帧,我们可以写:

// not to be permanently added to the project
#container{
    @include transform(scale(2) translateY(-300px) rotateX(-90deg));
}

这就是结果:

移动摄像机

动画必须专注于一张幻灯片,保持静止一段时间,然后移动到下一张幻灯片。在创建这种效果时,我们通常面临以下两个主要问题:

  • CSS keyframes只接受百分比值,但我们更愿意使用秒作为测量单位(例如,说“在 2 秒内移动到下一张幻灯片,然后保持静止 1 秒”)

  • 我们需要为每个幻灯片处理两个keyframes规则(移动和静止);最好为我们处理这个的是有一个函数

我们可以通过使用 SASS 轻松解决这两个问题。首先,我们可以创建一个函数,通过接受动画的总长度来将秒转换为百分比值:

$total_animation_duration: 11.5;
@function sec_to_per($sec, $dur: $total_animation_duration){
 @return 0% + $sec * 100 / $dur;
}

这个函数接受两个参数——我们想要从秒转换为百分比的值以及动画的总长度。如果没有提供这个参数,则该值将设置为$total_animation_duration变量。

我们可以为这个项目创建的第二个函数接受move时间和still时间作为参数,并打印必要的关键帧,同时跟踪动画的进展百分比:

$current_percentage: 0%;
@mixin animate_to_and_wait($move, $still ) {

 $move_increment: sec_to_per($move);
 $current_percentage: $current_percentage + $move_increment;

 #{ $current_percentage }{ @content }

 @if $still == end {
 $current_percentage: 100%;
 } @else{
 $still_increment: sec_to_per($still);
 $current_percentage: $current_percentage + $still_increment;
 }

 #{ $current_percentage }{ @content } 

}

这个函数的作用基本上是将$move参数转换为百分比,并将这个值添加到全局变量$current_percentage中,该变量跟踪动画的进展。

然后我们打印一个关键帧,使用我们刚刚计算的百分比,包含 SASS 为我们填充的@content变量的值,该值是我们在函数调用后在花括号中放置的内容,例如:

myfunction(arg1, arg2){
  // everything here is placed into @content variable
}

如果$still等于end,我们希望静止阶段持续到动画结束,所以我们将$current_percentage变量设置为100%;否则,我们将这个变量与我们处理$move变量的方式相同,然后打印另一个关键帧。

动画乐趣

为了处理 CSS3 动画属性附带的所有实验性前缀,我们可以再次使用 Compass 动画插件(从命令行终端中使用gem install animation进行安装,然后从项目的根文件夹中使用compass watch . -r animation重新启动 Compass)。

我们还需要在application.scss中包含animation

@import "animation";

我们还需要编写一个小函数,它包装了动画插件提供的函数,并在每次从一个实验性前缀切换到另一个时重置$current_percentage

@mixin ext_keyframes($name){

  @include with-only-support-for($moz: true) {
    @-moz-keyframes #{$name} { @content; }
    }
    $current_percentage: 0%;
    @include with-only-support-for($webkit: true) {
    	@-webkit-keyframes #{$name} { @content; }
    }
    $current_percentage: 0%;
    @include with-only-support-for {
      @keyframes #{$name} { @content; }
    }
}

好!现在我们准备把事情放在一起并定义我们的动画:

/* == [BEGIN] Camera == */
@include ext_keyframes(camera){
 0%{
 @include transform(none);
 }

 @include animate_to_and_wait(0.5, 1.5){ 
 @include transform(scale(2) rotateX(-45deg));
 }

 @include animate_to_and_wait(0.5, 1.5){
 @include transform(scale(2) translateY(-300px) rotateY(-45deg));
 }

 @include animate_to_and_wait(0.5, 1.5){
 @include transform(scale(2) translateY(-300px) rotateX(-90deg));
 }

 @include animate_to_and_wait(0.5, 1.5){
 @include transform(scale(2) translateX(-600px) translateY(-300px) rotateX(-90deg));
 }

 @include animate_to_and_wait(0.5, 1.5){
 @include transform(scale(2) translateX(-600px) translateY(-350px) translateZ(-300px) rotateX(-90deg));
 }

 @include animate_to_and_wait(0.5, end){
 @include transform(scale(2) translateZ(-300px) translateY(-500px) rotateZ(-30deg));
 }
}
/* == [END] Camera == */

最后,我们必须向#container添加适当的动画属性:

#container{
@include animation(camera #{0s + $total_animation_duration} linear);
@include animation-fill-mode(forwards);
}

完成!在浏览器中进行最后一次重新加载就足以充分欣赏动画:

动画乐趣

步骤动画

我们现在将创建一个特殊的动画,它会与每次幻灯片更改同步切换我们项目的背景颜色。由于我们不希望颜色之间有任何淡入淡出效果,我们将引入步骤动画。

步骤动画让我们可以指定在每个声明的关键帧之间放置多少帧;这里有一个例子:

<!DOCTYPE html>

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style>
            div {
                width:  100px;
                height: 100px;
                background-color: red;
                position: absolute;
 -webkit-animation: diagonal 3s steps(5) infinite alternate;
            }

            @-webkit-keyframes diagonal {
                from {  
                    top: 0;
                    left: 0; 
                }
                to {  
                    top: 500px;
                    left: 500px; 
                }
            }

        </style>
    </head>
    <body>
        <div></div>
    </body>
</html>

如果我们现在在浏览器中运行这个小例子,我们会发现div元素的移动不是流畅的,而是由只有五帧组成。我们可以在步骤声明中添加一个特殊关键字startend(例如,step(5, end))来要求浏览器在每一步中跳过初始或最终关键帧。好!现在,我们可以将相同的概念应用到我们的介绍项目中。首先,我们需要定义一个改变background-color属性的动画:

/* == [BEGIN] bg == */
@include ext_keyframes(bg){
  0%{
    background: green;
  }
  #{sec_to_per(2)}{
    background: darkolivegreen;
  }
  #{sec_to_per(4)}{
    background: violet;
  }
  #{sec_to_per(6)}{
    background: orange;
  }
  #{sec_to_per(8)}{
    background: lightsteelblue;
  }
  #{sec_to_per(10)}{
    background: thistle;
  }
  100%{
    background: pink;
  }
}
/* == [END] bg == */

请注意我们如何使用sec_to_per函数以便使用秒而不是百分比;接下来,我们只需要使用animation属性将bg添加到#viewport

#viewport{
  @include animation(bg #{0s + $total_animation_duration} steps(1,start));
  @include animation-fill-mode(forwards);
}

这就是结果:

步骤动画

最后的修饰

现在我们已经定义了一个基本结构,并学会了如何创建一个在 3D 场景中移动的流畅动画,显然,下一步是丰富每个幻灯片,包括图片、视频、图表,以及我们可能需要实现我们目的的一切。

为了做到这一点,我们可以利用本书前几章已经积累的知识;例如,我们可以很容易地为第一张幻灯片定义一个淡入动画,如下所示:

div[data-sequence="1"]{
  @include animation(sequence_1 2s linear);
  @include animation-fill-mode(forwards);
}

/* == [BEGIN] sequence_1 == */
@include ext_keyframes(sequence_1){
  0%{
    color: rgba(0,0,0,0);
  }
}
/* == [END] sequence_1 == */

我们还可以向幻灯片添加自定义字体:

div[data-sequence="2"]{
  font-family: 'Meie Script', cursive;
}

这是结果:

最后的修饰

总结

CSS 动画和 3D 变换可以结合起来创建有趣的效果;当然,当我们转向这些类型的功能时,我们必须接受一些浏览器可能无法支持项目的事实。然而,我们总是可以使用一些特性检测库,比如 Modernizr,来解决这个问题,当这些功能不受支持时提供一些替代的可视化。

在下一章中,我们将完全使用 CSS3 创建一个漂亮的图表库!

第十章:CSS 图表

通过简单的谷歌搜索,我们发现有很多令人惊叹的图表库,比如Google Chart Toolsdevelopers.google.com/chart/)、Highchartswww.highcharts.com/)和gRaphaelg.raphaeljs.com/),仅举几例。我们可能不知道的是,有一些 CSS 技术可以让我们创建完全功能的图表,而不使用 JavaScript 库。在本章中,我们将探讨其中一些技术,以下是主题:

  • 创建一个条形图

  • 实现跨浏览器、灵活的盒子布局

  • 处理和显示data-*属性

  • 实现高级渐变

  • 添加更多的图表系列

  • 为图表添加动画

  • 创建一个饼图

创建一个条形图

要创建一个条形图,我们需要设置一个div元素数组,它们都对齐到底部,然后控制它们的高度属性。然后,我们需要找到一种聪明的方法来显示每个条的标签,以及可选的每个条的值。

要创建div数组,我们可以简单地使用一个具有position:relativediv容器,其中包含每个条的div元素,绝对定位为bottom:0。这种技术的问题在于我们需要在 CSS 中定义每个条的大小。我们还需要预先知道我们正在设计的图表的条数,这使得我们的 CSS 在不同的图表或动态修改我们正在设计的图表时变得不太适应。

为了解决这个问题,我们需要找到一个 CSS 结构,可以平均地将容器空间分配给子元素。我们在过去的章节中已经使用了灵活的盒子布局显示模式,但是,我们通常用它来水平和垂直居中元素。解决我们的条形图定位问题时,灵活的盒子布局就变得非常宝贵了。

让我们定义一个index.html文件,用以下结构来开发我们的项目:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
  <title>Charts</title>
  <link rel="stylesheet" type="text/css" href="css/application.css">
</head>
<body>
    <div data-bar-chart class="this_bar_chart">
        <div data-bar data-label="mon">
            <div class="value series1" data-value-percentage="40" data-value-label="40"></div>
        </div>
        <div data-bar data-label="tue">
            <div class="value series1" data-value-percentage="100" data-value-label="125"></div>
        </div>
        <div data-bar data-label="wed">
            <div class="value series1" data-value-percentage="80" data-value-label="112"></div>
        </div>
        <div data-bar data-label="thu">
            <div class="value series1" data-value-percentage="15" data-value-label="21"></div>
        </div>
        <div data-bar data-label="fri">
            <div class="value series1" data-value-percentage="10" data-value-label="14"></div>
        </div>
        <div data-bar data-label="sat">
            <div class="value series1" data-value-percentage="35" data-value-label="46"></div>
        </div>
        <div data-bar data-label="sun">
            <div class="value series1" data-value-percentage="58" data-value-label="67"></div>
        </div>
    </div>
</body>

在这里,我们可以识别这个结构的三个不同组件。首先是具有data-bar-chart属性的div元素;这是整个图表的容器;它还包含所有的条。然后,我们有具有data-bar属性的div元素;它们中的每一个在容器内保留了真正的条形图所需的空间,并在data-label属性中保存相应的条形标签。

最后,具有.value类的div元素代表图表的实际条形;条的高度用data-value-percentage属性表示,范围从0%100%。我们决定使用标准化值,因为这使得我们的图表 CSS 实现更通用,而不是固定在这个特定实例上。为了表示每个条的真实值,我们定义了另一个属性data-value-label

在开始之前,我们需要像往常一样设置我们的开发环境,所以我们需要在项目的根文件夹中创建一些文件夹,即cssimgjsscss。我们将在这个项目中使用 Sass 和 Compass,所以我们需要安装它们(如果还没有安装),首先安装 Ruby(www.ruby-lang.org/en/downloads/),然后从命令行终端执行gem install compass(或sudo gem install compass)。

最后,我们需要在项目的根文件夹中创建一个config.rb文件,可以通过从上一个项目中复制相同的文件来完成。

当一切都设置好后,我们可以在scss文件夹下创建一个application.scss文件,其中只包含以下一行:

@import "compass/reset";

然后,我们可以从命令行终端输入compass watch .,并验证相应的css/application.css文件是否存在。

将空间细分

因为我们不希望开发一个只适用于这个特定 HTML 代码的 CSS 条形图实现,我们必须区分与此图表相关的属性和更通用且可重复使用的属性。因此,主要元素有一个名为this_bar_chart的类。我们可以专门为这个图表使用这个类,例如,为这个图表定义widthheight,因此我们可以在application.scss中编写以下内容:

.this_bar_chart{
    width: 600px;
    height: 400px;
}

很好!我们现在需要实现弹性盒布局,以便在所有data-bar元素之间平均分配具有data-bar-chart属性的元素的空间。

一个小的复杂之处在于目前有两种不同的 flexbox 语法(正如我们在第九章中看到的,创建介绍),并且对这两种语法的浏览器支持都不完整。为了解决这个问题,我们将同时实现两种语法。我们需要将容器的display属性设置为box(旧语法)或flex(新语法),然后为每个data-bar元素设置box-flex: 1(旧语法)或flex: 1(新语法)属性。

通过指定每个data-bar元素具有相同的 flex 增长因子,所有这些元素将同步填充容器空间,速度相同,从而导致每个元素具有相同的宽度。

以下是要添加到application.scss中的代码:

*[data-bar-chart]{
    display: -moz-box;
    display: -webkit-box;
    display: box;

    display: -moz-flex;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;

    /* temporary property only for this step */
    border: 1px solid black;    

    *[data-bar]{
        -webkit-box-flex: 1;
        -moz-box-flex: 1;
        box-flex: 1;

        -moz-flex: 1;
        -webkit-flex: 1;
        -ms-flex: 1;
        flex: 1;

        /* temporary property only for this step */
        border: 1px solid black;    
    }
}

如果我们在浏览器中尝试我们的项目(Chrome,Firefox,Internet Explorer 10),我们会注意到容器空间在所有子元素之间均等地分割,无论数量如何:

分割空间

添加 Internet Explorer 8 和 9 支持

Internet Explorer 10 之前的版本不支持任何形式的 flexbox。我们可以通过使用一个 polyfill 库(我们已经在第四章中介绍过,缩放用户界面)来轻松解决这个问题,该库名为 flexie.js(flexiejs.com/)。

要添加 flexie,我们首先需要下载 jQuery(jquery.com/),然后是 Selectivzr(selectivizr.com/),最后是 flexie 本身。这三个文件都必须放在js文件夹中。

最后,我们可以将这三个文件放在一个条件注释中,确保只有 Internet Explorer 8 和 9 需要执行这些额外的 HTTP 请求:

    <!--[if (gte IE 8)&(lte IE 9)]>
        <script src="img/jquery-1.8.2.min.js"></script>
        <script src="img/selectivizr-min.js"></script>
        <script src="img/flexie.js"></script>
    <![endif]-->

创建条形标签

很好!我们现在可以删除临时边框属性并继续下一步,创建条形标签。

目前,我们的条形标签包含在data-bar元素内,作为data-label属性的值,因此我们必须使用:after:before伪选择器与content属性结合使用才能打印它们。

我们还需要为这些标签保留一些空间,因为目前,容器的整个高度都被要创建的图表的条形占据了。

我们可以通过向容器添加padding-bottom(以及box-sizing属性以保持原始容器高度,如果支持的话),然后使用绝对定位将条形标签放在每个data-bar元素的外部和下方来实现这一点。

这是我们可以实现这种行为的一小段 CSS 代码:

@import "compass/css3/box-sizing";

*[data-bar-chart]{
    padding-bottom: 30px;
    @include box-sizing(border-box);

    /* temporary property only for this step */
    border: 1px solid red;

    *[data-bar]{
        position: relative;

        /* temporary property only for this step */
        background: green;

        &:before{
            display: block;
            content: attr(data-label);
            position: absolute;
 top: 100%;
            left: 0;
            width: 100%;
            text-align: center;
            padding-top: 10px;
        }
    }
}

我们使用top:100%属性将:before选择器的内容移动到包含元素之外,而不指定精确的高度值,通过这样做,我们尽可能地保持我们的 CSS 图表指令的通用性。

现在在浏览器中重新加载项目,标签将正确放置在容器底部,位于其填充空间内:

创建条形标签

在继续之前,我们需要记住删除在此步骤中定义的临时属性。

设计条形图

每个条形包含一个.value元素,这个元素需要被设计以响应以下特征:

  • 它的高度必须符合其data-value-percentage

  • 它必须与其父元素data-bar的底部对齐

  • 它必须在某处显示其data-value-label

  • 它必须在自身和下一个图表条之间有一定的间距

让我们从最后两点开始。首先,我们可以使用绝对定位将.value元素放置在其父元素的底部,并与其父元素的右边和左边的边界的选择距离对齐。

这是所需的 CSS:

.value{    
    position: absolute;
    bottom: 0;
    left: 6%;
    right: 6%;
}

关于条的高度的所需行为可以通过从0100循环并打印一个 CSS 规则来实现,该规则根据当前循环索引设置height属性,如下所示:

@for $i from 0 through 100{
    *[data-value-percentage='#{$i}']{ height: 0% + $i; }
}

最后,我们可以使用与图表标签相同的技巧来打印每个条形的值,只是这一次,我们需要将文本放在每个有色条的正上方。我们还必须记住,与标签一样,如果条的height属性设置为100%,我们需要为这个文本保存一些空间;因此,我们需要向容器添加顶部填充(使用padding-top属性),如下所示:

*[data-bar-chart]{
    padding-top: 35px;

    .value:after{
        content: attr(data-value-label);
        position: absolute;
        font-size: 25px;
        display: block;
        bottom: 100%;
        padding-bottom: 10px;
        left: 0;
        width: 100%;
        text-align: center;
    }
}

在展示项目之前,我们需要为图表条提供至少一种颜色,这是另一个临时属性,因为当我们在本章后面介绍图表系列时,我们将替换它。

.value{
    /* temporary property only for this step */
    background: green;
}

在我们最喜欢的浏览器中重新加载后,我们的项目看起来像这样:

设计条

美化图表

现在是时候删除临时属性并稍微美化图表了。

有许多方法可以进一步美化我们迄今为止所做的工作,并使其看起来更好。首先,我们可以为标签和条值使用自定义字体;接下来,我们可以添加圆角和阴影。最后,我们可以使用渐变。

关于渐变,我们可以实现类似于我们在第二章中使用的技术,闪亮按钮,在那里我们使用渐变来处理高光和阴影,将background-color属性设置为条的颜色。

继续前进,我们将选择自定义字体,设置一些内阴影,指定border-radius属性,然后定义从透明到实色的渐变;这是所需的 CSS:

@import url(http://fonts.googleapis.com/css?family=Chivo);
@import "compass/css3/images";

.value:after, *[data-bar]:before{
    font-family: 'Chivo', sans-serif;
}

.value{
    background-image: -ms-linear-gradient(bottom, transparent, rgba(0,0,0,0.3));
    @include background-image(linear-gradient(bottom, transparent, rgba(0,0,0,0.3)));
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    box-shadow: 1px 1px 0px rgb(255,255,255) inset;
    border: 1px solid rgba(0,0,0,0.5);
    @include box-sizing(border-box);
}

我们必须明确指定-ms-linear-gradient,因为 Compass 不能处理这种属性的-ms-实验性前缀。实际上,Compass 的这种行为是正确的,因为微软表示最新的 CSS3 属性在新的 Internet Explorer 上可以无前缀地工作,但在撰写本文时,当前版本的 Internet Explorer 10 仍然需要-ms-前缀。

现在,我们可以通过简单地向.value元素添加临时的background-color属性来测试项目,如下所示:

.value{
    /* temporary property only for this step */
    background-color: green;
}

这是结果:

美化图表

在不支持我们用来美化项目的属性的浏览器上,结果可能会有所不同。但是,由于background-color属性设置为主要颜色,图表的关键信息仍然可用。

图表线

我们还可以使用背景属性来绘制通常标记和将条形图的背景分成步骤的水平线,每个步骤代表总高度的 20%。

要继续,我们需要创建一个线性渐变,除了它的第一个像素外,大部分是透明的,然后我们必须将此背景的大小设置为 20%,并重复它;这是所需的 CSS:

*[data-bar]{
    background-image: -ms-linear-gradient(top, rgb(99,99,99), rgb(99,99,99) 1px, transparent 1px, transparent);
    @include background-image(linear-gradient(top, rgb(99,99,99), rgb(99,99,99) 1px, transparent 1px, transparent));
    background-size: 100% 25%;
    background-repeat: repeat-y;
}

这是结果:

图表线

这并不是我们预期的结果;最后一行比条的底部略高。这可能是因为浏览器发现很难在容器大小不是我们希望背景适合的次数的完美倍数时对齐重复的渐变。

我们可以尝试使用一个描述五个小灰色条带的单一渐变的不同方法;以下是替换先前代码的 CSS 代码:

*[data-bar]{
    background-image: -ms-linear-gradient(top, rgb(150,150,150), rgb(150,150,150) 0.5%, transparent 0.5%, transparent 24.5%, rgb(150,150,150) 24.5%, rgb(150,150,150) 25%, transparent 25%, transparent 49.5%, rgb(150,150,150) 49.5%, rgb(150,150,150) 50%, transparent 50%, transparent 74.5%, rgb(150,150,150) 74.5%, rgb(150,150,150) 75%, transparent 75%, transparent 99.5%, rgb(150,150,150) 99.5%, rgb(150,150,150) 100%);
    @include background-image(linear-gradient(top, rgb(150,150,150), rgb(150,150,150) 0.5%, transparent 0.5%, transparent 24.5%, rgb(150,150,150) 24.5%, rgb(150,150,150) 25%, transparent 25%, transparent 49.5%, rgb(150,150,150) 49.5%, rgb(150,150,150) 50%, transparent 50%, transparent 74.5%, rgb(150,150,150) 74.5%, rgb(150,150,150) 75%, transparent 75%, transparent 99.5%, rgb(150,150,150) 99.5%, rgb(150,150,150) 100%));
}

尽管代码看起来不够优雅,但结果比之前的尝试要好得多。

图表线

我们目前的解决方案并不完全完美,因为我们被迫使用百分比来指定灰色条带的大小,这可能会导致当图表的大小增加太多时出现粗条带。综上所述,这是迄今为止最好的解决方案,所以让我们坚持下去。

现在我们可以删除临时的 CSSbackground-color属性,并转移到下一个图表增强,系列。

图表系列

在我们的标记中,所有的.value元素都有另一个类.series1;这是因为我们希望我们的图表支持多个系列,所以让我们在标记中添加一个新的.series2元素集:

<input type="checkbox" class="series1" checked id="series1"> <label for="series1">show serie 1</label>
<input type="checkbox" class="series2" checked id="series2"> <label for="series2">show serie 2</label><br/>
<div data-bar-chart class="this_bar_chart">
    <div data-bar data-label="mon">
        <div class="value series1" data-value-percentage="40" data-value-label="40"></div>
 <div class="value series2" data-value-percentage="60" data-value-label="60"></div>
    </div>
    <div data-bar data-label="tue">
        <div class="value series1" data-value-percentage="100" data-value-label="125"></div>
 <div class="value series2" data-value-percentage="30" data-value-label="20"></div>
    </div>
    <div data-bar data-label="wed">
        <div class="value series1" data-value-percentage="80" data-value-label="112"></div>
 <div class="value series2" data-value-percentage="70" data-value-label="80"></div>
    </div>
    <div data-bar data-label="thu">
        <div class="value series1" data-value-percentage="15" data-value-label="21"></div>
 <div class="value series2" data-value-percentage="50" data-value-label="60"></div>
    </div>
    <div data-bar data-label="fri">
        <div class="value series1" data-value-percentage="10" data-value-label="14"></div>
 <div class="value series2" data-value-percentage="90" data-value-label="100"></div>
    </div>
    <div data-bar data-label="sat">
        <div class="value series1" data-value-percentage="35" data-value-label="46"></div>
 <div class="value series2" data-value-percentage="20" data-value-label="25"></div>
    </div>
    <div data-bar data-label="sun">
        <div class="value series1" data-value-percentage="58" data-value-label="67"></div>
 <div class="value series2" data-value-percentage="10" data-value-label="12"></div>
    </div>
</div>

我们可以假设每个图表系列都带有一个复选框和一堆元素,所有这些元素都有相同的.seriesx类,其中x14之间的数字(我们不支持每个图表超过 4 个系列)。

在这种假设下,我们可以设置一个 Sass @for循环,完成以下任务:

  • 为系列设置background-color

  • 如果相应的复选框未被选中,则隐藏系列

为了动态创建不同颜色的系列,我们可以依赖于hsl坐标系统(色相饱和度亮度),因为通过简单地改变色相分量,我们可以获得保持相同饱和度和亮度的颜色,从而产生更愉悦的组合。

要隐藏系列,我们必须创建一个相当复杂的 CSS 规则,基本上是说,如果具有类似series1的类的复选框被选中,就获取data-bar-chart系列中具有相同系列类(在这种情况下是series1)的所有元素,并将它们的opacity属性设置为1

以下是相应的 CSS:

.value{
    opacity: 0;
}

@for $i from 1 through 4{
    input:checked.series#{$i}[type='checkbox'] ~ *[data-bar-chart] .series#{$i}{
        opacity: 1;
    }
    *[data-bar-chart] .series#{$i}{
        background-color: hsl(0deg + 10 * $i, 50%, 50%);
    }
}

以下是结果:

图表系列

添加一些动画

我们使用opacity来隐藏系列以响应复选框的取消选中,以便我们可以添加transition指令并淡入和淡出系列:

@import "compass/css3/transition";

.value{
    @include transition(opacity 0.4s);        
}

Internet Explorer 8 和 9

令人惊讶的是,即使是 Internet Explorer 8 也能理解并应用我们之前定义的复杂规则。这一成就归功于 Selectivzr,这是一个我们与 flexie 一起包含的库,它有能力在 IE6 到 IE8 中启用一些 CSS3 选择器。唯一的问题是这些浏览器不支持opacity属性,所以我们必须检测这一点,并切换回更好支持的display:none属性。

我们可以使用自定义的 Modernizr 构建来完成这项任务,所以让我们从官方网站(modernizr.com/download)下载它,注意从CSS3列中选择opacity复选框。

接下来,将文件重命名为modernizr.js后,我们可以将其包含在 HTML 文件中,如下所示:

<script src="img/modernizr.js"></script>

最后,我们需要在系列的循环中添加另一个规则,如果html元素上存在no-opacity类,则使用display:none而不是opacity: 0

这是新的循环,包括.no-opacity选择器:

.value{
    opacity: 0;
}

@for $i from 1 through 4{
    input[type='checkbox'].series#{$i}:not(:checked) ~ *[data-bar-chart] .series#{$i}{
        opacity: 1;
    }

 .no-opacity input[type='checkbox'].series#{$i}:not(:checked) ~ *[data-bar-chart] .series#{$i}{
 display:none;
 }

    *[data-bar-chart] .series#{$i}{
        background-color: hsl(0deg + 10 * $i, 50%, 50%);
    }
}

旋转图表

通过利用 CSS transform属性,我们可以轻松地将这个图表从垂直条形图转换为水平条形图。然而,这个过程不能像我们为条和系列所做的那样通用地定义,因为它严重依赖于图表的大小;因此,我们将使用.this_bar_chart选择器。

为了实现这一点,我们将改变图表的widthheight属性(将它们都设置为500px以更好地处理后续的旋转),将整个图表旋转90度,然后再次旋转文本元素以保持可读性。

这是 CSS 代码:

@import "compass/css3/transform";

.this_bar_chart{
    @include transform(rotate(90deg) translate(10px,-10px));
    width: 500px;
    height: 500px;

    *[data-bar]{

        &:before{
            @include transform(rotate(-90deg) translate(-7px,0px));
        }

        .value:after{
            @include transform(rotate(-90deg) translate( 5px ,0px));
        }    
    }
}

这就是结果:

旋转图表

当然,在不支持 CSS transform属性的浏览器中,这种最后的实现既不起作用,也不容易获得。

仅使用 CSS 和 HTML 创建饼图

在本章结束时,我想向你介绍一个非常聪明的技术,我从 Patrick Denny(Atomic Noggin Enterprises 的负责人)那里学到了,可以仅使用 HTML 和 CSS 创建饼图。

该技术的核心是如何获得所需大小的图表切片;这是通过在 HTML 结构上使用clipborder-radius属性来实现的(我们可以创建一个新的pie.html文件):

<div class="hold">
<div class="slice slice1"></div>
</div>

首先,我们使用.slice选择器和border-radius属性创建一个圆形;然后将圆形切成两半。

.slice{
  position: absolute;
  width: 200px;
  height: 200px;
  clip: rect(0px,100px,200px,0px);
  border-radius: 100px;
}

然后,我们使用clip属性切掉圆形的另一半,如下所示:

.hold {
  position: absolute;
  width: 200px;
  height: 200px;
  clip: rect(0px,200px,200px,100px);
}

现在,通过在.hold上使用clip属性,我们使用widthheight属性设置为200px切掉圆形的另一半。最后一步很容易,我们可以通过旋转.slice来展示所选大小的饼图切片:

.slice1{
  -moz-transform: rotate(30deg);
  -webkit-transform: rotate(30deg);
  -o-transform: rotate(30deg);
  transform: rotate(30deg);
  background-color: red;
}

这就是结果:

仅使用 CSS 和 HTML 创建饼图

通过将一些这样的 HTML 结构堆叠在一起,可以获得一个完全功能的纯 CSS 饼图。

有关更详细的步骤,请参阅 Patrick 的博客atomicnoggin.ca/blog/2010/02/20/pure-css3-pie-charts/.

总结

在本章中,我们使用了本书早期介绍的资源和技术来构建一个相当复杂的视觉图表。这个项目展示了 CSS 如何被用来开发否则需要 JavaScript 或其他技术(如 Flash 或 Silverlight)的组件。

posted @ 2024-05-24 11:15  绝不原创的飞龙  阅读(3)  评论(0编辑  收藏  举报