它开始于,正如许多事情一样,一次愚蠢的对话。 在这种情况下,我和我们的前端技术能力总监(又称“老板”)Mundi Morgado 谈话。
对话是这样的……
Mundi Morgado 我想要你构建一个可视化屏幕阅读器。
Nathan Smith 什么?
Mundi Morgado 我想要一个 CSS 库,它可以让你看到文档的结构。 它不应该使用 class
,这样你就会被迫关注语义。 另外,还要使其可主题化。
Nathan Smith 好的,让我看看我能想出什么。
一周后,我们得到了现在我们称之为
Construct.css: 没有类别的临时 CSS 库
为什么是临时的? 好吧,它并不真正打算成为一个功能齐全、生产就绪的样式框架。 更确切地说,它就像文档语义的辅助轮,带有一些缓冲道(想想:保龄球)来让你走上正轨。
它是我们 TandemSeven 持续努力的一部分,旨在在我们整个组织中推广代码素养。 随着我们越来越多的 UX 设计师开始分享对项目可访问性和语义的责任,我们以一种允许 UX 和开发更具协作性的方式进行构建是有意义的。
Construct 有四个主要方面。
Construct: “基本”
首先是基本的 Construct.css
文件,它对大多数常见的 HTML 元素应用了基本的外观和感觉。 查看 基本页面 的示例。
Construct: “方框”

其次,有 construct.boxes.css
。 这将以视觉方式区分其他不可见的语义元素,例如:header
、main
、nav
等。 这样,人们就可以在页面被创作时看到这些元素的位置(或不存在的位置)。 这是一个带有 语义方框 的页面,它们被勾勒出来。
Construct: “主题”

第三,可以通过 construct.theme.css
进行主题化。 这主要只是一个示例——灵感来自 PaperCSS——关于如何使 UI 看起来“手绘”,以防止观察者过于关注外观和感觉,而不是文档的语义。 这是一个 示例主题 的实际效果。
Construct: “调试”

最后,有一个 construct.debug.css
文件,它突出显示并指出了无效或有问题的标记。 这是一个 极端的示例,说明一个 HTML 页面上的“所有”错误。 它的灵感来自 a11y.css,虽然它在什么被认为值得注意方面有所不同。
例如,当多个 div:only-child
彼此嵌套时,我们会指明“div 泛滥”。 我们也对 type="checkbox"
包含在 <label>
内有自己的看法。 这是为了在复选框(或单选按钮)与其文本标签之间原本空白的间隙内最大程度地提高可点击性。
所有这些 CSS 文件都可以单独应用,也可以组合应用。 Construct 主页上还有一个书签(标记为“获取 Construct!”),你可以使用它来获得任何网页的结果。

