带有 CSS 区域和排除对象的杂志式网站版式

Christian Cantrell
Christian Cantrell

简介

网络是一个功能非常强大的文字平台,Adobe 在这一领域拥有丰富的经验和专业知识。因此,在 Adobe 寻找方式来帮助推动网络向前发展时,进一步发展网络的文本功能似乎是我们很明显的起点。 Web 通常假定文本的垂直方向为单列。虽然可以在图形周围使文本流动,甚至可以使用 CSS 将文本格式设置为多列,但仍然很难在网络上实现真正的杂志式布局。通过 CSS 区域CSS 排除功能,Adobe 致力于将桌面发布的强大功能引入现代浏览器。例如,在下面的屏幕截图中,我们使用了 CSS 排除功能来让文本沿着山的轮廓流动:

CSS 排除的实际应用示例
CSS 排除的实际应用示例

以下屏幕截图中的文档还使用了 CSS 排除功能来让文本环绕图片中的形状,还使用 CSS 区域将文本的格式设置为列和引用引述:

CSS 区域的实际运用示例
CSS 区域的实际应用示例

CSS 区域

在深入了解 CSS 区域之前,我想先介绍如何在 Google Chrome 中启用区域。启用 CSS 区域后,您可以尝试本文中提及的一些示例,并自行创建。

在 Google Chrome 中启用 CSS 区域

从 Chrome 20 版(确切地说是 20.0.1132.57 版)起,CSS 区域功能是通过 chrome://flags 接口启用的。如需启用 CSS 区域,请按以下步骤操作:

  1. 在 Chrome 中打开新的标签页或窗口。
  2. 在地址栏中输入 chrome://flags
  3. 使用在网页中查找(Ctrl/Command + f),然后搜索“实验性网络平台功能”部分。
  4. 点击启用链接。
  5. 点击底部的立即重新启动按钮。

有关 Chrome 标志的详情,请参阅我的博文 Chrome 标志简介

重新启动浏览器后,您就可以立即开始体验 CSS 区域了。

CSS 区域概览

CSS 区域可让一块基于语义标记的文本自动流入“方块”(当前为元素)。下图展示了文本(文本流)和框(文本流入的区域)的分离:

内容流入指定区域
内容流入指定区域

我们来看一个实际的 CSS 区域用例。我不仅是 Adobe 的开发者,还是一名科幻小说作家。我经常根据知识共享许可在网上发布我的作品,为了让作品可以在尽可能多的设备和浏览器中展示,我经常使用类似下面这样的非常简单的格式:

无样式人类遗留项目示例
旧版人类无样式项目示例

利用 CSS 区域,我打造了一种视觉效果更丰富而且功能性更强的体验,因为导航更方便,阅读也更舒适:

显示区域的人类遗产项目
人类遗产项目与区域。

出于演示目的,我添加了在此原型中显示 CSS 区域的功能。下面的屏幕截图展示了这些区域的排列方式,使得它们给人一种环绕图形和中心引述的柱子的感觉:

显示区域的人类遗产项目
显示区域的人类遗留项目

您可以在此处对此原型进行实验(并查看源代码)。使用箭头键进行浏览,按 Esc 键可显示区域。您也可以在此处获取早期的原型。

创建命名的流

使一段文本流经区域所需的 CSS 非常简单。以下代码段会将名为“article”的已命名数据流分配给 ID 为“content”的 div,然后将名为“article”的已命名数据流分配给类为“region”的任何元素。其结果是,“content”元素中包含的文本会自动流过具有“region”类的所有元素。

<!DOCTYPE html>
<html>
<head>
    <style>
    #content {
        { % mixin flow-into: article; % }
    }

    .region {
        { % mixin flow-from: article; % }
        box-sizing: border-box;
        position: absolute;
        width: 200px;
        height: 200px;
        padding: 10px;
    }

    #box-a {
        border: 1px solid red;
        top: 10px;
        left: 10px;
    }

    #box-b {
        border: 1px solid green;
        top: 210px;
        left: 210px;
    }

    #box-c {
        border: 1px solid blue;
        top: 410px;
        left: 410px;
    }
    </style>
</head>
<body>
    <div id="box-a" class="region"></div>
    <div id="box-b" class="region"></div>
    <div id="box-c" class="region"></div>
    <div id="content">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent eleifend dapibus felis, a consectetur nisl aliquam at. Aliquam quam augue, molestie a scelerisque nec, accumsan non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin cursus euismod nisi, a egestas sem rhoncus eget. Mauris non tortor arcu. Pellentesque in odio at leo volutpat consequat....
    </div>
</body>
</html>

结果如下所示:

上述代码的结果
上述代码的结果

