HWeb-标准的创造性-全-
HWeb 标准的创造性(全)
一、语义结构,肮脏漂亮的呈现
西蒙·科里森
www.collylogic.com
在 2006 年开始厄斯金设计之前,西蒙·科利森是 Agenzia ( www.agenzia.co.uk
)的首席网页开发员,在那里他为唱片公司做了许多网页项目。Simon 热情地确保他构建的所有东西都是可访问的,并且符合当前的网络标准。
西蒙与人合著了博客设计解决方案和 CSS 掌握(均由 ed 之友出版)。他的第一本个人书籍开始 CSS Web 开发 ( www.csswebdevelopment.com
)于 2006 年由 Apress 出版。
在办公室之外,Simon 经营着一个很受欢迎的博客 Colly Logic ( www.collylogic.com
),他是一个所谓的 Britpacka 集体的活跃成员,这个集体由懒散的设计师和开发人员组成,他们都热衷于负责任的网页设计。当西蒙离开笔记本电脑时,他很可能出现在酒吧或音乐会上,不停地谈论好音乐、足球或饼干。
西蒙曾在许多城市生活过,包括伦敦和雷克雅未克,但现在他回到了他心爱的诺丁汉,那里的草是绿色的,女孩也很漂亮。
简述
对于唱片公司来说,建立一个符合网络标准的网站是一个挑战。多年来,Flash 一直是乐队网站的必备工具。此外,还有深不可测的导航挑战、点唱机插件、框架、可疑的调色板和无处不在的Loading
栏。虽然网络看起来很流行,但在音乐行业,它仍然是 1999 年。正如丹·鲁宾将在本书第二章中阐述的那样,情况正在好转,但是好的网站仍然少之又少。
对于 Dirty beautiful Things 网站(www.dirtyprettythingsband.com
),我和我在 Agenzia 的设计师同事有机会在 2004 年夏天重新推出的 Libertines ( www.thelibertines.org.uk
)网站的基础上再接再厉(见图 1-1") )。我们按照 Web 标准构建了 Libertines 网站,以负责任的方式使用 Flash 元素。我们不顾一切地将内容和表现分开,以利用越来越多的用户通过移动设备访问网站,并确保核心内容的持久性,不管未来的重新设计如何。碰巧的是,英国摇滚的巨大希望在过度和不良行为的漩涡中解散了,但该网站仍然拥有不断增长的用户群体。
18 个月过去了,Libertines 的联合创始人 Carl Barat 组建了一个新的乐队,音乐媒体和乐迷们对此寄予厚望。由于各种错误的原因,前放荡不羁的名人皮特·多赫提充斥着各种小报,现在轮到卡尔和他的公司把欢乐带给成千上万把灵魂出卖给放荡不羁者的粉丝了。从一开始,很明显,任何新网站都将是受欢迎的一方,根据我们从 Libertines 网站获得的经验,很容易说服唱片公司,网络标准是唯一的出路。
Hannah Bays 为乐队创作的大量原创作品扔给了我们,虽然独特而又酷,但似乎没有一件适合网页设计。事实上,整合设计是一个漫长的过程,当我们开始只用 XHTML 和 PHP/MySQL 构建网站的核心内容时,这个过程就被搁置了。
图 1.1。浪子网站的最终版本(www.thelibertines.org.uk
)
本章将加入我们完成 XHTML 和数据库的过程,并返回到表示方法,看看这是如何通过使用 CSS 的一个单独的层来实现的。重点将放在应用外观的视觉接触,如自定义背景,标题和 CSS 的其他部件,留下核心内容不妥协。图 1-2, launched in January 2006") 显示了网站的最终版本。
图 1.2。完成的脏漂亮的东西网站(www.dirtyprettythingsband.com
),于 2006 年 1 月推出
语义结构
我们在这里举着 Web 标准的旗帜,所以我们需要确保文档(XHTML plus 内容)与表示(CSS 和装饰性图像)是分离的。通过使用外部样式表应用所有装饰性的表示丰富性,文档(XHTML)保持纯净和集中。由于所有的表示材质都与标记分开保存,所以站点范围内的样式变化可以轻而易举地完成。我们只需要修改一个 CSS 文件,而不是更新网站中的每个页面,这使得大规模的重新设计变得轻而易举。同样重要的是,如果需要,用户可以通过将自己的样式表应用到网站来控制内容。
一个重要的步骤是给添加到核心内容的任何部分(divs
)起一个有意义的名字,而不是表象的名字。比如刊头叫masthead
,主栏叫content_main
,等等。这种方法也适用于较小的细节,比如用于改变元素颜色或强调的 id 和类名。创建一个名为green_heading
的类是没有意义的,如果这个类以后可能会被 CSS 用来将标题渲染成蓝色。取而代之的是,类被赋予一个描述元素角色的名字,而不是它的风格,比如date_heading
。在大多数情况下,甚至不需要这样的挂钩,因为样式表中的后代选择器用于选择其他元素的子元素。您将在本章后面的“内容”一节中看到它是如何工作的。
图 1-3 as styled purely by the browser's default style sheet") 显示了由浏览器自己的默认样式表设计的网站截图。该站点是一个功能完整的数据库驱动的站点,它完成了构建的第一阶段,并展示了即使样式表不可用,一切都可以很好地运行。
随着构建阶段的结束,团队现在可以开始认真考虑表示,以及如何将核心内容用作网站外观和感觉的框架。是时候变脏,变漂亮了。
图 1.3。文档(XHTML plus 内容)的样式完全由浏览器的默认样式表决定
肮脏漂亮的展示
当纯粹用 XHTML 构建时,明智的做法是对站点的布局有一个好的想法。很自然地,我们会考虑列和核心区域,比如页眉和页脚,并且这通常会通过 Photoshop 布局(或者甚至是使用铅笔和纸的草图)来传达。)来自团队里的一个设计师。但在《肮脏美丽的事物》网站上,情况并非如此,因为我们都还没有达成一个我们自己都同意的布局,更不用说唱片公司的人了。
通过对功能齐全的 XHTML 站点应用外部样式表,我们有机地解决了这个问题。这意味着,当我们失去了一个一致同意的设计,我们开始通过 CSS 的实验来建立一个外观和感觉。通过创建和修改 CSS 规则,我们测试了从布局(流动、固定或弹性)到字体选择的一切。这种方法很慢,并且需要 Photoshop 一直打开来创建和调整背景图像,这些图像可能会也可能不会进入最终的设计,但这是一个令人兴奋的过程,它消除了已交付的、一成不变的设计的约束。
有了 Hannah Bays 的所有原创作品,并慢慢开发高度、宽度、边距和填充等参数,一个令人满意的设计很快就会实现。由于样式表应用于站点的所有页面,所以很容易看出什么在站点范围内起作用。艺术品的哪些区域可以作为背景?某些区域可以作为背景瓷砖吗?从收集的源材质中,文本和核心元素的通用调色板是否可以实现?
下一步是与同事讨论设计,包括抚摸下巴和喝咖啡,然后确保客户对目前的进展感到满意。然后,我们一点一点地琢磨这些成分,使设计更加严谨,然后再进行可用性测试。最后,网站启动了,收集到的 Agenzians 人可以去酒吧。
背景图片
也许从 XHTML 到精心设计的漂亮风格的最戏剧性的变化发生在主背景图像与报头及其后代相遇的地方。大量精心的 Photoshop 工作使用了一系列背景图像,如图 1-4 所示。
图 1.4。拆分成几层,各种背景图片并置。
注意
在你开始拼命使用背景图片之前,重要的是要考虑这会如何影响最终用户。虽然没有以前那么重要,但你仍然需要意识到,有些人正在使用非常慢的调制解调器下载网页。一个典型的网页可能包含大约 15KB 的文本,因此下载速度非常快。再加上 35KB 的背景图片,你就大大降低了访问者的浏览速度,至少在浏览器缓存图片之前是如此。保持图像使用光线,只有在真正必要时,或者当你为你知道将使用超高速宽带连接的观众设计时,才过度使用光线。
在任何情况下,如果图像不显示或被用户手动禁用,我们都不会使用背景图像来传达重要信息。例如,我们不使用背景图像来显示网站的标题、任何导航项目或任何类型的扁平文本内容,除非我们在图像关闭的情况下也使用标准文本来提供这些信息。背景图像是装饰性的装饰品,当然不能被认为是“内容”,这就是为什么它们在样式表中作为单独的表示元素被引用。
背景、标题和菜单
我们将从背景、标题和主菜单这三个主要层在现有 XHTML 上的样式开始。
一个内嵌图像已经出现:主要的脏漂亮的东西标志。徽标(图 1-5 )是一个动画 GIF,当页面第一次加载时,它会一个字母一个字母地拼出乐队名称。该徽标在文档流中,位于刊头的左上方。
图 1.5。Dirty beautiful Things 徽标变成了一个带有锯齿状背景的动画 GIF。
查看这一部分的 XHTML,您会看到波段名也在<h1>
元素中指定,该元素使用display:none
隐藏。这是为了确保如果图像和 CSS 被禁用,该网站仍然是品牌。
<div id="wrapper">
<div id="masthead">
<a href="home"><img src="logo.gif" alt="Dirty Pretty Things" /></a>
<h1>Dirty Pretty Things - Official Website</h1>
</div>
...rest of content...
</div>
背景
接下来,我们将主背景图像(图 1-6 )应用到<body>
元素。虽然比理想的要大得多,但是 JPG 的图像将会像瓷砖一样工作。我更喜欢使用 GIF 格式的背景图像,但是这个大背景图像的细节层次和图像层次要求 JPG 格式有更大的灵活性。
图 1.6。主背景图像,将被放置在中心并水平平铺
我们在 CSS 中使用背景速记将平铺显示放在顶部中央,并确保它只水平平铺。查看正文选择器的 CSS,您会看到除了主字体规则和其他项目之外,所有背景规则都使用 CSS 简写进行组合,顺序为background-color
、background-image
、background-position
、background-attachment
和background-repeat
。
body {
margin: 0;
padding: 0;
font-size: 95%;
font-family: Georgia, 'Lucida Grande', Verdana, sans-serif;
text-align:center;
color: #333;
background: #C6C2B6 url(img/background.jpg) center top fixed repeat-x;
}
注意
由于图像是水平重复的(repeat-x
),无论浏览器窗口有多宽,它都会占据整个网站的宽度。但是,页面background-color
是在背景拼贴底部的平面颜色上采样的。与这种颜色结合的图像将完成整个背景,并给人一种背景图像高得多的印象。
在速记中,我们使用了background-attachment:fixed
来防止背景与页面的其余部分一起滚动,并使用background-position:center top
来确保第一个背景图像平铺的中点与浏览器窗口的中点保持一致。填充宽度所需的所有其他图块将放置在该初始居中图像的左侧和右侧。
刊头
下一个任务是控制包装和报头。刊头背景图片(图 1-7 )是在 Photoshop 中创建的,其中有一个徽标的展平版本。在导出图像之前,徽标已被移除并替换为黑色。我们知道,只要没有对刊头应用填充,动画徽标就会出现在刊头背景的左上角。
图 1.7。报头背景图像
现在可以为模板定义一些关键规则了。已经定义了body
选择器,并将所有元素设置为居中对齐(text-align:center
)以便我们的内容区域居中,重要的是将下一个主要元素(用 ID wrapper
定义)设置为text-align:left
,这是一个所有后代都将继承的值。边距值(0 auto 0 auto
)将确保wrapper
的左右边距相等,无论浏览器窗口的宽度是多少。
#wrapper {
width:779px;
margin:0 auto 0 auto;
text-align:left;
}
现在,基于报头背景图像的宽度和高度,在报头声明中指定了相等的值。使用试错法来指定边距值,以便在固定背景的顶部对齐我们想要的标题。通过指定 15px 的上边距,我们可以确保当内容开始在固定背景上滚动时,在刊头上方刚好有足够的主背景图像可见。background-color
是从刊头所在的主背景区域取样的。这个区域包含了几种颜色,但是做了一个粗略的匹配,所以如果用户在等待报头图像的加载,事情看起来还可以。
#masthead {
width:768px;
height:254px;
margin:15px 0 0 8px;
color:#030;
background: #ECE4D9 url(img/masthead.jpg) no-repeat
}
最后,主内容选择器返回到wrapper
中定义的full-width
,允许所有剩余内容重新开始,并指定新的背景图像。这个新图像向各个方向平铺,尽管内容区域的宽度仅够显示垂直平铺。此拼贴扩展了刊头背景中使用的边缘,给人一种又长又旧的页面的感觉。
#content {
width:779px;
background: url(img/content.gif)
}
导航菜单
下一步是将主导航菜单添加到报头中(见图 1-8 在做出最终决定之前,我们尝试了各种报头想法)。在 XHTML 阶段,我们知道我们想要将导航分成两部分,因为我们已经为 Libertines 网站做了类似的事情,并且发现这是一种充分利用报头空间的极好方式。
图 1.8。各种报头创意
标记如下所示。请注意,两个列表都有相同数量的列表项,并且每个菜单列表都有唯一的 ID ( m1
和m2
)。
<ul id="m1">
<li><a href="#" id="nav_a">Home</a></li>
<li><a href="#" id="nav_b">News Archive</a></li>
<li><a href="#" id="nav_c">Band Diary</a></li>
<li><a href="#" id="nav_d">Gigs</a></li>
<li><a href="#" id="nav_e">Downloads</a></li>
</ul>
<ul id="m2">
<li><a href="#" id="nav_f">Discography</a></li>
<li><a href="#" id="nav_g">Gallery</a></li>
<li><a href="#">Merchandise</a></li>
<li><a href="#" id="nav_h">Members Area</a></li>
li><a href="#" id="nav_j">Forums</a></li>
</ul>
我们还知道每个菜单项可能有独特的处理方式,所以每个链接都有自己独特的 ID,很快就会派上用场。
如果没有 CSS,这两个列表将以正常的流程出现,一个在另一个下面。因此,我们使用 margin 值来推动和拉动每一个位置。第一个(m1
)被简单地给定了 20px 的左边距,使其远离报头的左边缘。第二个(m2
)从刊头的左边缘移动 130 像素,从其自然位置移动 160 像素。因此,m2
被移到了另一个菜单的右下方,然后拉高到足以与它对齐,这要感谢大的负顶部边距。不需要定位或浮动。
#masthead ul#m1 {
margin: 0 0 0 20px;
padding-top:5px;
}
#masthead ul#m2 {
margin:-160px 0 0 130px;
}
有了我们想要的无序列表,我们可以开始考虑列表项了。每个都被赋予一个height
和width
,加上一些其他的自定义值,所有的都将共享。
#masthead ul li {
width:135px;
height:25px;
list-style-type: none;
padding-top:4px;
font-size: 12px;
letter-spacing:0.07em;
}
接下来,我们选择每个列表项中的链接。大多数是跨各种状态的常见链接方法,但我们发现包含display:block
以防止应用于每个链接的各种边距值导致链接聚集在一起并造成巨大破坏是至关重要的。
#masthead ul li a {
display:block;
padding-left:11px;
text-decoration:none;
font-weight:bold
}
#masthead ul li a:link, #masthead ul li a:visited, #masthead ul li a:active {
color:#FFF;
}
#masthead ul li a:hover {
color:#F00;
}
有了所有这些,两个菜单被准确地放置在我们想要的位置,现在我们可以考虑单独处理每个链接了。
前面我提到过,我们已经给了每个链接一个唯一的 ID。这些钩子现在可以让我们玩得开心了。在 Photoshop 中,我创建了三个不同的背景图像,它们都适合我们的<li>
元素的给定宽度。我们现在可以随机地将这三种背景中的任何一种应用于每个 ID,并为每个 ID 设置一个唯一的左边距,这给了我们稍微随机的对齐,以及不同的背景。
下面的 CSS 显示了十个 id 中的三个是如何在样式表中定义的:
#nav_a {;
background: url(img/buttonback1.gif) no-repeat;
}
#nav_b {
margin-left:6px;
background: url(img/buttonback2.gif) no-repeat;
}
#nav_c {
margin-left:4px;
background: url(img/buttonback3.gif) no-repeat;
}
...etc...
除非你仔细研究最终结果,否则似乎每个列表项都有一个独特的背景,而实际上我们只是简单地交替使用这三个。
在每个模板上,一个 ID 被添加到<body>
元素中,目的是用 CSS 应用程序的一个小技巧轻松地突出显示主菜单中的当前页面。
<body id="home">
在 CSS 中,<body>
中的id
选择器和附加在每个菜单链接上的id
之间的关系被粘合。第一部分#home
,将动作指向元素选择器为home
的实例。第二部分#m1 a#homebutton
,在#m1
列表中查找被标识为homebutton
的链接。如果找到匹配,则执行该操作。注意,为了节省空间,这里只显示了三个匹配。
/* Highlight the current page */
#home #m1 a#homebutton, #news #m1 a#newsbutton, #diary #m1 a#diarybutton {
color: #F00;
}
因此,每当用户查看主页时,链接颜色将被设置为红色,但在滚动时仍然是白色。从 CSS 来看,应该清楚新闻模板用的是<body id="news">
,日记模板用的是<body id="diary">
。剩下的唯一工作是为每个需要与模板对应的唯一链接 ID 复制 CSS,将目标分组到一个整洁的无所不包的定义中。
内容亮点
有了背景、标题和主导航之后,我们可以更仔细地看看主要内容区域中发生的一些有趣的事情。我们将探究标题、表格和定义列表。
标题
Dirty beautiful Things 网站大量使用了 2 级(<h2>
)和 3 级(<h3>
)标题。最值得注意的是,二级标题用在所有页面的主要内容区域和侧边栏的顶部,但是有不同的外观。这是通过在样式表中使用后代选择器实现的。
下面的标记反映了一个简化的侧栏。注意,主内容中的<h2>
元素也没有惟一的 ID 或类。
<div id="sidebar">
<h2>The Band</h2>
<p>Dirty Pretty Things are Carl Barat, Anthony Rossomando, Didz Hammond and Gary Powell.</p>
</div>
图 1-9 显示了用于两个<h2>
元素的两个背景图像。
图 1.9。两个参差不齐的背景图像用于两个不同大小的级别 2 标题。
主内容区域中使用的<h2>
的 CSS 规则相对简单。注意blackhead.gif
被指定为不重复的背景图像,并且使用了足够的填充来确保整个背景可见。
h2 {
margin:0 0 2px 10px;
padding: 6px 0 5px 13px;
font:14px/165% italic Georgia, Arial, Helvetica, sans-serif;
letter-spacing:0.2em;
color:#FFF;
background: #EAE5D8 url(img/blackhead.gif) left bottom no-repeat
}
现在,我们继续讨论侧边栏的<h2>
。巧妙的部分是用后代选择器完成的。当<h2>
元素在侧边栏中时,选择器#sidebar h2
告诉浏览器应用这些规则。如果找到匹配,则使用不同的背景图像blackheadside.gif
,并缩小页边距和font-size
。
#sidebar h2 {
margin-top:3px;
font-size:12px;
background: #EAE5D8 url(img/blackheadside.gif) left bottom no-repeat;
}
注意
后代选择器对于避免不必要的类和 id 是必不可少的,否则它们可能会破坏 XHTML 文档。通过使用后代选择器,CSS 规则可以变得更加具体,并且后代选择器可以沿着树向下移动多远——可能是父代的子代的子代?
表格
从本质上讲,表是用于表格数据的,而且仅仅是表格数据。大多数负责任的设计师仍然使用表格。您可能无法避免它们,但重要的是只在必要时使用它们。
注意
表格数据不能单独由 CSS 布局来组织,或者如果你试图这样做,它将没有什么意义。许多设计人员想出了用纯 CSS 布局和定位来呈现复杂表格数据(如日历、时间表等)的方法。这很好,当然也算是一项成就,但是如果去掉样式表,一切都会分崩离析。该表的美妙之处在于它在语义上呈现信息,为 CSS 样式提供大量元素,并且在样式表不可用的情况下仍然非常有意义。
对于 Dirty beautiful Things disco graphy,一个表中包含了与每首歌曲相关的多行信息。如果一首歌曲分配了歌词、音频、视频、吉他标签或图像,则会在相应的单元格中放置一个小复选图像。标记相当简单,没有任何额外的 id 或类。
<table>
<thead>
<tr>
<th>Title</th><th width="11%">Released</th>
<th><abbr title="Lyrics">L</abbr></th>
<th><abbr title="Audio">A</abbr></th>
<th><abbr title="Video">V</abbr></th>
<th><abbr title="Guitar">G</abbr></th>
<th><abbr title="Images">I</abbr></th>
<th>Comments</th>
</tr>
</thead>
<tr>
<td><a href="if_you_were_wondering/">Wondering</a></td>
<td>08/05/06</td>
<td><img src="img/discotick.gif" alt="Y" /></td>
<td><img src="img/discotick.gif" alt="Y" /></td>
<td><img src="img/discotick.gif" alt="Y" /></td>
<td><img src="img/discotick.gif" alt="Y" /></td>
<td>-</td>
<td>26</td>
</tr>
</table>
这就产生了图 1-10 中所示的基本表格。
图 1.10。由浏览器的默认样式表样式化的 discography 的表格数据
使用一些非常简单的 CSS 就彻底改变了这一点。由于表格下的页面背景,许多工作已经完成,但是进一步定义行和表格标题是有用的。
首先,我们控制表格本身,关闭默认边框(border:0
),设置字体大小(font-size:11px
),并确保表格将填充可用空间(width:100%
)。
table {
width:100%;
margin: 10px 0 20px 0;
border:0;
font-size:11px;
}
然后,我们在每一行的顶部放置一个虚线边框,并添加特定的填充,消除对过时的表示标记的需要,如cellpadding
或cellspacing
。
td, th {
margin: 0px;
border-top:1px dashed #999;
padding: 3px 5px 3px 5px;
}
注意
值得注意的是,cellspacing
没有可靠的 CSS 等价物,所以很多设计师仍然利用这个表象属性。
这些非常简单的规则,以及桌子被放置在一个吸引人的背景上的事实,给了我们一个更加有趣的如图 1-11 所示的桌子。
图 1.11。由于一些非常简单的 CSS,discography 表发生了根本性的变化。
定义列表
使用还是不使用定义列表?这是一个永恒的、无处不在的、越来越沉闷的问题。我们到底应该用它们做什么?就我个人而言,每当我需要比传统无序列表多一点的结构时,我都会使用它们,但不需要表的复杂性。
所有的定义列表都由两个主要部分组成:术语和描述。定义列表是使用三个基本元素构建的:容器(<dl>
)、定义术语(<dt>
)和定义描述(<dd>
)。这个简单的结构适合我们对侧栏标题和游览日期的需求,其中日期充当定义术语,标题或地点是描述。结果是逻辑配对提供了所有必要的样式挂钩。
<h2>Forthcoming Gigs</h2>
<dl>
<dt>25/11<dt>
<dd><a href="liverpool_academy">Liverpool Academy...</a></dd>
<dt>26/11</dt>
<dd><a href="birmingham_academy">Birmingham Academy...</a></dd>
<dt>27/11</dt>
<dd><a href="nottingham_rock_city">Nottingham Rock City...</a></dd>
<dt>28/11</dt>
<dd><a href="leeds_university_refectory">Leeds University Refectory...</a></dd>
</dl>
注意
虽然我觉得这种配对是对定义列表的恰当使用,但是要注意屏幕阅读器会在每一项之前说出标签“术语”或“定义”
图 1-12 显示了浏览器中未样式化的结果。每个定义术语和定义描述由换行符和默认左边距分隔。
图 1.12。由浏览器默认样式表设计的定义列表
现在,可以使用元素选择器来设计整个定义列表的样式。以选择器dl
、dt
和dd
为例,可以应用非常相似的样式将整个部分组合在一起。我们发现没有必要修改定义列表元素本身,尽管如果我们愿意的话,我们可以为dl
选择器添加声明。我们的第一步是让术语定义变得更难。主要的工作是将定义术语浮动到左边,这将允许定义描述被重新定位到它的右边并内嵌。
dt {
float:left;
padding:2px 0 7px 0;
line-height:130%;
font-weight:bold;
}
接下来,定义描述元素被样式化。非常简单,关键任务是移除默认边距,该边距将它从包含的<dl>
元素的左边缘推开。
dd {
margin:0;
padding:2px 0 7px 0;
line-height:130%;
}
这给我们带来了一个完整的、巨大变化的定义列表。请注意,它足够灵活,可以扩展和适应我们放置它的任何容器,这使它成为侧边栏导航的理想组件。所有这一切的美妙之处在于,在(X)html 中不需要额外的标记,CSS 会处理好一切。最终结果见图 1-13 。
图 1.13。CSS 再次出手相救。多亏了一些 CSS 规则,定义列表更加清晰,也更加一致。
这样,由于样式表的强大功能,内容区域的关键组件都被控制住了。内容本身保持纯净和清白,网页设计师可以睡得很香。
结论
当然,构建肮脏美好事物网站并不像本章所说的那么简单,还有更多问题(包括代码和现实世界相关的)有时会让生活变得非常困难。这个故事的重要寓意是,由于网络标准,尽管用户数量巨大,该网站使用的带宽相对较少(我没有将视频下载计入该带宽),尽管装饰复杂且包罗万象,但它仍然可以在移动和打印设备上访问。
我很乐意分享更多我在这次构建中的经历,但是没有空间让其他人讲述他们的故事,所以我邀请你查看源代码(注意,自从我管理这个站点以来,代码可能已经改变)并在这个站点中挖掘。没有更好的方法来弄清楚事物是如何工作的,非常欢迎您来探索。
二、使用 CSS、Flash 和 JavaScript 驯服野生 CMS
丹·鲁宾
www.webgraph.com
丹·鲁宾(Dan Rubin)整天将音乐、设计和字体设计融入南佛罗里达的阳光海滩。从声乐指导和表演到平面设计(几乎是字面意义上的)以及这两者之间的一切,丹尽可能地将他的才华尽可能地分散开来,同时仍然留出时间来喝一杯好茶和偶尔小睡一会儿。
他对所有创造性和艺术性事物的热情也不仅仅是自私的努力。你不需要逗留太久,就会发现他对无伴奏爵士乐和理发店和声(他对roundersquartet.com
的设计只是这两个世界碰撞的一个例子)、界面设计、可用性、网络标准、排版和一般平面设计进行了教育。除了对 Blogger、CSS Zen Garden、Yahoo!小企业和微软的门户网站,Dan 是《层叠样式表:将内容与表示分离》一书的特约作者,也是《CSS Web 开发入门》一书的技术评论员,同时也是《CSS 技术入门》一书的合著者他在自己的博客superfluousbanter.org
上写关于网络标准、设计和生活的文章,并在livefromthe101.com
上发布播客。他的专业作品可以在他的公司网站webgraph.com
上找到。
设置场景
你的客户要求你重新设计公司的网站,但有一个主要限制:你不能定制用于输出内容的标记,因为它是由一个内容管理系统生成的。“等等!”你会说,“CMS 不是应该把内容和输出格式分开吗?”不幸的是,似乎在大多数情况下,特别是对于企业级 CMS,不管是出于无知还是精心的计划,定义 CMS 用来呈现内容的标记的模板是设计者的禁区。"但是我怎么可能在如此陈旧的限制下设计出吸引人的东西呢?"虽然说服您的客户允许定制输出模板是最好的途径,但事实是有时这是不可能的,您只能使用您所给的。正是在这些艰难的时刻,网络标准,特别是 CSS,以及它们的得力助手 JavaScript 和 Flash,来拯救你了。
这是 Geffen/Universal Media 向我提出的挑战,当时该公司要求我重新设计疯狂流行乐队生命之屋乐队的宣传网站。我将向你展示我如何使用 CSS,一些聪明的设计,和一些很酷的工具和技术来击败 CMS 输出到提交。我还将向您展示如何在您自己的项目中使用这些相同的实际方法。
在我们开始我们的旅程之前,让我们比较一下原始网站和我的重新设计(见图 2-1 )。你可以在原著中看到一些“模板心态”的设计公式在起作用。
图 2.1。左边是原始设计,包括所有三列中的模板内容块
就市场部而言,一旦我们提出一个可以接受的设计,我们的工作就完成了。像素已经画在屏幕上了,就没别的事了吧?啊,但是我们知道的更多,不是吗?
当然,创建网站模板不仅仅是设计。不知何故,我们必须从概念到草图到视觉合成到标记到风格。然后,经过一些浏览器测试,我们完成了一个成品。这个过程本身通常就足够了。在这种情况下,我还需要考虑 CMS 规定的特定标记要求。我将马上回顾每一个步骤,但首先,对于门外汉来说,这是一个 CMS 速成班。如果您已经熟悉 CMS 的基本功能,请随意将这本书递给附近需要这方面教育的同事,并在他阅读接下来的几段并看着漂亮的图片时,自己喝一杯茶。
CMS 速成班
一般来说,CMS 就像他们的名字所暗示的那样:帮助管理内容。更广义地说,它们允许内容制作者在数据库中存储和组织他们网站的内容。当访问者请求站点中的特定“页面”时,将从 CMS 中检索适当的内容并显示在用户的浏览器中。
通常,CMS 是一个安全的 web 应用程序,由站点管理员、内容创建者、编辑者或任何负责发布或编辑站点内容的人使用。它可以处理文本、标记、链接、图像、音频、视频几乎任何类型的可以存储在数据库中的内容。
通常,一个简单的 CMS 可能会与你的布局模板交互,如图 2-2 所示。
图 2.2。大局中的一个基础 CMS。在本例中,一个模板定义了布局并从 CMS 中提取内容。
一些更复杂的 CMS 在存储的内容和显示给用户的内容之间增加了额外的层,如图 2-3 所示。
图 2.3。这个 CMS 添加了一个额外的模板层来定义与页面布局分开的内容块。
本章讨论如何使用多层模板解决第二类系统产生的问题。这些层通常结合起来创建每个页面样式的基本框架。设计者通常可以访问布局模板。内容模板包含各种类型内容的大量标记,通常是设计者的禁区。更重要的是,由于对内容模板的访问受限或不灵活的输出要求,这些内容块通常包含不受您控制的标记。
CMS 挑战
不需要太多的网页浏览就能在各种网站上找到内容的通用视觉格式。这种通用模式通常由这些网站背后的特定 CMS 的内容模板中的限制规定。你可以在受欢迎的博客 CMSs 管理的网站、运行昂贵的商业软件包的网站,甚至是定制的系统上看到这些共同的元素。
Geffen/Universal Media 用来管理其艺术家网站的定制 CMS 就是这种情况。为了简化内容格式的大规模更新,所有网站都使用相同的数据类别,如新闻、媒体、照片、事件等。在此 CMS 上运行的网站共享一组内容模板。虽然这使得对给定内容块的调整更容易实现,但这种方法导致许多网站具有相似的外观。这在很大程度上是由于设计者认为内容需要看起来一样,因为标记不能在一个站点一个站点的基础上定制。
对于我的生命之屋乐队网站重新设计项目,Geffen/Universal Media 没有提出任何具体目标,只是希望设计能比现有网站更好地反映乐队的形象和当前的宣传摄影。从设计的角度来看,这就像一张白纸,这基本上是我得到的指示,但有一个很大的限制:我制作的任何东西都必须与现有的 CMS 内容模板一起工作。
这不是我为该公司工作的第一个艺术家网站,所以我已经熟悉了 CMS 模板的集中结构所施加的大多数限制。这意味着我已经确切地知道设计必须适应什么类别和类型的内容,以及在我面前有什么限制。
像我早期的项目一样,这次重新设计有一个紧张的截止日期和预算,这以前导致了一个快速和肮脏的设计过程:采用一组基本的内容和布局模板一个带有页眉和页脚的标准两列布局,并在不超出预算的情况下,将它们的外观设计得尽可能不像模板。如果我没有变得如此讨厌这种方法对我的设计的限制作用,生命之屋乐队网站的重新设计也会是一样的。
因此,面对另一个无聊的设计项目,我提出了一个实验:让我看看在不改变预算或时间表的情况下,我能在多大程度上调整内容和布局,以打破基本的模板模式。收到客户的许可后,是时候进入正题,开始快速设计一些东西了。
一毛钱的设计
我的主要目标是制作一个不像默认 CMS 布局模板的布局。当然,由于它必须支持的内容类型与所有其他艺术家网站相同,新布局需要与默认布局有一些共同之处。我认为结合这些需求的最简单的方法是使用相同的元素,但是定位要稍微不同,这样,对于不经意的观察者来说,这个网站看起来就不会和其他的一样。这也为一个必须在几周内完成的项目提供了一个很好的起点。
视觉元素
我已经从唱片公司的市场部收到了乐队的宣传资料。结合所需的内容和导航,这些给了我以下的视觉元素到设计中:
-
该乐队的标志艺术品
-
来自乐队最新照片拍摄的高分辨率照片
-
当前专辑封面
-
十个类别的主要导航
-
各种内容块(事件、视频、新闻、邮件列表注册等)
我还决定在主页上突出显示新闻部分的一篇专题文章。因为这是 CMS 不支持的,这就产生了另一个挑战。因此,有了这张清单、一支铅笔、一张白纸和一张已经在我脑海中形成的图片,我开始快速勾画一些缩略图,直到我找到一个“感觉正确的”(见图 2-4 )。
图 2.4。埃涅!米涅!米涅!米奈!莫!
注意
我强烈地感觉到设计过程必须是有机的。仅仅因为我们是为一个技术媒介而设计,并不意味着我们的过程也必须是冷酷的和经过计算的。我喜欢使用缩略图草图,因为这是一种很好的方式,可以在纸上表达我的想法,而不用担心太多的细节。我几乎每一个设计都是从花几分钟画出小布局开始的,这是我所知道的最好也是最快的方法,几乎可以立即比较多个布局创意。你可以在www.sinelogic.com
的“预算设计”中找到更多这样的设计捷径。
下一步是花一些时间在 Photoshop 中把草图变成像素。配色方案借鉴了乐队最新专辑封面上的照片,加上一些灰色调。经过一段时间激烈的像素推进,我完成了图 2-5 所示的合成图。
图 2.5。最初的 Photoshop 合成
在这个阶段,一些有趣的视觉元素融入了设计:
-
“预告”文章的透明背景(显示下面的照片)
-
挑逗性的标题与乐队的标志字体相同(富兰克林哥特式压缩)
-
页脚列出了最近的新闻标题(这看起来很容易复制,只是 CMS 不支持)
-
整个布局中的垂直文本标题(也以富兰克林哥特式压缩字体呈现)
到目前为止,您可能已经迫不及待地想要了解“技巧和诀窍”这一部分了,所以如果您对标记不感兴趣,请继续阅读。但是在开始样式化之前,我们真的需要打下 XHTML 的基础。
标记只是一个外壳
由于 CMS 控制着大部分内容块周围的标记,最简单的方法就是创建一个 shell 布局,用<div>
来包含每个数据模块。该设计基本上是两列加一个页脚,徽标和导航位于左列。折腾了几个比特的样本内容,我得到了这个:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Lifehouse [homepage]</title>
<link rel="stylesheet" type="text/css" href="c/styles.css" media="all" />
</head>
<body class="homepage">
<div id="wrapper">
<div id="content">
<h1 id="logo">
<a href="/" title="link to the homepage">Lifehouse</a>
</h1>
<div id="content-primary">
<div id="sidebar-tab"><!-- for presentation only --></div>
<div id="teaser">
<h2>latest news</h2>
<a href="#">
<img src="p/thumbnail_teaser.jpg" width="122" height="122" alt="" class="fullsize" />
</a>
<h3><a href="#">Article heading</a></h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut...</p>
<p class="entry-footer">Published on 5/14/2006 |
<a href="#">Link</a> |
<a href="#">Comments (0)</a>
</p>
<a class="readmore" href="#">read more...</a>
</div><!-- #teaser -->
</div><!-- #content-primary -->
<div id="nav">
<ul>
<li id="nav-home"><a href="#">Home</a></li>
<li id="nav-news"><a href="#">News</a></li>
<li id="nav-dates"><a href="#">Dates</a></li>
<li id="nav-music"><a href="#">Music</a></li>
<li id="nav-videos"><a href="#">Videos</a></li>
<li id="nav-photos"><a href="#">Photos</a></li>
<li id="nav-extras"><a href="#">Extras</a></li>
<li id="nav-links"><a href="#">Links</a></li>
<li id="nav-forum"><a href="#">Forum</a></li>
<li id="nav-store"><a href="#">Store</a></li>
</ul>
</div><!-- #nav -->
</div><!-- #content -->
<div id="sidebar-wrapper">
<div id="sidebar">
<div class="module" id="mod-events">
<h3>events</h3>
</div><!-- .module -->
<div class="module" id="mod-videos">
<h3>videos</h3>
</div><!-- .module -->
<div class="module module-alt" id="mod-members">
<h3>members</h3>
</div><!-- .module -->
<div class="module" id="mod-online">
<h3>online</h3>
</div><!-- .module -->
</div><!-- #sidebar -->
</div><!-- #sidebar-wrapper -->
<div id="footer-wrapper">
<div id="footer" class="clearfix">
<div id="morenews">
<h2>more news</h2>
<ul>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
<li><a href="#"><strong>5/14/2006</strong> Article heading</a></li>
</ul>
<a class="more" href="#">more...</a>
</div>
<div id="extras">
<h2>extras</h2>
<a href="#" class="thumbnail"><img src="p/thumbnail
wallpaper.jpg" width="150" height="112" alt="" /></a>
<h3>Lifehouse wallpaper - 800x600</h3>
<p><a href="#"> Download</a></p>
<p>5/14/2006 | <a href="#">Link</a> | <a href="#"> Comments (2)</a></p>
<div id="external-links">
<a id="logo-geffen" href="http://geffen.com/">Geffen</a>
<a id="privacypolicy" href="#">Privacy Policy</a>
<a id="myspace" href="#">visit our <span>myspace</span></a>
</div>
</div>
</div><!-- #footer -->
</div><!-- #footer-wrapper -->
</div><!-- #wrapper -->
</body>
</html>
当在浏览器中查看时,它以原始的、无样式的形式呈现得相当好,如图 2-6 所示。
无样式页面中内容的顺序很重要,尽管不是这个站点的首要任务,因为目标受众在普通电脑上使用现代浏览器。然而,所有的内容至少在没有样式的情况下被适当地格式化了,所以网站在基本层面上是可访问的,这比最初的设计要好得多。
图 2.6。裸体午餐...呃,内容
注意
有没有发现自己在处理生成的标记时迷失在一片混乱中?如果您坚持使用一个会生成本章所讨论的那么多嵌套 div 的 CMS。通过用相应的开始标记的 ID 或类来注释每个 div 的结束标记,可以使您的标记更容易阅读(对于您和其他人)。看一看示例标记。看看与没有注释的标签相比,将有注释的 div 的结束标签配对起来有多容易?
注意侧边栏中的“模块”(方便地分配了类.module
)是空的。这些只是容器,实际的 CMS 生成的内容将很快放入其中,所以现在,我们不去管它们。重要的是它们是存在的,由于它们唯一的 id,它们可以被单独定位,并且它们在文档流中的顺序是可以互换的。
虽然这些标记中的大部分应该是不言自明的,但是我将在下一节讨论少数的.clearfix
类。稍后,在“分析和润色”部分,我将回顾我用来为主导航设置无序列表样式的技术(导航不是由 CMS 生成的)。
注意
我说过 CMS 内容模板不包括列表选项,那么页脚的<ul>
是怎么回事?由于项目的时间限制。我能够说服当权者给内容模板添加一个列表选项。这给了我设计列表样式所需的适当挂钩,并为该样式使用更合适的标记。清理标记,然后施展你的 CSS 魔法,这比处理混乱要好得多。拥挤的加价。也就是说。该外观也可能是使用原始的非列表标记重新创建的。它只是需要更多的 CSS 技巧来完成类似的结果,这会不必要地使项目复杂化并延长时间表。
布局和风格
现在我们进入了重要的内容,所以你可能想让你的家人知道,除了在下一节给你端茶倒水之外,他们在相当长的一段时间内不会看到或听到你的消息。毕竟,你确实需要大脑补充能量。实际上,我们不会对布局本身进行深入的讨论,它并没有那么时髦,但是我们会回顾创建它所需的基本风格。
注意
我发现在一个规则块中组织 CSS 属性很有帮助,特别是在浏览器测试中检查/调整样式表时,或者在启动后必须进行调整时。以下是我更喜欢的顺序:
-
显示方法(显示:或浮动:)
-
背景设置
-
定位方法(位置:)
-
位置(x,y)
-
宽度/高度
-
边距/填充
-
颜色
-
文本设置(字体/行高/对齐)
-
边界
这是又一个简单的节省时间和烦恼的方法,应该会让你的生活变得轻松一点。任何给你的标记或样式增加一些结构和顺序的东西都会给你带来回报。
定位元件
虽然我们并不试图用整体布局来完成任何开创性的工作,但在我们进入实质之前,有必要回顾一下每个主要元素是如何定位的。首先,清除浮动:
.clearfix:after
{content:".";
display:block;
height:0;
clear:both;
visibility:hidden;
}
.clearfix {display:inline-block;}
/* Hides from IE-mac \*/
.clearfix {display:block;}
/* End hide from IE-mac */"
body {
background:#111 url(../i/bg_body.gif) repeat-x;
margin:0;padding:0;
font-family:'lucida grande', tahoma, sans-serif;
font-size:small;
}
#wrapper {
width:800px;
}
#content
被设置为float:left
,使其包含它所包含的两个浮点(#content-primary
和#sidebar-wrapper
):
#content {
float:left;
}
#content-primary
也被设置为position:relative
,允许#sidebar-tab
和#teaser
被绝对定位,但在其包含元素的范围内。请参阅道格·鲍曼的《制造绝对相对》(http://stopdesign.com/articles/absolute/
)来很好地概述这一技术。
#content-primary {
float:left;
background:url(../p/homepage_photo.jpg) no-repeat;
position:relative;
height:549px;
width:520px;
padding:0;
color:#fff;
}
h1#logo {
position:absolute;
z-index:100;
left:29px;
top:39px;
margin:0;padding:0;
}
h1#logo a {
display:block;
background:url(../i/logo_lifehouse.gif) no-repeat;
width:38px;
height:175px;
text-indent:-5000px;
}
#sidebar-tab {
background:url(../i/bg_sidebartab_events.png) no-repeat;
position:absolute;
right:0;
top:0;
width:20px;
height:194px;
}
#teaser {
background:url(../i/bg_teaser.png) no-repeat;
position:absolute;
left:0;
bottom:0;
width:507px;
padding:20px 0 30px;
color:#EFB32F;
font-family:'lucida grande', verdana;
line-height:1.3;
}
#nav
被清除,以确保其落在#content-primary
之下。类似地,.module
被清除以允许每个模块中的任何内容被浮动,因为每个后续的.module
将清除任何这样的浮动。
#nav {
background:url(../i/bg_nav.gif) repeat-x;
width:520px;
height:53px;
margin-top:0;
border-top:3px solid #111;
clear:both;
}
#sidebar-wrapper {
float:right;
background:#1C1C1C url(../i/bg_sidebar_wrapper.gif) no-repeat left bottom;
width:277px;
margin:0 0 3px;
padding:0 0 10px;
}
#sidebar .module {
color:#A07029;
padding:15px 15px 15px 0;
border-top:1px solid #111;
clear:both;
}
#footer-wrapper {
background:#1B1B1B url(../i/bg_footer_wrapper.gif) repeat-x;
padding-right:10px;
clear:both;
border-top:1px solid #333;
}
#footer {
background:#262626 url(../i/bg_footer.gif) repeat-x;
padding:20px 0 20px;
}
这没有什么奇怪的。它是容器(或包装器)、一些列的浮动和一些绝对定位的混合物。因为这一章的目的不是教你 CSS 的基本原理,我在这里就不赘述了。如果你觉得自己在任何基础知识上落后了,我推荐阅读西蒙·科利森的初学 CSS Web 开发(出版社,ISBN: 1-59059-689-7)。
注意
我喜欢根据约定俗成的 type_id_label 来给图片命名,这样更容易记住每张图片的用途(比如logo_footer_geffen.gif
)。此外,文件命名约定使第三方或您的客户更容易确定与您的设计和代码相关的“是什么和为什么”。虽然样式指南也可以完成这项任务,但项目的预算或时间表通常不允许创建样式指南,或者即使存在样式指南,也可能无法供将来进行更改的人使用。
瞄准 CSS 选择器
我之前展示的基本 shell 标记中包含的生成标记的一个大问题是,在决定使用哪种标记时,我没有发言权。在描述中包含嵌套段落的定义列表可能是最合适的,但我可能会被迫处理一些嵌套的包含到处都是<span>
的<div>
。您可以通过查看现场的源代码来了解这一点。啊。再喝一杯(浓茶)来帮助你克服这个心理印象。
谢天谢地,尽管这听起来很糟糕,但有一种相对简单的方法可以挖出来。CSS 选择器有许多形状和大小。许多设计师习惯于只做基础工作:
-
ID s:在 CSS 中作为
#footer
出现,在(X)HTML 中作为元素上的属性值出现,比如<div id="footer">
,其中div
是元素,id
是属性,footer
是值。 -
类:在 CSS 中作为
.readmore
出现,在(X)HTML 中作为元素上的属性值出现,比如<p class="readmore">
,其中p
是元素,class
是属性,readmore
是值。 -
元素选择器:目标任意(X)个 HTML 元素。例子有
ul
、p
、div
、body
、table
,甚至html
。
然而,除了这些基本的选择器之外,我们还有更多的选择,这就是外壳标记发挥作用的地方。
通过查看 CMS 生成的标记,我们可以创建后代选择器,专门针对我们想要关注的内容,而不去管其他内容。后代选择器允许我们对嵌套在标记的特定层次结构中的元素进行样式化。例如,选择器#sidebar #mod-media img.fullsize
的目标是包含在 ID 为mod-media
的元素中的fullsize
类的任何img
,该元素也包含在 ID 为sidebar
的元素中,如果你问我的话,这是非常具体的。
还记得侧边栏模块上的 id 吗?他们的目的现在将被阐明。例如,查看侧栏中的视频模块的标记和内容块,在本例中做了一些清理(但只是一点点),由 CMS 生成:
<h2><a href="/videos/default.aspx"><span>Videos</span></a></h2>
<div class="item summary stream">
<img srcimg/video.jpg" alt="Blind" class="fullsize">
<h4>Blind</h4>
<p class="url">Windows: <a href=
"http://music.yahoo.com/video/24649242/?">300K</a><br /></p>
<p><span class="subtype">[Videos] </span>"Blind" Music Video</p>
<p class="byline"><span class="date">10/20/2005</span>
<span class="permalink"> |
<a href=" /videos/default.aspx?mid=2333">Permalink</a>
</span><span class="comment-icon"> |
<img srcimg/comment_icon.gif" alt="Comment" /></span>
<a href="/forum/topic.aspx/cid/133/tid/70679">
Comments (197)</a></p>
<div class="clear"> </div>
</div>
不是你见过的最差的 ?? 价格,对吗?我喜欢的类太多了,注释图标可以用 CSS 显示,而不是使用内嵌图像(那<br />
在那里做什么?).但同样,围绕这些内容的标记意味着在标签的 CMS 管理的每个艺术家网站上使用,所以额外的挂钩在这种情况下是有意义的。有更好的方法来提供一些钩子,但是那是一个单独的讨论。
这种标记完全不受我的控制,但我仍然需要对它进行样式化。并且很可能给生成的<div>
和<img>
标签上的类属性分配了通用的类名,我的钩子(类item
、summary
和stream
)在整个站点的许多其他地方使用。这意味着我需要一些方法来定位这个部分中的类,以便处理视频模块的任何特定需求。因为 CMS 的内容模板生成的标记中没有提供这些挂钩,所以我在布局模板中创建它们,将它们包装在调用生成内容的代码中:
<div class="module" id="mod-media">
. . .
</div>
这个<div>
包装了标记并提供了两个重要的工具:
-
class="module"
将被分配给每个包装器,比如这个,它允许你在每个块之间共享一些基本的格式(边距、边框等等)。 -
id="mod-media"
允许您单独定位此块,并指定适合显示此内容的特定样式。
所以,假设对于侧边栏中的大多数模块,我想将分配给class="fullsize"
的任何<img>
标记设置为在模块中向右浮动,在左侧和底部有 5 个像素的边距,没有边框。有了新的挂钩,通用样式现在应该是这样的:
#sidebar .module img.fullsize {
float:right;
margin:0 0 5px 5px;
border:none;
}
这个选择器使用我分配给我的包装器的类<div>
和#sidebar
,所以如果我决定在布局中的任何地方使用module
类,这个规则将不适用。
这样,我现在可以设置视频模块的样式,使图像向左浮动,而不是向右浮动,并相应地调整边距:
#sidebar #mod-media img.fullsize {
float:left;
margin:0 5px 5px 0;
}
通过将选择器中的类(.module
)替换为 ID ( #mod-media
),我指示浏览器首先应用通用样式,然后覆盖浮动和边距设置。
注意
id 比类具有更高的特异性。关于特异性如何计算的详细解释,请参见http://molly.com/2005/10/06/css2-and-css21-specificityclarified/
;更轻松的概述,请参见http://stuffandnonsense.co.uk/archives/css_specificity_wars.html
。
同样的方法可以让你摆脱 CMS 生成的标记为你挖的几乎所有的洞。虽然我们没有人真的想使用像body#events #content .item .module .summary #membership p.permalink span {}
这样难看的选择器,但是如果你陷入困境,需要完成工作,将选择器与一两个包装器<div>
组合并分层会让你的生活压力小很多。
排版
我是一个字体设计迷,字体通常在我的设计中占据显著位置。HTML 文本很难控制。即使使用 CSS,字体选择也仅限于安装在用户操作系统上的字体,而且排版控制实际上是不可能的。然而,我的设计使用了乐队选择的字体,富兰克林哥特式压缩。我不想妨碍网站的基本可访问性。我并不想在这方面追求完美,但同时,使用糟糕的标记将人们完全拒之门外也是不负责任的。但不知何故,我需要让那种类型显示出来。为固定标题输入 CSS 图像替换,为需要定期更改的文本输入 sIFR,例如由 CMS 生成的文章标题。
让我们从垂直的文本标题开始。它们是需要特殊处理的最明显的候选者,因为仅仅使用 HTML 文本是没有办法模仿它们的。
制造竖排文字的假象
应该垂直的标题都是静态的。由于它们不需要定期更新,我可以使用 Photoshop 中渲染的图像。竖排文字的效果不容易用其他方式复制。我可以用 Flash 做到这一点,但用这种方法更容易,因为 sIFR 方法(接下来讨论)不支持旋转文本,Flash 项目可能更难定位。我可以使用 CSS background-image
属性将每个图像分配给一个容器<div>
,但是在 HTML 文件中没有实际的标题,如果标记是可访问的,即使是基本的,这也不是理想的情况。
好消息是,让一个 HTML 标题(<h1>
、<h2>
等等)完全按照我想要的那样做也很容易,出于某种显而易见的原因,这种方法被称为图像替换*。有多少种处理图像替换的方法,就有多少种带有奇怪 CSS 错误的 Internet Explorer 版本,但我更喜欢迈克·伦德尔设计的一种方法,称为 Phark 方法(以他的个人网站http://phark.net
命名)。让我们以侧边栏标题(包装在模块<div>
中)为例:
<div class="module" id="mod-videos">
<h3>videos</h3>
</div><!-- .module -->
注意
关于图像替换技术的更多内容,Dave Shea 为你收集了一个很好的对比来做书签和参考:http://mezzoblue.com/tests/revised-image-replacement/
。
如果没有任何特定的样式,这个标题将会像您预期的那样显示。但是我们的想法是隐藏默认的文本输出,并用在图形编辑器中创建的文本图像替换它,下面的 CSS 规则就是我所需要的:
#sidebar .module h3 {
background:url(i/header_module_events.gif) no-repeat;
width:18px;
height:58px;
margin:0;
text-indent:-5000px;
}
该规则处理以下内容:
-
设置背景图像并指示浏览器不要平铺图像。
-
定义尺寸(此处设置为等于背景图像的宽度和高度)。
-
扼杀利润。替换标题时,最好将页边距设为零,然后根据需要调整位置。
-
最后,巧妙的部分是:将文本在标题位置的左边缩进 5000 像素。这很重要,因为否则标题的文本仍然会显示在渲染的背景图像之上。
图 2-7, after the background image is applied (middle), and after setting text-indent:-5000px; (right)") 显示了应用 CSS 规则之前、期间和之后的标题。
图 2.7。默认状态下的标题(左),应用背景图像后的标题(中),以及设置文本缩进后的标题:-5000 px;(右)
这样就解决了垂直文本的问题,乍一看,这似乎是两种替换情况中更具挑战性的一种。然而,现实情况是,第二种情况——对必须保持可编辑的文本使用特定的字体——实际上更具挑战性,您将在下面看到。
sIFR 我的木材
当您处理 CMS 生成的内容时,每个页面上的大部分内容都在不断变化。在这种特殊情况下,假设乐队成员愿意或能够为他们通过 CMS 发布到网站的每个新闻项目创建自定义的图形文本标题是不公平的。然而,作为一名设计师,我更希望主页上的主要文章标题采用与乐队标志相同的字体。幸运的是,在很大程度上由于一些天才程序员的非凡努力,sIFR 拯救了我。
sIFR 代表可伸缩的因曼闪存替换(因曼,就像肖恩·因曼,他构思了最初的 DOM 替换方法,启发了 sIFR)。用 Mike Davidson 的话来说,sIFR 是“一种在不牺牲可访问性、搜索引擎友好性或标记语义的情况下将丰富的版式插入网页的方法”,Mike Davidson 是 sIFR 的创始人之一,也是网页版式质量的全面倡导者唷。更简单地说,sIFR 是一种在现代可视化浏览器中使用特定字体来替换 HTML 文本的方法,结合了 Flash 和 JavaScript。它不妨碍可访问性,没有 Flash(或关闭了 JavaScript)的访问者将看到普通的 HTML 文本,应用了 CSS。哦,我有没有提到它是免费的?
要利用 sIFR 开发商的无私慷慨,您必须首先准备好以下物品:
-
Macromedia(现在是 Adobe) Flash 版本 6 或更新版本(完整版本,不是插件)
-
您想要使用 sIFR 渲染的字体
-
一些空闲时间
一旦你满足了这些要求,将 sIFR 融入任何项目的过程都相当简单:
-
从
www.mikeindustries.com/sifr/
下载最新版本(撰写本文时为 2.0.2)。 -
在
http://wiki.novemberborn.net/sifr/
阅读文档。
好的,所以没有那么简单,但是所有的步骤都在文档中有概述。如果你按照说明做,它会顺利地工作。尽管如此,我还是会给你一个简单的概述,以及一个例子。
选择字体后,第一步是导出 Flash ( .swf
)文件。sIFR 的创建者友好地将 Flash 文档(.fla
)与其余的文件放在一起,所以您只需在 Flash 中打开该文件,双击该文件中心的文本框,并指定字体。导出文件(在这个例子中,文件被命名为franklingothiccondensed.swf
)。我们练习的 Flash 部分到此结束。
sIFR 下载还包括两个 CSS 样式表一个用于屏幕,一个用于打印和一个 JavaScript 文件(这就是神奇之处)。您可以将这些样式复制并粘贴到您自己的屏幕上,并打印样式表,正如 sIFR 文档中所建议的那样,或者在您的文档的<head>
中链接到它们以及 JavaScript 文件:
<link rel="stylesheet" type="text/css" href="sIFR/sIFR-screen.css" media="screen" />
<link rel="stylesheet" type="text/css" href="sIFR/sIFR-print.css" media="print" />
<script src="sIFR/sifr.js" type="text/javascript"></script>
现在你必须从你的 HTML 文件中调用 sIFR,并告诉脚本替换什么。您也可以将这些“替换语句”放在 JavaScript 文件本身中;有关详细信息,请参见 sIFR 文档。
<script type="text/javascript">
if(typeof sIFR == "function"){
sIFR.replaceElement(named({sSelector:"#teaser h3",
sFlashSrc:"/sIFR/franklingothiccondensed.swf",sColor:"#EFB32F",
sLinkColor:"#EFB32F", sFlashVars:"offsetLeft=0&offsetTop=0",
sWmode:"transparent"}));
};
</script>
第一个参数定义了 CSS 选择器,因此脚本知道要替换哪个文本。下一个参数告诉脚本在哪里可以找到字体的 Flash 文件。接下来是文本的颜色(一个用于没有链接的标题,另一个用于有链接的标题,在本例中颜色相同),替换文本的位置,以及使 Flash 文本背景透明的设置。注意,对于sFlashSrc
参数,我为.swf
文件使用了一个相对于根目录的 URL。您可能需要尝试使用根目录相对或绝对 URL 来使事情正常运行。对于这个项目,我在本地开发时使用了一个相对 URL,但是当它上传到服务器时就停止了工作,需要对 URL 进行调整。
在我们进入展示和讲述阶段之前,让我们先快速回顾一下我们在这次替换中使用的标记,这些标记来自现场:
<h3><a href="/news/default.aspx/nid/8514" target="_self">
Norfolk Virginia</a></h3>
这是 CMS 生成的实际代码行,它嵌套在#teaser <div>.
中。如果你对周围的其他标记感兴趣,只需在实时网站的主页上查看源代码,但这并不影响 sIFR 的使用。
只剩下一个步骤,这就是被 sIFR 的创作者称为调音的过程。在这里,您可以使用font-size
、letter-spacing
、line-height
和height
CSS 属性指定“诱饵”样式,sIFR 脚本使用这些样式来确定最终呈现文本的大小和间距。这是可行的,但是你应该做好反复试验的准备,甚至可能偶尔咒骂你选择的文本编辑器,直到你替换的文本看起来大小和间距都合适。我们示例中调整后的 CSS 如下所示:
.sIFR-hasFlash #teaser h3 {
visibility:hidden;
letter-spacing:0;
font-size:24px;
line-height:22px;
}
最后,我们有了最终结果,如图 2-8 所示。
图 2.8。左边是普通的 HTML 标题(也是没有 Flash 的用户会看到的),右边是漂亮的 sIFRized 版本
注意
图像替换和 sIFR 有他们的位置。它们为我们提供了改进设计排版的方法。而不牺牲可访问性。但是你必须谨慎使用它们。尤其是图像替换。在大型网站上,为每个标题创建一个自定义图像可能不太实际,尤其是当这些标题经常变化的时候。
吐槽擦亮
如果我不特别提及,还有一些额外的细节可能会被忽略。例如,布局左上角的徽标可以很容易地包含在主乐队照片中,从而减少制作页面所需的图像数量。然而,将徽标制作成一个单独的图像文件允许我将其设置为<h1>
标签的背景,并使标签(以及徽标)链接到主页:
下面是 XHTML:
<h1 id="logo">
<a href="/" title="link to the homepage">Lifehouse</a>
</h1>
这是 CSS:
h1#logo {
position:absolute;
z-index:100;
left:29px;
top:39px;
margin:0;padding:0;
}
h1#logo a {
display:block;
background:url(../i/logo_lifehouse.gif) no-repeat;
width:38px;
height:175px;
text-indent:-5000px;
}
瞧,我现在有了一个可点击的标志。
用火狐?您可能已经注意到,这种图像替换技术(前面提到的 Mike Rundle 的 Phark 方法)会导致单击时浏览器的虚线链接边框一直延伸到浏览器窗口的左边缘,这不是很吸引人。幸运的是,这可以通过将a {outline:none;}
放到样式表中很容易地解决。图 2-9 and after (left) using {outline:none;} in the style sheet, when viewed in Firefox") 展示了前后效果。
另一项值得注意的是主导航(<div id="nav">...</div>
),它被标记为一个简单的无序列表。幸运的是,导航不是由 CMS 生成的,但可以手动标记,并包含在网站所有页面的服务器端,因为正如我前面提到的,CMS 输出模板不包含列表。导航条使用一种被称为导航矩阵(Navigation Matrix Reloaded,http://superfluousbanter.org/archives/2004/05/navigation-matrix/
)的技术的简化变体来创建基于图像的导航和悬停效果。整个导航仅使用 CSS 中引用的一个图像(nav_matrix.gif
)创建,如图 2-10 中的所示。
图 2.9。在 Firefox 中查看时,在样式表中使用{outline:none;}
的前(右)和后(左)
图 2.10。采用导航矩阵重载技术的导航条
设计的问题
在实现我所设想的设计时,我面临的一个更大的挑战是 Internet Explorer/Windows 和#teaser <div>
的透明背景。我有两个选择:
-
通过导出一个带有透明区域和照片的图像来伪造所有浏览器的透明度。
-
使用透明的 PNG 作为背景图像,并使用 JavaScript 或 Internet Explorer 条件注释来“修复”Internet Explorer 版本 6 和更早版本的 PNG 透明度(Internet Explorer 7 包括对 PNG 透明度的本机支持;更透明的 PNG 善良见第五章。
我选择了第二个选项,让乐队成员更容易自己替换主页图像。更具体地说,我决定使用 Internet Explorer 条件注释来允许 Internet Explorer 使用其专有的 AlphaImageLoader 过滤器正确处理 PNG 几个好方法见http://alistapart.com/articles/pngopacity/
。必要的代码放在文档的<head>
中,如下所示:
<!-- fixes IE 5.5/6 png transparency -->
<!--[if gte IE 5.5]>
<![if lt IE 7]>
<style type="text/css">
#sidebar-tab { filter:progid:DXImageTransform.Microsoft.
AlphaImageLoader(src='i/bg_sidebartab_events.png');
background-image:none; }
#teaser {
background-image:none;
z-index:10; }
#teaser-ie { filter:progid:DXImageTransform.Microsoft.
AlphaImageLoader(src='i/bg_teaser.png',sizingMethod='crop');
position:absolute;
left:0;
bottom:0;
width:507px;
height:230px;
bottom:-1px;
z-index:0; }
</style>
<![endif]>
<![endif]-->
我还使用这个块来调整每个受影响的<div>
的 CSS,这样标记都在一个地方。理想情况下,这个“一个地方”应该是外部样式表。不幸的是,Internet Explorer 条件注释只能在 HTML 文档中工作,尽管您可以将特定于 Internet Explorer 的样式放在一个单独的样式表中,并在条件注释中链接它。在准备一张新照片时,唯一需要定制的,并且如果客户忘记也不会“破坏”设计的,是照片底部边缘的阴影。但是,如果您不想在 Internet Explorer 6 和更早版本中使用 JavaScript 或专有解决方法来支持 PNG 透明度,并且如果在您的生产环境中创建特殊图像不成问题,那么伪造透明度仍然是一个可行的选择。
这样的#挑逗
设计完#teaser <div>
后,我意识到我只考虑了一个单行的<h3>
标题(有或没有 sIFR)。由于<div>
的高度和背景图像已经指定,如果使用更长的文章标题,这肯定会引起问题。所以我决定完全移除#teaser
的指定高度,并增加<div>
的底部填充。因为入口页脚和read more
按钮是绝对定位的,所以不受这个变化的影响;只有<h3>
标题和<p>
会与填充互动。我调整了透明背景图片(bg_teaser.png
)使其更高(300px 比较合理),并重新保存了底部带有阴影的乐队照片。这张照片原本是放在#teaser
的背景图片上,但是现在<div>
的高度是可变的,这样就不行了。然而,我仍然可以在bg_teaser.png
上保留左边的阴影。这给出了一个在#teaser <div>
中使用多行标题的解决方案,如图图 2-11 所示。
图 2.11。这是一个微妙的区别,但主照片底部的阴影允许图像的底部融入下面导航栏的背景色,并与#teaser <div>
的透明背景配合得很好。
使用 Internet Explorer
在 Internet Explorer 6 中还可以看到一些奇怪的东西(不足为奇),但这些几乎都通过使用 Star-HTML hack 得到了解决,这是 Holly hack 的一部分,以 Holly Bergevinand 命名,并重新声明了问题元素的规则。Star-HTML hack 利用了 Internet Explorer 版本 6 和更早版本的渲染引擎所识别的额外元素。这个通用选择器 ( *
)在<html>
之外,由于它被任何非 Internet Explorer 浏览器忽略,所以可以用来向 Internet Explorer 发送特定的样式。
以主页上显示的乐队标志 CSS 为例:
h1#logo {
position:absolute;
left:15px;
top:20px;
margin:0;padding:0;
}
body.homepage h1#logo {
z-index:100;
left:29px;
top:39px;
}
Internet Explorer 版本 6 对绝对定位和z-index
不太友好,因此徽标被呈现为不可见的。它仍然在那里,但浏览器选择不在我们可以看到的地方显示它。因此,不要花费大量的时间来制定一个在所有浏览器上都能正常工作的解决方案——这是理想的情况,但是请记住,紧张的预算和时间表——改变<h1>
在 Internet Explorer 中的显示方式才是正确的做法。Star-HTML hack 允许我们将不同的风格发送到 only Internet Explorer:
* html body.homepage h1#logo {
position:relative;
margin-bottom:-170px;
}
所以我把定位从position:absolute
改为position:relative
,然后调整底部边距,直到 logo 定位到我想要的位置(本例中为 170 像素)。黑客之所以能成功,是因为它有更高的特异性(* html body.homepage h1#logo
比body.homepage h1#logo
更具体),所以它不仅是唯一能理解这一规则的浏览器,还赋予了它比之前的规则更高的优先权。
还有一件事:因为允许这种攻击工作的缺陷在 Internet Explorer 7 中已经被修复,所以最好将这种攻击和任何其他针对 Internet Explorer 的攻击放在一个单独的样式表中,并在条件注释中链接它,这将在版本 7 中隐藏它,如下所示:
<!--[if lte IE 6]>
<link rel="stylesheet" href="css/iehacks.css" type="text/css" media="screen" />
<![endif]>
当时间至关重要时,hacks 可以保护您的理智和您的项目免受 Internet Explorer 6 和更早版本中糟糕的标准支持的影响。但是一旦新的版本出现,这种情况就不一定了。如果您使用 hacks,那么您必须做好准备,如果将来 hacks 中断,您可能不得不重新访问旧的项目。
注意
尽管 Internet Explorer 在渲染错误方面受到了很多负面的关注。事实是所有的浏览器都有 bug。我们只需要找到最好的方法来解决那些给我们带来最多问题的问题。
结论
如果你检查现场站点的源代码,你会注意到有多少不必要的标记仍然存在于他们的 CMS 结构中。优化输出是一项持续的工作,我将继续向标签的开发团队提出建议。最后,如果您因为某种原因必须使用不受您完全控制的 CMS,重要的是要记住,仍然有许多技巧和工具可供您使用,以帮助您避免陷入在生成的标记的寒冷和狭窄的范围内创建无聊设计的窠臼。作为一名设计师,挑战 CMS 是值得的,而不是为了满足技术要求而简化你的设计。当网站上线时,你一定会睡得更好*
三、纽约杂志:天哪,多么优雅的<body>
ethan marcotte
www.vertua.com
伊森·马科特从事在线设计和开发已经近十年了,他仍然对有这么多东西需要学习感到惊讶和兴奋。他是 Vertua Studios ( www.vertua.com
)的联合创始人和设计主管,这是一家熟悉标准的设计工作室,致力于构建优雅、可用的网站。
Ethan 已经成为基于标准的网页设计领域备受尊敬的声音。他是网页设计世界和西南偏南互动会议的重要发言人,他还经营着一个流行的(如果不经常更新的话)sidesh0w.com
博客。他的客户包括《??》杂志、哈佛大学、华特·迪士尼公司和道富银行。
长大后,伊森想成为一名不可阻挡的机器人忍者(www.unstoppablerobotninja.com
)。哔。
莫‘新城,莫’风格
老实说,重新设计《??》杂志网站对我来说是一种改变。我的小公司 Vertua ( www.vertua.com
)通常标榜自己是一家提供全面服务的网店,或者至少是一家“一人军团”工作室所能提供的全面服务。然而,这个项目是一个受欢迎的改变,因为它本质上是一个“纯代码”的工作:该杂志与另一家工作室合作设计新网站,并正在寻找一个人来构建基于标准的模板。(我想在这里开一个“龙争虎斗”的玩笑,但我认为这很愚蠢。)
重新设计的模板列表令人印象深刻:该杂志在网站上有多年的遗留内容,都以不同的布局和模板呈现。此外,该杂志的内容管理系统(CMS)功能强大,但相当轻便——许多页面内容是由一组技术熟练但基本上无技术含量的内容制作者手工制作的。那么,基于标准的设计如何让杂志员工的生活更轻松呢?
在这一章中,我将讨论如何将多个class
值赋给一个元素(在这种情况下,body
可以真正简化你的代码,使你的级联样式表(CSS)更加模块化。通过编写 CSS 代码来检测您在body
元素的class
中写入的不同“切换”,您的样式表可以在页面上以截然不同的方式设计相同的标记。结果是所需的 HTML 模板数量大幅减少,并且能够对页面设计进行彻底的更改。
对于本章的大部分内容,我们将集中在通用文章模板上,如图 3-1 所示。
闲聊结束后,让我们一步一步地看一下如何将这些放在一起。
图 3.1。成品文章模板
开始使用
文章模板的交付要求它是一个灵活宽度的设计,在右边有一个固定宽度的侧边栏。图 3-2 向我们展示了页面需要如何伸缩。
图 3.2。页面布局要求。左列灵活;右列是固定的。
为了给 New York 杂志创建一个带有固定宽度侧边栏的流动宽度内容区域,我使用了 Ryan Brill 的负边距技术,这在http://alistapart.com/articles/negativemargins
有详细介绍。在研究了这种方法之后,我们可以创建一个空的标记外壳,准备接收内容的设计:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<head>
<title>Article Template</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link type="text/css" rel="style sheet" href="/style/screen.css"
media="screen, projection" />
<!--[if lt IE 7]>
<link type="text/css" rel="style sheet" href="/style/patches-ie.css"
media="screen, projection" />
<![endif]-->
</head>
<body id="www-newyorkmag-com">
<div id="wrap">
<div id="content">
<div id="content-layout">
<div id="content-primary-wrap">
<div id="content-primary">
</div><!-- /end #content-primary -->
</div><!-- /end #content-primary-wrap -->
<div id="content-secondary">
</div><!-- /end #content-secondary -->
</div><!-- /end #content-layout -->
</div><!-- /end #content -->
</div><!-- /end #wrap -->
</body>
</html>
根据 Brill 文章中概述的方法,我们不仅需要两个内容区域的两个div
(发明名称为content-primary
和content-secondary
),还需要一个围绕content-primary
的包装器div
,姑且称之为content-primary-wrap
。忠实的信徒们,请继续关注:稍后我们会在这个包装上看到更多内容。
构建 CSS
在内容区域上方,您可能会注意到文档的head
中有两个link
元素:一个用于screen.css
,另一个用于patches-ie.css
。在我们的linked
样式表screen.css
中,我放置了以下内容:
import url("core.css");
/*\*//*/
import url("patches-mac.ie5.css");
/03/
在第一行中,import
规则(www.w3.org/TR/CSS21/cascade.html#at-import
)引用了另一个外部 CSS 文件core.css
。这个样式表包含了我们的大部分 CSS 规则,没有任何特定于浏览器的样式表补丁。core.css
所有浏览器都可以看到,包含我们纯粹的、无黑客的风格规则。
说到黑客,这正是第二条import
语句所包含的内容。那一系列看起来奇怪的 CSS 注释实际上是 IE5/Mac 带通滤波器(www.stopdesign.com/examples/ie5mac-bpf
),它可以防止除了麦金塔上的 Internet Explorer 5 (IE5)之外的任何浏览器看到其中的代码。所以现在,有了我们的半隐藏的patches-mac.ie5.css
,我们可以在浏览器中放置任何样式规则来解决 CSS 错误。
为什么要为这种 CSS 中的 CSS 方法费心呢?这种方法的好处之一是易于维护:如果我们需要停止支持 IE5/Mac,通过分离这些代码,我们可以更容易地删除这些 CSS“补丁”。我们不需要筛选一个样式表的几百行代码,相反,我们可以将这些修复隔离在一个单独的文件中。一旦我们真的需要停止对那个浏览器的支持,我们可以简单地删除我们的screen.css
文件中的那个import
语句,就和 IE5 说拜拜了。
回到我们的 HTML 模板,我们看到patches-ie.css
被一些奇怪的注释包围着:
<!--[if lt IE 7]>
<link type="text/css" rel="style sheet" href="/style/2/ie-width.css"
media="screen, projection" />
<![endif]-->
这些被称为条件注释,它们是一个仅用于 IE 的 HTML 扩展,允许我们在标记中构建编程逻辑。我们可以为 Windows 上的特定版本的 Internet Explorer 提供大量标记,在这个特殊的注释语法中指定版本号和其他条件。
注意
条件注释是专有的。HTML 的非标准扩展,因此。这些年来,它们一直是争议不小的话题。你可以在微软的网站(http://msdn.microsoft.com/workshop/author/dhtml/overview/
ccoment _ ovw . ASP)以及众多的行业博客上找到更多关于条件评论的信息,比如 mezzoblue ( www.mezzoblue.com/archives/2005/11/03/ie7_conditio
)。
在这里,我们使用那些条件注释来隐藏我们的第二个链接,它来自于比版本 7 ( <!--[if lt IE 7]>
)更早的 IE ,然后链接到patches-ie.css
中的另外两个样式表,就像这样:
import url("patches-win.iex.css");
media tty {
i{content:"\";/*" "*/}} import 'patches-win.ie5.css'; /*";}
}/* */
与我们的 IE5/Mac 特定的 CSS 文件一样,这两个import
规则引入了外部样式表,用于修补不同版本的 IE/Windows 中的错误:第一个规则适用于 Windows 上所有版本的 Internet Explorer,第二个规则仅适用于 IE5/Windows。更好的是,地球上所有其他非 IE 浏览器都不知道这些特定于浏览器的变通办法,因为这些条件注释被符合标准的浏览器视为常规 HTML 注释。因此,到patches-ie.css
的链接对大多数浏览器来说是不可见的,可以被 IE/Windows 轻松解析。
在这一点上,你可能想知道(阅读:“疯狂地对着天花板尖叫”)为什么你要这样麻烦地分叉你的 CSS。为什么要链接到两个单独的样式表,它们只存在于import
其他 CSS 文件中,其中一些除了不同浏览器特定的 CSS 错误的变通方法之外什么都没有?(参见图 3-3 。)
正如你可能意识到的,你有很多方法可以在你的其他 CSS 规则旁边插入浏览器特定的 CSS 补丁(见http://cssdiscuss.incutio.com/?page=CssHack
中的一些例子)。虽然将所有 CSS 保存在一个文件中只会让您担心一个 CSS 文件,但跟踪和更新各个浏览器的修复程序很快就会变成一场噩梦。使用多样式表方法,您可以智能地将 CSS 补丁隔离到特定于浏览器的样式表中,这将使未来的更新变得轻而易举。当你停止支持一个浏览器时会发生什么?好吧,不用在超过 2000 行的样式表中搜索你写在主 CSS 文件中的每个 IE5/Mac hack,你可以简单地删除在一个link
ed 样式表中对该浏览器 CSS 补丁的import
引用。努夫说。
有了这个 hack 管理框架,我们现在可以开始做一些有趣的事情了:对文档的标记应用一些基本的样式。是的,我知道是时候了。
图 3.3。我们的 CSS 架构
注意
这里概述的方法受到了 Molly Holzschlag 的优秀文章“集成 Web 设计:长期 CSS 黑客管理的策略”(www.informit.com/articles/article.asp?p=170511&rl=1
)的启发。这篇文章写于将近两年前,今天读起来和它第一次发表时一样好。阅读它,标记它,冲洗,重复。
添加图层样式
好了,关于浏览器 bug 和 CSS 补丁说够了。让我们给core.css
添加一些基本的样式:
body {
background: #FFF;
color: #000;
font: 62.5%/1.5 Arial, Helvetica, Verdana, Geneva, sans-serif;
margin: 0;
padding: 0;
}
#wrap {
margin: 0 auto;
min-width: 770px;
max-width: 980px;
}
#content {
background-color: #EBEAE8;
border-top: 1px solid #D6D5D3;
padding: 7px 8px 9px;
}
#content-layout {
background: #FFF;
}
通过这几行代码,我们彻底改变了稀疏的 XHTML 的外观。我们在body
上建立一些基本的字体和颜色属性;给#content
来点。。。嗯,灰色;通过在我们的“包装材质”div
上放置自动镶边来突出我们的设计。在图 3-4 中,你可以看到我们已经走了多远:我们的两个即将建成的柱子被很好地框住了,尽管它们是直接叠在一起的。
图 3.4。div
s 的默认堆叠顺序
此外,我们将min-width
和max-width
值应用于#wrap
,确保我们的设计符合客户的目标。模板的宽度将扩大到 980 像素,然后缩小到 770 像素。看起来不错,不是吗?
嗯,差不多了。通常会有一个蓝色“E”形的小问题:Internet Explorer 及以下版本不支持max-width
和min-width
属性。对于 Windows 上的 IE,我们有很多解决方法。然而,对于《??》杂志来说,我选择了下面这段 CSS:
#wrap {
width:expression(document.body.clientWidth > 980 ? "980px" :
(document.body.clientWidth < 772 ? "770px": "auto"));
}
expression()
是一个动态属性,是微软的专有发明(http://msdn.microsoft.com/workshop/author/dhtml/reference/methods/setexpression.asp
),只能在 Windows 上的 Internet Explorer 中工作。它混淆了样式表和脚本之间原本清晰的界限,正如你所看到的,我已经直接在#wrap
规则中写了一小段 JavaScript。当由 Internet Explorer 加载时,这个 JavaScript 检测页面的宽度(使用document.body.clientWidth
,另一个 IE 专用的代码),如果窗口变得比我们的目标宽度大或小,就限制#wrap div
的宽度。
在这一点上,观众中的标准化者可能会问,“但是 Ethan,你为什么要在一个符合标准的模板中使用非标准代码呢?你住在哪里,好让我朝你讨厌 W3C 的房子扔尖棍子?”
正是在这一点上,我们众多的 CSS“补丁”文件派上了用场。由于这只是针对 IE/Windows 的修复,我们可以安全地将无效代码放在我们的patches-win.iex.css
文件中。藏在这个单独的文件中,我们的非标准代码对符合标准的浏览器和设备是隐藏的,是的,这包括受人喜爱的 W3C CSS 验证器。
负的边距和分栏等等!天啊。
有了一些标记和 CSS 基础,是时候完成我们的专栏了。瑞安·布里尔的负边际技术(http://alistapart.com/articles/negativemargins
)是通过三个简单的步骤实现的:
-
在固定宽度的侧边栏上设置宽度(这里是
content-secondary
)。 -
向左浮动
content-primary-wrap
容器,将其宽度设置为 100%。 -
在容器上设置一个负数
margin-right
,它的宽度应该等于第一步中工具条的宽度。
所需的 CSS 就像听起来一样简单。首先,我们需要浮动我们的两列,就像这样:
#content-primary-wrap {
float: left;
width: 100%;
}
#content-primary {
padding: 0 12px 0 13px;
}
#content-secondary {
float: right;
}
在这一点上,快速预览我们的代码显示没有太多变化,除了我们丢失了部分灰色边框(图 3-5 )。
你可能会问,以蒂姆·伯纳斯·李的名义,这是怎么回事?或者你可能不是。我们真的不知道。
图 3.5。布局没变,但是我们的边界在哪里?
清算时间
尽管我们的两个内容div
看起来与之前的没有太大的不同,但它们仍然是浮动元素,因此,它们将从它们的非浮动容器content-layout div
中“逃离”。所以我们在文档顶部看到的边界实际上是content-layout
的整个边界:它包含的两个浮动的div
已经从它们的父容器中流出,有效地完全折叠了我们可怜的容器。所以content-layout
现在没有非浮动元素来给它一些高度,我们可爱的四边边框看起来只是一条粗灰线。这自然不行。但不要一开始就咬牙切齿、揪头发。谢天谢地,有一个解决方案。
确保我们的集装箱。。。嗯,包含两列,我们用http://positioniseverything.net/easyclearing.html
写的“简易清除法”。首先,我们将以下内容放在我们的core.css
文件中:
#content-layout:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
这里我们使用了:after
伪元素(www.w3.org/TR/CSS21/generate.html#x5
)在容器的末尾,在所有其他元素之后生成额外的内容(即一个句点)。这些额外的内容对用户来说是隐藏的,但是变成了一个块级的元素,它将clear
标记中在它之前的所有其他浮动。不幸的是,这一规则在 IE 的任何当前版本中都不起作用。更重要的是,为了安抚 IE 的 Windows 版本,我们实际上需要两个解决方案:一个是最新版本的 7,在撰写本文时这是一个公共测试版(但应该会在书上架时正式发布),另一个是对目前使用的早期版本的黑客攻击。
为了让 IE7 运行得更好,我们只需在我们的:after
规则后插入以下内容:
#content-layout {
display: inline-block;
display: block;
}
值得称赞的是,网页设计师和标准大师 Roger Johansson ( www.456bereastreet.com
)是第一个发布这个解决方案的人。display: inline-block;
触发 IE7 中的非标准hasLayout
属性,强制容器自动包含其中的任何浮动元素。
注意
更多关于 hasLayout 的信息,请阅读 Ingo Chao 关于该主题的优秀文章:www.satzansatz.de/cssd/onhavinglayout.html
。当然,这样做很可能会让你的眼睛斜视和/或头发脱落,所以如果你完全相信这一点,也许是最好的。
为了在旧版本的 IE/Windows 中触发hasLayout
,让我们将以下规则添加到我们的patches-win.iex.css
文件中:
#content-layout {
height: 1%;
}
最后,让我们打开我们的patches-mac.ie5.css
文件,添加以下内容:
#content-layout {
display: inline-table;
}
正如法国设计师所言, le voil 图 3-6 向我们展示了我们的彩车再次被遏制,我们的边境线坚强、灰暗、骄傲地屹立着。
图 3.6。随着一些清理的实现,我们的边界又回来了。
我从没说过我擅长这个“隐喻”的东西。继续前进。
获得列-tastic(最终)
现在我们的容器已经回到正轨,我们需要做的就是给我们的div
应用适当的边距:给content-primary-wrap
一个负的右边距,给content-primary
一个右边距。这些边距中使用的值应该等于或大于我们现在要设置的content-secondary
的宽度。
对于 190 像素宽的窄右列,我们可以使用以下代码:
/*
Set the column offset on the content divs
*/
#content-primary-wrap {
margin-right: 200px;
}
#content-primary {
margin-right: 200px;
}
/*
Set the right-hand column width
*/
#content-secondary {
width: 190px;
}
至此,我们的列效果完成,如图图 3-7 所示。
图 3.7。最后,我们的两列布局成型。
正如你在图 3-8 中所看到的,这种灵活宽度的布局在较小的窗口宽度中令人钦佩地缩小了,这应该会让我们的客户非常非常高兴。
图 3.8。我们灵活的宽度设计令人钦佩地缩小到更小的窗口宽度。万岁!
简单,是吗?但是请记住,我们有两个右边的列宽需要考虑:一个是我们刚刚编码的窄列宽,另一个是 360 像素的宽栏。我们必须为这个新的侧边栏宽度构建一个单独的模板吗,或者我们可以重用到目前为止所做的工作吗?你不喜欢引导性问题吗?
我的班芙是不可阻挡的
在这里,我们终于可以利用body
元素上的class
属性了。如果我们知道我们有两个独立的侧边栏列宽,为什么不相应地分配一个class
来“描述”文档呢?所以对于我们狭窄的广告栏宽度,我选定了
<body id="www-newyorkmag-com" class="ad-column-180">
对于更宽的一个,我用
<body id="www-newyorkmag-com" class="ad-column-300">
注意
严格地说,这些类名可能不是最理想的。一般来说,尽量避免使用表示元素向用户显示的方式的类或 id 值(例如红色链接、大标题,甚至 ad-column-300)。避免使用这些“表象”名称的主要原因是它们不是特别经得起未来的考验:当你重新设计你的网站时会发生什么,所有那些红色链接元素都需要变成绿色?ad-layout-1 或 ad-layout-2 可能更抽象,可能更好一些;然而,我们认为制作人员可能会更好地管理更具描述性的名称,剩下的就是历史了。或者类名。或者什么的。
实际上,这给了我们的模板一种切换,我们可以利用我们的 CSS。如果我们知道这两个模板中的一个将被应用于我们的body
元素,我们可以相应地修改我们的样式规则:
/*
Set the column offset on the content DIVs
*/
body.ad-column-180 #content-primary-wrap {
margin-right: 200px;
}
body.ad-column-180 #content-primary {
margin-right: 200px;
}
body.ad-column-300 #content-primary-wrap {
margin-right: 370px;
}
body.ad-column-300 #content-primary {
margin-right: 370px;
}
/*
Set the right-hand column width
*/
body.ad-column-180 #content-secondary {
width: 190px;
}
body.ad-column-300 #content-secondary {
width: 360px;
}
在这里,我们充实了我们最初的规则,并用适当的body.ad-column-180
和body.ad-column-300
选择器为两个单独的列宽做准备。
注意
正如你可能已经注意到的,ad-column-300 列实际上将是 360 像素宽。我从来都不擅长数学,但这个小小的命名差异是有原因的:更宽的栏将容纳 300 像素宽的广告横幅。因为非技术内容生产者最终将“拥有”这些模板,所以我选定了一个对他们有意义的类名。
此外,我们可以将不同的水平重复背景图形应用到内容布局中(参见图 3-9 和 3-10 ),以在右栏后面产生“灰色”背景的错觉。
图 3.9。右侧窄栏的灰色背景图像
图 3.10。右侧宽栏的灰色区域
我们需要做的就是用下面的 CSS 替换我们的content-layout
规则:
#content-layout {
background-color: #FFF;
background-position: 100% 0;
background-repeat: repeat-y;
}
body.ad-column-180 #content-layout {
background-image: url("bg-ad-column-180.gif");
}
body.ad-column-300 #content-layout {
background-image: url("bg-ad-column-300.gif");
}
我们栏目的“背景”会自动调整,如图图 3-11 和图 3-12 所示。
图 3.11。窄图形是用body
元素上的ad-column-180 class
应用的。
图 3.12。通过将class
切换到ad-column-300
,我们已经为更宽的列切换到更大的图像。
你可能会问,这种方法有什么好处?依靠这种class
驱动的切换,我们的其余标记可以在两种布局场景中重用:只需将模板的body
中的ad-column-180
改为ad-column-300
,我们就可以立即改变标记的显示样式。图 3-13 和 3-14 显示了这在两个不同的实时、生产就绪的文章模板上的实际应用,两者之间唯一的变化是body
元素的class
属性中的几个字符。这两个页面上的其余标记是相同的。我不知道你怎么想,但我对这种事情很感兴趣。
图 3.13。我们完成的文章模板,在body
元素的class
属性中有ad-column-180
。
图 3.14。完全相同的模板代码,但是在class
中有ad-column-300
。很辣。
智能模块
因此,我们的页面级布局由新的class
驱动的开关控制,让我们试着对页面的细节进行同样的控制。早些时候,我提到除了body
元素的class
属性,页面的标记还包括右边一栏中的一小部分内容块,或模块。就像我们刚刚创建的两列一样,这些模块会自动调整大小,以适应侧边栏的宽度。
让我们快速看一个例子,“现在在。。."模块。在其窄格式中(图 3-15 ,内容堆叠在一个单列中;然而,当content-secondary
栏为宽格式时,两个内容区域并排放置(图 3-16 )。(我想郑重声明,这张图不是我选的。)
图 3.15。狭义版的“现在在……”模块。我想郑重声明,我们没有选这张照片。
使用我们从构建两列布局中学到的知识,让我们在这里尝试相同级别的标记重用。如果我们能有一个 HTML 模板用于这两种不同的模块布局,那将是理想的。
为此,让我们快速回顾一下本模块中不同的内容领域。我们可以看到有两个不同的内容区域,顶部有一个标题(图 3-17 )。即使在两栏版本中也是如此(图 3-18 ),但是第一个内容区域出现在右栏,第二个内容区域出现在其左侧。
图 3.16。“现在流行...”的宽、两栏版本模块
图 3.17。解剖我们的狭窄模块
图 3.18。两栏宽格式布局
因此,有了对两种布局选项(单列和两列)中不同内容区域的基本理解,我们就可以构建一个基本的标记框架了。
<div class="block module-in-section">
<div class="head">
<h5><img srcimg/title-in-section.gif"
alt="Now In Section" /></h5>
<h6><img srcimg/title-slideshow.gif" alt="slideshow"
/></h6>
</div>
<div class="content">
<div class="row columns-2">
<div class="column col-1">
<img srcimg/156x78-in-section.jpg"
alt="Now In Section" />
</div>
<div class="column col-2">
<h5><a href="#">Apocalypse Guide</a></h5>
<p>How to avoid avian flu, wash off a dirty bomb, evacuate,
medicate, and otherwise live through a disaster.</p>
<ul>
<li><a href="#">Bulleted Link 1</a></li>
<li><a href="#">Bulleted Link 2</a></li>
<li><a href="#">Bulleted Link 3</a></li>
</ul>
</div>
</div>
</div>
</div><!-- /end div.module-in-section -->
这里有一些事项需要注意:
-
模块的
class
属性的block
值是我在整个纽约杂志模板中放置的一段可重用的标记。它只是简单的一个简单的清算方法的挂钩,并被添加到那些规则中(.block:after
等)。)以便它包含内部的任何浮动。 -
内容区域中的
row
类提供了类似的功能:如果它的任何一个子“列”div
被浮动,它们不会逃脱它们的父“行”div
。 -
模块的
class
(module-in-section
)中的另一个值充当这种类型模块的一种唯一标识符。我不能在这里使用一个真正的id
属性,因为一个页面上可能会出现不止一个这种类型的模块,而且根据 HTML 规范,id
在页面中必须是唯一的。
标记就绪后,我们可以继续对其进行样式化。鉴于该模块的宽版本和窄版本具有相同的美学品质(如排版、颜色和背景图像),我在这里就不讨论它们了;相反,让我们专注于构建我们的两个内容栏。
在单列模式中,我们可以让标记的源顺序为我们工作。第一列将简单地堆叠在第二列之上,如图图 3-17 所示。然而,我们可以应用一点 CSS 来清理一些东西:
.module-in-section .content {
padding: 0 14px 13px;
}
body.ad-column-180 .module-in-section .col-1 {
text-align: center;
margin: 7px 0 10px;
}
第一个规则只是对我们的内容div
应用一些填充,给我们的列一些喘息的空间。第二条规则将列内容(即img
)居中,并添加一些顶部和底部边距。但是你可能已经注意到了,我们在第二个规则前面加上了同样的body.ad-column-180
规则,我们用它来创建我们的窄边栏(在“我的班级-fu 是不可阻挡的”部分)。一个class
,两个独立效果。轻量级代码是一件美好的事情,不是吗?
从这个逻辑出发,我们可以将ad-column-300 class
应用到body
元素来创建我们的两列效果。我们需要浮动每一列div
,第一列浮动到右边,第二列浮动到左边,如图 3-19 所示。
body.ad-column-300 .module-in-section .column {
width: 157px;
}
body.ad-column-300 .module-in-section .col-1 {
float: right;
}
body.ad-column-300 .module-in-section .col-2 {
float: left;
}
图 3.19。让我们的两列布局就位的代码
所以我们有了它:我们的body
属性中的一个单独的class
值可以作为我们侧边栏中每个模块的一种交通警察。我们已经编写了一些期望(并利用)出现ad-column-180
或ad-column-300
的 CSS,让我们的标记完全保持原样。
附加类别,附加控制
此时,我们已经在class
属性中使用了一个值。通过使用ad-column-180
或ad-column-300
,我们可以立即重新格式化右侧边栏及其内容。正如我们已经讨论过的,class
属性可以接受多个值,但是我们可以使用body
元素来控制页面表示的其他方面吗?我又开始问引导性问题了。
从小尺寸开始(980 像素)
当我参与纽约杂志网站的重新设计时,我被告知制作团队希望能够选择性地覆盖某些页面的灵活宽度设计,有效地将页面宽度固定在 980 像素。据推测,某些页面可能包含宽幅媒体(例如,大幅照片),而客户希望确保页面布局的其余部分保持不变。
让我们再来看看我们的#wrap
规则,它包含了我们的min-width
和max-width
参数:
#wrap {
margin: 0 auto;
min-width: 770px;
max-width: 980px;
}
因此,如果这是默认行为,让我们给我们的body
元素添加一个新的class
值来覆盖它的 say,“fixed
”?
<body id="www-newyorkmag-com" class="ad-column-180 fixed">
我们现在可以放入下面的 CSS,将我们的灵活设计变成静态设计:
body.fixed #wrap {
width: 980px;
}
现在我们有了。既然我们现在已经在我们的wrap
块上设置了宽度,那么min-width
和max-width
属性就过时了。我们设计的容器div
固定在要求的 980 像素,这满足了我们客户的要求。
注意
关于多个类名的一个警告:老版本的 IE5/Mac 有一个空格解析错误(www.macedition.com/cb/ie5macbugs/substringbug.html
),当遇到一个类名是另一个类的子串时,这个错误会导致它变得混乱。因此,尽量使你所应用的类类型的类别尽可能的独特,并在浏览器中进行彻底的测试。
搭售 JavaScript
我们可以用body class
调用一些其他的视觉效果,但是让我们暂时离开设计。毕竟这个技术远不是一招小马。我们还可以将类附加到文档中,作为 JavaScript 驱动的行为的标志,为网站的设计增加另一层基于标准的吸引力。
例如,在一些文章页面上有一组链接(图 3-20 )用于动态增加(图 3-21 )或减少(图 3-22 )页面上文章文本的大小。何必呢?嗯,这实际上是一个相当方便的辅助功能。如果读者视力下降,他可能不知道如何改变浏览器的文本大小。在页面上放置几个链接会以一种直接且易于使用的方式展示类似的功能。
图 3.20。出现在网站某些页面上的文本大小控件
图 3.21。文本大小可以增加。。。
图 3.22。。。。或者通过使用页面上的链接来减少。我看得出来,你被难倒了。
受古老的基于 JavaScript 的样式表切换器(www.alistapart.com/stories/alternate
)的启发, New York 杂志的文本大小调整代码执行许多不同的任务。当用户点击其中一个链接时,一个额外的类被添加到body
元素中,当它出现时,将相应地改变文章模板中文本的大小。此外,存储在用户浏览器上的 cookie 会记住用户的选择,并在用户返回时自动应用适当的文本大小。这是一段相当复杂的 JavaScript,但这可能是因为我们并不是 JavaScript 程序员的塞缪尔·L·杰克逊。
需要注意的是,你在图 3-20 中看到的链接实际上并不存在于 HTML 中。页面加载后,JavaScript 将三个“A”链接注入到页面中,确保控件只对浏览器支持它们的用户可用。我们对body
元素的class
表现出近乎强迫性的痴迷,我们可以使用该属性来触发我们的功能,这将允许我们的内容制作者将一个简单的单词放入class
来调用该功能,从而将链接放入页面。
一如既往,让我们从我们的元素开始吧我知道,令人震惊。但是这一次,让我们使用text-sizer
作为我们的新类:
<body id="www-newyorkmag-com" class="ad-column-180 fixed text-sizer">
在我们从文档的head
中引用的scripts.js
文件中有一个名为buildTextSizer
的函数:
<script type="text/javascript" src="/path/to/scripts.js"></script>
这个函数有相当多的代码,所以如果你不是 JavaScript 忍者也不用担心(毕竟,我肯定不是)。幸运的是,我们最关心的部分发生在顶部:
function buildTextSizer() {
if (document.getElementsByTagName && document.createElement
&& document.getElementById) {
var trigger = document.getElementsByTagName("body")[0];
if (findWord("text-sizer", trigger.className)) {
if (document.getElementById("article-content")) {
var container = document.getElementById("article-content");
} else {
var container = document.getElementById("content-primary");
}
if (container) {
// Build elements
var slugs = new Array("small", "medium", "large");
var controlContainer = document.createElement("div");
var topList = document.createElement("ul");
var innerList = document.createElement("ul");
var listItem = document.createElement("li");
var span = document.createElement("span");
var labelText = document.createTextNode("Text Size:")
// Loop over the text size "slugs", and build a link for
//each one
for (var i = 0; i < slugs.length; i++) {
var text = document.createTextNode("A");
var anchor = document.createElement("a");
var item = document.createElement("li");
anchor.appendChild(text);
anchor.setAttribute("href", "javascript:textIt('txt-" +
slugs[i] + "');");
anchor.setAttribute("title", "Make the story text " +
slugs[i] + ".");
item.appendChild(anchor);
item.setAttribute("id", "txt-" + slugs[i]);
innerList.appendChild(item);
}
// Assemble everything, and insert it into the
//document
span.className = "label";
span.appendChild(labelText);
listItem.appendChild(span);
listItem.appendChild(innerList);
topList.appendChild(listItem);
controlContainer.setAttribute("id", "text-size");
controlContainer.appendChild(topList);
container.insertBefore(controlContainer, container.
childNodes[0]);
}
}
}
}
/*
Find full word (needle) in a string (haystack)
*/
function findWord(needle, haystack) {
return haystack.match(needle + "\\b");
}
从本质上讲,我们这里有两个独立的函数:一个用于构建文本大小控制(buildTextSizer
,继续我的获奖名称),另一个函数是第一个函数使用的findWord
。在用粗体突出显示的代码部分中,buildTextSizer
检查我们的body
元素(var trigger = document.getElementsByTagName("body")[0];
),并搜索其class
属性中是否存在text-sizer
字符串(findWord("text-sizer", trigger.className)
)。如果搜索结果匹配,那么函数的其余部分继续构建文本大小调整链接;如果没有找到匹配(即如果text-sizer
没有出现在body
元素的class
属性中),那么函数停止运行,链接永远看不到。
由于该函数被设置为在站点的每个页面上触发,内容制作者可以有选择地将class
放在他们希望显示大小控制的页面上,而在其他页面上省略它。如果在body
元素中找不到text-sizer
,该功能将会自动失败,这为网站的作者提供了切换该功能的灵活性和方便性。这当然是件好事。
总结
这一章绝不是在杂志网站上使用body
元素的class
属性的详尽列表;我很乐意给你提供这样一份清单,但我不认为我能强迫你耐着性子看完托尔斯泰长度的一章关于body
元素的内容。相反,我希望这一章中的信息能激发一些想法,让你明白这种多类值技术如何能给你带来好处,以及它的智能应用如何能减少你的站点的模板数量,减少标记混乱,并给你足够的 JavaScript 和 CSS 的钩子。在纽约杂志网站上,我们使用了额外的class
es 来动态替换特定区域的徽标,并自动突出显示当前的导航标签。这方面的其他应用完全取决于你。
Web 标准是关于改进对设计的控制,而不是维护庞大的代码。拥抱你的body
,你的网站将更容易维护、更新和改进。当然,你的用户会喜欢的。
四、为打破常规而设计
安迪·克拉克
www.malarkey.co.uk
www.stuffandnonsense.co.uk
在英国,安迪·克拉克(马拉其)有广告背景。1998 年,他创办了自己的设计咨询公司 Stuff and nullness。从那以后,他为英国迪斯尼商店、英国心脏基金会、救助儿童会和英国世界野生动物基金会设计了网站。
安迪对设计和网络标准充满热情;他弥合了设计和代码之间的鸿沟。在工作室之外,Andy 是 Web 标准项目的成员,他在 2006 年重新设计了该组织的网站,并且是 W3C 的 CSS 工作组的特邀专家。
Andy 是国际知名的培训师和会议发言人,他定期培训设计师和开发人员 Web 标准的创造性应用。他在自己的个人网站上写一些关于设计和流行文化的内容,这些都是胡扯(www.stuffandnonsense.co.uk
),他还是《超越 CSS 的 ??:网页设计的艺术()的作者,该书于 2006 年由 New Riders 出版(www.transcendingcss.com
)。
担忧?
“哦宝贝,怎么了?”她说。“你看起来压力很大,很担心。”
“我是,”我回答。“我担心我们的车需要新轮胎,除了一纸袋蘑菇和一管番茄酱,我们冰箱里没有喝茶用的东西,我妈妈的脚又有问题了。”
我可以告诉你,这可不是闹着玩的。如果担心自己的问题还不够糟糕,担心别人的问题会让人抓狂。尽管如此,一种叫做WorrySome.net
的新型虚拟服务形式的帮助就在眼前。
这将不是你常见的或普通的网络应用程序,有足够的风险资本在新德里发动一场小战争和一个呼叫中心。这个网站是“由真实的人,为真实的人”的人会为你担心,当然是收费的。只要你继续付费,这项服务将让你自由自在地生活,无忧无虑。
我需要一个新网站,而你正是这份工作的合适人选。这是一个棘手的问题,但不要担心;帮助就在你身边,引导你把这个设计变成现实。如果你觉得在任何阶段你开始变得哪怕是一丁点儿担心,你总是可以付钱给一个担心的人来减轻你的负担。
在这一章中,你将把WorrySome.net
主页从 design visual 变成一个使用 XHTML 和 CSS 的工作原型。你将从有意义的、内容外的标记开始,这总是开发一个创造性的、基于标准的设计的第一步。您将学习如何使用强大的 CSS 选择器和布局技术来实现这种设计。
担心网络
我个人很高兴WorrySome.net
能代我担心,因为这两年,我一直在担心网页设计。网络是一个年轻的、充满活力的媒介,人们应该喜欢与他们访问的网站互动。创建人们喜欢使用的网站是创造性网页设计的主要目标之一。但是网页设计者和开发者经常关注标记、CSS、Ajax 的技术方面,或者可用性和可访问性的“科学”,而不是通过好的设计来联系访问者的情感。
我们已经看到了 CSS 提供的工具的进步,以及主流 web 浏览器对这些工具更广泛的支持。我希望我们不要再担心支持过期的浏览器,而是通过创造新的、鼓舞人心的设计来打破当前的思维模式。
为WorrySome.net
设计
由于WorrySome.net
是一种处理世界忧虑的新颖的新方法,该网站呼吁一种打破当今许多闪亮的网络应用程序的熟悉惯例的设计。摘要要求这种设计是开放和友好的。它必须让访问者感觉“像一个值得信赖的朋友一样受欢迎,而不是像一个顾客一样。”
在设计WorrySome.net
的外观时,设计采用了多种形式。我用许多不同的界面想法做了几个实验性的布局,只有少数几个进入了最终的设计。图 4-1 显示了你将在本章中使用的那个。
让我们开始将WorrySome.net
主页的设计变成现实吧。您将使用有意义的 XHTML 标记和 CSS,但不只是任何旧的 CSS。这个最小的标记将要求你使用你可能还没有在自己的工作中实现的技术和 CSS 选择器。您可以从www.friendsofed.com
下载所有必要的文件。
图 4.1。WorrySome.net
主页
别担心,从涨价开始
从事网络工作的设计师的目标之一应该是传达意义。我说的不仅仅是通过视觉设计来强化品牌价值所表达的意思,还有通过为内容选择的 XHTML 元素所传达的意思。根据元素的含义而不是视觉表现来选择合适的元素,这将有助于你创造出尽可能灵活和易于理解的设计。(当然,你还需要确保你的标记尽可能的精简和灵活。)您希望确保在没有 CSS 所提供的视觉丰富性的情况下,元素能够传达内容的全部含义。
回头看图 4-1 ,写下页面上每个视觉元素的含义。在此设计中,您会看到以下元素:
-
一个包含网站名称和标语的品牌区,歌词来自鲍勃·马利,一位悠闲生活方式的大师
-
导航链接列表
-
三个标题,每个标题后面都有几段文字和一个烦恼者的内嵌图像
-
标题后面是一系列人们经常担心的话题:从阴谋到乔治·w·布什(我想不出有什么联系,没有,先生)
-
网站信息,通常包含法律注释、版权信息和设计致谢
完成内容大纲后,您就可以充实最适合表达其含义的标记了。在这一点上,您应该只关心描述这个内容的含义,而不是任何 division 元素或表示性标记技巧。
你应该从内容大纲的顶部开始,然后向下,所以我们从品牌开始。
添加内容元素
在 WorrySome 的主页上,网站名称可以作为页面顶级标题的一个不错的选择。
<h1>Worrysome.net</h1>
许多设计者会选择在内部页面上改变这一点,选择二级标题,保留顶级标题作为页面名称。在内部页面上,这个名称可能还会包含一个返回主页的链接,这在主页上是多余的。
接下来是标语。写在牙买加马利家的台阶上,那里烟雾缭绕,这是《三只小鸟》的摘录,非常适合作为一个放松的网站。浏览 XHTML 规范的页面,如果需要的话,寻找一个lyric
元素。我就在这里等着,唱着“纯净而真实的甜美歌曲”,直到你回来。
已经回来了?
在没有更合适的元素来标记雷鬼伟人的这首诗的情况下,一个blockquote
元素就可以了;毕竟他唱过那些词。
<blockquote cite="http://www.bobmarley.com/songs/songs.cgi?threebirds">
<p>Don' worry 'bout a thing, cos every lil thing is gonna be alright.</p>
</blockquote>
注意
注意引用的 URL 来源已经被引用。尽管这些信息在访问者的网络浏览器中是看不到的,但你还是应该注明你引用的任何引文的来源。关于使用脚本显示引用的 URL 的示例,请参见 Jeremy Keith 的书DOM scrp ting:Web Design with JavaScript and the Document Object Model(编辑之友,ISBN: 1-59059-533-5)。
如果你仍然“心情愉快”,那么构成主导航的订阅页面、忧虑列表和购物车的链接列表是有序的,任何特定链接都没有任何重要性或权重。无序列表是标记这些链接的合适选择。
<h4>Main navigation</h4>
<ul>
<li><a href="#" title="Subscribe">Subscribe</a></li>
<li><a href="#" title="Worrylist">Worrylist</a></li>
<li><a href="#" title="Worrycart">Worrycart</a></li>
</ul>
但是标题在那里做什么?导航链接列表顶部的低级标题可以帮助使用屏幕阅读器(或其他形式的辅助技术)的访问者了解列表的用途。您可以选择使这些标题可见或隐藏,也许是通过在屏幕上缩进它们。这种技术被称为提供一个嵌入式替换。
现在我们真的“卡住了”,是时候转移到这个主页上感兴趣的主要内容了:这个站点提供的服务的描述。您将为每个内容区域选择一个二级标题,后面是相关内容。
<h2>Worriers</h2>
<p>Introduction text</p>
<img src="img/worryone-i.png" alt="Lynda" />
<p>Name and role of worrier</p>
<p>Further descriptive text</p>
<h2>Worries</h2>
<p>Introduction text</p>
<img src="img/worrytwo-i.png" alt="Andy" />
<p>Name and role of worrier</p>
<p>Further descriptive text</p>
<h2>Worry done</h2>
<p>Introduction text</p>
<img src="img/worrythree-i.png" alt="Brain" />
<p>Name and role of worrier</p>
<p>Further descriptive text</p>
一个第三级标题自豪地宣布网站的专家担忧小组可以从你肩上卸下的主题列表。因为这个列表是按字母顺序排列的,所以选择一个有序列表而不是无序列表是否最合适是有争议的。对于这个例子,我选择了无序,因为没有一个条目比它的兄弟条目更重要。
<h3>Recently worried about</h3>
<ul>
<li><a href="#">Worry item</a></li>
<li><a href="#">Worry item</a></li>
<li><a href="#">Worry item</a></li>
</ul>
你现在几乎在页面的底部,在网站信息和一个方便的链接回到顶部,以保存访问者的滚动手指。
<p><a href="http://www.stuffandnonsense.co.uk"> Stuff and
Nonsense Ltd.</a> A demonstration site by Andy Clarke</p>
<ul>
<li><a href="#worrysome-net" title="Top of this page">Top</a></li>
</ul>
有了整洁地编写的有意义的标记,现在你有机会在开发浏览器中预览你的页面(见图 4-2 )并验证你的代码以确保没有错误出现。
注意
如果像我一样,您选择的开发浏览器是 Firefox,您可以找到许多开发人员扩展,它们将在您工作时保持您的标记有效,尤其是 Chris Pederick 的基本 Web 开发人员工具栏。您可以从http://chrispederick.com/work/webdeveloper/
下载 Web Developer 工具栏。
图 4.2。在浏览器中预览页面,以确保在没有字体的情况下阅读时内容是有序的,这是开发视觉上富有表现力但又易于访问的设计的第一步。
从内容中添加分部出来
总的来说,web 设计人员对标记和 CSS 的理解在最近几年有所发展,但是自从我们使用表格进行布局以来,我们对使用 CSS 完成视觉设计的方式的想法几乎没有改变。剥去许多基于标准的网站的外观,它们的 W3C“有效的 XHTML 和 CSS”徽章在阳光下闪闪发光,你会发现大量无意义和不必要的<div>
和<span>
元素。
从内容着手意味着只从结构元素开始,比如标题、段落和列表。这是让您的标记不受表示元素影响的理想方法。
接下来,您将只对那些您先前选择的相关元素进行分组,并为每个元素赋予一个标识来描述它所包含的内容。
注意
关于语义元素命名的主题已经写了很多。前 CSS 武士约翰·奥尔索普创建了 WebPatterns ( www.webpatterns.org
),一个致力于元素命名惯例的网站。我关于命名约定主题的原始文章可以在 All That Malarkey ( www.stuffandnonsense.co.uk/archives/whats_in_a_name_pt2.html
)找到。
为了避免代码重复,下一个示例只显示了内容区域,而不是再现标记的每一个细微差别。
<div id="branding">
<h1>Worrysome.net</h1>
<blockquote cite="http://www.bobmarley.com/songs/songs.cgi?threebirds ">
<p>Don' worry 'bout a thing, cos every lil thing is gonna be alright.</p>
</blockquote>
</div>
<div id="nav_main">
<h4>Main navigation</h4>
<ul>
<li><a href="#" title="Subscribe">Subscribe</a></li>
<li><a href="#" title="Worrylist">Worrylist</a></li>
<li><a href="#" title="Worrycart">Worrycart</a></li>
</ul>
</div>
<div id="content">
<div id="content_main">
<div id="worriers">
<h2>Worriers</h2>
<p>Introduction text</p>
<img src="img/worryone-i.png" alt="Lynda" />
<p>Name and role of worrier</p>
<p>Further descriptive text</p>
</div>
<div id="worriers">
<h2>Worriers</h2>
<p>Introduction text</p>
<img src="img/worryone-i.png" alt="Andy" />
<p>Name and role of worrier</p>
<p>Further descriptive text</p>
</div>
<div id="worries">
<h2>Worries</h2>
<p>Introduction text</p>
<img src="img/worrytwo-i.png" alt="Andy" />
<p>Name and role of worrier</p>
<p>Further descriptive text</p>
</div>
</div>
<div id="content_sub">
<h3>Recently worried about</h3>
<ul>
<li><a href="#">Worry item</a></li>
<li><a href="#">Worry item</a></li>
<li><a href="#">Worry item</a></li>
</ul>
</div>
</div>
<div id="siteinfo">
<p><a href="http://www.stuffandnonsense.co.uk"> Stuff and Nonsense Ltd. </a>
A demonstration site by Andy Clarke</p>
<ul>
<li><a href="#worrysome-net" title="Top of this page">Top</a></li>
</ul>
</div>
这些适当标识的部分不仅增加了结构,使您能够更容易地开发视觉布局,而且它们的标识符也有助于增强它们所包含的内容的意义。在基于内容的工作流中,这种最小化划分的使用应该始终是您的方法。这使得在编写标记时,内容而不是视觉布局成为您思考的中心。
如果需要额外的部门来完成任何特定的设计,应该一次添加一个部门,直到可以实现您的设计。对于这种设计,只需要一个额外的容器部分,包装所有的元素。除了html
和body
元素之外,这个容器是一种允许您进一步选择样式的常用方法。
<div id="container">
All document content
</div>
您可能已经注意到,在这个标记示例中,没有添加额外的标识符或class
属性,尽管设计很复杂。很少需要这样的表示属性,因为您的标记已经包含了所有的元素和属性(href
、title
、alt
等等),当您对基于标准的设计采用成熟的方法时,您可能会用到这些元素和属性。
完成标记中的元素和适当的划分后,但在开始使用 CSS 之前,您应该花点时间考虑再添加一个元素。这将有助于那些使用屏幕阅读器或不支持样式表的小屏幕浏览器浏览网站的访问者。
虽然期望每个访问者都能完全访问这个网站或任何其他网站是一个不可能的梦想,但在你的文档中添加一些不引人注目的内容会给一些人带来巨大的帮助。对于本例,您将添加一个简短的跳转链接列表,以允许屏幕阅读器用户跳转到主导航或主要内容。
<ul id="nav_access">
<li><a href="#nav_main">Skip to navigation</a></li>
<li><a href="#content_main">Skip main content</a></li>
</ul>
您应该将这个列表直接插入到开始的<body>
标签之下,容器部分之外。
满足你的灵魂(用 CSS)
如果标记是你的“朋克雷鬼派对”,那么将它转化为视觉设计布局应该是你“满足我的灵魂”的时间。(好了,这是最后一首鲍勃·马利的歌名说大话,我保证。)
从面条事件(www.thenoodleincident.com
)和蓝色机器人(www.bluerobot.com
)的早期开始,使用 CSS 实现设计布局已经走了很长的路。在实际使用 CSS 的那些日子里,定位是实现列和其他布局特性的首选方法。不久之后,随着设计者越来越雄心勃勃地尝试使用 CSS 来制作复杂的设计,定位让位于使用浮动。不幸的是,定位及其相关的z-index
堆叠已经有些失宠了。
尽管乍一看,定位很难理解,但它可能仍然是 CSS 设计工具中最强大的。实现WorrySome.net
布局将大量使用定位和z-index
以及浮动。
您还将使用图像替换和一整套 CSS 选择器。如果你使用 CSS 已经有一段时间了,其中的许多你会很熟悉;其他人可能看起来很奇怪。你将使用选择器,直到最近它还是网页设计者的梦想。不要担心“水牛战士”(对不起,我无法抗拒),我会解释每一个新的选择器和技术,因为我们的进展。
开往 Styleville 的火车
直到最近,你对可以在不同浏览器上可靠使用的 CSS 选择器和技术的选择还仅限于世界上最常用的浏览器微软的 Internet Explorer 6 for Windows 支持的几个简单的选择器。由于 Internet Explorer 7 开发人员的辛勤工作和对标准的热情,以及 web 标准项目(www.webstandards.org
)的 Molly E. Holzschlag 等人的奉献,这种情况现在已经改变了,他们在 Web 开发人员社区和微软之间架起了桥梁。我们都应该感谢他们的工作。
Internet Explorer 7 远非完美的浏览器(什么浏览器?),但确实让浏览器公平竞争。它对所谓的“高级”CSS 选择器有很好的支持,并修复了过去版本的渲染问题和错误,这些问题和错误近年来一直困扰着 web 设计人员。
我想要时尚
尽管它们对 CSS 的渲染大体一致,但大多数浏览器在应用作者或用户样式之前会有不同的页面默认渲染方式,即页面的外观,使用所谓的用户代理或浏览器样式,通常也称为默认样式。因此,您应该从在样式表中添加一些简单的规则开始,以保证任何元素的样式都符合您的意图,而不是浏览器的意图。这个例子包含了比您将要用来实现WorrySome.net
更广泛的元素,使您能够处理将来可能需要的任何元素。
/* =reset.css */
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form,
fieldset, input, p, blockquote, address, th, td {
margin : 0; padding :0; }
h2, h3, h4, h5, h6 {
font-size : 100%;
font-weight : normal; }
ol, ul {
list-style-type : none; }
table {
border-collapse : collapse;
border-spacing : 0; }
caption, th {
text-align : left; }
fieldset, img { border : 0; }
dt, address, caption, cite, code, dfn, em, i, strong, b, th, var {
font-style : normal;
font-weight : normal; }
q:before, q:after { content :''; }
除了这个 reset CSS,我还建议你在常用的文本元素上指定margin
和padding
。
/* =blocktext */
h2, h3, h4, h5, p, ul {
margin : 0 20px;
padding : .5em 0; }
造型WorrySome.net
许多网页设计者采用一种粒度方法来使用 CSS 实现他们的设计,而你将采用一种由外向内的方法,首先关注设计的外部元素,然后再处理细节。您将从应用设置布局结构的样式开始,从包含所有内容的html
、body
和container
部分开始。
应用于根元素html
的background-color
、background-image
和字体color
将使球滚动。一个细长的background-image
将水平重复(repeat-x
)来创建网站的条纹背景。
html {
background : #f7d8e8 url(img/html.png) repeat-x;
color : #333; }
注意
我选择的许多图像文件名都与它们所设计的元素密切相关。这种方法有助于节省时间,并减少几个月后返回网站时的混乱,或者当许多设计人员或开发人员在一起工作时。
接下来,您应该对body
元素应用样式。这种设计将是固定宽度的,并在浏览器窗口中居中。
body {
position : relative;
width : 740px;
margin : 20px auto 0 auto;
padding-top : 10px;
background : url(img/body.png) repeat-y;
font : 88%/1.5 Calibri, Trebuchet, "Trebuchet MS", Helvetica, Arial, sans-serif; }
您可能想知道为什么position : relative;
应该应用于body
元素。毕竟,body
不太可能被定位或偏离其自然位置。这个设计中的很多视觉元素都是绝对定位在页面上的。将position : relative;
应用到body
元素会将它建立为其任何定位后代的定位上下文。
接下来,轮到外container
师了。同样,该元素已经被建立为其后代的定位上下文,并位于body
的中心,以允许该元素的background-image
为页面深度创建微妙的投影。
div[id="container"] {
width : 700px;
margin : 0 auto; }
注意
如果您不熟悉示例中的选择器,请不要担心。这是一个属性选择器,由于在 Internet Explorer 6 中缺乏对它的支持,web 设计人员和开发人员很大程度上避免使用这种类型。
属性选择器由三部分组成:
-
您正在选择的元素(本例中为 div)
-
用于选择特定元素的属性(本例中为 id)
-
属性的值(本例中为容器)
您可以使用 div#container 或者甚至#container 来选择相同的元素,但是那样就没那么有趣了,不是吗?
造型基础页面分部
有了外部区域,您可以将注意力转向定义此设计的品牌、导航和内容区域。对于每一个,您将应用基本的样式,包括框和背景属性。从顶部的品牌区域开始。
div[id="branding"] {
height : 200px;
margin-bottom : 10px;
background : #f0a4c7 url(img/branding.png) repeat-x; }
这可以跟随主导航的外部划分。
div[id="nav_main"] {
background : #fedaeb url(img/nav_main.png) repeat-y; }
最后,将样式应用于其余的主要部分:
div[id="content"] {
margin-bottom : 80px; }
div[id="content_main"], div[id="content_sub"] {
width: 100%; }
div[id="siteinfo"] {
clear : both;
min-height : 120px;
padding: .5em 0;
background : url(img/siteinfo.png) repeat-x; }
通过在您的开发浏览器中加载页面,查看您的设计是如何完成的(参见图 4-3 )。对于如此少的努力来说,表现还不算太差,但你仍然只是第 40 名的新人,你还有一段路要走,才能登上排行榜的榜首。不用担心;这不会像你想象的那么难。
图 4.3。样式化基本页面 divs、html 和 body 来创建画布,在画布上创建WorrySome.net
设计
制作栏目
我想是史蒂夫·克鲁格在他的书《不要让我思考》中建议莱昂纳多·达芬奇发明了标签。我不确定是谁发明了柱子。也许是纳尔逊勋爵,但我跑题了。
WorrySome.net
的主页将它的三个主要内容区域分成几列,每一列都包含对该服务的不同解释,顶部是不同颜色的圆形图像。您的下一个任务是使用 floats 创建三个列。
以下规则将适用于所有三列。它将每个浮动到左边,并给它们一个最小的高度、宽度和边距;少量底部填充;和一个位于分区底部的background-image
。
div[id="worriers"], div[id="worries"], div[id="worrydone"] {
float : left;
min-height : 42em;
width : 220px;
margin-right : 20px;
padding-bottom : 1em;
background : url(img/worryone-b.png) no-repeat 0 100%; }
现在您可以选择将出现在最右边的worrydone
部分,并移除它的右边距,将其放在容器的最外边。
div[id="worrydone"] {
margin-right : 0; }
您的列制作考察的最终结果显示在图 4-4 中。
图 4.4。浮动师团让三个纵队配得上特拉法尔加广场
接下来,您将向这三列中的每一列添加单独的样式,从标题开始向下。您将使用流行的 Phark 负文本缩进技术(如第二章,“用 CSS、Flash 和 JavaScript 驯服野生 CMS 参见http://phark.net
。该方法对每个标题应用一个background-image
,并通过缩进大量像素将文本从浏览器窗口的左侧移开。
第一条规则适用于所有三个标题。
div[id="worriers"] h2,
div[id="worries"] h2,
div[id="worrydone"] h2 {
min-height : 50px;
margin : 0;
text-indent : 9999px; }
这应该遵循给每个标题添加一个唯一的background-image
的规则,如图 4-5 中的所示。
div[id="worriers"] h2 {
background : url(img/worryone-t.png) no-repeat 0 0; }
div[id="worries"] h2 {
background : url(img/worrytwo-t.png) no-repeat 0 0; }
div[id="worrydone"] h2 {
background : url(img/worrythree-t.png) no-repeat 0 0; }
图 4.5。使用图像替换来设计标题样式
介绍性文字的每一段都将使用不同颜色的背景图像和更大的字体,如图图 4-6 所示。同样,第一条规则是所有三个段落共有的。
div[id="worriers"] h2 + p,
div[id="worries"] h2 + p,
div[id="worrydone"] h2 + p {
min-height : 4em;
margin : 0;
padding : 5px 20px 40px 20px;
font-size : 120%;
line-height : 1;
color : #fff; }
div[id="worriers"] h2 + p {
background : #cc6195 url(img/worryone-m.png) no-repeat 0 100%; }
div[id="worries"] h2 + p {
background : #dd82ae url(img/worrytwo-m.png) no-repeat 0 100%; }
div[id="worrydone"] h2 + p {
background : #f0a4c7 url(img/worrythree-m.png) no-repeat 0
100%; }
图 4.6。三个介绍性段落的顺序明显不同
注意
等等,这些选择器里的+号是干什么的?这个符号被称为兄弟组合子,并形成一个相邻的兄弟选择器。这种类型的选择器根据其前面的元素选择一个元素;在这种情况下,紧接着 h2 的是 p 元素。Web 设计人员和开发人员在很大程度上避免了这种类型的选择器,因为在 Internet Explorer 之前的版本中缺乏对它的支持。
你的专栏正在成形。最后,你可以整理肖像和烦恼者名字周围的空间,将文字居中并缩小字体大小,如图图 4-7 所示。
div[id="worriers"] img,
div[id="worries"] img,
div[id="worrydone"] img {
margin-left : 15px; }
img + p {
padding-top : 0;
font-size : 92%;
text-align : center; }
图 4.7。使用属性和相邻选择器深入细节
虽然浮动和图像替换在您的脑海中还记忆犹新,但是让我们来看看令人担忧的主题列表及其标题。再次使用 Phark 方法替换标题文本。
div[id="content_sub"] h4 {
height : 35px;
width : 300px;
padding : 0;
background : url(img/h4.png) no-repeat;
text-indent : 9999px; }
该网站的专家团队可以代表您担心的主题列表将被转换为三栏式设计。您可以通过浮动每个列表项并使用一个background-image
来提供一个装饰性的项目符号来完成这个效果。
div[id="content_sub"] ul {
float : left;
padding-bottom : 80px; }
div[id="content_sub"] li {
display : block;
float : left;
width : 190px;
padding-left : 20px;
background : url(img/li.png) no-repeat 0 50%; }
结果如图 4-8 所示。
图 4.8。浮动列表项目:用简单列表创建列设计的有效方法
样式页脚
页脚有时可能是设计中被忽略的部分。通常,他们最简单的处理会让你的视线离开页面底部。
页脚有一个较小版本的主标志和一个非常有用的顶部链接,这样网站的访问者就不用担心滚动到页面顶部会喘不过气来。
首先,开始设计页脚中的一小段文本。将其内容转换为大写字母,并减小其字体大小和对比度以降低其重要性。
div[id="siteinfo"] p {
padding-top : 40px;
font-size : 82%;
text-transform : uppercase;
color : #999; }
div[id="siteinfo"] p a {
display : block; }
注意
如果你能更具体地说明你的目标锚,也许通过设计不同于你网站上其他页面的外部链接,这不是很好吗?现在,在很多情况下你可以。除了 Internet Explorer 7 之外,所有支持标准的主流浏览器都支持如下选择器:
div[id="siteinfo"] a[href^="http"] {
display : block; }
带有^的选择器是一种子串匹配属性选择器,有点拗口,但却是 CSS 中最有趣的选择器类型之一。从现在开始,我将引用 tas 子串选择器,以节省墨水和树。
这种类型的选择器有不同的种类,其中许多可以使完成这种设计更加容易。此处的示例针对 siteinfo 部分中包含的所有链接,其中 href 以 http 开头。随着本章的深入,您将看到更多的子串选择器。
现在是时候将注意力转向添加小的WorrySome.net
标志了。查看该页面的标记可以发现,这里没有内嵌的徽标图像。在一个无序列表中,只有到页面顶部的链接。从删除列表中的所有边距开始。
div[id="siteinfo"] ul {
margin : 0; }
现在,您可以使用定位和图像替换的组合,将这个不起眼的链接转换为闪亮的徽标。首先,设置它的比例并绝对定位它:左边 240 像素,上面 50 像素。
div[id="siteinfo"] ul a {
position : absolute;
display : block;
top : 50px;
left: 240px;
height : 120px;
width : 230px; }
应用一个背景图像,将它的文本滑动到屏幕之外,你就可以开始了。
div[id="siteinfo"] ul a {
background : url(img/a-t.png) no-repeat;
text-indent : 9999px; }
好吧,也许你还没有完成。在您的浏览器中预览结果,您将会看到徽标并没有像您预期的那样正好位于页脚上方。相反,它被定位在body
元素之上 50px,因为这是它最近的定位祖先,如图 4-9 的顶部所示。
你可以很容易地补救这一点。通过应用position : relative;
但不应用偏移,使站点信息部分本身成为定位上下文。
div[id="siteinfo"] {
position : relative; }
图 4.9。更正页脚徽标的位置。你可以说我老土,但我认为它在底部更好看。
设计主导航
理解子字符串选择器的功能和灵活性非常重要,因为您将在设计该页面的主导航时更多地使用它们。同样,您将使用一系列不同的技术,包括定位和图像替换。主导航将如图 4-10 所示。
图 4.10。主导航的最终外观
但是如何实现这一点呢?您的第一个简单任务是从视图中删除标题,这一次是将它放置在浏览器窗口的顶部边缘。
div[id="nav_main"] h4 {
position : absolute; top : 9999px; }
现在标题被取消了,除了浏览没有样式的页面的人,您可以设置无序导航列表的比例,并添加包含所有三个按钮图形的背景图像。
div[id="nav_main"] ul {
width : 310px;
height : 38px;
margin-left : 200px;
padding : 0;
background : url(img/li_nav_main.png) no-repeat; }
该列表中的导航链接将通过定位三个锚点来完成。接下来,建立这个无序列表作为定位上下文,并将所有列表项设置为内联显示,而不是块级元素。
div[id="nav_main"] ul {
position : relative; }
div[id="nav_main"] li {
display : inline; }
将position : relative;
应用于列表以建立定位上下文,现在为所有三个锚点设置一个通用规则。
div[id="nav_main"] a {
position : absolute;
top : 0;
display : block;
height : 38px;
width : 104px;
text-indent : 9999px; }
根据锚的title
属性,这将遵循针对单个锚的特定规则。这些title
属性为每个链接添加了一个可视化的工具提示,并允许您定位它们的锚点。
a[title="Subscribe"] {
left : 0; }
a[title="Worrylist"] {
left : 104px; }
a[title="Worrycart"] {
left : 208px; }
注意
您已经在前面的选择器示例中遇到了^符号,并了解到它针对的是以特定值开头的属性。相比之下,$符号针对的是以特定值结尾的属性。
<a href="#" title="Worries in your Worrycart">Worrycart</a>
a[title$="worrycart"] {
left : 208px; }
在浏览器中预览完成的结果。为了说明锚点的位置,我给图 4-11 中的每个锚点添加了一个红色边框(使用 Firefox 的 Web Developer 工具栏的轮廓功能)。
图 4.11。显示完成的结果,并突出显示锚点
品牌设计
我希望,尽管有了所有的新技术和选择器,你的头脑没有烦恼。页面上还有一个区域需要你去处理,这是我在所有网站设计中最喜欢的部分:品牌。
此时,您已经使用了所有的选择器技术和类型,将品牌区域的顶级标题和 Marley 的“三只小鸟”的摘录转换为有吸引力和有意义的视觉元素。
由于这些元素将被定位为创造标志“打破常规”的视觉效果,因此首先要将品牌部门转变为这些元素的定位环境。
div[id="branding"] {
position : relative; }
现在你已经准备好放置顶层标题和blockquote
。使用 Phark 图像替换方法将文本替换为background-image
。
h1 {
z-index : 1;
position : absolute;
left : 50px;
top : 30px;
height : 178px;
width : 379px;
background : url(img/h1.png) no-repeat;
text-indent : 9999px; }
div[id="branding"] blockquote {
z-index : 2;
position : absolute;
left : 225px;
top : 85px;
height : 103px;
width : 198px;
background : url(img/blockquote.png) no-repeat;
text-indent : 9999px; }
注意
为了确保醒目的块引用始终位于其相邻标题的前面,块引用被赋予了更高的 z 索引。关于 z-index 的创造性灵活性的更详细的解释,请阅读我在http://24ways.org/advent/zs-not-dead-baby-zs-not-dead
的 24ways 文章。
这样做的结果是,你引人注目的网站口号变成了更有吸引力的东西,如图 4-12 所示。
图 4.12。定位和z-index
相结合,创建网站的品牌,没有什么顾虑。
对无障碍的家伙微笑
你的工作几乎完成了,但是WorrySome.net
品牌的一个重要方面仍然缺失:黄色笑脸。如果您向后翻页查看该页面的标记,您会看到每个元素和属性都被使用了;没有任何东西被浪费。除了保存方便的跳转链接的无序列表之外,什么都没有。
不用担心;这个元素很快就会对你有用。许多 web 设计人员和开发人员选择隐藏这些嵌入式辅助功能,通常将它们推到屏幕之外,看不到。您将使用此元素,通过附加笑脸background-image
来赋予品牌区域以生命。
ul[id="nav_access"] {
position : absolute;
top : 33px;
right : 50px;
height : 291px;
width : 340px;
margin : 0;
padding : 0;
background : url(img/a-access.png) no-repeat;
text-indent : 9999px; }
在你的浏览器中预览商标区所有的笑脸,你会发现这张脸并没有对你微笑。CSS 缺少了一个重要的部分,以及一个重要的教训。除非您为任何定位的元素指定了一个z-index
值,否则那些在文档顺序中跟随它的元素将始终位于顶部。它们越靠近结束的</body>
标签,它们在堆叠顺序中的位置就越高。
可访问性列表出现在文档的最开始,因此在堆叠顺序中,它位于所有其他定位的元素之后。你可以纠正这一点,通过给笑脸一个明确的z-index
来恢复它的快乐,这将确保它在设计中占据应有的位置,如图 4-13 所示。
ul[id="nav_access"] {
z-index : 2; }
图 4.13。微笑,帮助有特殊需求的访客的隐藏工具也可以作为视觉设计元素的挂钩。
我们会让你再看看最终的设计(图 4-1 ),看看它的辉煌:
处理传统浏览器
尽管已经有很多关于 CSS 的书,但是很少有人涉及到被认为是高级选择器的实际应用。一个原因是网络上最常用的浏览器,Windows 的 Internet Explorer,不支持这些选择器。Web 设计人员和开发人员避免使用这些选择器,或者他们集中精力限制他们的设计,以便页面在不同年龄或功能的浏览器上显示相同。
如果网页设计要进步,就必须找到新的方法。并不是你在本章中使用的所有技术在所有浏览器中都是一样的。不用担心;这是故意的。
在许多工作环境中,web 设计人员和开发人员必须处理浏览器的功能,这些功能仍在日常使用中,但也远远超出了最佳使用日期。不幸的是,这一群乌合之众的老浏览器包括 Windows 版的 Internet Explorer 6。幸运的是,有一个 solutionone 可以让你完全采用开发WorrySome.net
时一直在使用的技术和选择器。
狄恩·爱德华兹的 IE7 脚本(http://dean.edwards.name/IE7/
)使用 JavaScript 将样式表解析成 Internet Explorer 6 和更早版本可以理解的形式。它们使您能够在样式表中使用 CSS2 甚至一些 CSS3 选择器,并将 Internet Explorer 的旧版本转换成一个无忧的新浏览器,该浏览器能够理解以下内容:
-
子选择器
-
相邻兄弟选择器
-
属性值选择器
-
:first-child
、:last-child
、:only-child
和:nth-child
结构伪类 -
:before
和:after
生成的内容
注意
狄恩·爱德华兹的 IE7 脚本可能不适合在所有情况下使用。这不是企业级解决方案,也不适合高流量网站。
随着越来越多的人将浏览器升级到 Internet Explorer 7,随着其对 CSS2.1 的支持增加,Internet Explorer 6 的用户数量将会减少。这使得 Edwards 的解决方案成为中低流量网站的设计者和开发者的有用选择,他们的目标是在更广泛的浏览器中安全地使用所谓的高级技术。
微软建议设计者和开发者停止使用 CSS hacks,转而使用微软专有的条件注释。这些注释仅受 Internet Explorer for Windows 支持,通过将注释放在文档的<head>
部分,可以很容易地定位 Internet Explorer 的版本。
条件注释最常见的用途是提供特定的样式表,以解决旧版 Internet Explorer 中的错误和呈现错误。它们可以很容易地用于为需要它们的浏览器提供狄恩·爱德华兹的 IE7 脚本。例如,此注释将只为版本 7 之前的 Internet Explorer 版本提供脚本。
<!--[if lte IE 7]>
<script src="js/ie7-standard-p.js" type="text/javascript"></script>
<![endif]-->
</head>
没关系!
在这一章中,你已经了解到,当使用以前被认为是先进的 CSS 来创建一个打破常规的设计布局时,没有什么可担心的。现代的 CSS2.1 选择器、浮动和定位是用最少的非表示性标记创建引人入胜的网站设计的完美工具。
您已经看到了如何在精通 CSS 的 web 浏览器中将这些工具和技术付诸实现,以及如何使用巧妙的脚本来填补旧的、更糟糕的浏览器中的漏洞。我期待着看到你将如何利用你所学到的东西。
我的西装熨好了,我的皮鞋擦得锃亮了,摩登女郎们正在布赖顿大街上行进。是时候让你在剩下的章节里见到“王牌面孔”了。
五、PNG 透明在网页设计中的创造性运用
杰夫·克罗夫特
www2.jeffcroft.com
杰夫·克罗夫特是一名专注于基于标准的开发的网页和平面设计师,他在堪萨斯州的劳伦斯生活和工作。作为世界在线的高级设计师,杰夫在www.lawrence.com
和www.ljworld.com
等获奖新闻网站工作。杰夫还在www.jeffcroft.com
经营一个受欢迎的博客和个人网站,在那里他写了许多话题,包括现代网络和图形设计。
自从有网可织以来,杰夫就一直在织网。他在 1994 年用 SimpleText 和 Netscape Navigator 1.1N 在 Macintosh Performa 600 上创建了他的第一个网页。他于 1996 年开始全职从事网络工作,并一直坚持到现在。
尽管 Jeff 喜欢技术和小工具,但网络真正激发他灵感的是它沟通和联系人们的能力。杰夫是设计的忠实消费者,几乎在任何地方都能找到灵感。
当杰夫不在电脑前工作时,他喜欢摄影、音乐、电影、电视和在镇上度过一个美好的夜晚。
PNG、GIF 和 JPEG
PNG 图像被网页设计社区广泛忽视,这是有原因的。直到最近,还不可能充分利用这种格式并让它在所有浏览器中可靠地工作。但是,有了 Internet Explorer 7 中对 PNG 的适当支持,以及一些方便的 JavaScript 和 CSS 技巧来解决旧浏览器的问题,我们可以使用 PNG 图像来大大增强我们的设计词汇。
什么是 PNG?
PNG 通常读作“ping”,代表便携式网络图形。这是一种无损压缩位图图像格式。简单地说,这是一种保存图形图像的方式,可以在不降低图像质量的情况下减小文件大小。它最初是作为无处不在的 GIF 格式的替代品而创建的,GIF 格式过去需要成像软件的生产商获得专利许可才能合法使用它(GIF/LZW 专利已经过期,所以这不再是一个因素)。PNG 也是国际标准(ISO/IEC 15948:2003)和 W3C 官方推荐标准(www.w3.org/TR/PNG/
)。
除了作为一种免费的格式,PNG 还为网页设计者提供了几个优于 GIF 的实用优势:
-
更大的压缩率:对于大多数图像来说,PNG 文件比 GIF 文件更小。
-
更大的颜色深度:PNG 提供高达 48 位的真彩色,而 GIF 只允许 256 色调色板。
-
alpha 通道透明:GIF 只提供二进制透明,而 PNG 通过启用 Alpha 通道来实现透明,从而允许几乎无限的透明效果。
值得一提的是,PNG 和 GIF 一样不支持动画。有一个相关的标准叫做多图像网络图形(MNG,www.libpng.org/pub/mng/
),它确实允许这两种情况,但是它没有得到网络浏览器或图像软件的广泛支持。
那么为什么 GIF 还这么受欢迎呢?
你可能想知道为什么 PNG 不是网络上最常用的图像格式,如果它像广告宣传的那样好的话。答案在很大程度上在于对格式和浏览器支持的误解。因为 Internet Explorer 6 和更低版本不支持 PNG 的所有功能(包括 alpha 通道透明度),人们似乎认为(尽管是错误的)Internet Explorer 根本不支持 PNG,或者至少不支持透明度。实际上,Internet Explorer 5 和 6 都支持足够多的 PNG 规范,使 PNG 图像在功能上等同于(或优于)非动画 GIF 图像。所有其他著名的浏览器,包括 Firefox、Netscape 6 和更高版本、Mozilla、Opera 6 和更高版本、Safari 和 camino,都完全支持 PNG 透明。
除了对浏览器支持的误解之外,GIF 对动画的内置支持是(并将继续是)它成功的一个关键原因。然而,近年来,随着其他技术(特别是 Flash)在动画中变得越来越常见,GIF 的这种使用变得不那么流行了。
透明性是 GIF 和 PNG 的一个关键特征,这也是它们中的任何一个被选为网页设计者选择特定图像元素的格式的原因。尽管 PNG 对透明性提供了更广泛的支持,但 web 设计人员经常需要创建图像的 GIF 版本,以适应较旧的浏览器。使用 CSS,可以将 GIF 图像发送到旧的浏览器,将高质量的 png 发送到理解它们的浏览器。但是创建两个图像对网页设计者来说是额外的工作,这经常导致人们满足于最小公分母,仍然是 GIF 图像。
最后,GIF 仍然如此受欢迎有几个原因,但大多数是基于误解或越来越不常见的用例场景。掌握了 PNG 如何工作以及如何在各种浏览器中可靠使用的一些新知识,您应该能够利用这种格式提供的所有优势,而不必依赖 GIF。
JPEG 呢?
JPEG 是网络上另一种无处不在的文件格式,对于照片(或类似照片)图像来说,它几乎总是比 PNG 或 GIF 更好的选择。巴布亚新几内亚无意与 JPEG 竞争。在处理照片时,JPEG 的有损压缩(每次保存图像时都会导致质量下降)会产生比 PNG 小得多的文件。另一方面,当 PNG 中的图像是文本、艺术线条、徽标、单色等时,它会生成较小的文件。
不起眼的巴布亚新几内亚的一些伟大用途
现在让我们来看看 PNG 在网页设计中的一些重要用途。我已经将每个例子的所有文件包含在本章代码下载的一个单独的文件夹中,可以在www.friendsofed.com
找到。
渐变
在过去的几年里,两种或两种以上颜色之间的渐变已经成为网页设计者最好的朋友。特别受欢迎的是微妙的,几乎不明显的渐变填充,它增加了深度和纹理的感觉,而不明显和俗气。
GIF 有时候是渐变的好选择。如果渐变是简单的双色渐变,GIF 通常就可以了。但是,GIF 256 色限制通常会在更复杂的渐变过渡中产生明显且难看的条纹。另一方面,JPEG 可以呈现非常令人愉悦的渐变,但通常会以文件大小超出预期为代价。虽然 JPEG 渐变通常“足够好”,但请记住,JPEG 确实使用有损压缩,这意味着再现的图像永远不会像原始的未压缩图像那样高保真。
考虑通常用于按钮、框和其他任何东西的典型背景渐变样式。它可能看起来有点像图 5-1 。从左上方顺时针方向,我们有原始(未压缩)图像、GIF 版本、PNG 版本和 JPEG 版本。您可以看到,PNG 产生的文件最小(515 字节)。比 GIF 图小四倍左右。JPEG 比 PNG 稍大,为 637 字节,由于有损压缩,它的质量也较低(不可否认,在这个简单的例子中,人眼察觉质量差异的能力是值得怀疑的)。
图 5.1。Photoshop 的“存储为 Web 格式”面板显示不同格式的同一图像的文件大小差异
需要在任何背景下工作的图像
有时有必要创建一个在各种背景下都能很好工作的图像。一些常见的例子是徽标和图标。这些情况传统上是 GIF 文件的领域,但是有几个原因说明 PNG 可能是更好的选择。在标志和其他简单艺术品的文件大小的较量中,巴布亚新几内亚几乎总是赢家。此外,PNG 固有的透明性使得创建一个可以在任何背景上工作的文件变得简单。PNG 确实提供了二进制透明性与 gif 一样,但也提供了更令人兴奋的 alpha 通道种类,其中像素可以是部分透明的,而不是简单的开或关。使用后者确实会增加文件大小,有时会超过(二进制)透明 gif 的大小,但也允许对艺术作品的边缘进行抗锯齿处理,这有助于在背景上放置更优雅的文件。
对于堪萨斯州托皮卡的 KTKA 频道新闻网站(www.49abcnews.com
),世界在线的工作人员精心制作了漂亮的天气图标,在网站的标题中显示当前的天气状况。但是,由于一个聪明的编程,使得标题在日落时从白天的配色方案变为夜间的配色方案,天气图像需要在不同的背景下工作得同样好。看看图 5-2 和图 5-3 。
图 5.2。www.49abcnews.com
头球,白天
图 5.3。www.49abcnews.com
头球,夜间
通过使用 PNG,我能够公正地处理设计师的作品,无论它是出现在白天还是夜晚的背景上。而且,如果我们选择在某个时候改变背景,我就不必重新制作任何天气图标,因为透明的 PNG 文件在任何东西上看起来都很棒。
如果我选择使用 GIF,我会被限制在 GIF 的二进制透明性。结果看起来会像图 5-4 。我想我们都同意这还不够好。
图 5.4。www.49abcnews.com
标题,nightime,用 GIF 图片代替 PNG
半透明的 HTML 覆盖
一种非常常见的图形设计技术是在照片或其他图像上覆盖一个部分透明的区域,通常包含文本。这使得文本可读,而不会完全模糊下面的图像。设计师 Wilson Miner ( www.wilsonminer.com
)在 Gingeroot 珠宝网站(www.simplygingeroot.com
)上很好地利用了这一点,你可以看到图 5-5") 。
图 5.5。www.simplygingeroot.com
,由天才的威尔逊·迈纳(www.wilsonminer.com
) 设计
Wilson 将他的透明区域和文本包含在 JPEG 图像中。他事先用 Photoshop 制作了它们。这很好,并且完全适合网站的需求。但是,如果半透明区域的文本需要经常改变,甚至对每个访问者都不一样,那该怎么办呢?在这种情况下,将文本放在图像中是不实际的。该文本将需要在 HTML 和 CSS 精心制作。使用 PNG 的透明 alpha 通道,我们可以模仿 Wilson 的风格,而无需将文本放入图像本身。
我将从我女儿 Haley Madysan 的一张照片开始,并将其放入一个简单的 XHTML 页面中,使用一些基本的 CSS 样式(这是代码下载中的haley_example/index.html
)。注意,我使用嵌入的 CSS 样式表只是为了演示。在现实世界中,使用链接的外部样式表通常会提供更大的灵活性、更少的代码重复和更实用的文件管理。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Haley's web site</title>
<style>
body {
font-family: "Lucida Grande", Helvetica, Arial, sans-serif;
background-color: #304251;
color: #304251;
margin: 20px auto;
width: 720px;
}
#feature {
position: relative;
width: 720px;
height: 439px;
}
#feature-content {
position: absolute;
bottom: 0;
left: 0;
height: 125px;
width: 720px;
background-color: #dfdfdf;
}
#feature-content h1 {
margin: 0;
padding: 0;
line-height: 125px;
padding: 0 30px;
font-weight: normal;
font-size: 2.3em;
}
#feature-content a {
float: right;
font-size: .6em;
color: #fff;
text-decoration: none;
text-transform: uppercase;
}
</style>
</head>
<body>
<div id="feature">
<img src="haley.jpg" alt="Haley Madysan Croft" />
<div id="feature-content">
<h1>Sweet. Smart. Beautiful. <a href="/haley"
title="Haley Madysan Croft">Learn more </a></h1>
</div>
</div>
</body>
</html>
这样,我或多或少复制了你在 Wilson 的 Gingeroot 网站上看到的,除了没有任何透明度,如图 5-6 所示。
图 5.6。模仿 HTML 和 CSS 的www.simplygingeroot.com
风格,但是没有透明度
现在我将在 Photoshop 中创建一个 1×1 像素的图像。我用浅蓝色填充图像,设置图层透明度为 70%。最后,我使用 Photoshop 的 PNG-24 设置保存图像,启用透明度。然后我简单地使用这张图片作为叠加的背景,而不是你在图 5-6") 中看到的纯灰色。
#feature-content {
position: absolute;
bottom: 0;
left: 0;
height: 125px;
width: 720px;
background-image: url('transparent.png');
}
结果与原始的非常相似,但是有了 HTML 和 CSS 文本,它变得更加灵活,如图 5-7 所示。
图 5.7。通过 PNG 图像格式添加透明度几乎复制了www.simplygingeroot.com
样式。
Wilson Miner 实际上在www.simplygingeroot.com
现场的不同区域使用了相同的概念。在展示可用产品的页面上,一个透明的 PNG 图像被用来在销售项目产品照片的左上角显示一个On Sale
标志,如图图 5-8 所示。通过一次性创建On Sale
图像,并使用透明背景将其保存为 PNG 图像,Wilson 避免了为每个嵌入旗帜的产品图像创建单独版本的需要。
图 5.8。www.simplygingeroot.com
的项链部分使用 PNG 图像,透明背景覆盖在产品图像上,在左上角显示一个On Sale
标志。
我还在探索汽船(www.exploresteamboat.com
)上使用了这种技术,这是一个致力于科罗拉多州汽船泉事件、娱乐和活动的网站,如图 5-9 所示。
图 5.9。通过透明 PNG 的方式在图像上放置一个半透明的盒子。
在另一个创意例子中,设计师布莱恩·贝洛索(www.avalonstar.com
)使用了一个固定在页面底部的透明 PNG 图像来创建一种“淡入”效果,当你向下滚动页面时,文本似乎凭空出现。在www.revyver.com
(见图 5-10 和图 5-11 )发现的效果比描述的要好,所以一定要亲自检查一下。此外,树图形位于页面文本内容的前面,产生了意想不到的视觉效果。当你第一次看到它时,它有一种令人惊叹的因素。
图 5.10。在www.revyver.com
,设计师布莱恩·贝洛索使用了一种透明的 PNG 来创建一种当你向下滚动页面时的“淡入”效果,并将他的作品放在页面文本内容的前面。
图 5.11。通过在 Photoshop 中查看 Bryan 的页脚 PNG 图像,我们可以了解如何构建透明 alpha 通道来实现设计的效果。
水印
另一种常见的图形设计技术是在图像上叠加微妙的水印。这可能纯粹是为了视觉风格,但也可能是为了表明图像的版权所有者或来源。
在我的个人网站上(www.jeffcroft.com
,见图 5-12 ,我展示了我拍摄的大量照片(目前超过 2000 张)。这些照片实际上是上传到 Flickr ( www.flickr.com
),流行的在线照片分享网站,然后通过 Flickr 的 open API 在我的网站上本地显示。
图 5.12。www.jeffcroft.com
上的照片详情页
如果我想把我的个人标志放在我所有的照片上呢?是的,可以在 Photoshop 中打开每个图像,应用徽标,然后重新保存图像。然而,当处理成千上万的频繁更新的图片时,这变得非常不切实际,有时当我不在电脑旁边时(例如,当我通过手机向 Flickr 发送照片时)。如果 logo 是自动添加的不是很好吗?巴布亚新几内亚可以帮助做到这一点。
用于在页面中显示照片的 HTML 如下所示:
<a class="photo-container" href="http://www.flickr.com/photos/jcroft/2622915/">
<img class="full-size-photo" src=http://static.flickr.com/2/2622915_8b78c1207d.jpg
alt="CTA, a photo by Jeff Croft" />
</a>
我创建了一个 80×80 像素的白色版本,然后在 Photoshop 中将不透明度设置为 15%。使用 Photoshop 的标准 PNG-24 优化设置保存,15%的半透明性会保留在生成的 PNG 图像中。然后,我简单地将该图像添加到我的 HTML 中:
<a class="photo-container" href="http://www.flickr.com/photos/jcroft/2622915/">
<img class="full-size-photo" src="http://static.flickr.com/2/2622915_8b78c1207d.jpg"
alt="CTA, a photo by Jeff Croft " />
<img class="watermark" src="http://media.jeffcroft.com/img/jeffcroft_logo_watermark.png"
alt="Watermark" />
</a>
然后使用一点 CSS 将其定位在正确的位置:
a.photo-container {
position: relative;
display: block;
}
img.watermark {
position: absolute;
top: 2em;
left: 1em;
}
结果是一个水印看起来像是嵌入在照片本身,但实际上是一个独立的 PNG 图像位于它的上面,如图图 5-13 所示。通过把它放入我的内容管理系统的模板中,我不用做 2000 多次就可以在每张图片上添加水印。
图 5.13。微妙的www.jeffcroft.com
徽标标记通过透明 PNG 出现在照片的左上角。
如果您想变得更聪明,您甚至可以使用 DOM 脚本来动态插入水印的附加(X)HTML 标记。
面具
PNG 图像和它的 alpha 通道透明度的另一个方便的用途是遮罩它下面的图像。从技术上来说,这与你刚才看到的水印非常相似,但它实现了不同的视觉效果。
这一次,我将在 Photoshop 中制作一个更大版本的徽标。我不把 logo 做成白色,而是把它做成透明的,图像的其余部分是白色,因为白色是页面的背景色,如图图 5-14 所示。
图 5.14。在 Photoshop 中创建用作透明 PNG 蒙版的图像
正如我所说的,它的技术方面与上一节中的水印几乎相同,从 HTML 开始:
<a class="photo-container" href="http://www.flickr.com/photos/jcroft/2622915/">
<img class="full-size-photo" src="http://static.flickr.com/2/2622915_8b78c1207d.jpg"
alt="CTA, a photo by Jeff Croft" />
<img class="mask" src="http://media.jeffcroft.com/img/jeffcroft_logo_mask.png"
alt="Mask" />
</a>
然后是 CSS:
a.photo-container {
position: relative;
display: block;
}
img.mask {
position: absolute;
top: 0;
left: 0;
}
最终结果见图 5-15 。
图 5.15。PNG 蒙版放在照片的上面,创造出一种“打孔”的效果。
变色图标
通过使用你在前一个例子中看到的蒙版概念,一些人只用 CSS 就创建了可以改变颜色的图标。这个想法既简单又巧妙:将一个带有图标符号“打孔”的透明图像放在一个正方形、矩形或其他具有单色 CSS 背景的形状上,这样就有了一个图标。通过简单地改变背景的 CSS 颜色,你给人以图标改变颜色的印象。
也许我们需要一组表示常见运动的图标,如图图 5-16 所示。
图 5.16。一些常见的体育图标
我已经用图标符号形状的透明穿孔创建了白色图像,正如我在上一节的蒙版示例中对徽标所做的那样,如图图 5-17 所示。
图 5.17。为每个图标创建“挖空”的 PNG 蒙版
在将图像缩小到合适的尺寸(我选择了 48×48 像素)后,我使用 Photoshop 的 PNG-24 默认设置并启用了透明度来保存它们。然后我创建了一个简单的 XHTML 文件来引用每张图片(代码示例下载包中的sports_icons_example/index.html
):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>OMG Sports Icons!</title>
<style>
img {
background-color: #cc0000;
}
</style>
</head>
<body>
<img src="running.png" alt="Running" />
<img src="biking.png" alt="Biking" />
<img src="skiing.png" alt="Skiing" />
<img src="soccer.png" alt="Soccer" />
</body>
</html>
我已经指定了#cc0000
(一种强烈的红色)作为这个文件中图像的背景色。其结果是红光透过冲压出来的形状,如图图 5-18 showing through the image.") 所示。请注意,由于 PNG 对部分透明像素的支持所允许的抗锯齿,边缘非常平滑。
图 5.18。透明的 PNG 图像作为网络图标。注意 CSS 背景色(红色)透过图像显示出来。
正如你在图 5-19 中看到的,我可以简单地通过改变 CSS 中的颜色值来改变图标图像的颜色:
img {
background-color: #000066;
}
图 5.19。在 CSS 中改变背景颜色会改变图标的外观颜色。
当你需要重新设计你的网站时,这种简单的颜色变化会非常方便。不用重新制作所有的图标图像,你只需要在 CSS 中改变一次颜色。这也是为链接图像实现简单鼠标悬停效果的好方法。例如,您可以使用红色作为标准颜色,蓝色作为悬停样式(代码示例下载包中的sports_icons_example/index_links.html
):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>OMG Sports Icons!</title>
<style>
a:link img,
a:visited img,
a:active img {
background-color: #cc0000;
}
a:hover img {
background-color: #000066;
}
</style>
</head>
<body>
<a href="/running/"><img src="running.png" alt="Running" /></a>
<a href="/biking/"><img src="biking.png" alt="Biking" /></a>
<a href="/skiing/"><img src="skiing.png" alt="Skiing" /></a>
<a href="/soccer/"><img src="soccer.png" alt="Soccer" /></a>
</body>
</html>
你可以使用额外的 CSS 来做更多有创意的事情,比如添加一个边框(见图 5-20 ):
img {
background-color: #000066;
padding: 2px;
}
图 5.20。使用 CSS 通过添加边框进一步增强图标的外观
丹·塞德霍尔姆(www.simplebits.com
)早在 2003 年(www.simplebits.com/notebook/2003/07/24/magic_icons_for_lazy_people_like_me.html
)就在他的博客中写过一个非常类似的技术,PJ·小野里(www.somerandomdude.net
)在他的网站(www.somerandomdude.net/srd-projects/sanscons/
)上提供了一组基于它的图标(称为 Sanscons)。
然而,我通过使用一个带有穿孔符号的白色图像颠倒了他们的概念。他们在透明的背景上使用了一个白色的符号,这也非常有效。在这种情况下,您的 CSS 背景颜色会作为围绕符号的正方形或矩形而发光,而不是作为符号本身发光。
丹和 PJ 都使用透明的 gif 文件而不是 png 文件。这完全符合他们的需求,因为他们创建的图标风格是位图,看起来像小精灵。通过使用 png,您可以应用相同的技术,但是要利用抗锯齿边缘和部分透明来用于更详细的图标。
好的,但是它能在什么浏览器上工作呢?
我知道你在想什么:所有这些 PNG 透明的东西看起来真的很好,但是它实际吗?
好消息是,几乎所有现代浏览器都完全支持 PNG 图像,包括我在示例中充分利用的 alpha 通道透明度。Safari(所有版本)、Firefox(所有版本)、Opera(6 版及更高版本)、Netscape(6 版及更高版本)和 Mozilla(所有版本)都会很乐意做我要求它做的一切。坏消息是,我没有提到的浏览器是你的用户最有可能使用的:Internet Explorer。
Internet Explorer 6 和更低版本不支持 PNG 格式内置的 alpha 通道透明度。由于这几年来它一直是绝大多数网络冲浪者的选择(或非选择)浏览器,这个明显的漏洞让许多网页设计师远离 PNG。但是,随着 Internet Explorer 7 的发布,我们终于在所有主流浏览器中完全支持 PNG alpha 透明。此外,还有一些方法可以让 alpha 透明的 png 在 Internet Explorer 6 及更低版本中工作。所以,如果你想使用这些效果,没有什么可以阻止你。Internet Explorer 6 和它的老版本比它应该做的工作多了一点,但这是完全可能的。
Internet Explorer 变通方法:AlphaImageLoader
Internet Explorer 包括几个专有的过滤器。它们在 CSS 中使用,但是它们不是任何官方 CSS 规范的一部分。换句话说,它们不是网络标准。不幸的是,Internet Explorer 6 和更低版本不完全支持 PNG 图像格式(W3C 推荐),但微软确实包括了一个克服这一弱点的过滤器:AlphaImageLoader。
根据微软关于该主题的官方网页(http://msdn.microsoft.com/workshop/author/filter/reference/filters/alphaimageloader.asp
),AlphaImageLoader“在对象的边界内以及对象背景和内容之间显示图像。”换句话说,AlphaImageLoader 确实加载了 PNG 图像的全部透明度,但它是作为自己的层加载的,位于它所应用到的对象的内容之下。以这种方式加载的 PNG 图像的行为类似于背景图像,而不是前景图像(尽管它们实际上位于对象的背景之上)。
简而言之,您可以简单地将 AlphaImageLoader CSS 应用于一个img
元素,并让它产生想要的结果。这样做将加载图像,透明度保持不变,但也会将图像作为没有透明度的对象的前景内容再次加载(从而模糊您的透明版本)。
你不能使用一个透明的 PNG 作为一个(X)HTML 元素(比如一个<div>
)的 CSS 背景图像,并期望 AlphaImageLoader 过滤器使它在 Internet Explorer 中像预期的那样工作。记住 AlphaImageLoader 在对象的背景和前景之间插入图像。所以,虽然它会加载你的透明图片,但它还是会加载你的 CSS 背景图片,没有你漂亮的半透明像素。
AlphaImageLoader 的实际应用
让我们回到前面的一个例子,试着让 Internet Explorer 正确地加载图像。还记得托皮卡的电视台 49 频道吗?你当然知道。图 5-21 显示了网站在 Internet Explorer 6 中的样子。
图 5.21。www.49abcnews.com
页眉,在 Windows 的 Internet Explorer 6 中显示,PNG 透明度保持不变
标题的天气部分的 HTML 看起来像你所期望的那样:
<div id="weather">
<a href="/weather/"><img id="weatherImage"
src="http://media.49abcnews.com/img/overcast-night.png"
alt="Overcast" /></a>
<p><a href="/weather/">Currently in Topeka, KS:<br />
<strong>82 Overcast</strong><br /><span>Get the forecast
and more...</span></a></p>
</div>
你可以看到问题中的图像是一个 PNG 文件,但是 Internet Explorer 可以完美地加载它。这里的秘密成分是 JavaScript。我实际上使用了一点 DOM 脚本来动态移除img
元素,并用一个div
元素来替换它,您可以猜到它是 AlphaImageLoader CSS 应用于它的。JavaScript 在条件注释中被引用,这是微软在 ie 浏览器中内置的另一个方便但完全不标准的习惯用法。条件注释使您可以针对特定版本的 Internet Explorer 编写代码。所有其他浏览器都会忽略这些代码,所以不会影响它们。在www.49abcnews.com
的<head>
元素中,你会发现:
<!--[if lte IE 6]>
<script src="http://media.49abcnews.com/js/fixWeatherPng.js" type="text/javascript"></script>
<![endif]-->
由于第一行是if lte IE6
,只有在低于或等于(这就是lte
所代表的)6 的 Internet Explorer 版本显示时,这个脚本元素才会包含在呈现的文档中。其他所有浏览器,包括即将发布的 Internet Explorer 7,都将完全忽略它。
那么 JavaScript 文件fixWeatherPng.js
中到底有什么呢?看一看:
window.attachEvent("onload", fixWeatherPng);
function fixWeatherPng() {
var img = document.getElementById("weatherImage");
var src = img.src;
img.style.visibility = "hidden";
var div = document.createElement("DIV");
div.style.filter = "progid:DXImageTransform.Microsoft.
AlphaImageLoader(src='" + src + "', sizing='scale')";
*// Some 49abcnews.com-specific CSS styling omitted for brevity.*
img.replaceNode(div);
}
我们来一步步分析这个脚本在做什么。首先,我们告诉浏览器,我们希望在页面加载时运行fixWeatherPng
函数。脚本的其余部分是函数本身。
首先,我们通过图像的id
属性找到要处理的图像,并将其存储在一个名为img
的变量中。我们将src
属性(图像文件的 URL)保存在一个名为src
的变量中。然后,我们通过将visibility
CSS 属性设置为hidden
来隐藏img
元素。
接下来,我们创建一个新的div
元素,并将其存储在一个名为div
的变量中。我们将使用之前保存的src
变量中的 URL,对其应用 AlphaImageLoader 过滤器。
最后,我们用新创建的div
元素替换原来的img
元素(它是隐藏的),这个元素附加了 AlphaImageLoader 的优点。
使用 DOM 脚本动态插入经过 AlphaImageLoader 过滤的位有一个好处,就是可以将不必要的无效 CSS 排除在 CSS 文件之外。同样,它将无意义的div
元素排除在(X)HTML 标记之外。由于所有这些都只在条件注释中引用,所以其他浏览器不可能被微软的专有代码所阻塞。
如果你一定要做一些无效的事情,至少你可以把它抽象出来,和所有不需要它的东西隔离开来。
结论
PNG 作为一种图像文件格式,比广泛使用的 GIF 提供了许多技术优势。事实上,它的优势如此之大,以至于 PNG 早就应该成为所有非照片图像的首选文件格式了。但是,Internet Explorer 缺乏对 PNG 的一些更令人兴奋的功能的适当支持,如 alpha 通道透明性,导致许多 web 开发人员回避它。但是有两个很好的理由让你不应该害怕巴布亚新几内亚。
首先,即使 Internet Explorer 6 和早期版本对 PNG 的支持并不完美,但仍然提供了 GIF 的所有功能(当然,除了动画)。PNG 几乎总是产生更小的文件,以实现更快的传输和更少的带宽使用。
第二,Internet Explorer 7 完全支持 PNG 的 alpha 通道透明性。不透明度选项的完整范围可以实现的效果实际上是无穷无尽的。我怀疑那些找到使用透明 png 的有趣方法的设计师们,比如在这一章中概述的那些,将会打开一扇全新的风格之门,这种风格迄今为止还没有在网上广泛出现。我已经给了你一些关于如何用 PNGs 和透明度来获得创造性的想法,但是不要就此打住。也找到你自己的!
六、网格设计
马克·博尔顿是一名来自英国加的夫的印刷设计师。他曾在悉尼、伦敦和曼彻斯特的设计机构担任艺术总监,为 BBC、T-Mobile 和英国航空公司等客户服务。马克在 BBC 做了四年的高级设计师,设计网站和网络应用程序,之后他创办了自己的设计咨询公司马克·博尔顿设计公司。
他是国际排版设计师协会的活跃成员,并在www.markboulton.co.uk
撰写设计期刊。
什么是网格系统?
网格系统是一个框架。这是一个用来创作作品的系统。放眼望去,你可以看到网格系统:城市街区、杂志、报纸、建筑立面。这是因为我们人类喜欢组织事物,而且,在许多西方文化中,当它们被组织成直线时,我们就能理解它们。因为网格系统,我们知道下一步看什么,下一步按什么或点击什么,下一步做什么。但是网格系统不仅仅是功能性的;他们可以很美。
一个网格系统可以用黄金分割来设计,这个比例几个世纪以来一直与美感联系在一起。如果某样东西被认为是美丽的,那么根据美学可用性效果,它也更有用。
注意
关于黄金分割的更多信息,请看http://en.wikipedia.org/wiki/Golden_section
。
网格系统在设计过程中非常重要。它们和排版一起决定了信息的视觉组织。
在这一章中,我将向你展示我是如何为最近的一个项目设计一个简单的网格系统的。不过,在我开始之前,我觉得有必要简要概述一下网格系统的设计,网格系统是如何产生的,以及它们在过去几十年中所经历的变化。
千古以来
没有人真正知道网格系统起源于哪里。有些人会争辩说,自从人类开始创作艺术以来,它们就一直存在。另一些人会说,当人类发展出书面语言时,它们就开始了。然而,我认为它们开始于设计师有意识地开发它们来解决构图问题的时候。我认为,这始于至少从中世纪就开始使用的维拉德图。
在第二次世界大战之前,网格系统是非常公式化的,简单明了的事情围绕着设备的比例建造的矩形结构,如维拉德图。它们往往受到生产技术的限制,有时会出现被图像打断的文本列。那个时代的网格系统很少使用空白作为设计手段,更不用说多种字体了。直到一些著名的设计师出现,包括挑战当时设计传统的约瑟夫·穆勒-布罗克曼。他们提出了一个新系统:一个更灵活的网格,有更多的工具供设计者使用,称为模块化网格。
这些设计师思维的重要性不应该被低估。我们所理解的现代印刷术和网格系统设计的大部分都来自于他们。从杂志设计(见图 6-1 )到网站(见图 6-2 )的平面设计中,可以看到他们工作的证据。
图 6.1。杂志版面,即使像这样复杂的版面,也使用网格系统来帮助设计者编排页面。
图 6.2。Khoi Vinh 的Subtraction.com
是少数几个使用网格系统取得巨大成效的网站之一。
比率和画布
比率是任何设计良好的网格系统的核心。那些比例有时候是有理的,比如 1:2 或者 2:3,有时候是无理的,比如 1:1.618(黄金分割比例)。设计网格系统的挑战是使用这些比率来创建和谐的组合。
使用我们选择的度量将比率应用于网格系统。然而,我们在网络上无法控制的一件事是网格位于浏览器窗口的画布上。
印刷设计的画布大小由媒体大小决定,无论是纸张、标牌、信封还是其他。Web 上网格设计的画布大小通常由浏览器窗口大小决定,而浏览器窗口大小又由用户的屏幕分辨率决定。这些都不是固定的。考虑到这种灵活性,设计师应该按照最低要求进行设计,这通常是大多数用户的平均屏幕分辨率。
我不会在这里引用数字,因为我可能会错,但在相当长的一段时间里,设计的屏幕分辨率一直是 800 600 像素。
随着 List Apart(见图 6-3 )和 Stylegala 等网站的重新推出,关于 1024 像素固定宽度网格的讨论又开始了。就实际的网格设计而言,画布的大小真的无关紧要。决定采用 1024 像素的应该是对用户屏幕分辨率的研究。如果某个网站的用户群显示使用该尺寸或以上的分辨率,那么使用该尺寸进行设计的决定是有效的。??
图 6.3。Jason Santa Maria 设计的 List Apart 使用了比标准宽度更宽的宽度。这为您使用网格生成的布局提供了更大的灵活性。
然而,正如一些人注意到的,即使你运行的分辨率高于 800 600,这是否意味着你的浏览器窗口占据了整个屏幕?我们不知道。我个人认为,这不仅是特定于平台的,还取决于个人及其经验水平。也许 PC 上更有经验的用户不会全屏使用浏览器。根据我对各种各样的人进行用户测试的经验,我看到许多 PC 上的新手用户以全屏方式运行浏览器,因为这是默认的;在 Mac 上,默认不是全屏幕。
现在您已经对网格系统有了一些了解,让我们看一个实际的例子。
将网格系统付诸实践
2005 年春天,国际文凭组织(IBO)找到我,让我设计一个内部网应用程序和一个他们自己开发的新的基于 web 的应用程序。选择我作为他们的设计师的令人兴奋的原因部分是因为这个项目需要使用 Web 标准来构建。
IBO 是国际教育领域公认的领导者。IBO 目前与 122 个国家的 1,785 所学校合作,为超过 200,000 名 3 至 19 岁的学生开发和提供课程计划。为了教很多学生,必须有很多老师,所有这些老师都需要访问文档资料。
几年前,IBO 开发了一个名为在线课程中心(OCC)的教师内部网,IBO 教师可以在这里访问和共享文档。在接下来的几年里,OCC 在用户和功能方面都有所增长,直到它变得明显需要重新设计。从一开始,IBO 就想要新的设计一些新鲜的、现代的、实用的东西。除了 OCC,我还被要求为 IBO 员工设计一个新的在线应用程序,名为“车间资源中心(WRC)”。这是一个复杂的应用程序,车间领导(IBO 有带教师的开发车间)可以从库中选择文档,然后将它们转换为 pdf 进行打印。它的工作方式有点像电子商务系统,将文档添加到用户的“购物车”中,然后签出进行转换和存储。
正如我所说的,这两个项目都必须使用 Web 标准构建,选择符合标准的浏览器,谢天谢地,没有包括 Internet Explorer 5!
注意
Internet Explorer 5 可能是一个挑战,正如你将在本书的其他部分看到的,包括 Ethan Marcotte 的第三章。Andy Budd 等人(编辑之友,ISBN:1-59059-614-5)在《掌握 CSS》中也对浏览器支持问题进行了很好的讨论。
在查看了简介之后,很明显这个项目的大部分内容都是印刷的。需要整合各种各样的内容类型,其中许多内容类型仍有待创建。因此,排版结构需要清晰简洁。此外,信息需要以四种语言呈现:英语、法语、西班牙语和中文。
将所有这些字体显示在屏幕上,不仅需要强大的排版层次,还需要强大的网格。
在我动笔或写代码之前,我关注的是用户需求和功能。一旦 IBO 同意了这些,每个人都很高兴,我们就从之前制作的线框开始进行视觉设计。
从笔开始
我喜欢用纸和笔开始每一个设计。这种方法有几个优点。绘图快速、便宜且容易。如果有什么不行,可以把那张纸扔掉,重新开始。你可以乱涂乱画,直到有东西能用为止。此外,与坐在电脑屏幕前相比,使用钢笔和墨水是一个更加有机的过程,可以让想法更快地流动。简而言之,既省时又省钱,还能让你远离电脑!
制作缩略图草图对于处理设计范围至关重要。在你花时间在电脑上之前,内容、功能和技术要求都可以先写在纸上。参见图 6-4 中我的初步草图示例。
图 6.4。在你接近电脑屏幕之前进行设计不仅省钱,还能保护你的眼睛!
分解元素
在与 IBO 合作了一段时间,并研究了该组织提供的现有网站和应用程序后,我们决定采用一种框架设计方法,这种方法可以应用于各种各样的模板。逐页设计这样规模的项目是不合理的。
我们进行的过程类似于传统的网页设计过程:简要,发现,线框,设计,生产,代码。然而,我在 IBO 中扮演了更多的伙伴角色。这帮助我彻底理解了组织的业务目标和用户的目标。所以当涉及到设计时,就容易多了。
一个重要的任务是分解网站的内容对象。现在,这可能看起来像是技术、信息架构,甚至是项目经理的任务。我实际上将内容分析视为设计师任务的一部分。由于这个框架将主要是排版和网格结构,将内容分解到微观层次对于理解它们之间的关系非常重要。我通过识别设计中的元素开始了这个过程。这些可以分解为宏观元素和微观元素。
宏元素
宏观元素是设计中的大东西,是所有东西都适合的结构。网格可能位于一个宏元素之上,因为其他元素可以放入其中。这是我为这个项目列出的宏元素列表(见图 6-5 )。
-
列
-
桅顶
-
主导航
-
页脚
微量元素
微元素是构成内容其余部分的比特。它们是视觉元素,需要单独考虑,但也需要在更广泛的设计系统中考虑。以下是 IBO 项目的微观要素(见图 6-6 ):
-
框出(侧边栏元素)
-
状态框
-
标题
-
列表(有序、无序和定义)
-
桌子
-
链接
-
段落
-
形象
图 6.5。可以先绘制宏元素(如柱)的草图。
图 6.6。微距元素位于微距元素的内部和周围,构成了页面的内容。
你大概可以看到这是怎么回事。这开始看起来像一个 CSS 文件的结构。
设计柱子
开发的线框非常复杂。有很多信息需要用户快速理解,其中大部分是排版信息。屏幕上会有很多单词。因此,显示这种类型的网格需要仔细考虑。
遵循我在本章前面讨论过的一些基本原则,我开始绘制较大的网格元素,即柱。三列布局是 Web 上一种流行的网格配置,这也许是有充分理由的。
三分法是一个合成法则。事实上,它更像是一个指南,主要由摄影师使用,但可以应用于平面设计。三分法则指出,将空间水平分成三份(如果你使用图像,也可以垂直分成三份),并使你的设计与这些线条对齐,可以创造更多的能量和张力。它帮助设计者从创造有趣的作品中去掉一些猜测。
根据三分法,我创建了一个六列网格。图 6-7 显示了我的笔墨渲染,图 6-8 显示了我在 Photoshop 中创建的版本。
您可能会注意到,在这一点上,我没有提到任何关于屏幕分辨率或浏览器窗口宽度的具体内容。我喜欢在最初的网格设计中保留这些,因为我在这个过程的这一点上处理比率和比例。在深入生产的本质之前,我不喜欢关注绝对的测量。
然后,可以将这些列划分为多个布局配置,如图图 6-9 所示。正如你所看到的,这里的布局选项对于大多数网站和应用程序来说是足够多样的。
图 6.7。最终六列网格草图
图 6.8。显示色谱柱配置和尺寸的最终表格
图 6.9。从六列网格中可以获得许多成分配置。此处显示的示例不仅说明了不同的列配置,还显示了这些列的相对视觉重点。
添加装订线、边距和填充
你可能已经注意到在我最初的网格设计中没有水槽。
檐槽是柱子之间的缝隙。它们在那里,所以不同列的文本或图像不会碰到一起。在网格系统设计中,有时(取决于你阅读的理论),檐槽与柱子是分开的。由于我们构建列的方式,这给我们在 Web 上设计网格系统带来了实际问题。
通常,但不总是,我们使用 Web 标准创建的列是div
s,它们使用 CSS 给定宽度、位置和样式。因此,理想情况下,我们不希望为檐槽创建单独的列。因此,我们将檐槽作为列的部分(参见图 6-10 ),并且它们是通过填充或者创建边距来实现的,用于放置在列内的元素或者有时是列本身。
图 6.10。本例中的檐槽是柱的一部分,而不是位于柱之间。
我总是选择向列中的元素添加填充。这可能意味着当我写样式表时,我最终会为自己创造更多的工作。我可以简单地添加一个空白。但是这样做可以确保当我想让元素相互碰撞时,我可以做到。这也避免了 Internet Explorer 的盒子模型问题,但由于在这个设计中它不是一个受支持的浏览器,我不必太担心这个问题。
所以,我有我的专栏全部六个。现在我可以开始添加水平元素,如页脚和报头。但在此之前,我需要解决一个重要的排版测量问题。
对于这个项目的构建,要求用户能够选择固定或灵活的布局。在固定布局中,列将由像素测量来确定,因此是固定的。在灵活布局中,百分比将用于列,而 ems 将用于排版。
em 是一种印刷度量单位,等于字体的宽度。所以,如果字体设置为 12px,那么 1em(宽度)就是 12px。当使用 em 作为度量单位时,我们遇到的问题是大多数浏览器的内部样式表将 1em 渲染为大约 16px。这本身是好的,但它使使用倍数和除法比它需要的更困难(除非,当然,你对 16 乘法表很在行)。为了解决这个问题,我将默认的 16px 减少到 10px(见图 6-11 )。我通过向 CSS 文件中的 body 标记添加声明来实现这一点:
body {
font-size: 62.5%;
}
注意
Richard Rutter 在 2004 年写了一篇名为“如何在 ems 中调整文本大小”的文章,详细介绍了这种技术。
图 6.11。10px 的装订线现在变成了 1em,因为我已经将正文的字体大小定义为 62.5%。
这使得 1em 大约为 10px (16 除以 100 乘以 62.5 等于 10),这是一个很容易处理的度量单位。现在我们可以把像素大小等同于 ems。例如,设置为 14px 的文字可以表示为 1.4em,9px 的文本可以表示为 0.9em,依此类推。
当我将它添加到 body 标记中时,我还添加了全局空白重置,它删除了所有浏览器默认的边距和填充。
{ margin: 0; padding: 0;}
注意
全局空白重置是由 Andrew Krespanis 在 2004 年 10 月(http://leftjustified.net/journal/2004/10/19/global-ws-reset/
)的www.leftjustified.net
首次开发的。
通过使用 CSS 通用选择器*
,我能够选择 HTML 文档中的所有元素,然后移除所有默认的填充和边距。
如果你想要更严格的样式,你可以使用雅虎的全局重置样式表,它可以移除浏览器中几乎所有的默认样式。这给了你一个完全空白的画布来开始你的工作。参见http://developer.yahoo.com/yui/reset/
了解更多关于该样式表的信息。
颜色和其他视觉元素呢?
这个项目的所有颜色和图形元素都由另一个单独的样式表控制。这是因为网格、版式和许多元素的基本样式(比如无序列表、定义列表和图像)需要在整个站点中保持不变,因为整个站点有两种不同的配色方案。很简单,用灰色设计,简单地根据网站覆盖颜色。
向容器div
中添加一个类来控制各种布局选项。这个三列布局有一个类c1-c2-c3
,但是布局选项是为各种各样的布局创建的,比如c1
、c1-c2
、c1-c2-c3-c4-c5
等等。这个命名约定告诉我两件事:六列网格中使用的列数以及文档顺序。例如,c1-c2-c3
可能看起来和c2-c1-c3
一样,但是文档顺序会不同:带有c2
的id
的<div>
会出现在c1
和c3
之前。
总而言之,OCC 有六种不同的样式表:
-
全局样式表
-
IBO 样式表,控制所有顶级颜色
-
用户可配置的布局样式表
-
一个排版样式表,它的元素是用户可配置的
-
颜色样式表,控制整体颜色
-
出于辅助目的的替代缩放布局
这被证明是该设计最具挑战性的方面之一。
构建 XHTML
一旦我有了网格的想法,我就开始考虑如何使用 Web 标准来构建它。首先,我构建了一个粗略的 XHTML 模板,开始开发 CSS。
注意
在任何多栏布局中,一个重要的考虑因素是文档顺序。在这一阶段,你应该试着把网格的外观放在一边,把注意力集中在文档的内容流上。先获取内容;稍后添加 CSS。
所以,我开始添加线框中规定的各种元素。一、实用程序菜单:
<!-- BEGIN #utilities -->
<div id="utilities">
<ul id="accessibility">
<li><a href="#">High contrast layout</a></li>
</ul>
<ul id="services">
<li><a href="#">IBO</a></li>
<li><a href="#">OCC</a></li>
<li><a href="#">IBIS</a></li>
<li><a href="#">WRC</a></li>
<li><a href="#">Log out</a></li>
</ul>
<!-- END #utilities -->
</div>
然后是刊头:
<!-- BEGIN #masthead -->
<div id="masthead">
<!-- branding -->
<div id="branding">
<h1><a href="#">International Baccalaureate Organization</a></h1>
<h2><a href="#" title="The IBO's Online Curriculum Centre
Homepage" accesskey="1">online curriculum centre</a></h2>
</div>
<ul id="language">
<li><a href="#">English</a></li>
<li><a href="#">Francais</a></li>
<li><a href="#">Espanol</a></li>
<li><a href="#">Chinese</a></li>
</ul>
<!-- END # masthead -->
</div>
报头包括几个重要的元素:组织的名称、您所在的服务(或站点)以及语言切换(这两个站点有四种语言版本)。
我尽量避免使用在意义上是表象的类名。例如,我可以将刊头命名为topbar
、topnavigation
或类似的名称,但这意味着一个表示性的位置,所以,masthead
就是这样。
一旦我有了这两个元素,我需要将它们包装在一个容器div
中,我将使用这个容器来定义网格的宽度,包括固定模式和灵活模式。
<div id="container">
然后,我可以在报头后面添加列。出于演示的目的,我在这里添加了虚拟内容。
<!-- BEGIN #c1 -->
<div id="c1">
<p>Some content goes here</p>
<!-- END #c1 -->
</div>
<!-- BEGIN #c2 -->
<div id="c2">
<p>Some content goes here</p>
<!-- END #c2 -->
</div>
<!-- BEGIN #c3 -->
<div id="c3">
<p>Some content goes here</p>
<!-- END #c3 -->
</div>
我这里有三列,分别是从c1
到c3
的id
。也许,从语义上来说,这些不是列的最佳名称,但是在命名约定中不要太抽象,我认为这是说明这一点的最佳选择。此外,出于开发目的,它们很容易记住,这在六列网格有数百种布局可能性时非常重要。
现在我有了 HTML 结构的基本框架。我有一个刊头和三个栏目。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<title>IBO: OCC logged in home page</title>
</head>
<body>
<!-- BEGIN #utilities -->
<div id="utilities">
<ul id="accessibility">
<li><a href="#">High contrast layout</a></li>
</ul>
<ul id="services">
<li><a href="#">IBO</a></li>
<li><a href="#">OCC</a></li>
<li><a href="#">IBIS</a></li>
<li><a href="#">WRC</a></li>
<li><a href="#">Log out</a></li>
</ul>
<!-- END #utilities -->
</div>
<!-- BEGIN #masthead -->
<div id="masthead">
<!-- branding -->
<div id="branding">
<h1><a href="#">International Baccalaureate Organization</a></h1>
<h2><a href="#" title="The IBO's Online Curriculum Centre
Homepage" accesskey="1">online curriculum centre</a></h2>
</div>
<ul id="language">
<li><a href="#">English</a></li>
<li><a href="#">Francais</a></li>
<li><a href="#">Espanol</a></li>
<li><a href="#">Chinese</a></li>
</ul>
<!-- END # masthead -->
</div>
</body>
</html>
图 6-12 显示了此时的 XHTML。现在让我们继续使用 CSS 进行样式化和布局。
图 6.12。XHTML。目前还没有应用 CSS,但是这个阶段对于显示文档顺序非常重要。
构建 CSS
我现在正处于网格发展的阶段,我需要移动东西。我总是发现首先开发一个基于固定像素的网格更容易,以确保我所有的比例都正确,并且构图正常。我在这方面使用的一个小技巧是由 Khoi Vinh 在他的优秀网站www.subtraction.com
上首次描述的。还记得我在 Photoshop 里做的网格图(图 6-8 )吗?在网格的整个开发过程中,我导出网格并将其作为背景添加到容器div
中。
#container {
background: url(img/grid_background.gif);
}
我现在有了一个网格的可视化表示(见图 6-13 )来开始对齐元素。网格可能是复杂的野兽,所以拥有一个可以如此容易地从模板中添加和删除的视觉参考对我们设计师来说真是一个好消息。
图 6.13。使用网格作为背景图像允许我将所有元素对齐到它。
设定容器的宽度
我通常从外部元素向内开始构建 CSS。我首先设置容器的宽度div
。
#container {
float: left;
width:750px;
margin:0 auto;
text-align: left;
background: #fff url(grid_background.gif) repeat-y;
}
声明非常简单。我定义了一个 750 像素的全局宽度;这是因为我的列宽是 125px 乘以 6。上下页边距被有效移除,左右页边距被设置为auto
以使div
在浏览器窗口居中(参见图 6-14 )。
图 6.14。网格居中
设置列宽
接下来,我使用浮动来定位列。首先,我向左浮动#c1
和#c2
,向右浮动#c3
。
#c1 {
float: left;
width: 250px;
}
#c2 {
float: left;
width: 250px;
}
#c3 {
float: right;
width: 250px;
}
这会将列定位在背景图像的网格标记上。为了确保这一点,我给每个div
添加了不同深浅的灰色作为背景色(见图 6-15 )。
#c1 {
float: left;
width: 250px;
background: #eee;
}
#c2 {
float: left;
width: 250px;
background: #ccc;
}
#c3 {
float: right;
width: 250px;
background: #999;
}
图 6.15。三列
现在,柱应该与垂直网格线完全对齐。但是报头和所有那些列表呢?嗯,用一点 CSS,我可以删除项目符号,将它们显示成一行,并确保报头的高度与其中的版式相关。
一旦对齐正确,我就移除列中的灰色,为了这个例子的目的,我给列增加一个高度,以显示当div
中有内容时网格将如何延伸。
设计标题和语言菜单的样式
报头只是向左浮动,宽度设为 100%,以确保div
一直延伸。
#masthead {
float:left;
width:100%;
background: #000;
margin: 1em 0 0 0;
}
正如你在 HTML 中看到的,语言菜单只是一个带有language
的id
的<ul>
。首先,我给它一点填充,这样文字就不会碰到浏览器窗口的顶部。这是以像素为单位设置的,不像我所有其他的排版尺寸,都是以 ems 为单位设置的,因为我不希望这个值在文本调整大小时改变。我希望它在右边,所以我将整个列表向右浮动,并给它一个none
的list-style
,以去掉子弹。
#language {
padding: 2px 0 0 0;
float: right;
list-style:none;
}
接下来,我将每个<li>
向左浮动,并在两边添加一些填充。请注意,此填充是以 ems 而不是像素来声明的。这是因为当调整文本大小时,我希望按比例保留这个空间。如果度量单位是像素,那么 Internet Explorer 中的空间会调整大小,但文本不会。最后,我在右边添加了一个边框,以便在视觉上分隔元素。
#language li {
float: left;
padding: 0 1em 0 1em;
border-right: 1px solid #fff;
}
然后我把链接变成白色,以突出黑色。我将用一个单独的颜色样式表覆盖这个默认颜色,但是我总是发现用黑白颜色开始开发更容易。
#language li a {
color: #fff;
}
注意
我发现首先用黑白设计有助于澄清我正在做的设计决定。如果颜色不是问题的一部分,那就把它去掉,下次再考虑。
图 6-16 显示了布局中的报头。
图 6.16。添加报头。现在布局真的成型了。
设计可访问性和服务菜单
我用对待语言菜单的方式来对待可访问性和服务菜单中的列表。我将<li>
元素向左浮动,并给它们填充和一个none
的list-style
:
#accessibility {
float: left;
list-style:none;
padding-top: 15px;
}
#accessibility li {
float: left;
padding: 0 1em 0 1em;
border-right: 1px solid #ccc;
}
#services {
float: right;
list-style:none;
padding-top: 15px;
}
#services li {
float: left;
padding: 0 1em 0 1em;
border-right: 1px solid #ccc;
}
我还在这些列表的顶部添加了一些填充。请再次注意,这是以像素为单位的固定填充。调整文本大小时,我不希望此值改变。
品牌设计
任何网站的品牌都非常重要。在这里,品牌不仅仅是你在哪里,你在做什么服务的标志,它还是一个导航装置。这两个标题将链接到相关网站。网站品牌部分的h1
和h2
组合在一起,作为同类商品的一个分支,一个div
,带有branding
的id
。我浮动左边,给它一些空白和填充,并添加 IBO 标志作为背景图像。
#branding {
float: left;
text-align: left;
margin: 1em 0 0 1em;
padding: 0 0 1em 6em;
background: url(ibo_logo.gif) no-repeat 0 0;
}
右侧填充如此之大的原因是为了容纳 IBO 标志,当我开始添加颜色和圆角时,我会马上添加它。
使用 ems 确定标题的大小。给h1
一个相当于 14px 的值,给h2
一个相当于 18px 的值。
#branding h1 {
font-size: 1.4em;
}
#branding h2 {
font-size: 1.8em;
font-weight: bold;
}
然后我把链接涂成白色。
#branding h1 a,
#branding h2 a {
color: #fff;
}
图 6-17 显示了这些增加的内容。
图 6.17。添加品牌和菜单。在这里,我添加了 IBO 标志作为报头的一部分,和其他菜单一起。
它开始看起来像一个网站
事情现在开始看起来更像一个网站。我坚持我建立的网格,设计开始感觉正确。
现在我已经有了基本的列结构和报头,我添加一些示例内容:标题、链接、段落和无序列表。我仍然是黑白的,所以我可以专注于对齐网格,并确保排版处于良好的工作状态。
接下来,我开始给报头添加颜色(通过一个单独的彩色 CSS 样式表)。
现在谈谈排版。在这个阶段,我从 body 标签上的背景声明中删除了网格图像。我现在不需要它了,因为我知道一切都正常。
最后,我给列中的元素添加了一些边距,以创建一些水平对齐。
设计的问题
这个项目面临许多挑战。这是一次重新设计,所以有遗留的内容,品牌方面的历史,以及所有重新设计带来的一定程度的用户期望。这些都是必须考虑的。
CSS 是一个特殊的挑战。不过,原因可能出乎你的意料。你以为我会说 ie 浏览器有问题,对吗?啊哈!不,那不是问题所在。
作为一名设计师,我很幸运能为一个梦想中的客户工作,他基本上会说,“哦,忽略不符合标准的浏览器。”当然,我问他们是否确定。起初,我不太明白这是为什么,但我很快意识到这是因为在推荐和支持的软件方面,我们面对的受众非常有限。在 IBO 中,Firefox 实际上是推荐的浏览器。当然,对于 IBO 的读者来说,Internet Explorer 仍然是主流浏览器,但是我使用 Firefox 作为主要浏览器来开发设计,以确保代码尽可能符合标准。后来,我将注意力转向了 Internet Explorer 6,并添加了变通方法以确保它也能在该浏览器中工作。很高兴目标浏览器允许我将黑客攻击保持在最低限度。
注意
在这一章中,我没有涉及 CSS 黑客。为什么呢?嗯,我认为它们超出了这里的范围,可能会混淆对构建网格的解释。如果您的设计需要,您可以在中添加 hacks。在这本书里有更多关于黑客的内容,CSS Mastery(ED 之友,ISBN: 1-59059-614-5)包含了关于黑客的全面信息。
正如我所说的,这个项目的挑战是如何管理 CSS。
这个网站的定制选项非常丰富。所有这些选项都将保存到用户配置文件中,这意味着系统必须记住用户保存的字体大小、布局等等。概要规定用户必须能够改变布局(固定或灵活)和字体大小。此外,还有一个为视障人士设计的缩放布局。除此之外,两个站点,OCC 和 WRC,都必须共享相同的元素,比如网格和排版;唯一的区别是颜色。如果所有这些都要使用不同的样式表来解决,这是一个复杂的问题。
克服这些问题并不容易,但是由 IBO 和我自己承担的项目过程对我帮助很大。有规格,线框,用例,和概念所有你在这样规模的项目中期望的文档。然而,这个过程很小,而且与客户很亲密。一次又一次的重复。我想说,一起解决问题是这个项目真正的成功之处。
结论
我在这里创建的是一个简单的网格系统,而不仅仅是列。
对于不经意的观察者来说,这只不过是使用标准 250 像素宽的三列布局。然而,我仔细地构建了网格,考虑到我使用这个六列网格系统的所有组成选项。这允许各种各样的布局选项,重要的是,这些选项相互关联。
为了子孙后代,也为了让你了解新设计的根本不同,我提供了一些截图来显示之前和之后的状态。图 6-18 和 6-19 为 OCC 现场,图 6-20 为 WRC 现场。我肯定你会同意,新网站远远超过旧网站。
图 6.18。前世的 OCC
图 6.19。新的 OCC 设计通篇使用 CSS 布局,并为用户提供改变字体和大小的选项,并为视障用户提供高对比度布局。
图 6.20。新 WRC 与 OCC 共享许多基本样式表。唯一的区别是不同的colour.css
,它覆盖了基础colour.css
。
七、跨越类型鸿沟:CSS 的经典排版
罗布微信
www.robweychert.com
Rob Weychert 是一名平面设计师、艺术家、作家和思想家,以其对细节神经般的一丝不苟而闻名。自 20 世纪 90 年代末以来,Rob 一直为娱乐、旅游、医疗保健、教育、出版和电子商务等不同行业的客户提供印刷和交互式平面设计解决方案。
当他不专心于设计时,Rob 将大部分时间花在了仔细研究音乐和电影,写俳句,丝网印刷,拍照,骑着他的 BMX 在家乡费城的街道上巡游。他也在他的个人网站www.robweychert.com
上写关于这些主题和所有设计的东西。
铅字简史
像所有的艺术一样,它(印刷术)基本上不受进步的影响,尽管它也不能不受变化的影响。
罗伯特·布林赫斯特,
排版风格的要素
印刷术有着丰富而传奇的传统,就像大多数艺术形式一样,它的制作过程进展缓慢。
自 15 世纪古腾堡发明了具有里程碑意义的活字印刷术(一种由金属雕刻而成的单个字母组成的印刷方法)之后,印刷术技术直到工业革命才出现重大变化。随后的照相排字机和 Monotype 机以及(相对短命的)照相排字工艺本质上是同样古老的印刷方法的更快、更有效的模型。然而,麦金塔在 1984 年将字体和设计引入数字时代是一种激进的新方法的培养。然后,不到十年后,就在平面设计师开始习惯于印刷的数字制作工具时,一种全新的媒介被推给了他们。
万维网是一种革命性的获取信息的方式,因此,它需要一种不同的方式来思考设计。然而,随着几个世纪的印刷传统被抛在身后,以及相对较新的计算机的采用,大多数设计师很难考虑除了印刷以外的任何东西,而且 HTML 从未打算适应平面设计也没有帮助。网站的另一半是由计算机爱好者设计的,无论是专业的还是业余的。他们对网络令人兴奋的潜力有了更好的理解,但对视觉设计原理知之甚少或一无所知。
网页设计的早期黑暗时代就这样过去了。语义的、灵活的文档看起来一点也不好,好看的文档一点也不语义或灵活。跨越世纪之交,对标准化 web 技术的更大重视旨在解决这个问题。
CSS 给了设计者设计漂亮网页所需的工具,而不会破坏 HTML 的底层语义结构。以前闻所未闻的印刷控制现在成为可能。但是这个时候,许多设计师已经在网上成熟了,并且忘记了经过时间考验的传统和细致入微的印刷设计惯例。
今天,在网络越来越复杂的初期,它的大部分排版仍然是软绵绵的。
本章旨在通过将印刷排版丰富的视觉传统与网络排版激动人心的动态功能相结合,来帮助阻止这种趋势。我将展示他们可能会在哪里汇合,他们应该在哪里同意不同意,以及仅仅几个简单而微妙的排版技术如何给页面注入新的生命。
认识你的文字面孔
设计者对于在网络上设置字体的第一个抱怨似乎是跨平台可用的字体数量很少。虽然这可能会令人沮丧,但重要的是要记住,选择字体只是排版设计的一个方面。印刷世界中一些最好的印刷工把他们的武器库限制在几个他们通过反复学习和使用而非常熟悉的字体上。即使适用于网络的有限的字体选择确实提供了较少的多样性,它也迫使我们保持同样的良好习惯。幸运的是,一些可用的面孔是为屏幕设计的最好的例子。对这些字体的深刻理解,包括它们的创造者和历史背景,对我们使用它们大有裨益。
介绍格鲁吉亚
如果你怀疑一个漂亮的设计可以用一种普通的字体来实现,我希望这一章能改变你的想法。我要讲的大部分内容将使用 Georgia,这是微软在 1993 年委托著名字体设计师 Matthew Carter 设计的衬线字体。根据一项正在进行的代码风格网站调查(www.codestyle.org/css/font-family/sampler-CombinedResultsFull.shtml
)的最新结果,Georgia 出现在大约 91%的 MAC、84%的 PC 和 53%的 Unix 系统上,使其成为世界上最普遍的衬线字体之一。谢天谢地,这也是一个出色的设计。
乔治亚很像 Times New Roman ( 图 7-1 vs. Georgia (bottom)") ),是仅有的分布较广的衬线字体之一。像泰晤士报新罗马,格鲁吉亚是一个永恒的设计,体现了几个不同的历史时期的审美和实用的特点。然而,就我们的目的而言,乔治亚在屏幕上胜过泰晤士报新罗马,因为那是它被设计来显示的地方。
图 7.1。泰晤士报新罗马(上)对佐治亚(下)
流程
通常,当创建数字字样时,其印刷形式优先。首先绘制文字形状的轮廓,然后基于这些轮廓绘制小文本大小的位图版本(为了在屏幕上清晰,以防它会出现在屏幕上)。换句话说,与分辨率无关的设计被改造成极低的分辨率。结果各不相同,但在许多情况下,小文本在屏幕上几乎难以辨认。在格鲁吉亚问题上,卡特决定逆转这一进程。因为这种字体主要是为屏幕设计的,所以首先绘制位图(8 到 12 点大小),然后绘制轮廓以适应它们(图 7-2 )。这为基于屏幕的衬线字体带来了前所未有的优雅清晰度,这种分类因其模糊性而臭名昭著。
图 7.2。佐治亚州轮廓 vs 佐治亚州位图
这项工作的合适人选
马修·卡特被选中承担这项任务绝非偶然。他是一位著名的字体设计师和印刷历史学家的儿子,20 世纪 50 年代末,他开始在金属上切割原始设计和经典字体的复刻,然后继续共同创立了著名的 Bitstream 和 Carter & Cone digital type foundries。基于他几十年的经验,令人印象深刻的客户名单(包括时代杂志、纽约时报和华盛顿邮报)和大量工作(最著名的是贝尔百年纪念,以及后来的屏幕上的 Verdana),很少有人能声称更有资格设计一种永恒、清晰的字体,用于屏幕上,但仍然植根于传统。
好了,现在你已经掌握了乔治亚是如何、为什么以及何时产生的,你已经准备好用一个实际的例子来使用它了。让我们建造一些东西!
Poe 的页面
为了展示一些可能性,我为恐怖片的原创大师埃德加·爱伦·坡做了一个小小的主页。基本布局由宽度大致相等的四列组成。两个中间的列组成了主要的内容区域,左侧是导航列,右侧是侧边栏列。页眉和页脚横跨四列的整个宽度。
图 7.3。示例项目
以下是标记的简化版本:
<body>
<!-- FULL PAGE WRAP -->
<div id="wrap">
<!-- NAV -->
<ul id="nav">
<li>Home</li>
<li>About</li>
<li>News</li>
<li>Works</li>
<li>Contact</li>
</ul>
<!-- MAIN CONTENT -->
<div id="main">
<p>Main content copy.</p>
</div>
<!-- ABOUT -->
<div id="about">
<p>About copy.</p>
</div>
<!-- FOOTER -->
<h6 id="footer">Copyright information.</h6>
</div>
</body>
您可以在http://wsc.robweychert.com/
在线查看该案例研究,并且可以从www.friendsofed.com/
下载源文件。该页面已在以下浏览器中经过测试:
-
用于 Windows 的 Internet Explorer 6 和 7
-
适用于 Windows 和 Macintosh 的 Firefox 1.5
-
适用于 Windows 和 Macintosh 的 Opera 9
-
用于 Macintosh 的 Safari 1.3 和 2.0
注意
因为这一章的重点是排版,案例研究的某些方面在这里就不讨论了,但是在其他地方有详细的记录。其中包括 Roger Johansson 的一般页面布局的弹性设计方法(“固定宽度还是可变宽度?有弹性!”www.456bereastreet.com/archive/200504/fixed_or_fluid_width_elastic/
)和道格拉斯·鲍曼(Douglas Bowman)的滑动门技术,用于展开标题和导航(“CSS 的滑动门”,www.alistapart.com/articles/slidingdoors/
)。
一个可读的行长度
我将讨论的大部分技术集中在案例研究的主要内容领域(#main
)。这一节将演示其比例是如何设计的,以及这些比例对布局其余部分的影响。
的数目。。。
由于#main
是页面层次结构中最重要的元素,我想确保它具有适当的焦点权重,并且可读性最大化。第一步是确定线条长度的宽度。单列中可读文本集的标准范围是 45 到 75 个字符(包括空格)。这个范围允许行以合理的增量断开,防止它们变得太长或太短而不便于阅读。许多排字工人认为大约 66 个字符的行是理想的,我也是其中之一,所以这个例子将使用这个宽度。这是一个关键的比例,我想确保它尽可能保持良好,这将是如何将页面放在一起的一个重要因素。
注意
好吧,我们承认:这本书打破了这里多次概述的行长惯例,但我们有我们的理由。这主要是因为我们不仅要处理文本,还要处理代码段;如果你不得不把代码分割成几个片段来适应页面,代码行的可读性会变得很差。
如果我使用的是液体布局,线条长度会根据用户浏览器窗口的宽度而增减(图 7-4 )。另一方面,如果我使用固定宽度的布局,当用户调整浏览器文本的大小时,行的长度会增加或减少,如果浏览器窗口比我的页面指定的宽度小,水平滚动的可能性也存在(图 7-5 )。由于对这两个选项都不满意,我选择了弹性布局,这给了我两全其美的选择(图 7-6, but also contracts with the window to avoid horizontal scrolling (right).") )。
布局将随着文本大小或浏览器大小的调整而扩展和收缩,但不会扩展超过指定的宽度。这里的折衷方案是,我的行会随着浏览器窗口的收缩而变短,但太短的行比太长的行更容易阅读,也是水平滚动的一个令人愉快的替代方案。
图 7.4。流动布局:当浏览器窗口扩大时,许多人会发现这一行太长,阅读起来不舒服。
图 7.5。固定宽度布局:如果浏览器窗口太小,水平滚动是必要的。
图 7.6。弹性布局,扩展和收缩:当浏览器窗口扩展(左)时,行长度保持所需的比例,但也会随着窗口收缩以避免水平滚动(右)。
全能的 em
弹性布局的秘密武器是 em。在印刷和网络世界中,em 是字体大小的正方形,基于其最宽字符的宽度或其最低下行字母(如小写字母 p 的词干)到其最高上行字母(如小写字母 b 的词干)或发音符号(图 7-7 )的距离。在 CSS 中,em 基于父元素的font-size
。因此,如果我在 ems 中指定我的布局的宽度,它将随着文本大小的调整而扩展和收缩,保持文本大小和行长度之间的比例,从而保持我的 66 个字符的行完整无缺。
图 7.7。一个乔治亚 em
我的布局的弹性将适合大多数桌面浏览器分辨率,但我仍然希望它的预期比例(图 7-8 )在 800600 浏览器窗口中舒适地放置,因此 740px 的原生页面宽度是谨慎的。为了在 ems 中指定这个宽度,我必须做一些数学计算。
图 7.8。近似布局比例
我的主要内容区域(#main
)需要有 66 个字符的宽度,这也需要是布局全宽(#wrap
)的 50%左右。如果我用 ems 中指定的宽度设置一个 Georgia 文本块,计算几行中的字符数(包括空格),然后用这些行的平均值除以文本块的宽度,我会发现 Georgia 对一个 em 来说平均是 2.2 个字符。因此,一个 66 字符的度量应该是 30em,这将使#wrap
成为 60em。如果我用 740 像素除以 60,我得到 12.3 像素,然后瞧!我有一个font-size
。
但是有一个问题。Internet Explorer 不会使用以像素为单位指定的font-size
来调整文本的大小。幸运的是,现代浏览器对关键词的渲染相当一致,我知道font-size: small
的分辨率大约是 13px。有了这个数字和一点点数学知识,我可以在我想要的结果范围内达到比例。
首先,我需要重新计算#wrap
的宽度,因为它是基于我的font-size
的,现在它有点大了。如果我用 740 像素除以 13 像素,#wrap
的新宽度(四舍五入)是 57em。为了保持我的 66 字行,#main
的宽度将保持为 30em,这是#wrap
宽度的 53%(再次取整)。最后,考虑每列 2%的页边距将得到图 7-9 所示的比例。
图 7.9。最终布局比例
翻译
带着自鸣得意的简单,CSS 简洁地显示了我如此努力和冗长地确定的比例:
body {
font-family: Georgia,"Times New Roman", Times, serif;
font-size: small;
}
#wrap {
max-width: 57em;
}
#nav {
width: 19%;
margin-right: 2%;
}
#main {
width: 53%;
margin-left: 2%;
margin-right: 2%;
}
#about {
width: 19%;
margin-left: 2%;
}
图 7-10 展示了结果:一个可读性很强的文本块,其比例将保持合理预期的一致性。关于在 CSS 中使用 ems 的另一种方法,参见第六章“网络网格设计”
图 7.10。#main
一个可读行的长度为
段落缩进
现在#main
有了一个舒适的宽度,我将把注意力转向其中的段落。自从段落元素出现以来,web 浏览器通常使用默认的下边距来呈现段落元素,通常在 1em 到 2em 之间。这就产生了段落之间空行的效果,这种习惯在印刷中肯定不是没有过。然而,在印刷中很可能看到的是一种在网络上相对罕见的描绘惯例:段落缩进。
空行有时会破坏连续的文本,段落首行的缩进可以标记新的段落,同时保持文本的连贯性。不过,在网络上应该谨慎使用。段落缩进作为空行的替代方法在打印时效果很好,因为文本还有其他物理拆分方式,如多列和分页。网络上的冗长写作偶尔也会给读者的眼睛一种空行所能提供的放松,因此,在简洁的陪伴下,网络上的段落缩进是最快乐的。
由于只有三个简短的段落,#main
是段落缩进的完美候选。我只需要做两件事:
-
缩进除第一段以外的所有段落的第一行。
-
去掉那些空行。
简单缩进
一个既能缩进段落又能去掉空行而不修改标记的非常简单的方法是这样做:
p {
text-indent: 2.1em;
margin: 0;
}
缩进 2.1em 在我的文本块中雕刻出一个正方形(图 7-11 ),看起来很棒。不幸的是,尽管结果很完美,但是这个 CSS 规则缩进了我的第一段。除了缩进之外,在段落之间使用空行是多余的,同样,缩进文本中的第一段也是多余的。两种形式的描述一前一后只会强调重点,就像添加一些大的红色文本,上面写着,“看,一个新段落的诞生!”好吧,也许没那么糟,但我还是不想做。
图 7.11。每个段落缩进都有一个空格。许多排字工人认为正方形是理想的缩进。
不完美世界中的相邻选择器
使用相邻选择器是我合乎逻辑的下一步:
p {
margin: 0;
}
p+p {
text-indent: 2.1em;
}
p+p
相邻选择器将只处理紧接在另一个段落前面的一个段落。理论上,这正是我想要的,因为第一段明显没有另一段在前,因此会忽略这条规则。在一个完美的世界里,这会完成手头的任务,我现在应该在酒吧了。遗憾的是,Internet Explorer 6 不支持相邻的选择器,现在它将我的所有段落呈现为一个模糊的斑点。
*当我在整理我的个人网站时遇到类似的问题,我是这样解决的:
p {
text-indent: 2.1;
margin: 0;
}
h2+p {
text-indent: 0;
}
所讨论的内容区域以两个标题开始:一个标题(h1
)和一个日期戳(h2
),然后是第一段(p
)。h2+p
相邻兄弟选择器在兼容的浏览器中实现了预期的效果,在 Internet Explorer 中实现了缩进的第一段,这是在标记不可更改的情况下的必要妥协。
在我目前的情况下,这不是一个可行的选择,因为#main
的第一段前面没有另一个元素,但也没有什么能阻止我稍微改变标记。我只需要给第一段一个类first
:
<p class="first">First paragraph copy goes here.</p>
然后在我原来的 CSS 中再添加一条规则:
p {
text-indent: 2.1em;
}
.first {
text-indent: 0;
}
任务完成(图 7-12 )!
图 7.12。具有优雅段落缩进的内聚文本块
这个文本块的形状很好,但是还需要一点点。这种排版风格的最后两个要素包括创造性地使用大写字母,这将为页面增添足够的视觉趣味来完成设计。
首字下沉
首字下沉是一种被称为 versal 的书写形式,这种书写传统可以追溯到欧洲中世纪时期最早的照明手稿(图 7-13 )。一个 versal 的目的是庄严地介绍文本。首字下沉通过显著放大段落的第一个字母并在前几行为其留出空间来做到这一点。
图 7.13。15 世纪 70 年代晚期的一页小时书,一种常见的照明手稿
作为另一个优雅的保险措施,我的读者会立即被#main
要说的话吸引,首字下沉将非常适合这一页。
以下是我希望首字下沉包括的属性:
-
正文应该换行。
-
它应该是文本大小的六倍左右。
-
顶部应该与第一行文本的顶部齐平。
-
左侧应该与文本的左侧保持齐平,但右侧和底部应该有一个小的边距,以避免与文本对接。
不完美世界中的伪元素
因为我直接处理第一段中的第一个字母(它已经有了一个类first
),所以使用 CSS :first-letter
伪元素选择器是一个合理的起点。因此,我的首字下沉规则将被发送到p.first:first-line
,并且很容易从我的期望属性列表中逐行翻译出来。
p.first:first-letter {
float: left;
font-size: 6em;
line-height: .75em;
margin-right: .1em;
margin-bottom: .1em;
}
难得的是,这段代码在我测试过的所有浏览器中都呈现出合理的一致性,除了 Mac 版 Firefox 的,它神秘地在首字下沉的顶部和底部插入了额外的空间(图 7-14 )。再怎么摆弄line-height
、padding
或margin
也不会让它在不同浏览器中表现一致,如果段落以em
或strong
(或span
,我稍后会添加)这样的行内元素开头,结果会更加不同。遗憾的是,我不得不尝试不同的解决方案。
图 7.14。火狐:first-letter
bug
跨越到救援
对加价的另一个小调整将是必要的。我将我的 O 包装在一个span
(带有一个drop
类)中,如下所示:
<p class="first"><span class="drop">O</span>ctober 7, 1849...</p>
我给这门课的规则和以前完全一样,就像这样:
.drop {
float: left;
font-size: 6em;
line-height: .75em;
margin-right: .1em;
margin-bottom: .1em;
}
有效!我的首字下沉已经到位(图 7-15 )。
图 7.15。一款纯 CSS 格鲁吉亚首字下沉
图像替换
但是我还是不开心。尽管我很爱乔治亚,但那个大乔治亚还是不太适合我。这一页需要一些更具装饰性的东西,所以我创建了自己的华丽首都 O ( 图 7-16 )。这种字体大致基于波多尼字体,这是一种精致的现代衬线字体,在爱伦坡出生前一年设计。
图 7.16。我的定制 versal
我将使用图像替换技术,而不是把我的通用图像放在标记中。
注意
我使用的特殊图像替换技术只是众多技术中的一种,其中大部分是由 Dave Shea 在他的mezzoblue.com
网站(www.mezzoblue.com/tests/revised-image-replacement/
)上编辑的。虽然我在这个例子中推荐这个方法,但是其他的方法可能更适合不同的情况,我建议对它们进行复习。你也可以在第二章“用 CSS、Flash 和 JavaScript 驯服野生 CMS”中找到更多关于图像替换技术的内容
图像替换正如它所说的那样:它用图像替换元素的 HTML 文本。在这个例子中,它将通过使用负的text-indent
从元素中吸出文本,然后放入我的 versal 图形作为背景图像来完成。
.drop {
width: 83px;
height: 83px;
float: left;
text-indent: 9999px;
background-image: url(o.gif);
background-repeat: no-repeat;
background-position: top left;
margin-right: .1em;
margin-bottom: .1em;
}
我的浮动和边距与前一个例子相同,我的文本负向缩进到另一个星系,宽度和高度的值对应于我的通用图形的宽度和高度。它在我测试的所有浏览器上都很棒,除了 Internet Explorer。那个浏览器选择了负缩进我的整个段落,而不仅仅是我的span
,导致段落完全消失(图 7-17 )。
图 7.17。Internet Explorer 文本缩进错误
幸运的是,这个问题可以通过简单地将span
声明为块级元素来解决:
.drop {
width: 83px;
height: 83px;
display: block;
float: left;
text-indent: 9999px;
background-image: url(o.gif);
background-repeat: no-repeat;
background-position: top left;
margin-right: .1em;
margin-bottom: .1em;
}
我定制的首字下沉已经到位了(图 7-18 )!
图 7.18。我闪亮的首字下沉
全部大写
我对我的首字下沉很满意。然而,让我的段落从如此夸张的介绍直接变成小得多的小写文本在视觉上有点不协调。我将用最后一点改进来解决这个问题,之后#main
将准备好与公众见面。
如果你没有在排版世界里学过,你可能已经在网络世界里学过了,尤其是在电子邮件和即时消息中:用全部大写字母设置的文本应该非常谨慎地使用。大写锁定键的错误组合会给你的观众留下你在对他们大喊大叫的印象,这不是让他们回来的最好方法。记住这个规则,所有的大写字母仍然可以发挥很大的作用,有节制的使用将在我巨大的首字下沉和它引入的小得多的正文之间建立一个逻辑桥梁。由于我的日期戳October 7, 1849
(爱伦坡的死亡日期)已经作为一个简洁、独立的文本开头,所以它是一个完美的全大写处理的候选。
您的第一反应可能是在标记中设置全部大写的文本,从而避免我将要描述的更加费力的 CSS 过程。这不是一个好主意,原因有很多,其中最主要的是您以后可能希望对文本进行不同的样式化。如果这是一个用来构建许多页面的模板,那么在一个 CSS 文件中更改一个规则要比在几十个(或者几百个或者几千个)HTML 文档中更改标记容易得多。此外,正如您将在下面的示例中看到的,在设置所有大写文本时,可能需要考虑更多的内容,而不仅仅是在大写锁定状态下键入。
首先,我将最后一个span
添加到我的标记中,作为日期戳的目标:
<p class="first"><span class="datestamp">
<span class="drop">O</span>ctober 7, 1849:</span> Lorem ipsum...</p>
现在,我只需要两个简单的、不言自明的 CSS 规则来使我的datestamp
类以期望的方式运行:
.datestamp {
text-transform: uppercase;
letter-spacing: .13em;
}
给text-transform
一个值uppercase
将确保我的文本全部大写。它还可以取值lowercase
(所有小写文本)和capitalize
(每个单词的第一个字母大写)。至于letter-spacing
,当文本全部大写时,一点额外的字母间距是易读性的关键。通过少量的实验,我发现非常精确的 0.13em 测量对于这种情况是理想的(图 7-19 )。
图 7.19。#全大写的美因日戳格鲁吉亚
文本图与标题图
我的段落介绍看起来不错,但是我还有最后一条鱼要钓。让 Georgia 如此珍贵的原因之一是,它是唯一一种在网络上广泛使用的字体,包括文本数字(有时被称为旧式数字 ) ( 图 7-20 vs. Times New Roman's titling figures (bottom)") ),本质上是小写数字。就像小写字母一样,文字数字的上升和下降对正文的破坏性比大写字母要小,称为标题数字。理想情况下,字体将包括文本和标题图,但通常只包括后者。Georgia 有一个奇怪的区别,它只包含文本数字,因此,我的大写日期戳包含小写数字。这不行。
图 7.20。佐治亚州的文字数字(上)与泰晤士报新罗马标题数字(下)
泰晤士报新罗马,其相似性和格鲁吉亚和包括所有权的数字,是这个问题的完美解决方案。此外,我发现 Times New Roman 的所有大写字母比 Georgia 的所有大写字母略轻一些。通常情况下,我会避免在同一个文档中使用如此相似的字体,但是在这种情况下效果很好,因为这里的想法是创造一个一个字体的假象。再有一个简单的 CSS 规则,#main
将最终完成(图 7-21 ):
.datestamp {
font-family: "Times New Roman", Times,
Georgia, serif;
text-transform: uppercase;
letter-spacing: .13em;
}
图 7.21。#main
完成
小型大写
我还选择了一个排版元素,应用在 Poe 主页右侧的#about
段落中。我希望第一行以小型大写字母出现。
#about
的基本标记和#main
非常相似。
<div id="about">
<p class="first">Edgar Allan Poe lorem ipsum...</p>
<p class="more"><a href="#" title="Edgar Allan Poe: About">
More About Poe </a></p>
</div>
小型大写字母通常比其字体的 x 高度略高,x 高度是其小写字母 x 的高度。因此,它们实际上是小写大写字母。它们通常用于缩写、首字母缩略词,如在本案例研究的例子中,以比 versal 更柔和的方式标记文本的开始。为此,介绍性段落的前几个词或整个第一行可以用小型大写字母。
公平警告
对于外行人来说,小型大写字母很容易被误认为是大写字母的缩略版本。但是,在一个设计良好的字体中,小大写和大写字母是分开画的,两者的细微差别保持了字母笔画粗细的一致性(图 7-22, the full cap (center), and the true small cap (right).") )。真正的小型大写字母在小字体时更容易阅读,并且不会扰乱文本的流动,而仿小型大写字母(例如,8 点大写字母设置在一组 12 点字体中)几乎总是引人注目和分散注意力。
图 7.22。请注意仿小笔帽(左)、全笔帽(中)和真小笔帽(右)之间的笔画宽度差异。
当面对下面一组 CSS 规则时,web 浏览器会将指定的文本设置为乔治亚斜体,这当然是一种不同于 Georgia 常规字体的设计,而不仅仅是一种倾斜的斜体。
font-family: Georgia;
font-style: italic;
然而,如果我要添加一个规则font-variant: small-caps
,文本不能设置为真正的小型大写字母,因为格鲁吉亚没有提供。同样,绝大多数最广泛、公开传播的数字文本字体不包括一组小型大写字母。因此,web 浏览器默认使用仿小型大写字母。在 Quartz (Mac OS)和 ClearType (Windows)等屏幕字体平滑技术出现之前,这并不是什么大问题,因为人造小型大写字母的比例差异的微妙之处被像素化字母的低分辨率所取代(图 7-23 vs. smoothed small caps (bottom)") )。然而,随着屏幕字体平滑变得越来越普遍,我们不能再依靠浏览器来掩盖自己的印刷错误??。仿小型大写字母现在在屏幕上看起来和印刷时差不多。
图 7.23。像素化小型大写字母(上)与平滑小型大写字母(下)
虽然早在网络出现之前,假的小型大写字母就已经成为合法的嘲笑对象,但可以说它们在网络上是可以接受的,因为真正的小型大写字母还没有出现。尽管它们可能很显眼,但它们仍然有能力增强和丰富页面。是否使用它们是你自己的决定。
不完美世界中更多的伪元素选择器
我最后一次尝试使用伪元素选择器以心痛告终(还记得:first-letter
?),但是,为了小盘,我准备再试一次,这次用:first-line
。不出所料,:first-line
会影响其目标元素的第一行文本。在这种情况下,目标元素将是#about
的第一段。
#about p.first:first-line {
font-variant: small-caps;
text-transform: lowercase;
letter-spacing: .1em;
}
到目前为止,这些 CSS 规则应该是相当基本的,但是有一行可能引起了质疑。为什么我用了text-transform: lowercase
?原因既有优惠的,也有实际的。小型大写字母本质上既可以作为大写字母,也可以作为小写字母。因此,通常需要大写的单词(如专有名词)在以小型大写字母设置时,可能会也可能不会以全大写字母开头。出于一致性的考虑,我通常更喜欢而不是使用小型大写字母,因为首字母缩写词通常使用小型大写字母来代替构成它们的完整大写字母。至于实际的关注,我不希望任何更多的注意力吸引到我的人造小帽比必要的。让他们和完整的帽子坐在一起会招致比较,并进一步暴露他们的不真实(如图 7-23 中的所示),所以如果可以的话,我更喜欢把他们放在他们自己的同类中。 vs. smoothed small caps (bottom)")
这可能是合理的推理,但 Internet Explorer 和 Safari 不会听进去。每一种都有自己的缺陷,阻碍了预期效果的发生。Internet Explorer 不会认可font-variant
和text-transform
规则的任何组合,Safari 也不会认可应用于伪元素选择器的text-transform
规则。因此,Internet Explorer 和 Safari 都将标记的大写字母显示为全大写。Firefox 和 Opera 可以正确显示文本。
特别是 Safari 的 bug 使得我想要的效果不可能在不同的浏览器上一致地呈现,因为它是我在这里尝试使用的伪元素选择器first-line
和文本转换规则lowercase
的组合。因此,要避免对我的标记进行进一步的更改,并确保我的小型大写字母只保留在文本的第一行,这需要一个折衷方案:Internet Explorer 和 Safari 将显示一个可能混合了全大写字母和仿小型大写字母的包。看着完成的页面(图 7-24 ),我想我绝对可以做得更差!
图 7.24。所有的努力都是值得的!
结论
毫无疑问:印刷世界和网络世界是不同的地方,各有各的特点。不过,它们至少有一个非常重要的共同点,那就是它们的存在是为了帮助人们交流。虽然我们应该承认并尊重这样一个事实,即在印刷上对一方有效的东西对另一方可能无效,但我们也应该尊重读者,他们可以从许多技术中受益,这些技术的效用与任何一种媒体都没有必然的联系。我在这一章中概述的技术应该已经给了你一个很好的想法,让你可以在网上激活类型。*
八、打印魔法:使用 DOM 和 CSS 拯救地球
伊恩·劳埃德经营着Accessify.com
,一个致力于促进网页可访问性并为网页开发者提供工具的网站。具有讽刺意味的是,他的个人网站 Blog Standard Stuff 与博客标准毫无关系(这是一种文字游戏),尽管偶尔可以在那里找到与标准相关的精华。
伊恩全职为全国建筑协会工作,在那里他尽力影响基于标准的设计(“在不同程度上!”).他是 Web 标准项目的成员,为可访问性任务组做出了贡献。除了网络标准和可访问性,他喜欢写他的国外旅行,最近从工作和所有网络事物中抽出“一年时间”(但后来在他休假的一年里写的比以往任何时候都多)。他发现自己的大部分时间都被一个苛刻的老太太占用了(放松,这只是他的旧大众野营车)。
伊恩最近为 SitePoint 写了他的第一本书,名为使用 HTML & CSS 以正确的方式构建你自己的网站(在这本书中,他向完全的初学者教授基于 Web 标准的设计)。
一种印刷技术诞生了
拯救地球?这是怎么回事?好吧,我会在适当的时候说的。首先,我想带你回到过去。好的,当我写这篇文章的时候,仅仅是一个月前,但是正是在这个时候,我得到了你将在这一章中读到的东西的灵感。
我正坐在办公桌前忙着某件事,这时我的同事对我说,“有没有什么方法可以让你的网页只打印某个特定的部分?”
“哦,是的,”我回答说,“这很容易,你可以用打印 CSS 样式来做到这一点。您想打印页面的哪一部分,不想打印哪一部分?”
“嗯,那要看情况,”他回答道,准备抛出众所周知的曲线球。“我们有很长很长的利率页,但根据这个人的账户,他们会希望打印不同的部分。”
我仔细考虑过了。我记得在一些浏览器上,可以选择页面的一部分,然后只打印那一部分,这是在Print
对话框中提供的一个选项,但这并不理想,因为它要求用户知道这个选项的存在。此外,它并不是在所有浏览器上都可用。对于技术人员来说,可以使用一个油滑的猴子【1】脚本来打印一个给定的选择,但这可能只占我们预期受众的 0.001%。我的同事想要的是浏览器没有以任何明显的格式提供的东西。所以我告诉他,“对不起,你不能这么做。恐怕除非他们知道突出显示某个部分,然后选择“打印选择”,否则这是不可能的。”
然后我回到我当时在屏幕上推动的像素。然而,这个想法一直萦绕在我的脑海里。我个人很讨厌你想打印一页的一个部分,结果却打印了七页废话,其中六页马上就被扔掉了。难道没有办法解决这个问题并减少浪费的打印输出吗?然后我想到了:打印 CSS 的混合;一些好的、可靠的语义标记;一些不引人注目的 JavaScript 可能正好达到他的目的。所以我开始进行概念验证。
基本想法
我知道使用 DOM、JavaScript 和 CSS 可以动态地改变网页在屏幕上的外观,我想知道同样的理论是否适用于打印。没有任何迹象表明这不可能发生。我记得在 List Apart ( http://www.alistapart.com
)中读过 Aaron Gustafson 的文章,他演示了一种显示脚注的方法,我知道这种行为有一定的范围。我只是不确定这会有多“动态”。找到答案的唯一方法就是尝试一下。于是,我开始有了这个想法:
对于任何具有可能需要单独打印的特定部分的给定页面,比如促使我进行调查的利率页面,使用 JavaScript 动态切换显示属性(也就是说block
或none
),但是对于打印视图,仅切换。
这项技术的其他目标包括
-
这项技术必须不引人注目。【2】它应该符合渐进式增强的类别,【3】并且所有的行为都应该从一个共享的 JavaScript 文件进行控制。
-
一定要好看,不仅仅是工作。
-
它必须基于完全干净和有效的标记。
-
这项技术的目的需要对用户来说非常明显。
令人欣慰的是,所有这些目标都是可以实现的,我将在接下来的几页中展示这一点。
准备基础
有了一个清晰的最终目标和一些明确的目的,我开始着手一个简单的概念证明。这是视觉效果看起来有点难看的过程的一部分,但功能部分得到了解决,所以不要担心,我会在本章的后面让它看起来很漂亮。我知道最终的目的是什么(将它应用到冗长的利率页面上,该页面需要切割以供打印),但首先,我创建了一个包含基本内容的虚拟页面。
分割页面
这项技术的关键是知道你想打印和不想打印的页面部分。在我看来,这可以使用 CSS 类来完成,显而易见的选择是使用类名section
,或者,如果你想让它的用途非常清楚,使用类似于print_section
的类名。【4】所以,这里有一个简单的 HTML 页面,包含一些部分
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>A sectioned-up web page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
.print_section
{
border:1px solid gray;
background:#e1e1e1;
margin:0 0 15px 0;
padding:10px;
}
</style>
</head>
<body>
<h1>Sectioned up page</h1>
<div class="print_section">
<h2>Section 1</h2>
<p>This is section 1\. Nothing much to see here, just a bunch of meaningless words.</p>
</div>
<div class="print_section">
<h2>Section 2</h2>
<p>This is section 2\. You'd be better off watching paint dry than reading the content here.</p>
</div>
<div class="print_section">
<h2>Section 3</h2>
<p>This is the final section. Hurrah!</p>
</div>
</body>
</html>
图 8-1 显示了这个虚拟页面。
图 8.1。分割页面,准备打印
这将是访问者将看到的基本页面(忽略我用于虚拟页面的占位符内容)。现在的诀窍是在那些浏览器的基础上增加额外的好的层次,这些浏览器正在进行工作渐进增强。
识别截面
页面的基本结构已经有了,但是如果这项技术要发挥作用,还需要一些其他的东西。如果我们在任何给定的时间都不能确定我们正在处理的是哪个部分,我们如何神奇地切换这些部分的外观呢?线索就在这个问题中:标识,或者id
,这些部分。是的,我们只需要给部分添加一个id
属性,就像这样:
<div id="sect1" class="print_section">
<h2>Section 1</h2>
<p>This is section 1\. Nothing much to see here, just a bunch of
meaningless words.</p>
</div>
<div id="sect2" class="print_section">
<h2>Section 2</h2>
<p>This is section 2\. You'd be better off watching paint dry than
reading the content here.</p>
</div>
<div id="sect3" class="print_section">
<h2>Section 3</h2>
<p>This is the final section. Hurrah!</p>
</div>
这暂时不会对事物的视觉方面产生影响,但是它将提供我们 JavaScript 行为所需要的钩子。现在让我们来看看它的具体细节。
伪代码第一
在本章的前面,我用简单的英语描述了这种技术应该做什么。现在我将转向伪代码的世界,描述这里将要发生的事件的顺序。
浏览器开始加载页面,并在包含所有行为规则的外部.js
文件中包含一段 JavaScript。当页面加载后,JavaScript 启动并执行以下操作:
-
首先,它遍历整个文档寻找
div
元素并创建一个数组 -
接下来,它一个接一个地遍历这个由
div
元素组成的数组。如果div
元素的类名为print_section
,它会执行以下操作:-
它会创建一个按钮或链接,显示“打印此部分”或类似内容。
-
然后,它对该按钮应用一个行为。单击时的行为是首先检查被单击链接的 id。如果 id 与当前节匹配(当浏览器浏览文档中所有可打印的节时),使该节可见(在打印视图中)。如果单击的链接的 id 不匹配(当浏览器搜索文档中所有可打印的部分时),它会隐藏当前部分(同样,仅用于打印)。
-
-
在浏览器中调出
Print
对话框,如图图 8-2 所示。不幸的是,要避免这一步并让它直接印刷是不可能的。用户仍然可以再单击一个按钮。在图 8-2 中的 Firefox 示例中,是OK
按钮。图 8.2。打印前出现
Print
对话框。 -
当创建了每个部分的可视元素并定义了它们的相关行为后,它们就会被插入到页面中。
所有这些步骤都应该很快完成。除非网页很长,或者网页上有一个元素下载时间比其他元素长得多(例如,来自外部网站的图像像狗一样运行,而不是灰狗),否则用户不会注意到页面外观的不协调变化。
注意
页面上的单个低速加载元素会延迟这种效果的原因是因为 JavaScript 在页面加载后运行。这是因为一旦页面上的所有内容都被下载了,所有链接到其他位置的图像、脚本和 CSS 文件都会发生 onload 事件,而不仅仅是页面的标记。
这就是虚拟页面的攻击计划。让我们看看一些真正的代码,它们将帮助我们实现这一目标。
活动策划
首先需要做的是设置页面加载事件。现在有许多不同的方法可以做到这一点,关于哪种方法是最好的也有同样多的观点。我敢打赌,这本书的许多读者都有能力做出自己的决定,并能适应我正在使用的建议的addEvent
函数(来自斯科特·安德鲁:http://tinyurl.com/qcmrd
)。然而,即使事实并非如此,你,亲爱的读者,不知道我在说什么,也不要担心,这真的是太担心最小的细节了。走开,这里没什么可看的,没什么可看的。。。
addEvent
功能启动网页。它告诉浏览器它需要运行什么(哪个 JavaScript 函数)和什么时候运行(在什么事件上,例如,当页面加载时或者当某个东西被点击时)。这是:
function addEvent(elm, evType, fn, useCapture)
{
if(elm.addEventListener)
{
elm.addEventListener(evType, fn, useCapture);
return true;
}
else if (elm.attachEvent)
{
var r = elm.attachEvent('on' + evType, fn);
return r;
}
else
{
elm['on' + evType] = fn;
}
}
如果你觉得这看起来像天书,不要担心。你不需要理解它做的每件事或如何做。您只需要知道它是为了解决一些跨浏览器 JavaScript 问题而创建的,它允许您使用三个参数来调用它:
-
你在处理什么元素(
elm
) -
事件是什么(点击、加载、聚焦)(
evType
) -
当元素触发了事件时,您希望它执行什么 JavaScript 函数(
fn
)
这是一个你如何称呼它的例子:
addEvent(window, 'load', addPrintLinks, false);
或者,说白了,当window
(或者更准确地说,这个窗口中的文档)已经load
ed 时,请运行addPrintLinks
功能。这个函数还没有建立,所以为了测试它是否工作,我暂时在这里放了一个简单的警告:
<script type="text/javascript">
function addPrintLinks()
{
alert("Here's where the cool stuff will happen");
}
addEvent(window, 'load', addPrintLinks, false);
</script>
警报看起来像图 8-3 。
图 8.3。动作中的警报占位符
从伪代码到实代码
列出了完成这项工作所需的步骤之后,让我们来看看将完成这项工作的实际代码(我将引用我前面的描述):
浏览器开始加载页面,并在包含所有行为规则的外部.js
文件中包含一段 JavaScript
在前面的例子中,我展示了嵌入页面本身的脚本。现在是时候将脚本放在它们自己的文件中,并在网页中引用它们了,就像这样:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html >
<head>
<title>A sectioned-up page</title>
<script type="text/javascript" src="print_sections.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
...
当页面加载后。。."
这是我们之前提到的AddEvent
部分。addEvent
函数应该在.js
文件中,正如下面对该函数的调用一样:
addEvent(window, 'load', addPrintLinks, false);
".。。JavaScript 启动并执行以下操作:
首先,它遍历整个文档寻找div
元素并创建一个数组。"
function addPrintLinks()
{
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{//loop through the array of divs and do something cool
}
}
为页面创建新的打印链接
接下来,它一个接一个地遍历这个由div
个元素组成的数组。如果div
元素的类名是print_section
。。."
function addPrintLinks()
{
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{
if (el[i].className=="print_section")
{//focus on only divs that are print sections
}
}
}
".。。它执行以下操作:
它会创建一个按钮或链接,显示“打印此部分”或类似内容。
然后,它对该按钮应用一个行为。"
实现这些目标需要相当多的 JavaScript,下面是(在适当的地方有注释解释每个部分在做什么):
function addPrintLinks()
{
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{
if (el[i].className=="print_section")
{
// create the anchor element
var newLink = document.createElement("a");
// give it some text content
var newLinkText = document.createTextNode("print this section only");
// create a container for the link to
// go in and give it a class
var newLinkPara = document.createElement("p");
newLinkPara.setAttribute("class","printbutton");
// set up the 'print this section' link
newLink.setAttribute("href","#");
// the print button will need a unique ID
// (this will be used to tell the function
// what section is to be shown or hidden)
var btId = "printbut_" + el[i].id;
newLink.setAttribute("id",btId);
// add the text for the link to the anchor element
newLink.appendChild(newLinkText);
// add the anchor element to the paragraph element
newLinkPara.appendChild(newLink);
//add the behaviors for the new link
newLink.onclick = togglePrintDisplay;
newLink.onkeypress = togglePrintDisplay;
}
}
}
此时,我们已经创建了将用于打印输出的链接。它被放在一个段落中,但该段落尚未插入到文档中。它目前在浏览器内存的某个地方处于浮动状态,但我们一会儿就会看到。然后继续进行伪-实代码翻译:
点击时[链接的]行为是首先检查被点击的链接的id
。如果id
与当前部分匹配(当浏览器浏览文档中所有可打印部分时),使该部分可见(在打印视图中)。如果点击链接的id
与不匹配(当浏览器搜索文档中所有可打印的部分时),它会隐藏当前部分(同样,仅用于打印)。
在浏览器中调出Print
对话框:"
使用以下几行脚本附加了链接的行为:
newLink.onclick = togglePrintDisplay;
newLink.onkeypress = togglePrintDisplay;
这意味着当点击链接或者当链接获得焦点并且检测到按键事件时,浏览器应该运行一个名为togglePrintDisplay
的新函数。
添加行为
我们来看看togglePrintDisplay
函数里有什么。我将首先一次性展示所有内容,然后逐一解释各个组成部分,解释发生了什么:
function togglePrintDisplay(e)
{
var whatSection = this.id.split("_");
whatSection = whatSection[1];
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{
if (el[i].className.indexOf("print_section")!=-1)
{
el[i].removeAttribute("className");
if (el[i].id==whatSection)
{
//show only this section for print
el[i].setAttribute("className","print_section print");
el[i].setAttribute("class","print_section print");
}
else
{
//hide the sections from printout
el[i].setAttribute("className","print_section noprint");
el[i].setAttribute("class","print_section noprint");
}
}
}
if (window.event)
{
window.event.returnValue = false;
window.event.cancelBubble = true;
}
else if (e)
{
e.stopPropagation();
e.preventDefault();
}
window.print();
}
这是一步一步来的。首先,该函数从被点击的链接中获取id
。这个id
应该是类似于printBut_sect2
的东西。它是以编程方式创建的(抓取父容器的sect2
的id
,并将其附加到新创建的按钮上),就像这样:
var btId = "printbut_" + el[i].id;
当然,使用更短的变量名是可能的,但是我这里的目标是可读性。请注意,id
不能以数字开头;因此,我在id
值中使用了下划线。这使得以后检索该值变得非常容易,如下面的代码片段所示。我们使用一个split
函数,在下划线处进行分割。这创建了一个包含两个值的数组:printBut
和我们真正想要的部分,在下面的例子中是sect2
。
这两个值被传入一个变量whatSection
。不过,我们真正想要的只是数组的第二部分。因为数组从 0 开始,所以使用[1]
引用数组中的第二项;因此,我们使用whatSection[1]
获得我们想要的信息。该值随后被传递到whatSection
变量中(覆盖其先前的数组值;whatSection
现在不再是数组,变成了字符串)。
function togglePrintDisplay(e)
{
var whatSection = this.id.split("_");
whatSection = whatSection[1];
现在我们已经有了要寻找的id
,我们需要再次浏览文档,寻找所有的div
元素,特别是类名为print_section
的div
元素:
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++) //loop through all the divs found
{
if (el[i].className.indexOf("print_section")!=-1)
{
因此,在这个阶段,我们看到一个在类名中某处有值print_section
的div
。注意,我选择在脚本的前面使用indexOf
方法来匹配className
属性是有原因的。在下一部分中,我将把print_section
类名与print
或noprint
类名结合起来,这样直接的==
比较就不起作用了。然后继续向上。
首先,我们去掉为当前正在查看的元素找到的任何className
属性(记住,我们正在遍历一个集合),然后检查我们之前确定的id
。如果id
匹配(例如,sect1
,sect2
),那么我们知道被点击的按钮与这个div
相关,我们可以通过设置print
的class
以及恢复print_section
类名来相应地处理它(否则,这个脚本在第一次用于任何给定的页面后都会失败):
el[i].removeAttribute("className");
if (el[i].id==whatSection)
{//if this is the section to be printed, set a 'print' classname
el[i].setAttribute("className","print_section print");
el[i].setAttribute("class","print_section print");
}
else
{
// otherwise hide the section from printout
el[i].setAttribute("className","print_section noprint");
el[i].setAttribute("class","print_section noprint");
}
}
}
注意,这里似乎需要加倍努力:同时设置class
属性和className
属性。这是解决 Internet Explorer 和其他浏览器处理事物的方式之间的差异的一种变通方法。这不是一个大的开销,几乎不值得创建一个新的自定义函数来处理它(如果你愿意,你可以这样做)。
脚本的最后一部分只是取消浏览器执行被点击元素的默认动作。在这里的例子中,我选择启动打印功能的元素是一个链接(准确地说是锚点),我给了它一个#
的href
属性。通常,具有该属性的链接会再次调用同一个页面。如果这种情况发生在我们的页面中,当选择打印按钮时,页面会重新加载并出现跳转,这不是我们想要的效果。以下代码阻止默认操作发生:
if (window.event)
{
window.event.returnValue = false;
window.event.cancelBubble = true;
}
else if (e)
{
e.stopPropagation();
e.preventDefault();
}
注意
使用一个链接,将一个 href #放在那里,然后将该链接用于其他目的,这通常被认为是一种不好的做法。那我到底为什么要这么做?我如何为这些行为辩护?嗯,这通常是一个坏主意的原因是,当 JavaScript 关闭时,以这种方式编写的链接会失败。然而,我们使用 JavaScript 来创建这项技术;因此,绝对没有这种情况发生的危险。
另一个解决方案可能是动态地编写按钮而不是链接。然而,这本身就存在一些问题。首先,这可能会限制您的样式选项(与 button 或 submit 类型的输入相比,您对 link 元素有更多的 CSS 控制)。其次,这其实不是一种形式,对吧?如果您动态地编写表单按钮,您需要在它周围包装一个表单元素。此外,如果是一个表单,您应该使用按钮或提交类型的输入?毕竟,没有提交表单数据进行处理。所以,我选择了主播。有人开枪打我吗!
最后,我们调用Print
对话框。所有的类名都被改变了,这影响了它们的显示,所以我们已经准备好了:
window.print();
}//end of the togglePrintDisplay function
将新链接写入文档
不过,我们还没有完成。从addPrintLinks
函数调用了togglePrintDisplay
函数,第一个脚本还没有完全完成。有一件小事,就是在网页加载后将按钮(包含在段落中)插入网页。这是通过这条线实现的:
el[i].insertBefore(newLinkPara,el[i].firstChild);
概述:这些脚本的作用
现在我们差不多完成了。所以来回顾一下:
-
页面加载,
addPrintLinks
运行。 -
addPrintLinks
调用另一个函数togglePrintDisplay
,在这里设置行为。 -
当为每个所需的元素设置了行为后,
addPrintLinks
恢复,将新的链接追加到 web 页面中。
以下是完整的脚本:
function addEvent(elm, evType, fn, useCapture)
{
if(elm.addEventListener)
{
elm.addEventListener(evType, fn, useCapture);
return true;
}
else if (elm.attachEvent)
{
var r = elm.attachEvent('on' + evType, fn);
return r;
}
else
{
elm['on' + evType] = fn;
}
}
function addPrintLinks()
{
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{
if (el[i].className=="print_section")
{
var newLink = document.createElement("a");
var newLinkText = document.createTextNode("print this section
only");
var newLinkPara = document.createElement("p");
newLinkPara.setAttribute("class","printbutton");
//set up the 'print this section' link
var btId = "printbut_" + el[i].id;
newLink.setAttribute("id",btId);
newLink.appendChild(newLinkText);
newLink.setAttribute("href","#");
newLinkPara.appendChild(newLink);
//add the behaviors for the new link
newLink.onclick = togglePrintDisplay;
newLink.onkeypress = togglePrintDisplay;
//insert the para and the two links into the DOM
el[i].insertBefore(newLinkPara,el[i].firstChild);
}
}
}
function togglePrintDisplay(e)
{
var whatSection = this.id.split("_");
whatSection = whatSection[1];
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{
if (el[i].className.indexOf("print_section")!=-1)
{
el[i].removeAttribute("className");
if (el[i].id==whatSection)
{
//show only this section for print
el[i].setAttribute("className","print_section print");
el[i].setAttribute("class","print_section print");
}
else
{
//hide the sections from printout
el[i].setAttribute("className","print_section noprint");
el[i].setAttribute("class","print_section noprint");
}
}
}
if (window.event)
{
window.event.returnValue = false;
window.event.cancelBubble = true;
}
else if (e)
{
e.stopPropagation();
e.preventDefault();
}
window.print();
}
addEvent(window, 'load', addPrintLinks, false);
CSS 呢?
哇,关于脚本的所有讨论,你可能已经忘记了还有一个非常重要的方面,那就是打印 CSS 样式。脚本改变了文档中各种元素的类名,但是在某些地方我们需要定义这些项目的外观。需要将以下 CSS 添加到文档中。第一部分纯粹是装饰性的(为了让链接看起来“扣扣的”),但是重要的部分用粗体突出显示:
.print_section p.printbutton
{
float:left;
}
.print_section p.printbutton a
{
text-decoration:none;
background:white;
display:block;
float:left;
margin:3px;
padding:10px;
border:1px solid red;
}
media print
{
.noprint, .printbutton
{
display:none;
}
.print
{
display:block;
}
}
通过包含在media print
后的花括号内,样式被限制为打印显示。因此,显示样式的切换不会影响屏幕视图(或其他媒体类型)。
将所有这些缝合在一起,最终的结果看起来像图 8-4 。
图 8.4。完成的原型页面
如果点击OK
按钮,将只打印页面的一部分。在这个例子中,它是第二部分,正如 Firefox 中的打印预览功能所展示的那样(见图 8-5 )。
图 8.5。打印一个部分的功能正在工作!
几处改进
在我们从概念验证阶段进入实际应用之前,有一个小问题需要解决。考虑一下这个场景:
如果用户点击了一个Print this section
链接,然后点击了Cancel
,会发生什么?也许用户最终想要打印整个页面?
如果您检查各种事件何时被触发以及它们做了什么,您会注意到有一个问题。通过在第一次激活一个打印部分按钮后单击浏览器的打印按钮,只有部分部分将被打印。这是因为 CSS 显示已经设置好了,浏览器的打印按钮不会对此产生影响。
一个解决方案是写出两个链接:一个写着print this section
,另一个写着print the whole page
。这个想法是为了阻止人们转向他们的浏览器打印按钮,如果他们刚刚使用了print this section
按钮。或者,您可以创建另一个函数,当某个可预测的事件发生时,将每个部分的显示重置为默认值。例如,这可能发生在当鼠标指针经过页面标题区域时,如果它从页面的一部分移动到浏览器的顶部,这几乎肯定会发生,如图图 8-6 所示。
图 8.6。使用mouseover
事件重置显示,该事件检测用户是否被浏览器的打印按钮吸引
下面是我的第一个解决方案所需的代码(为每个部分添加第二个打印按钮的更简单的解决方案)。在addPrintLinks
功能中(粗体显示的附加内容):
var btId = "printbut_" + el[i].id;
newLink.setAttribute("id",btId)
newLink.appendChild(newLinkText);
newLink.setAttribute("href","#");
newLinkPara.appendChild(newLink);
//set up the print all link
newLink2.setAttribute("href","#");
var bt2Id = "printall_" + el[i].id;
newLink2.setAttribute("id",bt2Id);
newLink2.appendChild(newLinkText2);
newLink2.setAttribute("href","#");
newLinkPara.appendChild(newLink2);
//add the behaviors for the new links
newLink.onclick = togglePrintDisplay;
newLink.onkeypress = togglePrintDisplay;
newLink2.onclick = printAll;
newLink2.onkeypress = printAll;
注意,新链接将调用一个名为printAll
的新函数。这是togglePrintDisplay
函数的变体(我相应地复制、粘贴和修改了它)。该功能简单地运行所有可打印部分,并重置显示,以便它们可用于打印目的:
function printAll(e)
{
var el = document.getElementsByTagName("div");
for (i=0;i<el.length;i++)
{
if (el[i].className.indexOf("print_section")!=-1)
{
el[i].setAttribute("className","print_section print");
el[i].setAttribute("class","print_section print");
}
}
if (window.event)
{
window.event.returnValue = false;
window.event.cancelBubble = true;
}
else if (e)
{
e.stopPropagation();
e.preventDefault();
}
window.print();
}
注意
对原始函数(togglePrintDisplay)进行抽象并不难,这样它就可以处理打印部分和打印全部的场景,而不是拥有两个独立的函数。然而,我现在选择将它们分开,主要是为了阅读的清晰。
修改后的概念验证页面现在看起来像图 8-7 。
图 8.7。完成的原型页面,进行了改进
请注意,您可以将为每个部分添加一个print the whole page
链接的想法与我之前提出的关于当鼠标经过标题区域(或类似区域)时重置打印区域以显示所有内容的建议结合起来,这是众所周知的“带括号”方法。
让我们看看它的实际效果吧!
嗯,您已经看到了该技术背后的理论,并且在一个基础页面上看到了它的演示,但是该技术的实际应用呢?正如我在本章开始时提到的,灵感来自一位同事,他问当打印的人可能只对利率的一部分感兴趣时,是否真的需要打印整页利率。图 8-8 显示页面。
图 8.8。如你所见,这是很长的一页!
在代码中滑动
在概念验证的例子中,我将所有的函数放在一个单独的.js
文件中。这包括了addEvent
功能。如果你有可能在整个网站中不止一次地使用addEvent
功能,最好将.js
文件分成两部分:
-
一个包含
addEvent
函数的文件(它可以在站点的任何地方用于任何目的) -
另一个包含打印处理函数的文件(因为它只需要在得到这种打印处理的页面上调用)
这就是我在实际应用中所做的(相关的利率页面可以在www.nationwide.co.uk/savings/rates.htm
找到)(但是请不要太仔细地研究其余的标记,你可能会感到不安。老实说,我并不能完全控制上线的内容!).以下是文档标题中 JavaScript 的链接:
<script src="/_common_scripts/add_event.js" type="text/javascript">
</script>
<script src="/_common_scripts/print_sections_handler.js"
type="text/javascript"></script>
</head>
这是加入的行为。现在需要的是在当前页面的相关部分添加额外的标记。下面是一个使用最小部分(只有两个表格行)的示例,其中包含打印功能所需的新标记:
<div id="sect4" class="print_section">
<table cellpadding="4" cellspacing="0" width="100%">
<thead>
<tr>
<th>accounts for over 65s</th>
<th>interest tier</th>
<th>AER %</th>
<th>AER* %</th>
<th>gross p.a. %</th>
<th>net p.a. %</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<td class="textbottom" valign="top">
<a href="/pdf/P8865_Sep05.pdf" target="_blank">
Monthly Income 65+</a><br>passbook account
with a guaranteed regular income for the over 65's
<br><br><small>effective from: 01/09/2005</small>
</td>
<td>1+</td>
<td><strong>4.50</strong></td>
<td>n/a</td>
<td>4.41</td>
<td>3.53</td>
<td>
<a href="http://www.multimap.com/clients/places.cgi?
client=nationw_01" onclick="popUpWin(this.href,'console',
600,500);return false;">apply in branch</a><br>
<a href="http://www.nationwide.co.uk/pdf/P8865_Sep05.pdf"
target="_blank"><img src="rates_files/pdf_icon.gif"
alt="view leaflet>view leaflet</a>
</td>
</tr>
</tbody>
</table>
</div>
页面上的每个部分都需要以这种方式标记,id 为 sect#,其中#是部分编号。
标记现在有了必要的钩子,脚本可以抓取并写入打印链接。但是这些看起来怎么样?嗯,比概念验证中显示的要好得多!图 8-9 显示了上下文中链接的建议设计。
图 8.9。打印链接设计
不出所料,这个设计是在 Photoshop 中完成的,但很大程度上是关于如何构建的想法。在图 8-9 的例子中,链接按钮可以使用图像来创建(脚本需要修改,以便写出img
元素而不是文本)。图 8-10 显示了两种方法:第一组链接是背景图片上的 HTML 文本,第二组基于img
元素。
图 8.10。创建链接按钮的两种方法
为了建立在概念验证阶段并进行对等翻译,我将展示如何使用 CSS 背景图像和真实文本的组合来实现这一点,而不是使用img
元素。
重要的:我应该警告你,这种方法的缺点是,当你在页面上放大文本时,按钮的背景图像不会相应地放大,文本可能会脱离按钮区域,如图 8-11 所示。编写一个img
元素可以解决这个问题,但是我会把这个问题留给你去试验(真的,改编脚本来做这件事并不困难!).
图 8.11。当文本位于按钮顶部时,在 Internet Explorer 中放大到最大尺寸的效果
设计打印链接
不管链接采用什么方法,都需要应用一些 CSS。在概念证明中,我使用了一个段落,其中包含了两个链接。这可以用来包含一个背景图像(图 8-10 中的灰色部分包含一个打印机),而链接本身需要背景。图 8-12 显示了组成部件。
图 8.12。用于设计打印链接样式的背景图像和按钮
在查看 CSS 之前,简单回顾一下:这些按钮的 HTML(一旦通过 JavaScript 生成并插入到 DOM 中)完全符合概念验证。此处显示的粗体标记是将动态生成的部分:
<div id="sect1">
<p class="printbutton">
<a href="#" id="printbut_sect1">print this section</a>
<a href="#" id="printall_sect1">print whole page</a>
</p>
<table><!table stuff goes here --></table>
</div>
为了让它看起来像建议的设计,CSS 需要将整个部分推到右边,应用必要的背景,并在第一个链接的左边留出足够的空间,以便小打印机图标可见。下面是让我们做到这一点的 CSS:
p.printbutton
{
background-image:url(print-button-para-bg.gif);
background-repeat:no-repeat;
background-color:#576374;
float:right;
padding:6px 0 0 40px;
}
#sect1 p.printbutton a
{
background-image:url(print-button-bg.gif);
background-repeat:no-repeat;
padding:2px 0;
margin:0 10px 0 0;
color:white;
text-decoration:none;
width:110px;
height:19px;
font-weight:bold;
font-size:x-small;
display:block;
float:left;
text-align:center;
}
齐心协力
脚本已经被插入到文档头中,生成的链接的样式已经被定义,页面已经应用了所有必要的id
来使技术工作。图 8-13 显示了在屏幕上看到的最终结果(在 Firefox 的 Windows 版本中)。
图 8.13。完成的网页
别管那些了,拯救地球怎么样?
啊,那个!我说我会回到主题,这是所有关于节约纸张,避免不必要的打印输出。有了你在本章中看到的 JavaScript 魔法,用户现在可以只打印他们感兴趣的部分,这就是证据。首先,图 8-14 显示了在“之前”应用脚本之前页面的打印输出结果
图 8.14。唷,这么多纸!
并且图 8-15 显示了应用脚本“after”后的结果
图 8.15。好多了!
眼尖的人会注意到,出现在页面上用于有选择地打印某些部分的控件没有显示在打印输出上。你为什么需要打印出来?您不能单击它们或在纸上做任何事情,所以这些动态生成的控件在打印样式表中被关闭,如下所示:
media print
{
p.printbutton
{
display:none;
}
}
您会发现这种技术有许多不同的用途,但本质上,它在有大量内容的地方非常方便,有时可能只需要打印其中的一部分。在我的个人网站上,这项技术首次被提出的原始帖子仍然有效,可供评论(见http://tinyurl.com/p9tqb
),包括人们对改进这项技术的任何建议。
结论
如果我能从这个练习中得出一个结论,那就是很容易因为浏览器做不到而放弃一个想法。有了 DOM、CSS 和 JavaScript,您可以定制构建功能以适应各种创造性的想法,并为长期存在的浏览器问题提供解决方案,这些问题看起来永远无法解决。网络上的印刷品经常被忽视,但事实并非如此。一些微调可以走很长的路。而且在这个过程中还可以拯救一两棵树,这都是好的。
【1】grease monkey 是火狐的一个扩展,允许你给浏览器附加额外的功能。要了解更多信息,一个很好的起点是深入了解 Greasemonkey ( http://diveintogreasemonkey.org/install/what-is-greasemonkey.html
)。
【2】不引人注目的脚本在 HTML 中不需要内联事件处理程序,比如 onclick 或 onmouseover。所有的脚本都是集中的,使得站点范围的管理和维护更加容易(类似于使用共享样式表集中管理站点的表示)。
【3】渐进式改进是一种网页设计方法,它向所有人提供对网页基本版本的访问,但为浏览器增加了一层额外的行为或样式(或两者都有)。您可以在http://en.wikipedia.org/wiki/Progressive_Enhancement
找到关于这项技术的更多信息和相关文章的链接。
【4】如果我们使用 XHTML 2.0,我们可以选择使用 section 元素。更多信息见www.w3.org/TR/xhtml2/mod-structural.html#edef_structural_section
。标记(仅应用少量 CSS 来帮助识别边界):
九、使用 JavaScript 创建动态界面
卡梅隆·亚当斯有一个法律学位和一个科学学位。很自然,他选择了 web 开发这一职业。当被追问时,他自称为“网络技术专家”,因为他喜欢参与图形设计、JavaScript、CSS、Perl(是的,Perl)以及那天早上他喜欢的任何东西。他经营自己的公司,为政府部门、非营利组织、大公司和小型创业公司工作过。
除了帮助他的客户名单,卡梅伦还在全国各地的许多研讨会上授课,并在世界各地的会议上发表演讲,如媒体和网络方向。他在 2006 年发布了他的第一本书,JavaScript 选集,这是关于现代 JavaScript 技术最完整的问答资源之一。
卡梅伦住在澳大利亚的墨尔本,在马拉松比赛间隙,他喜欢踢足球,为愤怒的邻居混音。
针对不同需求的不同布局
随着互联网变得越来越普及,用户开始从越来越多的设备访问网页,如笔记本电脑、台式机、PDA、移动电话、冰箱等。。。谁知道拐角处会有什么?每个新设备都有不同的设计限制。手机肯定没有台式电脑的屏幕空间,所以用户可以和应该查看内容的方式将会大不相同。
即使在同一台设备上,不同的用户也有不同的需求。访问者来你的网站做不同的事情,你的界面应该能够适应所有这些。您希望能够为用户提供适合其所选设备和用途的最佳体验。
本章探索了使用 JavaScript 和 CSS 处理这些设计问题的技术。我称这些为动态接口。我们将着眼于创建两种类型的布局:分辨率相关的布局和用户控制的模块化布局。示例的所有源文件都可以从www.friendsofed.com/
下载。
*# 分辨率相关的布局
很明显,不同的设备有不同的显示能力。这一点在比较移动设备(如 PDA)和它们的桌面兄弟时表现得最为明显。PDA 的分辨率可以低至 240320,而桌面显示器可以高达 20481536 甚至更高。即使在台式机上,分辨率的差异也是惊人的。你的父母可能正在浏览一个(现在是史前的)800600 系统,而你的超级用户姐姐正在浏览一个 1280960 屏幕。两者之间的差异是巨大的,可用面积增加了 150%以上。并且每一种的设计要求同样不同。将宽屏和双屏系统组合在一起,可能性就会爆炸。
有了如此大的差异,说“好吧,创建一个适用于所有分辨率的流畅布局”就不再可行了我们在这里讨论的是页面布局方式的根本区别。分辨率越高,人们看到的信息就越多,因此您的设计应该考虑到这一点。分辨率越低,人们看到的信息就越少,您的设计也应该能够处理这种情况。
问题是,如果你试图让你的设计一刀切,你会遇到不可调和的分歧。在 1280960 中,四列文本可能会为你的内容产生最简洁、最有条理、最可见的结构,但是给一个 800600 显示器上的人四列文本只会产生一团乱麻。相反,在 800600 时工作得相当舒适的单栏设计在 1280960 时看起来会痛苦地拉伸。
传统上,一个答案是简单地限制页面的宽度,将其锁定在一个固定的宽度,在 800 像素(或您选择的基本分辨率)或以上的宽度下产生良好、可读的行长度。然而,这往往不公平地限制了那些利用高质量技术的人。分辨率较高的用户从他们的投资中得不到任何好处;他们只是在内容的两边获得了更多的空间。
最近的统计(www.thecounter.com/stats/2006/August/res.php
)表明,屏幕分辨率为 800600 的用户约占市场的 16%,约 55%的用户分辨率为 1024768,约 21%的用户分辨率高于 1024768(任何特定网站的实际统计数据将因受众而异)。通过设计 800600 的分辨率,你在为少数人的情况惩罚你的大多数用户,尽管是相当大的少数人。
那么,有没有一种方法可以让大屏幕用户从辛苦获得的像素中受益,而不损害低分辨率用户的利益呢?是的,通过提供不同尺寸的不同设计。
起初,这可能听起来工作量过大。但是由于基于标准的布局的灵活性,很可能甚至很容易使用完全相同的 HTML 标记并通过改变 CSS 提供不同分辨率的不同设计。
通过比较 UX 杂志(www.uxmag.com
)和白皮书(www.whitepages.com.au
)网站的不同布局,如图图 9-1 到图 9-4") 所示,你可以看到,页面的设计美学不必为了适应不同的浏览器大小而有很大的改变。大多数情况下,只需对主要内容区域进行重组,就足以创建更流畅的布局,最大限度地利用屏幕空间。针对更宽屏幕优化的布局通常会降低页面高度,从而在折叠上方显示更多内容,使用户无需滚动即可看到页面的更多部分。假设你的标题、菜单、表单和其他网站附件都是用一些漂亮的、健壮的 CSS 编码的,它们应该很容易适应任何一种设计。
图 9.1。UX 杂志网站布局针对 800600 的最大化浏览器窗口进行了优化
图 9.2。UX 杂志网站布局针对 1024768 的最大化浏览器窗口进行了优化
图 9.3。针对较窄的浏览器窗口(宽度小于 1200 像素)优化的白页网站布局
图 9.4。针对更宽的浏览器窗口(宽度大于 1200 像素)优化的白页网站布局
浏览器大小,而非分辨率
尽管窗口大小通常与屏幕分辨率有关,但两者并不相同。浏览器窗口不一定要最大化,所以不能假设浏览器大小会和屏幕分辨率匹配。根据轶事证据,1024768 和更低分辨率的用户倾向于用最大化的窗口浏览,而分辨率更高的用户更有可能用多个未最大化的窗口进行多任务处理。
我在这里提出的解决方案没有基于屏幕分辨率的假设(即使它被称为依赖于分辨率的布局)。我们的目标是浏览器窗口的布局,所以如果大屏幕上的用户碰巧在小窗口中浏览,他们仍然会得到最佳的小窗口布局。
分辨率相关的布局也不仅仅适用于固定宽度的网站。您仍然可以将流畅的布局作为样式表的一部分。这意味着您将获得两个世界的最佳结果:布局不仅考虑了浏览器大小之间的小不一致(侧边栏、滚动条、最大化/非最大化等等),而且还处理了大的变化,为我们提供了足够的额外空间来保证内容的重组。
多个 CSS 文件
创建分辨率相关布局的第一步是创建不同分辨率所需的不同样式表。
交替样式表和样式切换已经被普遍使用了一段时间。人们普遍认为,当用户打印出一页时,他们需要的格式与他们在屏幕上看到的不同。这就是为什么我们有交替打印样式表。许多网站都有替代的样式表,用于更大的文本、高对比度的布局,或者只是简单的不同外观。所以我们在这里要做的是为不同的分辨率制作一个替代的样式表。这通常需要更改内容以获得更大的页面宽度。
与任何替代样式表一样,首先需要定义基本样式。你的目标是哪个基线人群?目前,800600 通常被认为是最低的普通桌面分辨率,所以我们将使用它作为我们的默认大小。
接下来,您需要决定哪个(些)决议将获得一个替代样式表。什么分辨率会从不同的布局中获得最大的好处?您可以为许多不同的分辨率提供许多不同的布局,但是为了简单起见,我们将只创建一个备选样式表。我们将使用 1024768 作为备用布局的开关。它不像 800600 到 12801024 那样激进,但仍然提供了足够的空间来保证布局的改变。
图 9-5 显示了我们的基本设计,针对 800600 进行了优化。它是流动的,所以它也适用于较小的屏幕。图 9-6 显示了我们针对 1024768 和更高分辨率的设计。
图 9.5。针对 800600 优化的示例页面
图 9.6。针对 1024768 优化的示例页面
这两种布局之间有两个主要变化:
-
较大样式的内容呈现在页面的四列中,而较小样式的内容保持在一列中。四列布局可以在更高的分辨率下更好地利用空间,并降低页面高度。在较小的屏幕尺寸下,列变得太窄而没有用,因此单列产生更好的内容宽度。
-
菜单的位置不同。当使用水平菜单时,随着菜单项数量的增加,我们经常会在较小的屏幕上遇到问题。最终,它们会绕成两行。为了解决这个问题,我们将菜单的方向从水平改为垂直,并将其放置在内容旁边。
使用 CSS,通过修改宽度和浮动元素,可以相对容易地实现这两种改变。例如,在 800600 中,三个主要内容面板都使用其默认的浏览器样式:跨越其容器 100%的块元素。为了让它们在 1024768 样式表中并排排列,我们让它们向左浮动并提供一个宽度:
.panel
{
float: left;
width: 26.5%;
}
这两种布局都使用了包含在resolution.htm
中的完全相同的 HTML 标记,所以您只需要修改样式表。
因为备用样式表是主样式表的补充,所以我们继续对 800600 样式表进行编码,就像我们通常所做的那样,无论如何它都会被应用。为了获得更宽的样式表,我们扩展了基本样式。我们不需要编写一个全新的样式表;我们只是修改和增加已经存在的风格。
正如您将在本例的可下载文件中看到的,基本样式表main.css
有 442 行。另一个样式表wide.css
要小得多,只有 190 行。
为了在页面上包含两个样式表,我们使用了link
元素:
<link rel="stylesheet" type="text/css"
href="css/main.css" />
<link rel="alternate stylesheet"
type="text/css" href="css/wide.css"
title="Wide" />
主样式表的rel
属性被设置为stylesheet
,因此它将自动应用于页面。通过将替换样式表的rel
属性设置为alternate stylesheet
,我们向浏览器表明,除非指定,否则不应该应用它。为备用样式表包含一个title
属性也很重要,这样我们可以在以后识别它。
但是当我们需要它的时候,我们如何打开替换样式表呢?
开启风格
我们有几个选项可以向用户提供这种新的依赖于分辨率的样式表,其中大多数都非常无效:
-
在 HTML 中包含替代样式表,并希望用户使用的浏览器允许在替代样式表之间切换,并且意识到有替代样式表,然后会费心打开它,如果它适用于他(不会发生)。
-
使用页面内样式表切换器。在每个页面上创建一个小部件,允许用户指定她希望页面以何种风格显示(就像 Paul Sowden 在他的文章《与列表分离》(on A List Apart,
http://alistapart.com/stories/alternate/
)中描述的那样)。这有点像原生浏览器选项,除了它更容易被更多的浏览器看到和访问。然而,以我的经验来看,用户不会特意去改变页面的默认外观,即使这对他们有好处。 -
自动做。将更宽的样式表提供给能够处理它的浏览器。给用户最好的体验,而不用动一根手指。
你可能已经猜到我们要走第三条路,因为这就是分辨率相关布局的意义所在。方法是使用一些 JavaScript 在页面加载时检测窗口大小,如果合适的话应用更宽的样式表。如果 JavaScript 恰好关闭,页面不会爆炸。相反,用户将获得基本的样式表,如果设计得当,它应该仍然完全可用。就把这种替代布局看作是大屏幕用户和 JavaScript 用户的额外收获吧(这是一个相当大的用户群体)。
JavaScript 应该放在 HTML 中的样式表声明之后:
<link rel="stylesheet" type="text/css" href="css/main.css" />
<link rel="alternate stylesheet" type="text/css" href="css/wide.css" title="Wide" />
<script type="text/javascript" src="scripts/resolution.js"></script>
这很重要,因为 JavaScript 将会立即运行并尝试处理样式表,所以如果它们还没有被包含进来,就会有问题。
在resolution.js
中要做的第一件事是检查浏览器窗口的大小:
checkBrowserWidth();
function checkBrowserWidth()
{
var theWidth = getBrowserWidth();
if (theWidth == 0)
{
addLoadListener(checkBrowserWidth);
return false;
}
if (theWidth >= 960)
{
setStylesheet("Wide");
}
else
{
setStylesheet("");
}
return true;
};
checkBrowserWidth()
首先使用getBrowserWidth()
获得浏览器的宽度:
function getBrowserWidth()
{
if (window.innerWidth)
{
return window.innerWidth;
}
else if (document.documentElement && document.documentElement.clientWidth != 0)
{
return document.documentElement.clientWidth;
}
else if (document.body)
{
return document.body.clientWidth;
}
return 0;
};
实际上有几种方法可以确定浏览器窗口的宽度,这取决于你使用的浏览器,而getBrowserWidth()
中的每个条件都符合其中一个。火狐、Mozilla、Safari、Opera 使用window.innerWidth
。Internet Explorer 6 和 7 使用document.documentElement.clientWidth
。
奇怪的是,document.documentElement.clientWidth
在 Internet Explorer 5 和 5.5 中存在,但它总是被设置为 0,所以我们必须检查这一点,如果是这样的话,就默认为document.body.clientWidth
。唯一的问题是body
元素必须在 Internet Explorer 5 之前存在。 x 可以计算出浏览器窗口的宽度,所以一旦页面被加载,我们必须重新安排对getBrowserWidth()
的调用。这是在checkBrowserWidth()
里面的第一个条件下完成的。如果getBrowserWidth()
返回 0,说明浏览器很可能是 Internet Explorer 5。 x ,所以我们创建了一个新的 load 事件监听器,并在进行任何样式表切换之前等待页面加载。
addLoadListener()
是一个通用的页面加载事件处理程序,它抽象了浏览器事件处理中的一些差异:
function addLoadListener(fn)
{
if (typeof window.addEventListener != 'undefined')
{
window.addEventListener('load', fn, false);
}
else if (typeof document.addEventListener != 'undefined')
{
document.addEventListener('load', fn, false);
}
else if (typeof window.attachEvent != 'undefined')
{
window.attachEvent('onload', fn);
}
else
{
return false;
}
return true;
};
这个和另一个抽象的事件处理程序包含在event_listeners.js
中,因为您可能会在其他项目中经常用到它们。
然而,大多数浏览器不需要经历繁琐的页面加载。checkBrowserWidth()
将立即收到浏览器宽度,并能够检查它。这是通过一个简单的条件来完成的,我们对照预定值检查theWidth
。在我们的例子中,我们使用 960 作为阈值,因为对于分辨率为 1024768 的最大化窗口大小来说,这是一个很好的安全值,如果我们减去浏览器 chrome(如滚动条)的空间。如果您希望您的样式表以不同的分辨率更改,只需将该值更改为您需要的值。
如果窗口宽度大于或等于 960 像素,我们通过使用适当的样式表标题调用setStylesheet()
来激活替代样式表,在本例中是Wide
:
function setStylesheet(styleTitle)
{
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++)
{
if (links[i].getAttribute("rel") == "alternate stylesheet")
{
links[i].disabled = true;
if(links[i].getAttribute("title") == styleTitle)
{
links[i].disabled = false;
}
}
}
return true;
};
setStylesheet()
获取一个样式表标题并遍历页面中的所有link
元素,检查它们的rel
属性是否与alternate stylesheet
匹配。如果那个link
是一个替换样式表,它的disabled
属性被设置为true
(关闭任何未被选择的样式表),然后检查title
属性看它是否是Wide
。如果匹配,disabled
设置为false
(打开样式表)。
该循环完成后,所选的替代样式表将被打开,而所有其他样式表将被关闭。如果浏览器宽度不大于我们的阈值,则使用相同的函数。我们用一个空字符串(""
)调用setStylesheet()
,这将关闭所有可选的样式表,因为它们的标题都不匹配。
一旦所有这些都发生了,正确的样式表将被选择,页面现在应该以适合用户浏览器大小的最佳布局显示。
最后要做的事情是添加一个事件侦听器,以便在有人调整浏览器窗口大小时使用。当某人第一次访问一个页面时,检测浏览器的大小是很好的,但是如果他改变了窗口的大小,我们就要相应地改变布局。为此,我们只需为窗口调整大小事件添加一个事件监听器,再次调用checkBrowserWidth()
:
attachEventListener(window, "resize", checkBrowserWidth, false);
同样,这里我使用了一个通用的事件处理程序(来自event_listeners.js)
,它抽象了浏览器事件处理中的一些差异:
function attachEventListener(target, eventType, functionRef, capture)
{
if (typeof target.addEventListener != "undefined")
{
target.addEventListener(eventType, functionRef, capture);
}
else if (typeof target.attachEvent != "undefined")
{
target.attachEvent("on" + eventType, functionRef);
}
else
{
return false;
}
return true;
};
针对 Internet Explorer 5.x 的优化
如前所述,Internet Explorer 5。x 浏览器在获得浏览器宽度之前必须等待页面加载。交换样式表时,这可能会导致一点视觉闪烁。为了在某种程度上改善这一点,我们可以设置一个 cookie,在用户第一次访问网站时存储浏览器宽度,然后在重复加载时使用这个值。这样,用户只会在第一次访问时得到一个闪烁。为此,我们需要修改checkBrowserWidth()
:
function checkBrowserWidth()
{
var theWidth = getBrowserWidth();
if (theWidth == 0)
{
var resolutionCookie = document.cookie.match(/(^|;) res_layout[^;]*(;|$)/);
if (resolutionCookie != null)
{
setStylesheet(unescape(resolutionCookie[0].split("=")[1]));
}
addLoadListener(checkBrowserWidth);
return false;
}
if (theWidth >= 960)
{
setStylesheet("Wide");
document.cookie = "res_layout=" + escape("Wide");
}
else
{
setStylesheet("");
document.cookie = "res_layout=";
}
return true;
};
现在,一旦浏览器宽度已经确定,样式表已经切换,我们还设置了一个名为res_layout
的 cookie 来存储应用的样式表的标题。在重复访问时,如果浏览器宽度返回为 0(即,它是 Internet Explorer 5。 x ,我们检查res_layout
的 cookie 值是否存在,如果存在,立即相应地设置样式表。这消除了闪烁。我们仍然继续添加调用getBrowserWidth()
的负载监听器,所以如果用户在设置 cookie 后碰巧改变了浏览器的大小,我们可以将布局调整到新的宽度。
我们已经完成了分辨率相关的布局。让我们转向另一种方法。
模块化布局
根据用户的浏览器大小提供静态的布局选择是一回事,但是让用户个人控制他们在你的页面上看到什么以及他们在哪里看到它是另一回事。
模块化布局为用户提供了一个视觉和行为框架,允许他们从根本上改变界面在网页上的实时显示方式。一旦设计者将内容划分为独立的内容模块,这些模块就可以被展开和折叠,它们的顺序可以被重新组织,它们在页面上的位置可以被用户改变。
注意
允许用户定制页面不是你应该随意提供的。如果一个人在一生中只花十分钟的时间,他不太可能会花时间去研究一个界面并根据自己的喜好修改它。然而,当你提供的服务旨在让用户经常回到你的网站,当他们可以通过定制界面获得一些浏览或工作流程效率时,拥有一个可定制的布局可以是一个非常宝贵的工具。最有可能选择可定制布局的是 web 应用程序,在这种应用程序中,用户可能只想执行应用程序提供的任务的一部分,或者他们经常使用应用程序中的特定功能。
图 9-7 显示了一个带有模块布局的样本页面的默认视图,我们将构建它来演示动态布局技术。中心内容保持不变。我们将重点关注两侧的辅助模块。
图 9.7。模块化布局示例的默认视图
图 9-8 显示了一个修改后的界面视图,在用户通过折叠、重组和移动的组合按照自己的喜好重新排列模块后。
图 9.8。模块化布局示例的用户自定义视图
我们使用的例子反映了各种门户页面。用户可能会定期访问该网站以查看不同类型信息的集合,某些信息的重要性因用户而异。
所有的界面定制都必须用 JavaScript 来完成。这意味着不支持 JavaScript 的用户代理将无法访问定制功能。这些用户将收到页面的默认视图,并能够访问所有信息,就像它是一个普通的静态网页一样。
加价
页面内容的高层结构相当简单。左侧的数据模块包含在各自的部分中,接着是中心内容,然后是右侧的更多数据模块:
<div id="modules1">
. . .
</div>
<div id="news">
. . .
</div>
<div id="modules2">
. . .
</div>
这里要注意的最重要的事情是,如果用户将内容从一个模块区域移动到另一个模块区域,它必须被放置到适当的容器中。
注意
网页的 HTML 不必严格遵循 modular.htm 的模板。然而,示例中使用的 JavaScript 和 CSS 是为该标记定制的,需要根据特定页面的布局方式进行调整。
在每个模块区域内,我们使用一个带有类module
的div
将每个模块分成它自己的部分:
<div id="modules1">
<div class="module">
<h2>
Search
</h2>
<div class="moduleContent">
. . .
</div> <!-- END .moduleContent -->
</div> <!-- END .module -->
我们的大部分 JavaScript 将关注这个h2
元素,因为它提供了执行展开/折叠和移动动作的句柄。我们不关心包含在moduleContent div
中的 HTML,因为它是特定于每个模块的。我们只想抽象出我们的框架来处理每个模块块。
展开和折叠模块
扩展和折叠内容的能力并不是一个新概念,实际上非常简单。也许我们能做的最重要的事情就是让它不引人注目,容易接近。
另一方面,没有打开 JavaScript 的用户不应该被提供他们无法访问的功能。这意味着允许展开/折叠的元素应该通过 JavaScript 本身来包含。
JavaScript
当页面上包含expand_collapse.js
时,它会设置一个页面加载监听器,然后遍历每个模块并插入适当的 HTML 元素:
addLoadListener(initExpandCollapse);
function initExpandCollapse()
{
var modules = [document.getElementById("modules1"), document.getElementById("modules2")];
for (var i in modules)
{
var h2s = modules[i].getElementsByTagName("h2");
for (var i = 0; i < h2s.length; i++)
{
var newA = document.createElement("a");
newA.setAttribute("href", "#");
newA.setAttribute("title", "Expand/Collapse");
attachEventListener(newA, "mousedown", mousedownExpandCollapse,false);
newA.onclick = clickExpandCollapse;
var newImg = document.createElement("img");
newImg.setAttribute("src", "img/min_max.gif");
newImg.setAttribute("alt", "Expand/Collapse");
newA.appendChild(newImg);
h2s[i].appendChild(newA);
}
}
return true;
};
页面加载监听器是使用与分辨率相关的布局示例相同的event_listeners.js
文件中的addLoadListener()
函数添加的,并在页面准备就绪时调用initExpandCollapse()
。
initExpandCollapse()
首先创建感兴趣的模块元素的数组,然后遍历该数组,找到所有的h2
元素。对于每个h2
,我们创建一个新的anchor
元素,它包含一个img
元素(我们的小展开/折叠图标),然后我们对那个anchor
应用一些行为。通过使用一个anchor
元素而不是span
或div
,我们确保了展开/折叠功能是键盘可访问的。anchor
元素可通过键盘聚焦,当用户按下回车键激活它们时,会收到一个点击事件。
我们在anchor
标签上捕捉两个事件:鼠标按下和点击。mousedown 侦听器需要抵消我们将用于拖放内容模块的 mousedown。本质上,我们想要监听anchor
上的点击,但是每当有人点击鼠标按钮时,它之前也会有鼠标按下。因为anchor
在h2
内部,这将触发h2
的 mousedown 事件,所以我们需要在anchor
的 mousedown 事件触发时取消h2
的 mousedown 事件。这是通过mousedownExpandCollapse()
: 完成的
function mousedownExpandCollapse(event)
{
if (typeof event == "undefined")
{
event = window.event;
}
if (typeof event.stopPropagation != "undefined")
{
event.stopPropagation();
}
else
{
event.cancelBubble = true;
}
return true;
};
这个简短的函数主要是为了避免浏览器在事件处理上的差异。第一个条件检查一个event
对象是否被传递给了函数本身。这通常由事件侦听器自动完成,但在 Internet Explorer 5 中不会发生。 x 。如果事件对象不存在,我们默认为window.event
,也就是 Internet Explorer 5。 x 用途。
接下来,我们检查event
对象方法stopPropagation()
是否存在。这是阻止事件向上冒泡的 W3C 标准方法(也就是说,阻止anchor
上的 mousedown 事件也在h2
上注册)。如果stopPropagation()
存在,我们称之为。然而,Internet Explorer 使用名为cancelBubble
的event
对象的专有属性来停止事件冒泡。如果stopPropagation()
不存在,我们就把event.cancelBubble
设为true
,这也是一样的效果。
现在我们需要处理点击事件。您可能已经注意到,为了向anchor
添加 mousedown 事件监听器,我们使用了来自依赖于分辨率的布局attachEventListener()
的抽象函数,但是对于 click 事件,我们实际上使用了一个旧的事件处理程序,即onclick
属性。因为 Safari 不允许我们停止使用 W3C 事件侦听器添加的默认点击事件操作,所以我们需要恢复到点属性。实际上,这不会造成太大的问题。我们将事件处理程序添加到由我们自己的脚本创建的元素中,因此它不应该附加任何其他可能冲突的事件信息。
点击事件处理程序指向的函数是clickExpandCollapse()
:
function clickExpandCollapse()
{
if (!hasClass(this.parentNode.parentNode, "collapsed"))
{
addClass(this.parentNode.parentNode, "collapsed");
}
else
{
removeClass(this.parentNode.parentNode, "collapsed");
}
return false;
};
这在包围着h2
的div
上做了一个简单的类切换。如果那个div
没有collapsed
的类,我们添加这个类。如果它有一个collapsed
类,我们删除这个类。这使得完全通过 CSS 来设计折叠和展开模块的不同视图变得容易。
在处理clickExpandCollapse()
中的元素类时,我们总是使用这些自定义函数:hasClass()
、addClass()
和removeClass()
。这使得在同一个元素上使用多个类名变得更加容易。我已经将这些函数包含在它们自己的库文件class_names.js
中,所以它们可以很容易地包含在其他项目中:
function hasClass(target, classValue)
{
var pattern = new RegExp("(^| )" + classValue + "( |$)");
if (target.className.match(pattern))
{
return true;
}
return false;
};
function addClass(target, classValue)
{
if (!hasClass(target, classValue))
{
if (target.className == "")
{
target.className = classValue;
}
else
{
target.className += " " + classValue;
}
}
return true;
};
function removeClass(target, classValue)
{
var removedClass = target.className;
var pattern = new RegExp("(^| )" + classValue + "( |$)");
removedClass = removedClass.replace(pattern, "$1");
removedClass = removedClass.replace(/ $/, "");
target.className = removedClass;
return true;
};
对一个元素的类进行直接的字符串比较是不正确的,因为它实际上可以包含多个类,每个类之间用空格隔开。这与您不应该对类进行直接赋值的原因是一样的,因为您可能会覆盖现有的类。hasClass()
检查您正在搜索的值是否是多个类中的一个。addClass()
确保您不会覆盖任何现有的类或多次添加同一个类。removeClass()
只删除指定的类,而保持所有其他类不变。
现在 click 事件已经就绪,每次用户单击内容模块的展开/折叠链接时,它都会根据需要添加或删除collapsed
类。这就完成了我们的展开/折叠行为!
CSS 样式
既然我们已经向页面添加了一些新元素和类,那么是时候对展开/折叠小部件进行样式化了。扩展/折叠anchor
标签嵌套在h2
中,所以我们可以通过 CSS(来自main.css
)使用它:
.module h2
{
position: relative;
}
.module h2 a
{
position: absolute;
top: 50%;
right: 10px;
width: 9px;
height: 9px;
overflow: hidden;
margin: 4px 0 0 0;
cursor: pointer;
}
.module h2 a img
{
display: block;
}
为了让链接与h2
的右边对齐,我选择了绝对定位,并使用right
定位属性将它放置在距离边缘 10px 的位置(你也可以轻松地将元素向右浮动)。
h2
本身是相对定位的,这个对主播有重要作用。通常,当你绝对定位一个元素时,它将相对于整个页面定位;例如,如果您将它的顶部设置为 0,它将移动到页面的顶部。然而,当你将一个绝对定位的元素(比如我们的anchor
)嵌套在一个相对定位的元素(比如我们的h2
)中时,anchor
将会相对于h2
进行定位。所以当我们设置anchor
的right
位置时,它将从h2
的右边开始定位。
链接的尺寸取决于它所包含的图像的大小。小加号和减号图标的大小是 9px 乘 9px,所以这应该是链接的大小。实际上,包含图标的图像的大小是 9px 乘 18px。这是因为两个图标在同一个图像文件中,如图图 9-9 所示。
通过将两个图标放在同一个图像文件中,我们可以通过 CSS 影响展开/折叠链接的显示。通过将链接本身限制为仅一个图标的大小,然后将hidden
值应用到overflow
属性,我们有效地裁剪了图像的其余部分,使其看起来像只有一个图标。然后,在main.css
里,我们可以在它的“盒子”里移动图像,将图标从减号切换到加号:
图 9.9。用于展开和折叠的两个图标组合在一幅图像中
.collapsed h2 a img
{
position: relative;
top: 9px;
}
默认情况下,将显示减号图标,表示您可以折叠该模块。但是一旦模块被折叠,我们使用collapsed
类来指定图标图像现在应该相对于它的包含元素(链接)向上移动 9px,从而显示加号图标。
显示和隐藏模块的实际内容更加容易:
.collapsed .moduleContent
{
display: none;
}
现在展开和折叠的样式与行为相匹配,如图 9-10 和 ?? 9-11 所示,我们都完成了!
图 9.10。默认扩展视图中的日历模块
图 9.11。折叠后的日历模块
重组模块
展开/折叠是容易的部分。真正让用户控制的是能够指定模块本身的布局。我们将允许用户抓住一个模块的标题栏,拖动它在它的容器位置上下移动,或者把它换到另一个模块区域。因为重组行为完全独立于展开/折叠行为,所以我们可以将它包含在一个不同的文件modular.js
中,以便于维护。
拖放事件监听器
设置新行为的第一步是为拖放操作创建事件侦听器:
addLoadListener(initModular);
function initModular()
{
var modules = [document.getElementById("modules1"), document.getElementById("modules2")];
for (var i = 0; i < modules.length; i++)
{
var h2s = modules[i].getElementsByTagName("h2");
for (var j = 0; j < h2s.length; j++)
{
addClass(h2s[j].parentNode, "moduleDraggable");
attachEventListener(h2s[j], "mousedown", mousedownH2, false);
}
}
return true;
};
我们使用addLoadListener()
添加了另一个页面加载监听器,但是这一次它调用的是initModular()
,它很像initExpandCollapse()
,遍历模块区域,找到每个h2
元素。对于每个h2
,我们向其父类div
添加一个新类moduleDraggable
,并在h2
上创建一个鼠标按下事件监听器。我们将这个类放在h2
的父类div
上,这样一旦我们知道 JavaScript 已启用,我们就可以添加一些样式调整。我们想给用户一个提示,模块是可移动的(否则,他们可能不会意识到这个事实),所以我们在main.css
中使用这个 CSS 规则:
.moduleDraggable h2
{
cursor: move;
}
当用户将鼠标悬停在一个可拖动的h2
元素上时,光标变为移动光标,如图 9-12 中的所示。
通过收集信息和设置变量,mousedown 监听器在h2
上调用的函数为模块的拖放行为做了大量工作:
图 9.12。当鼠标光标悬停在模块的标题栏上时,它的外观会发生变化。
function mousedownH2(event)
{
if (typeof event == "undefined")
{
event = window.event;
}
if (typeof event.target != "undefined")
{
dragTarget = event.target.parentNode;
}
else
{
dragTarget = event.srcElement.parentNode;
}
dragOrigin = [event.clientX, event.clientY];
dragHotspots = [];
var modules = [document.getElementById("modules1"), document.getElementById("modules2")];
for (var i = 0; i < modules.length; i++)
{
var divs = modules[i].getElementsByTagName("div");
for (var j = 0; j < divs.length; j++)
{
if (divs[j] != null && hasClass(divs[j], "module"))
{
var modulePosition = getPosition(divs[j]);
dragHotspots[dragHotspots.length] =
{
element: divs[j],
offsetX: modulePosition[0],
offsetY: modulePosition[1]
}
}
}
var modulePosition = getPosition(modules[i]);
dragHotspots[dragHotspots.length] =
{
element: modules[i],
offsetX: modulePosition[0],
offsetY: modulePosition[1] + modules[i].offsetHeight
}
}
var position = getPosition(dragTarget);
var ghost = document.createElement("div");
ghost.setAttribute("id", "ghost");
document.getElementsByTagName("body")[0].appendChild(ghost);
ghost.appendChild(dragTarget.cloneNode(true));
ghost.style.left = position[0] + "px";
ghost.style.top = position[1] + "px";
attachEventListener(document, "mousemove", mousemoveDocument, false);
attachEventListener(document, "mouseup", mouseupDocument, false);
event.returnValue = false;
if (typeof event.preventDefault != "undefined")
{
event.preventDefault();
}
return true;
};
在它完成正常的事件对象抽象之后,mousedownH2()
接着开始寻找哪个元素被点击了。这在理论上可以通过引用this
对象来实现,但是不幸的是,当使用 W3C 事件模型添加事件时,Internet Explorer 不能正确地分配这个对象。相反,我们必须依赖于event
对象的目标元素属性,如果在我们的事件目标中有嵌套的元素,这可能是不准确的,但是在这里没有问题,因为h2
是这个分支中最深的元素。自然,Internet Explorer 不会像其他浏览器那样将这个元素称为target
;在 ie 浏览器里是srcElement
。所以我们必须检查event.target
是否存在(标准属性),如果不存在,就使用event.srcElement
(Internet Explorer 属性)。
一旦这些差异得到解决,我们就在一个名为dragTarget
的全局变量中创建一个对目标元素的父节点(围绕着h2
的div
)的引用,这样我们以后就可以在其他函数中使用它。
识别出目标后,我们创建一些全局变量,帮助跟踪用户拖动模块时发生的情况。dragOrigin
存储原始鼠标按下事件的坐标。每当事件被触发时,event.clientX
和event.clientY
记录两个坐标,这两个坐标定义了页面上事件发生的位置;在这种情况下,用户按下鼠标按钮。存储这个原始位置很重要,因为当用户移动鼠标时,我们想知道她从最初点击的位置移动了多远。
一页地图
是一个数组,存储了你可以拖动一个模块的所有可能的位置。你可以把它想象成一种地图。当用户拖动一个模块时,他们不会改变它在页面上的任意位置;相反,它们实际上改变了它相对于其他模块的位置。因此,我们没有用绝对坐标来指定一个模块的目的地,而是试图用它在其他模块中的位置来描述它的目的地:“将搜索模块移动到日历模块之下,星座模块之上。”为了做到这一点,我们需要创建一个页面地图,指示所有其他模块的位置。这样,当被拖动的模块在页面中移动时,我们知道被拖动的模块应该在每个静态模块的上面还是下面。不过,所有计算都是由 mousemove 处理程序完成的。目前,我们只关心创建地图。
我们可以在 mousemove 处理程序中创建地图,但是每次鼠标移动时,都必须重新进行计算。只需按下鼠标按钮一次就可以创建地图,这样效率更高。
为了创建映射,我们查看每个模块区域的内部,并遍历所有具有类module
的div
。对于那些div
,我们在dragHotspots
数组中创建一个新对象,它保存对模块的引用(element
)、模块左上角的水平页面偏移量(offsetX
)和模块左上角的垂直页面偏移量(offsetY
)。最后两个属性从getPosition()
函数中获取值,该函数获取一个元素并返回其位置:
function getPosition(theElement)
{
var positionX = 0;
var positionY = 0;
while (theElement != null)
{
positionX += theElement.offsetLeft;
positionY += theElement.offsetTop;
theElement = theElement.offsetParent;
}
return [positionX, positionY];
};
获取元素在页面上的位置过于复杂,但对此无能为力。元素只能辨别其相对于“偏移父元素”的位置(偏移父元素根据其定位、浮动等方式而变化)。为了获得它在页面上的绝对位置,我们必须递归地沿着偏移父树向上(使用element.offsetParent
),获得每个偏移父树的位置,并将它们加在一起返回一个总数。
一旦一个模块区域中的每个模块都被添加到dragHotspots
中,有一种特殊的情况是我们将模块区域本身添加到dragHotspots
中。这是为了让我们能够识别被拖动的模块何时位于模块区域中所有其他元素的下方;在这种情况下,我们把它移到最后。
鬼魂
创建地图后,我们需要创建可视元素,向用户指示他们正在拖动什么东西。它实际上是用户拖动的模块的副本,我们通过改变它的透明度使它看起来像幽灵一样,所以我称它为幽灵。
我们使用getPosition()
获得被拖动模块的当前位置,然后创建新的ghost
元素。这是一个绝对定位的空div
,包裹着被拖动模块的直接副本。空的div
代替了模块区域div
的约束,给了模块一个要填充的宽度;否则,它将扩展到 100%的宽度。因为它被绝对定位并插入到 body 元素的末尾,所以它可以自由地移动到用户鼠标光标移动的任何地方。
使用cloneNode()
方法创建被拖动模块的副本。cloneNode()
接受一个参数,该参数指定是希望节点的内容也被复制,还是只复制节点本身。通过给dragTarget.cloneNode(true)
打电话,我们说我们想要一份dragTarget
和的内容。我们将该副本添加到空壳中,然后将它放在原始模块上,这样看起来就像用户将幽灵拖出了它的身体。通过应用一点 CSS 不透明度,我们可以给幽灵一个幽灵般的外观:
#ghost .module
{
opacity: 0.65;
filter: alpha(opacity=50);
}
你可以在图 9-13 中看到结果。
为了跟踪按钮被按下时用户移动鼠标的位置,我们需要向整个文档添加一个 mousemove 侦听器。我们添加了一个 mouseup 侦听器来告诉我们用户何时释放了鼠标按钮。
图 9.13。原模块旁边的模块的幻影副本
现在我们已经为用户拖动模块做好了充分的准备。mousedownH2()
做的最后一件事是停止鼠标按下的默认动作。这可以防止浏览器在用户按下鼠标按钮时正常工作。在拖放的情况下,当拖动重影时,它会阻止页面上的文本被选中。
随着用户四处移动鼠标,文档 mousemove 事件监听器将不断调用mousemoveDocument()
:
function mousemoveDocument(event)
{
if (typeof event == "undefined")
{
event = window.event;
}
var ghost = document.getElementById("ghost");
if (ghost != null)
{
ghost.style.marginLeft = event.clientX - dragOrigin[0] + "px";
ghost.style.marginTop = event.clientY - dragOrigin[1] + "px";
}
var closest = null;
var closestY = null;
for (var i in dragHotspots)
{
var ghostX = parseInt(ghost.style.left, 10) + parseInt(ghost.style.marginLeft, 10);
var ghostY = parseInt(ghost.style.top, 10) + parseInt(ghost.style.marginTop, 10);
if (ghostX >= dragHotspots[i].offsetX -
dragHotspots[i].element.offsetWidth && ghostX <=
dragHotspots[i].offsetX + dragHotspots[i].element.offsetWidth)
{
var distanceY = Math.abs(ghostY - dragHotspots[i].offsetY);
if (closestY == null || closestY > distanceY)
{
closest = dragHotspots[i];
closestY = distanceY;
}
}
}
if (closest != null)
{
var ghostMarker = document.getElementById("ghostMarker");
if (ghostMarker == null)
{
ghostMarker = document.createElement("div");
ghostMarker.id = "ghostMarker";
document.getElementsByTagName("body")[0].appendChild(ghostMarker);
}
ghostMarker.marked = closest.element;
ghostMarker.style.left = closest.offsetX + "px";
ghostMarker.style.top = closest.offsetY + "px";
}
else
{
var ghostMarker = document.getElementById("ghostMarker");
if (ghostMarker != null)
{
ghostMarker.parentNode.removeChild(ghostMarker);
}
}
event.returnValue = false;
if (typeof event.preventDefault != "undefined")
{
event.preventDefault();
}
return true;
};
这个函数的首要任务是改变重影的位置,使它看起来像是在跟随用户的鼠标光标。我们通过计算用户从最初的鼠标按下点移动了多远来做到这一点,然后在 ghost div
上设置边距来反映这一差异。使用event.clientX
和event.clientY
再次获得鼠标光标的当前坐标,全局dragOrigin
变量用于原始鼠标按下坐标。
mousedownDocument()
然后需要使用我们的其他模块的映射来找出被拖动模块的新插入点。我们知道 ghost div
的位置,并且我们知道每个静态模块左上角的位置,所以很容易计算出 ghost 离哪个静态模块最近。我们希望在静态模块之前插入被拖动的模块。
对于dragHotspots
中的每一个条目,我们都快速测试一下幽灵的水平位置。如果 ghost 的任何部分都不在静态模块所在的模块区域的宽度内,我们就不希望将它视为被拖动模块的可行目的地。因此我们检查重影的右边缘是否在静态模块左边缘的右侧,以及重影的左边缘是否在静态模块右边缘的左侧。这意味着重影的某些部分与模块区域的宽度重叠。
然后,我们可以关心幽灵和静态模块之间的垂直距离。为此,我们测量重影顶部和静态模块顶部之间的差异,然后将其与已经找到的最小值进行比较。如果到当前静态模块的距离小于当前最小值,则该值成为新的最小值。在检查完所有的dragHotspots
条目后,我们知道是否有合适的位置可以插入被拖动的模块,如果有,在哪里。
如果有合适的位置,我们需要向用户显示,这样他们就知道如果释放鼠标按钮,拖动的模块将被插入到哪里。为此,我们创建了一个名为ghostMarker
的新元素,它只是一个位于候选插入点顶部的方形小块。ghost 标记是body
元素的绝对定位子元素,所以为了定位它,我们使用最近的静态模块的坐标。正如你在图 9-14 中看到的,通过一点 CSS 样式,ghost 标记很好地指出了被拖动的模块将被插入的位置。
图 9.14。显示拖动模块插入位置的重影标记
为了便于以后插入被拖动的模块,我们还创建了一个新的属性ghostMarker
,它记录了正在标记哪个静态模块:ghostMarker.marked
。
如果没有一个合适的位置来插入被拖动的模块,那么当我们实际移除ghostMarker
时,用户没有将 ghost 拖动到足够靠近某个静态模块的位置。因此,如果用户将重影从一个合适的位置移动到一个不合适的位置,它仍然不会表明他可以重新定位被拖动的模块。
mousemoveDocument()
中的最后一个操作再次停止了浏览器通常为鼠标移动执行的默认动作,防止用户拖动时发生不必要的动作。
重新定位的模块
当用户决定将拖动的模块放在哪里,或者不想移动它时,他将释放鼠标按钮。这是由mouseupDocument()
捕获的:
function mouseupDocument()
{
detachEventListener(document, "mousemove", mousemoveDocument, false);
var ghost = document.getElementById("ghost");
if (ghost != null)
{
ghost.parentNode.removeChild(ghost);
}
var ghostMarker = document.getElementById("ghostMarker");
if (ghostMarker != null)
{
if (!hasClass(ghostMarker.marked, "module"))
{
ghostMarker.marked.appendChild(dragTarget);
}
else
{
ghostMarker.marked.parentNode.insertBefore(dragTarget, ghostMarker.marked);
}
ghostMarker.parentNode.removeChild(ghostMarker);
}
return true;
};
大部分繁重的工作已经完成,所以这个函数只是把事情整理一下。首先,它从文档中删除了 mousemove 事件侦听器,因为我们不再需要知道用户何时移动鼠标。之后,我们移除ghost
元素,因为我们想要停止显示副本。
然后我们决定将拖动的模块放在哪里。如果ghostMarker
仍然存在,意味着用户在有一个有效的地方移动被拖动的模块时释放了鼠标按钮。使用ghostMarker.marked
属性,我们可以看到哪个元素之前插入了被拖动的模块。我们需要检查被标记的模块是否真的是一个模块,或者它是否是我们放在模块区域末尾的额外占位符。如果是一个实际的模块(它有一个module
类),我们可以使用insertBefore()
方法将被拖动的模块移动到被标记的模块之前。如果是一个模块区域,我们使用appendChild()
将被拖动的模块放在区域的末尾。无论哪种方式,我们都删除了重影标记,被拖动的模块现在就在它的新位置结束了,就在用户想要的地方!如果ghostMarker
不再存在,我们知道被拖动的模块无处可去,所以我们可以安静地完成。
图 9-15 显示了用户将一个模块从一个模块区拖动到另一个模块区的全过程。
图 9.15。将一个模块拖到另一个模块区
跟踪变化
本例中介绍的技术创建了一个用户可以随心所欲定制的页面。但是,如果他们第二天返回到该页面,而该页面没有保留他们的任何更改,那么这将是徒劳的。
虽然可以从服务器下载页面,并通过 JavaScript 重新排列模块,但这是一个相当笨拙的过程,可能会导致用户在页面切换到他们的定制布局之前看到默认布局。
最好的解决方案是在服务器上组装自定义布局,并按照用户的安排提供给他们。实现这一点最简单的方法是在用户的浏览器中存储一个 cookie,告诉服务器用户布局的顺序,服务器可以用它来组装适当的页面。
我们所要做的就是跟踪哪些模块在哪个模块区域以及它们的顺序。为此,我们需要确保每个模块在我们的标记中都有一个惟一的 ID。一旦完成,我们可以修改mouseupDocument()
函数来编写一些包含偏好数据的 cookies。这个更新的功能包含在modular_cookie.js
(你可以包含它而不是modular.js
):
function mouseupDocument()
{
detachEventListener(document, "mousemove", mousemoveDocument, false);
var ghost = document.getElementById("ghost");
if (ghost != null)
{
ghost.parentNode.removeChild(ghost);
}
var ghostMarker = document.getElementById("ghostMarker");
if (ghostMarker != null)
{
if (!hasClass(ghostMarker.marked, "module"))
{
ghostMarker.marked.appendChild(dragTarget);
}
else
{
ghostMarker.marked.parentNode.insertBefore(dragTarget, ghostMarker.marked);
}
ghostMarker.parentNode.removeChild(ghostMarker);
var modules1 = document.getElementById("modules1");
var modules1Modules = [];
var divs = modules1.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++)
{
if (hasClass(divs[i], "module"))
{
modules1Modules[modules1Modules.length] = divs[i].getAttribute("id");
}
}
document.cookie = "modules1=" + modules1Modules.join(",");
var modules2 = document.getElementById("modules2");
var modules2Modules = [];
var divs = modules2.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++)
{
if (hasClass(divs[i], "module"))
{
modules2Modules[modules2Modules.length] = divs[i].getAttribute("id");
}
}
document.cookie = "modules2=" + modules2Modules.join(",");
}
return true;
};
现在,当一个模块被重新定位时,会写入两个 cookies。对于每个模块区域,找到具有类module
的div
,并将它们的 id 添加到数组中。该数组将反映模块的顺序,因为getElementsByTagName()
按源代码顺序返回元素。一旦数组完成,它就被写入一个 cookie 中,根据具体情况,可以是modules1
或modules2
,每个 id 用逗号分隔。
当这些 cookies 被传递到服务器时,您应该能够使用它们来检查模块的顺序,并以适当的顺序将它们写入 web 页面。
结论
用户需求的多样性——无论是技术性的还是基于任务的——正在挑战传统的静态网页形式。创建动态接口是满足这些需求的方法之一。我在这里描述的两种技术展示了基于标准的 web 页面的灵活性如何改变我们为 Web 设计的方式。
*
十、无障碍滑动导航
德里克
费瑟斯通
boxofchocolates.ca
furtherahead.com
德里克·费瑟斯通(Derek Featherstone)迷人、令人惊讶、鼓舞人心,他有一种天赋,能够以全新的视角看待 web 开发的几乎每一个方面,并以一种重新点燃我们的热情的方式进行教学,让 Web 为每个人变得更好。费瑟斯通是国际知名的可访问性和 web 开发权威,也是受人尊敬的技术培训师和作家。
作为关于 HTML、CSS、DOM 脚本和 Web 2.0 应用程序的深入课程的创建者,Derek 从不忘记支持 Web 标准和通用可访问性的事业。自 1999 年以来,他通过自己的公司 the Further Ahead(www.furtherahead.com
)成为政府机构、教育机构和私营企业的抢手顾问。他丰富的经验和洞察力使他能够为观众提供直接适用的,非常简单的方法来应对网站设计中的日常挑战。
Derek 是可访问性任务组的负责人,也是 Web 标准项目的 DOM 脚本任务组的成员。他还在自己广受欢迎的博客和个人网站www.boxofchocolates.ca
上评论各种话题。
杀手锏
承认吧。你想要它。你知道,你网站上的杀手锏让博客圈说“aaaaaaahhhhhh。”为你赢得重启奖的那个。让你的网站脱颖而出的那个。好吧,算了。通过为访问者提供更容易的信息和链接,让他们的生活变得更容易的那个呢?
我们现在谈论的是老式动态 HTML 菜单系统的替代品,这种系统只需简单的鼠标悬停和点击,就可以访问所有的分类和子分类链接。这个替代品是滑动导航系统,它显示了很好分类的链接。
肖恩·因曼的博客(www.shauninman.com
)在他 2005 年的重新设计中展示了这一技术,完成了一个滑动导航和搜索标签,隐藏和暴露了他的网站的细节。他的博客类别、最近的帖子、搜索框和其他好东西都被隐藏起来,直到被一些忠实的 JavaScript 调用。
网站设计的另一个最新趋势是信息丰富的页脚。德里克·波瓦泽克在他 2005 年的重新设计中“拥抱了他的底部”(www.powazek.com/2005/09/000540.html
),使他的网站的页脚非常突出和有用,而不是模糊不清和充满版权声明(见图 10-1 )。他有这个行业中最好的助手之一。它的设计目的和意图是激励、提供更多信息,并为访问者提供背景。
在这一章中,我们将实现一个将滑动导航和信息丰富的页脚结合到一个系统中的网站,该系统为当今许多网站上的传统“选项卡式”导航增加了细节和可用性增强。而且,正如您所料,它从一开始就将可访问性考虑在内。
图 10.1。德里克·波瓦泽克博客的页脚。与普通的页脚相比,Derek 的页脚更有用,它为访问者提供了更多的上下文和与博客相关的功能。
辅助功能基础知识
网页可访问性最好被描述为一套指导原则或指南,帮助我们制作适合所有不同类型用户的网站,不管他们的能力如何。当我们编写网站代码时,我们努力将这些不同水平的能力考虑在内
-
视力障碍(不同程度的失明、低视力和色盲)
-
移动性或灵活性障碍(需要使用语音识别软件或硬件辅助工具来方便键盘使用或帮助补偿不同水平的精细运动控制)
-
听觉障碍(例如,需要多媒体音频和视频的字幕和/或抄本)
-
认知障碍(包括不同程度的阅读障碍、自闭症和其他学习障碍)
为了考虑到这种广泛的能力,我们需要做一些简单的事情。我们确保图像有适当的替代文本,以便视力受损的人有替代的表示,他们的屏幕阅读器软件可以读给他们听。我们实现了一些解决方案,允许用户调整页面上的文本大小,以便更容易阅读。我们在前景色和背景色之间有适当的颜色对比。我们标记表单域,使用结构化标记来确保屏幕上的元素使用最好的 HTML 元素,如标题、段落、列表、表格、表单按钮等等。
建设无障碍网站的一个长期问题是,作为开发人员、设计人员和内容创建者,我们几乎没有与残疾人一起工作的实践经验,以了解他们的真正需求以及我们如何避免为他们设置无障碍障碍。进入万维网联盟的网页可访问性倡议(www.w3.org/WAI
)。
无障碍指南
Web Accessibility Initiative (WAI)致力于让每个人都能更方便地访问 Web,并制定了一套指导原则来帮助开发人员构建可访问的网站。不用担心;这不完全取决于我们。WAI 还为浏览器制造商和创作工具供应商制定了指导方针,以便他们能够确保人们使用的工具能够促进可访问性。这些其他的指导方针用户代理可访问性指导方针(UAAG)和创作工具可访问性指导方针(ATAG)是重要的,但是超出了我们作为开发人员所做的范围。我们应该坚定地把注意力放在网站内容无障碍指南上(WCAG)。
WCAG ( www.w3.org/WAI/intro/wcag.php
)是一套指导方针,为我们创建无障碍网站提供了一些起点。然而,这并不意味着简单地遵循指南就能保证我们的网站是可访问的。为了创建可访问的网站,我们通常需要遵循指南中的原则,然后与使用辅助技术的人一起测试我们生成的网页。这确保了我们可以两全其美。我们通过遵守指导方针来实现技术合规性,并通过对残疾人的测试,我们创造了对他们实际有用的东西。
本章中的示例研究了 WCAG 的许多原理,包括用各种屏幕阅读器、语音识别软件和屏幕放大器进行测试的结果。
注意
要详细深入地了解可访问性,请阅读《Web 可访问性:Web 标准和法规遵从性》(编辑之友,ISBN: 1-59059-638-2)。
可访问性和 JavaScript
多年来,可访问性和 JavaScript 都是精通某个专业领域的专家的领域。这一领域的工作一直受到 web 开发人员代代相传的长期神话的阻碍。这个神话非常简单:为了确保你的网页是可访问的,它必须与 JavaScript 一起工作。
这种误解在很大程度上是由于 WCAG 检查站 6.3 ( www.w3.org/TR/WCAG10/wai-pageauth.html#tech-scripts
)造成的,该检查站规定:
注意
当脚本、小程序或其他编程对象被关闭或不受支持时,确保页面可用。如果不可能,请在另一个可访问的页面上提供等效信息。
1999 年,当 WCAG 1.0 发布时,这是一个合理的指导方针。屏幕阅读器和浏览器远没有今天这么先进。人们普遍认为(并传播)屏幕阅读器不理解 JavaScript。开/关场景曾经相当准确。
今天的屏幕阅读器确实理解了今天的浏览器所支持的大部分 JavaScript,然而许多人仍然坚持认为这是不可能的。他们坚持 WCAG 检查点 6.3 所必需的假设:作为二进制开/关场景的可访问性和脚本。
进入网络可访问性和辅助技术的现代时代。是的,一些残障人士可能会使用无法处理脚本的浏览器,甚至关闭 JavaScript,但大多数人可能会使用支持脚本的常规浏览器。这不再是一个非黑即白的问题。确保一个页面在脚本打开或关闭的情况下工作更多的是关于互操作性,而不是 ?? 的可访问性。
因此,创建可访问的 JavaScript 的技巧不仅仅是确保我们的解决方案可以打开或关闭 JavaScript。我们还必须确保我们的解决方案与不同能力的用户和辅助技术兼容。在我们学习本章的其余部分时,请记住这一点。
无障碍解决方案
根据我们的经验,基于标准的 web 开发方法为残障人士提供了基本的可访问性以及与不同设备的基本互操作性:结构化 HTML、用于表示的 CSS 以及用于提供行为的最终 JavaScript 层。这种方法将在本章的例子中使用,以确保我们已经涵盖了基础知识。我们将使用语义 HTML,使用 CSS 进行设计,并交付一个可以打开或关闭 JavaScript 的解决方案。一旦我们完成了这些,我们将添加更多的脚本来确保系统对于使用各种辅助技术的人来说工作良好。
在我们开始编写代码之前,让我们先来看一下解决方案的简要概述以及实现这一切的文件(这些文件可以从本书的页面www.friendsofed.com
下载):
-
基本 HTML 文件,
wscslide.html
-
样式表,
wscslide.css
-
JavaScript 函数,
wscslide.js
图 10-2 显示了我们试图用滑动导航实现的效果的简单线框。当页面加载时,在任何 JavaScript 生效之前,导航窗格的初始位置在内容之后的底部。一旦最近文章的主导航链接被激活,我们使用一些基本的 JavaScript 来改变应用于导航窗格的 CSS,使其在视觉上位于导航下方。
一旦我们做到了这一点,我们将使用一些额外的 JavaScript 来设置该窗格的高度为 0,然后通过一步一步地改变该高度来创建一个滑动效果,将高度返回到其初始值。例如,如果最近的文章窗格的原始高度是 180 像素,我们将高度设置为 0,然后将其更改为 90 像素,然后 135 像素,然后 158 像素,依此类推,直到高度回到其原始大小 180 像素。
在最终的解决方案中,导航窗格的初始状态将如图 10-3 所示。
图 10.2。滑动导航功能的线框
图 10.3。导航、内容和附加导航窗格在默认位置的初始状态
点击主链接后,导航窗格将滑动到最终位置,如图图 10-4 所示。
图 10.4。“暴露”状态,其中一个导航窗格位于其新位置,视觉上位于主导航的正下方
它们代表了每个选项卡的两种“状态”:当信息被隐藏时和当信息被暴露时(当我们稍后检查解决方案的 CSS 和 JavaScript 部分时,这将是很重要的)。
从原始的 HTML 开始
为了使任何网站或 web 应用程序工作,我们需要一个通过干净的 HTML 提供的内容和功能的坚实基础。HTML 不仅应该在内容上有意义,而且应该在功能上有意义。尽管大多数人倾向于认为 HTML 只是内容,但它确实通过表单域和链接提供了基本的功能。在本章的后面,我们将使用这些链接作为脚本交互部分的基础。
对于这个解决方案,我们需要实现 HTML 的三个主要方面:
-
航行区域
-
主要内容
-
页脚中的附加内容
导航将由一个无序列表组成,每个条目包含一个到相关页脚部分的链接。注意,这些hrefs
只是指向页面的另一部分。
<ul id="nav">
<li><a href="#drop-posts">Recent Posts</a></li>
<li><a href="#drop-services">Services</a></li>
<li><a href="#drop-about">About Us</a></li>
</ul>
对于这个实现来说,主要内容基本上是不相关的。在这个例子中,它的主要目的是确保当访问者第一次到达页面时,页脚内容是不可见的。
页脚内容相当简单,包括几个部分。在本例中,我们使用了一个About
部分、一个Recent Posts
部分和一个Services
部分。当详细查看该部分时,这些内容区域中的每一个都提供了对完整内容的一瞥,提供了各种摘要。我们将每个页脚部分包装在自己的<div></div>
中,以便稍后提供适当的样式。
请注意,在本例中,Recent Posts
窗格中链接的href
是空白的,纯粹是为了方便。在文档中包含这样的空白链接会让所有用户感到困惑,尤其是那些有残疾的用户,尤其是那些使用屏幕阅读器软件的用户。
<div id="drops">
<div id="drop-posts">
<h2>Recent posts:</h2>
<ul>
<li><a href="">Post 1</a></li>
<li><a href="">Post 2</a></li>
<li><a href="">Post 3</a></li>
<li><a href="">Post 4</a></li>
<li><a href="">Post 5</a></li>
<li><a href="">Post 6</a></li>
</ul>
</div>
<div id="drop-services">
<h2>Services</h2>
<p>things about services go here</p>
</div>
<div id="drop-about">
<h2>About Us</h2>
<p>Stuff about you goes here</p>
</div>
</div> <!-- ends drops -->
每个div
的ids
在本例中只是为了清晰起见,并不一定是最佳选择。在包含所有附加导航窗格的div
上放置一个drops
的id
意味着一个非常特殊的功能。如果我们以后改变这个网站,使它不再使用滑动导航,名称drops
可能会令人困惑。最好给包装器div
一个id
来更好地表示它的功能,比如supplemental
。然而,为了识别我们在这个例子中实现的行为,我们将保持id
为drops
。
还要注意,我们在每个页脚部分添加了标题,为该部分提供了一种“标记”。为了举例,我们将这些标记为<h2>
元素。您将需要决定<h2>
是否适合您的使用,或者其他更低级的标题是否更合适。
这些代码块将作为我们其余实现的基础。我们将在添加样式和功能时对其进行修改。
添加演示文稿
这个例子的 CSS 相当简单,大部分都是为了修饰这个例子。我们添加一些样式来布局基本页面,指定颜色和背景图像,并格式化文本。有了这些,我们可以专注于页脚本身和我们需要的样式,让导航从顶部向下“滑动”。
最终完成 CSS 需要一些工作,但是核心布局保持不变。对于这个版本的滑动导航,我们使用绝对和相对定位将导航窗格从页面底部移动到顶部。正是这个动作形成了核心 CSS“开关”,使导航成为可能并保持其可访问性。主要机制只是一个链接,链接到包含我们想要的信息的页面的另一部分。当一个窗格被暴露时,它会占据页面顶部的位置,就像前面图 10-4 中的窗格一样。
使用 JavaScript 在 CSS 状态之间切换
在 JavaScript 因其在 Web 上的使用方式而遭到诋毁的时代,内联脚本、事件处理程序和样式属性的修改只是简单的做事方式。这些特别的变化通常与修改和定义飞行高度、宽度、背景颜色和各种其他样式属性有关。
注意
我们已经从那些黑暗的日子里取得了显著的进步,并努力将我们的 HTML 从我们的 CSS 和 JavaScript 中分离出来。现代技术努力确保我们的页面在打开和关闭时都能使用 JavaScript。这被称为“不引人注目的脚本”,是本书其他地方以及其他现代脚本书籍中的标准,如 Jeremy Keith 的书 DOM Scripting(ED 之友,ISBN: 1-59059-533-5)。
在我们目前的网络浏览器中,我们有一个更加可预测的工作环境。这种可预测性使我们能够改变高度和宽度之外的属性,并在包括 CSS 定位在内的预定义状态之间进行切换。将所有需要的 CSS 更改存储在一系列 CSS 规则中,我们可以简单地使用一些 JavaScript 来根据需要更改适当的包含元素的类。
出于滑动导航的目的,我们有两个核心状态:
-
在初始页面加载时,我们的导航窗格出现在页面底部(见图 10-3 )。
-
当一个导航项目被激活时(用键盘或鼠标),导航窗格移动到页面顶部(见图 10-4 )。
我们使用 JavaScript 将导航窗格的类更改为“exposed”,这使它在页面顶部导航下方有了合适的位置。我们创建一个样式规则,将任何窗格放置在正确的位置(来自样式表,wscslide.css
):
/* apply to any div with class="exposed"
that is in the <div id="drops"> */
div#drops div.exposed {
position: absolute;
top: 0;
left: 0;
padding: 0; /* added to remove doubled-up padding when positioned */
}
请注意,这是可行的,因为我们将这些窗格定位在最近的相对定位的父窗格中,div#inner-wrap
:
div#inner-wrap {
background-color: #ccc;
color: #000;
position: relative;
margin: 0;
padding: 0.5em 0;
}
我们还使用 JavaScript 删除暴露的类,以将窗格返回到页面底部。
我们可以通过多种方式结合 JavaScript 来实现这一点。为了维护我们坚实的 HTML 基础,我们使用 JavaScript 拦截或“劫持”导航项目上的点击,并切换导航窗格的状态。它使用了一个名为toggle
的函数,可以在暴露的导航窗格和原始状态之间切换。最好将这些动态地分配给每个导航项目,但是为了清楚起见,这最终导致我们的导航链接如下:
<a href="#drop-posts" onclick="return toggle('drop-posts');">
Recent Posts
</a>
下面是来自wscslide.js
的toggle
函数的第一个版本:
function toggle(element) {
var inner = document.getElementById('inner-wrap');
/* if the scripting isn't supported, we want to return true so that
default behavior of clicking the link works (i.e., take the user
to the bottom part of the page)
*/
if (!document.getElementById) return true;
var elt = document.getElementById(element);
// do a test on the className property on the element to
// check for the exposed class
if (/exposed/i.test(elt.className)) {
// exposed state was found, so remove it
elt.className = elt.className.replace(/exposed/g,'');
} else {
// add exposed to current class to respect any
// styles/classes that already exist
elt.className += " exposed";
}
return false;
}
注意
在这种情况下,核心功能由 hrefs 提供。确保它们到位意味着我们实现了与脚本的基本互操作性,无论是开还是关。不要把这和残疾人无障碍混淆。在解决了基本的互操作性需求之后,我们将在后面讨论可访问性。
这个脚本的关键部分是我们使用脚本来接管 HTML 中的<a href="#drop-posts">
链接所提供的功能。这确保了在没有脚本支持的情况下使用浏览器的人将被带到页面的正确部分。
这是其余脚本的基础。有了这个主开关,我们就可以添加滑动行为了。
添加滑动行为
现在,我们已经使用脚本在每个导航窗格的暴露位置和正常位置之间切换,我们可以添加脚本来将导航从主导航的“下方”滑出。我们通过在激活exposed
状态时将窗格的高度改为 0,并编写一个通过改变其高度来显示导航窗格的函数来实现这一点。
注意
作为替代,当我们激活暴露状态时,我们可以在 content div 上放置一个适当的 padding-top,以允许导航窗格位于内容之上。使用 CSStop 属性的负值,我们可以隐藏顶部导航下方的导航窗格,并使用 JavaScript 将 div#inner-wrap 向下滑动适当的距离。然而,在该解决方案的早期测试中,一些测试者报告了“起伏不定”的滑动行为。这在一定程度上是由于滑动了一个包含页面上所有内容的 div。随着页面的变大,滑动变得不顺畅。
这种解决方案和示例中使用的解决方案都需要额外的脚本,但是从技术角度来看,它们对可访问性都没有显著的影响。(虽然一般来说滑动导航可能会有问题,也可能没有问题,例如对于有认知障碍的人来说。)
完成的脚本包括几个使其工作良好的附加特性:
-
简单的错误检查,防止用户在标签上点击两次。一旦导航窗格“移动”,该函数就返回,而不是再次调用它。
-
一种基本的重置功能,可用于将选项卡重置为其原始状态。
-
常数
SLIDEINTERVAL
,它让我们定义重复调用Reveal
函数的速度。在这种情况下,我们每 65 毫秒调用一次。
我们可以对这个脚本进行一次大范围的浏览,一步一步地查看我们所做的更改。例如,我们可以详细研究动画策略。然而,我想把重点特别放在细节的可访问性方面,让大部分核心脚本自己说话。您可以在整个脚本中找到注释来详细说明发生了什么。下面是最终核心脚本的清单,wscslide.js
:
var slideready = false;
var SLIDEINTERVAL = 65;
var revealTimer = null;
var moving = false;
window.onload = function() {
slideready = true;
}
function toggle(element) {
if (!slideready || moving) return false;
reset(element);
if (!document.getElementById) return true;
var elt = document.getElementById(element);
var initialheight= elt.offsetHeight;
Reveal(element, initialheight);
//return false;
}
/* reset function used to set all navigation pane divs back to their
initial state */
function reset(element) {
var elt = document.getElementById('drops');
var elts = elt.getElementsByTagName('div');
var exposed = document.getElementById(element);
for (i=0; i< elts.length; i++) {
// we only want to reset divs that are acting as navigation panes
// and exclude the current one that has been set to "exposed"
if (!/drop-/i.test(elts[i].id) || (exposed == document.getElementById(elts[i].id))) {
continue;
}
thiselt = elts[i];
thiselt.className = thiselt.className.replace(/exposed/g,'')
// set style back to overflow: hidden to remove visual artifacts
// when switching from one tab to another
thiselt.style.overflow = "hidden";
}
return;
}
function changeHeight(elt, dH) {
var thiselt = document.getElementById(elt);
// is this a reveal up or down? if up, the final target height is 0
var targetHeight = (dH < 0) ? 0 : dH;
// the current height of the element
var currHeight = thiselt.offsetHeight;
// the change in height required - to smooth the transition we reveal
// half of the remaining height of the pane with each iteration
var dHeight = Math.ceil((targetHeight - currHeight) / 2);
newHeight = currHeight + dHeight;
// if the difference is less than 1 pixel we'll stop moving,
//clear the interval and set the height to the exact height
// we started with
if (Math.abs(dHeight) <= 1) {
clearInterval(revealTimer);
moving = false;
newHeight = targetHeight;
}
// set the height to a new value
thiselt.style.height = newHeight + "px";
// if the height is now zero, we need to remove the "exposed" state
// set the height back to the original height and clear the JS set
// value for height so that it is reset to the value found in the
// original CSS
if (thiselt.offsetHeight == 0) {
thiselt.className = thiselt.className.replace(/exposed/g,'');
//force a repaint for getting around Safari rendering issue
thiselt.innerHTML = thiselt.innerHTML + '';
thiselt.style.height = '';
}
}
function Reveal(elt, dH) {
// prevent the function from doing anything if it is already active
if (moving) return;
var thiselt = document.getElementById(elt);
if (/exposed/i.test(thiselt.className)) {
// if we are exposed, we want to slide the pane up instead of down
dH = -dH;
// if we are sliding up, then we want to reset the overflow to hidden
thiselt.style.overflow = "hidden";
} else {
// this opens the tab and respects classes that already exist
thiselt.className += " exposed";
}
moving = true;
// run the changeHeight function at the specified interval;
// will run until we clear the interval
revealTimer = setInterval("changeHeight('" + thiselt.id "','" + dH + "')", SLIDEINTERVAL);
}
可访问性在其中起了什么作用?
这个脚本的核心是可访问的,因为它是可互操作的。这给我们带来了以下好处:
-
它可以在脚本打开或关闭的情况下工作。
-
它使用 HTML 固有的核心行为,即指向同一页面上其他位置的简单链接。
-
(一般来说)我们只改变页面组件的 CSS,我们不使用
display: none
改变任何 CSS(这是众所周知的从屏幕阅读器的内部 DOM 中移除元素;见http://css-discuss.incutio.com/?page=ScreenreaderVisibility
对这种经常令人沮丧的行为的讨论)。 -
所有链接都允许键盘激活。我们没有为此做任何事情;这是固有的行为。在我们的脚本中,我们确保不做任何会剥夺键盘功能的事情。
这是一个好消息,但这并不意味着我们没有进一步的义务。为了确保这个解决方案确实可行,我们需要做一些额外的测试。让我们来看看其中的一些测试。
视力低下
CSS 的构造使得文本在所有浏览器中都是可伸缩的。导航窗格的高度从未被定义为静态数字;允许有弹性。每个导航窗格的高度是在脚本运行时确定的。这意味着,如果用户打开和关闭窗格,调整文本大小,然后再次打开和关闭窗格,脚本和 CSS 会自动调整以适应大小。我们成对地声明了背景色和前景色,这样当一个窗格被打开并且文本被调整大小时,它仍然是可读的,即使它从包含它的窗格中掉出。图 10-5 显示了一个调整了文本大小的页面。
图 10.5。灵活的大小调整允许在不破坏设计的情况下调整字体和导航窗格本身的大小。
注意
当使用屏幕放大镜时,我们注意到关闭链接没有出现在视野中。这并不比任何其他不使用缩放布局的网站更难使用。当然,可以做更多的实验来实现完全左对齐的缩放布局。
语音识别
这种特殊的解决方案似乎与语音识别软件配合得很好(Dragon NaturallySpeaking 用于测试),只有一个小的例外。语音识别用户能够专注于页面中的链接。他们可能会说“链接”,并显示有编号的选项,如图图 10-6 所示。
图 10.6。使用 Dragon NaturallySpeaking 中的链接功能选择导航链接
如果使用该网站的人随后打开导航窗格,则在屏幕之外的链接将不能使用编号机制。然后用户可以再次说出“link”来重新填充链接数组,给出一个编号的链接选择,如图 10-7 所示。
图 10.7。当导航窗格打开时,窗格中包含的链接无法使用 links 数组。用户需要再次说出命令“link”来重新填充阵列,如此处所示。
如果用户重复“链接”命令,然后关闭导航窗格(或切换到另一个窗格),他会在每个链接原来所在的地方看到一个可视的“工件”(见图 10-8 中的例子)。这可以通过手动重新填充链接数组来解决。
图 10.8。链接数组的视觉效果。当使用 DOM 脚本效果在页面中移动或生成内容时,links 数组不会自动更新。它要求用户通过给出适当的命令(如“link”)来手动更新它
屏幕阅读器
用屏幕阅读器测试这个解决方案揭示了一个问题。如果我们在劫持href
s 时返回false
(这是避免页面“跳转”到href
指向的内部锚/ id
的常见做法),那么我们将屏幕阅读器的“光标”留在它原来的位置。虽然我们在视觉上将相关的导航窗格移动到顶部,但我们没有提供对屏幕阅读器的相同访问。事实上,返回false
会删除屏幕阅读器用户的功能。我们通过不允许页面跳转设置了一个障碍。
我们如何解决这个问题?这是我们必须再次检查可访问性和 JavaScript 的地方。如果我们仅仅停留在确保我们的页面在脚本打开或关闭时都可用,我们就不会意识到我们给屏幕阅读器用户带来的问题。重申一下,脚本开/关场景更多的是关于基本的互操作性,而不是关于残疾人的可访问性。
一种可能是不返回false
,让历史堆栈顺其自然。在某些情况下,这样做甚至是可取的。然而,这意味着我们在页面中得到视觉上不和谐的“跳转”,并且最终隐藏了主导航,从视图中移除了切换点。这种解决方案不是特别有用,而且很可能会让用户感到厌烦,让签署该解决方案的人无法接受。
另一种可能是返回false
,但是使用链接中的href
并将焦点放在导航窗格上。如果我们这样做,我们将允许屏幕阅读器用户继续在正确的位置阅读,并确保我们不会覆盖我们的导航或跳转到整个屏幕。该解决方案在本章的最终文件中实现。
另一个屏幕阅读器问题是close
链接。这可能会让屏幕阅读器用户感到困惑。在他们看来,他们只是简单地移动到页面的另一部分,那么他们为什么需要关闭任何东西呢?这个链接只有在视觉上看到页面的人才有意义。为了尝试解决这个问题,我们使用了一个关闭图标(典型的x
图形),带有“返回主导航”的可选文本,以及一个合适的href
。使用与主导航链接相同的策略,我们劫持href
将屏幕阅读器光标放在下一个逻辑位置。
仅供键盘使用
只使用键盘的用户会遇到许多与屏幕阅读器用户相同的问题。特别是,将焦点放在导航窗格中对于允许键盘用户正确导航至关重要。
随着时间的推移,一种可能变得流行的解决方案是对tabindex
属性使用无效值。将1
的tabindex
值分配给一个元素允许该元素以编程方式接收焦点,但不会将其添加到文档的自然 tab 键顺序中。这个解决方案有点争议,因为无效的 valueby 规范,HTML 要求tabindex
是 0 到 32767 之间的数字(详见www.w3.org/TR/REC-html40/interact/forms.html#adef-tabindex
)。
tabindex
解决方案是可接受的吗?IBM、Mozilla 和微软似乎都这么认为,尽管 IBM 的代表暗示他们知道这个解决方案不太正确(见www.csun.edu/cod/conf/2005/proceedings/2524.htm
):
注意
请记住,这还不是任何 W3C 或其他官方标准的一部分。此时,有必要修改规则以获得完整的键盘可访问性。
一个无效的值可能会有不可预见的后果,并且在节点上操作 DOM 来包含一个tabindex
值(在那里它不是一个有效的属性)似乎有点笨拙。我们实际上使用 JavaScript 只是为了掩盖这样一个事实,即我们正在做的事情根据 HTML 规范是无效的。
注意
1 tabindex 策略似乎得到了行业内的支持。web 超文本应用技术工作组(WHAT WG,www.whatwg.org
),一个由 Web 浏览器供应商和其他感兴趣的团体组成的组织,正在致力于开发 HTML5。在该规范中,tabindex 是一个允许用于任何元素的属性,并且允许负的 tabindex 值。
结论
这是最终可行的解决方案吗?也许是,也许不是。我们所知道的是,我们已经尽了最大努力来确保我们在解决方案中有一个互操作性和可访问性的基线,具体做法是:
-
通过简单的 HTML 代码提供内容和核心功能
-
确保我们使用不引人注目的脚本
-
确保我们的字体是可扩展的,并同时使用前景色和背景色
-
以创造性的方式使用 CSS 来修改事物出现的方式,同时保持逻辑、语义结构
-
使用 JavaScript,这样我们就可以利用链接和
href
来引导键盘用户或屏幕阅读器用户浏览页面
我们已经进行了基本测试,这使我们能够解决残疾人在使用该解决方案时将面临的许多问题。额外的测试将会告诉你这个解决方案对所有能力水平的用户是否有用。您必须对真实用户进行自己的可访问性测试。
在你的网站上试试这里展示的技术。本章已经给了你足够的信息来开始使用你自己的可访问的 JavaScript 解决方案。
第一部分:布局魔术
欢迎来到我们基于高级标准的网页设计之旅的第一部分。不出所料,第一部分涉及高级布局技术,因此重点主要放在 CSS 上。最终选择在这里收录(就像这本书的所有内容一样),是因为作者有一些有点有趣和不寻常的东西要说。面对现实吧,你在网上看到过多少关于 CSS 布局技术的教程?
作者们真的从帽子里拿出了一些特别的东西。Simon、Ethan 和 Andy 展示了在精益、语义标记的严格饮食下,通过巧妙使用类值、开箱即用定位等,实现设计精美的网站的不同方法。
Dan 向您展示了即使受到内容管理系统提供的内容的限制,您仍然可以创建外观精美的网站。他还加入了一些 Flash 图像替换和少量 DOM 脚本。
Jeff 专注于使用透明 png,以及 CSS 和 HTML,在您的站点上创建一些真正令人敬畏的设计效果。
作者谈到了他们应用时浏览器支持的不一致性,并解释了他们是如何解决这些问题的。你在这里看到的漂亮的布局是在没有借助很多黑客手段的情况下实现的!
旅途愉快。。。
第二部分:应用于 CSS 设计的有效打印技术
在旅程的下一部分,你将会看到如何将有效的印刷设计技术带入网页设计世界。无论你的背景如何,你都不得不承认这两种媒体之间有很多相似之处,印刷设计师学到的一些古老的经验对他们的数字兄弟来说非常有价值。
首先,马克专注于一个相当实验性的网页区域网格设计。他展示了网格在网站上使用时的强大功能。
然后,Rob 对印刷原则进行了有趣的研究。他展示了即使是一个简单的网站也可以通过在排版上花点心思来改变。
两位设计师都有印刷设计的背景,这一点显而易见。我希望有更多的他们这样的人能进入网络世界。
第三部分:DOM 脚本精粹
没有一些 DOM 脚本示例,哪本关于 web 标准的书是完整的?这就是为什么我把这本书的最后一部分致力于动态脚本技术来改善你的用户体验。
我特意尝试选择一些技术,这些技术对于设计性更强、开发性更差的人来说不会太可怕——不要害怕用 JavaScript 弄脏自己的手!
Ian 首先向我们展示了他拯救世界的技术:通过使用 DOM 脚本来指定只打印网页的重要部分,我们避免了浪费纸张和麻烦。
接下来,Cameron 演示了通过使用一些适当的脚本,在您的网页上为您的用户提供一个更加动态的交互环境并不困难。在他的案例研究过程中,他建立了一个动态的用户界面,无论用户使用什么样的分辨率/平台访问你的网站,该界面都会自动调整以获得最佳的浏览效果。他还展示了如何通过在不同的调色板中移动内容来创建用户可以定制的界面。
最后但同样重要的是,Derek 展示了如何实现一个有吸引力的滑动站点导航菜单,并保持其可访问性。我想在书中包含一些关于脚本和可访问性的内容,因为这是一个热门话题。Ajax 尤其会对页面的可访问性造成严重破坏,我们需要小心不要将残疾用户拒之门外。