书签将远程提取
sanitize.css
construct.css
construct.boxes.css
construct.debug.css
我们的“重置” – Sanitize.css
关于 Santitize.css 的简短说明:它是我的同事 Jonathan Neal(Normalize.css 的作者)创建的,作为一种半主观 CSS“重置”方式。 也就是说,它设置了许多默认值,我们发现自己作为开发人员总是要编写这些默认值,因此它成为一个良好的起点。
从技术上讲
好的,既然我们已经涵盖了“为什么”,让我们深入探讨“如何”。
基本上,它都是围绕使用(滥用?)::before
和 ::after
伪元素展开的。 例如,要显示块级标签的名称,我们使用以下 CSS。
语义标签(使用 ::before)
为了简洁起见,这里简化了 construct.boxes.css
文件。 它导致 <section>
标签带有虚线边框,并在左上角显示其标签名称。
section {
border: 1px dashed #f0f;
display: block;
margin-bottom: 2rem; /* 20px. */
padding: 2rem; /* 20px. */
position: relative;
}
section > :last-child {
margin-bottom: 0;
}
section::before {
background: #fff;
color: #f0f;
content: "section";
font-family: "Courier", monospace;
font-size: 1rem; /* 10px. */
letter-spacing: 0.1rem; /* 1px. */
line-height: 1.3;
text-transform: uppercase;
position: absolute;
top: 0;
left: 0.3rem; /* 3px. */
}
警告消息(使用 ::after)
同样,这里是从 construct.debug.css
文件中驱动标记警告消息的代码片段。 此特定示例会导致显示 <script>
标签,这些标签可能需要进一步优化或需要开发人员关注:内联 JavaScript 和/或没有 async
的外部 src
。
在内联 JS 的情况下,我们将 font-size
和 line-height
设置为 0
,因为我们正在使元素可见。 我们通过 text-indent: -99999px
将文本从页面上移除,只是为了确保它不会显示出来。 我们不希望随机的 JS 代码与合法的页面内容一起显示。
(请不要对“真实”内容这样做。 只是一个技巧,好吗?)
为了让我们的错误消息显示出来,我们必须将 font-size
和 line-height
重新设置为非零值,并移除 text-indent
。 然后,通过更多绝对定位,我们确保消息是可见的。 这使我们能够在页面内容中看到需要检查的位置(通过 DOM 检查器)来查找 <script>
插入点。
script:not([src]),
script[src]:not([async]),
script[src^="http:"] {
color: transparent;
display: block;
font-size: 0;
height: 2rem; /* 20px. */
line-height: 0;
margin-bottom: 2rem; /* 20px. */
position: relative;
text-indent: -99999px;
width: 100%;
}
script:not([src])::after,
script[src]:not([async])::after,
script[src^="http:"]::after {
background: #f00;
color: #fff;
display: inline-block;
font-family: Verdana, sans-serif;
font-size: 1.1rem; /* 11px. */
font-weight: normal;
left: 0;
line-height: 1.5;
padding-left: 0.5rem; /* 5px. */
padding-right: 0.5rem; /* 5px. */
pointer-events: none;
position: absolute;
text-decoration: none;
text-indent: 0;
top: 100%;
white-space: nowrap;
z-index: 1;
}
body script:not([src])::after,
body script[src]:not([async])::after,
body script[src^="http:"]::after {
top: 0;
}
script:not([src])::after {
content:
'Move inline <script> to external *.js file'
;
}
script[src]:not([async])::after {
content:
'Consider [async] for <script> with [src="…"]'
;
}
script[src]:not([src=""]):not([async])::after {
content:
'Consider [async] for <script> with [src="' attr(src) '"]'
;
}
script[src^="http:"]::after {
content:
'Consider "https:" for <script> with [src="' attr(src) '"]'
;
}
注意: 一些脚本确实需要同步加载。 你不希望你的“应用程序”代码在你的“框架”代码之前加载。 哎呀,一些内联 JS 可能也很好,尤其是如果你正在为关键渲染路径进行一些超速优化。 这些警告在某种程度上是“愚蠢的”,因为它们仅通过 CSS 选择器匹配。 谨慎使用,实际效果可能会有所不同等等。
JS 书签
前面提到的 JS 书签会找到页面中的所有 <link rel="stylesheet">
和 <style>
标签,并使它们失效。 这使我们能够添加自己的 CSS 来接管,以便只应用通过 Construct 直接提供的样式。
神奇之处在于将 rel="stylesheet"
设置为 rel=""
。 如果外部样式表没有这样标识,即使它被缓存,浏览器也不会将它的样式应用于页面。 同样,我们通过将 innerHTML
设置为空字符串来破坏任何内联 <style>
标签的内容。
这使我们能够保持标签本身的完整性,因为我们仍然希望验证 <body>
中没有 <link>
或 <style>
标签存在。
我们还会检查 <head>
中是否使用了过多的 <style>
标签。我们允许使用一个,因为它可以用于关键渲染路径。如果存在一个生成包含合并的内联样式页面的构建过程,那么它们很可能通过单个标签发出。
<a href="javascript:(
function (d) {
var f = Array.prototype.forEach;
var linkTags = d.querySelectorAll('[rel=\'stylesheet\']');
var styleTags = d.querySelectorAll('style');
f.call(linkTags, function (x) {
x.rel = '';
});
f.call(styleTags, function (x) {
x.innerHTML = '';
});
var newLink = d.createElement('link');
newLink.rel = 'stylesheet';
newLink.href =
'https://t7.github.io/construct.css/css/bookmarklet.css';
d.head.appendChild(newLink);
}
)(document);">
GET Construct!
</a>
开始使用 Construct 吧!
这几乎涵盖了所有内容。它是一个简单的插入,可以帮助你直观地了解文档语义并调试可能存在问题的标记。
我发现使用它非常有趣。在任何网站上快速点击一下书签,通常会发现很多可以改进的地方。
例如,以下是 CNN 网站在应用 Construct 后呈现的样子……

P.S. 联系安全部门!
如果你在 Twitter 网站上尝试这个,你可能会想知道为什么它不起作用。我们最初也对此感到困惑。这与网站的内容安全策略 (CSP) 有关。它可以用来禁止从外部加载 CSS 和/或 JavaScript,并可以将安全域列入白名单。
这可以在服务器级别或通过 <head>
中的 <meta>
标签设置。
<meta http-equiv="Content-Security-Policy" content="…" />
阅读 有关 CSP 的更多信息。
我知道你在想什么……
你不能用 JS 销毁那个
meta
标签吗?
在 Twitter 的情况下,它是从服务器发出的。即使它在 HTML 中,销毁它也不会影响实际的浏览器行为。它被锁定了。
好吧,那么……
你不能插入你自己的安全
meta
标签并覆盖它吗?
你可能这么想。但幸运的是,不行。一旦浏览器接受了该页面的 CSP,它就不能被覆盖。这可能是一件好事,即使它破坏了我们的乐趣。
我想这就像希望得到更多愿望一样。这是违反精灵规则的!
这太棒了!在语义和组织方面,网页设计需要更具主见性。除了过度的类/任意属性依赖之外,嵌套元素的依赖也太多。
我最大的困扰可能是,有些页面本可以使用定义列表,但反而使用了 7000 个嵌套的 div 或表格等等。自 HTML 4 以来一直可用的三个最少使用的表示元素是
<dl>
和<fieldset>
。很棒的项目!
太棒了。它让我想起了 Adam Morse 的 Pesticide 项目,但它更加健壮,例如,它会对元素类型提供反馈,等等,而 Pesticide 专注于快速调试的不同颜色轮廓。
我创建了一个问题并提交了一个请求,用于修复一些 关于描述列表的错误。但这是一项很棒的工作。我今天已经很好地利用了它。
好吧,这太棒了。
太棒了。我个人甚至没有使用它的需求,但这仍然令人兴奋。CSS 在某种程度上是我最喜欢的编程语言,这是一个很好的实际用例,可以展示它的能力(哈哈)。
太棒了!谢谢你。
我偶尔会使用 pesticide,它是一个非常棒的工具,可以直观地了解你的布局,并且它可以向你展示当有人试图通过强制使用假布局来欺骗 CSS 时,会产生难以修复的 bug。但我一直觉得它可以做得更多。我希望看到它成为一个简单的 pesticide 之类的浏览器扩展,可以打开或关闭。
非常酷的项目,我喜欢这个主题!感谢你提到了 PaperCSS :)