请注意,“content”div 中的文本并不知道其呈现方式。换言之,即使在流经不同区域时也可能完全在语义上保持不变。此外,由于区域只是元素,因此与任何其他元素一样,区域元素也是使用 CSS 来放置位置和大小的,因此与自适应设计原则完全一致。将元素指定为命名流的一部分仅仅意味着指定的文本会自动流过这些元素。

CSS 对象模型

CSS 对象模型 (CSSOM) 定义了可与 CSS 配合使用的 JavaScript API。下面列出了与 CSS 区域相关的新 API:

  • document.webkitGetNamedFlows():一个函数,用于返回文档中可用的已命名数据流集合。
  • document.webkitGetNamedFlows().namedItem("article"):一个函数,用于返回对特定命名流的引用。该参数与指定为 flow-intofrom-from CSS 属性的值对应的名称相对应。要获取对上述代码段中指定的已命名 Flow 的引用,您可以传入字符串“article”。
  • WebKitNamedFlow:已命名 floe 的对象表示法,其中包含以下属性和函数:
    • firstEmptyRegionIndex:一个整数值,指向与命名流关联的第一个空区域的索引。如需了解如何获取区域集合,请参阅下文的 getRegions()
    • name:包含流名称的字符串值。
    • overset:布尔值属性,即:
      • 当指定数据流的内容位于关联区域时,false
      • 如果内容不合适,需要更多区域才能包含所有内容,则 true
    • getContent():此函数会返回一个集合,该集合引用了流入命名流的节点。
    • getRegions():此函数会返回一个集合,该集合引用了保存指定数据流内容的区域。
    • getRegionsByContentNode(node):一个函数,用于返回对包含指定节点的区域的引用。这对于查找包含已命名锚点等内容的区域尤其有用。
  • webkitregionoversetchange 事件。每当相关内容的布局因任何原因(添加或移除内容、字体大小更改、区域形状更改等)并导致区域的 webkitRegionOverset 属性发生变化时,此事件就会通过 WebkitNamedFlow 触发。此事件有助于监听粗略的布局更改。它表示发生了重要事件,可能需要注意布局,例如需要更多区域、某些区域可能为空等。
  • webkitregionfragmentchange 事件。在此修改时未实现。每当关联内容的布局因任何原因发生变化时,此事件就会通过 WebkitNamedFlow 触发,类似于 webkitregionoversetchange,但不考虑 webkitRegionOverset 属性发生任何更改。此事件对于监听不一定会影响已命名流程的整个布局的细粒度布局更改非常有用,例如:内容从一个区域移到另一个区域,但整体内容仍然适合所有区域。
  • Element.webkitRegionOverset:为元素分配 flow-from CSS 属性后,它们会成为区域。这些元素具有 webkitRegionOverset 属性,如果它们是已命名的 Flow 的一部分,则会指明来自 Flow 的内容是否溢出了该区域。webkitRegionOverset 可能的值包括:
    • 如果内容超过区域可容纳的内容数量,则为“overflow”
    • 如果内容在该区域结束之前停止,则为“fit”
    • 如果内容未到达区域,则为“empty”

CSSOM 的主要用途之一是监听 webkitregionoversetchange 事件,并动态添加或移除区域,以适应不同数量的文字。例如,如果要设置格式的文本量无法预测(可能是由用户生成的)、浏览器窗口的大小调整了,或者字体大小发生变化,则可能需要添加或删除区域,以适应流中的变化。此外,如果您想将内容整理到页面中,则需要一种机制来动态修改 DOM 以及您的区域。

以下 JavaScript 代码段演示了如何根据需要使用 CSSOM 动态添加区域。请注意,为简单起见,该示例不会处理移除区域或定义区域的大小和位置的操作;该示例仅用于演示目的。

var flow = document.webkitGetNamedFlows().namedItem("article")
flow.addEventListener("webkitregionoversetchange", onLayoutUpdate);

function onLayoutUpdate(event) {
    var flow = event.target;
    
    // The content does not fit
    if (flow.overset === true) {
    addRegion();
    } else {
    regionLayoutComplete();
    }
}

function addRegion() {
    var region = document.createElement("div");
    region.style = "flow-from: article";
    document.body.appendChild(region);
}

function regionLayoutComplete() {
    // Finish up your layout.
}

如需查看更多演示,请参阅 CSS 区域示例页面

CSS 页面模板

利用 CSSOM 可能是实现分页和响应式布局等功能的最强大和灵活的方式,但 Adobe 使用文本和桌面发布工具的时间足够长,知道设计人员和开发者也希望通过一种更简单的方式获得相对通用的分页功能。因此,我们正在制定一个名为“CSS 页面模板”的提案,该方案允许完全以声明方式定义分页行为。

我们来看一下 CSS 页面模板的常见用例。以下代码段展示了如何使用 CSS 创建两个已命名的流:“article-flow”和“timeline-flow”。此外,它还定义了第三个选择器“combined-articles”,该选择器中会包含两个数据流。只需在“combined-articles”选择器中添加 overflow-style 属性,就意味着相应内容应自动沿 x 轴或水平分页:

<style>
    #article {
    { % mixin flow-into: article-flow; % }
    }

    #timeline {
    { % mixin flow-into: timeline-flow; % }
    }

    #combined-articles {
    overflow-style: paged-x;
    }
</style>

现在您已经定义了流并指定了所需的溢出行为,接下来就可以创建页面模板本身了:

@template {
    @slot left {
    width: 35%;
    float: left;
    { % mixin flow-from: article-flow; % }
    }

    @slot time {
    width: 25%;
    float: left;
    { % mixin flow-from: timeline-flow; % }
    }

    @slot right {
    width: 35%;
    float: left;
    { % mixin flow-from: article-flow; % }
    }
}

网页模板使用新的“at”语法进行定义。在上面的代码段中,我们定义了三个位置,每个位置对应一列。“文章流”中的文本将流经左侧和右侧的列,“时间轴流”中的文本将填充到中间的列中。结果可能如下所示:

页面模板示例
页面模板示例

请注意,文章文本(左侧和右侧列中的文本)为英语,中间的时间轴为德语。此外,文档可横向翻页,而无需任何 JavaScript 代码。所有操作都是在 CSS 中完全以声明方式完成的。

CSS 页面模板仍是一项提案,不过,我们提供了使用 JavaScript“shim”(也称为 polyfill)的原型,以便您可以立即试用这些模板。

如需从整体上详细了解 CSS 区域,请参阅 html.adobe.com 上的“CSS 区域”页面

CSS 排除

为了实现真正的杂志式布局,仅让文本流经区域还不够。要想制作出优质且具有视觉吸引力的桌面排版,一个关键要素就是能够让文字环绕在不规则图形和形状的周围或内部。CSS 排除功能将这种级别的制作质量带入网络。

以下屏幕截图来自 CSS 排除对象原型,显示了在与大型岩层的轮廓相匹配的路径周围动态流动的文字:

CSS 排除的实际应用示例
CSS 排除的实际应用示例

下一个屏幕截图展示了反之亦然:文本流在不规则形状的多边形内部:

文本流入不规则形状的多边形
文本流入不规则形状的多边形

要让文本在任意形状周围或内部流动,首先要开发和优化所需的算法。Adobe 正致力于直接为 WebKit 贡献代码。这些算法经过优化后,它们将成为其余 CSS 排除对象的基础。

有关 CSS 排除的详情,请参阅 html.adobe.com 上的“CSS 排除”页面;要详细了解 Adobe 针对 CSS 排除的基础技术所做的工作,请参阅 Hans Muller 撰写的名为 Horizontal Box: Polygon Intersection for CSS Excludes 的博文。

CSS 区域和 CSS 排除功能的现状

我第一次公开讨论 CSS 区域和 CSS 排除是在 2011 年 Google I/O 大会上的 Adobe Developer Pod 上。当时,我在自己的自定义原型浏览器中展示了演示。活动现场非常热烈,但旁观者发现我展示的所有功能都尚不支持任何主流浏览器,但明显感到失望。

我今年(2012 年)再次参加了 Google I/O 大会,这次与我的同事 Vincent Hardy 和来自 Google 的 Alex Danilo 一起,作为演示者(您可以在此处观看演示)。仅仅一年后,大约 80% 的 CSS 区域规范已在 WebKit 中实施,并且已经纳入到最新版本的 Google Chrome 中(请注意,CSS 区域目前必须通过 chrome://flags 启用)。对 CSS 区域的初步支持甚至已经登陆 Android 版 Chrome:

Chrome(Android 版)上的区域
Chrome(Android 版)上的区域

此外,CSS 区域和 CSS 排除功能已在 Internet Explorer 10 预览版中实现,目前已纳入 Mozilla 2012 年针对 Firefox 的路线图。Safari 的下一个主要版本应支持大多数 CSS 区域规范,后续更新应包含其余内容。

以下详细列出了自 2011 年 4 月向 W3C 提交首次提案以来,我们在 CSS 区域和 CSS 排除功能方面所取得的进展:

区域和排除进度
区域和排除进度

总结

总体而言,Adobe 在文本、字体和桌面发布方面拥有丰富的经验,包括 InDesign 等工具。尽管网络已经是一个非常强大的文本平台,但我们仍然希望利用我们的知识和经验进一步推动文本呈现。CSS 区域和 CSS 排除功能既能保持内容的语义结构,又能实现真正的杂志式布局,最终打造出更具表现力的网页。