进入重学 CSS 的第一步,首先需要找到一些线索。我们在前面的课程中讲学习方法的部分也讲过,要想建立知识体系骨架,我们需要一个完备性更权威,更全的线索。但是 CSS 现在标准的状态非常复杂,所以我们没有办法找到一份像 JavaScript 或者 HTML 中比较完备的现形标准,能把 CSS 的一切都浓缩在内。
不过这种情况也是我们平时学习知识的一种常态,知识并不是有人给我们总结好一本书,或者能有一个地方包含了所有的知识。一般来说知识都会分布在各种不同的文档当中。
根据 Winter 老师比较喜欢学习的办法:“学习一样新知识时,先找一个线索”,凡是对于编程语言,都会先从它的语法去了解它。
所以 CSS 也不例外,它也有自己的一套语法体系。但是 CSS 标准是分散开的,我们想找到它完整的语法是非常的不容易的。所以我们这里先从 CSS 2.1 语法标准开始。
CSS 2.1 确实是一个比较老的版本了,但是它有一个好处,在 2.1 的版本的时候建立了一个 Snapshot,也就是说没有其他版本去替代它。所以 CSS 2.1 的 Grammar Summary 部分是当时一个比较完整的一份语法列表。
当然现在我们已经大量的引入了 CSS3 了,所以这里面会有一些语法差异和不全。但是总体来讲是一个不错的起点,让我们可以先开始认识 CSS 的语法基础。
这里的语法是使用 “产生式” 来表达的。但是这里会有一些 CSS 中特别的表达方式和标准:
[ ]
—— 方括号代表组的概念?
—— 问号代表可以存在和不存在|
—— 单竖线代表 “或” 的意思*
—— 星号代表 0 个或 多个我们平时写都是在写普通的 CSS 规则,
charset
我们基本都不会用,一般我们都会用UTF-8
。
这里讲到的是 CSS 2.1 的 CSS 结构,在 CSS3 中我们有更多的 @
规则 和 CSS 规则,我们首先要在 CSS3 中找到这两块的所有内容,然后补充到这个总体结构中,那么我们就可以形成 CSS 的总体结构。这时候我们对 CSS 的语法认识就有完备性了。
@charset
: https://www.w3.org/TR/css-syntax-3/
@import
: https://www.w3.org/TR/css-cascade-4/
@media
: https://www.w3.org/TR/css3-conditional/
@page
: https://www.w3.org/TR/css-page-3/
@counter-style
: https://www.w3.org/TR/css-counter-styles-3/
@keyframes
: https://www.w3.org/TR/css-animations-1/
@fontface
: https://www.w3.org/TR/css-fonts-3/
@supports
: https://www.w3.org/TR/css3-conditional/
@namespace
: https://www.w3.org/TR/css-namespaces-3/
这里不是完整的列表,还有 3 个规则,因为 它们本身状态太年轻在讨论状态,要不就是已经没有浏览器支持了,或者是已经被废弃了。分别有
document
、color-profile
、font-feature
。 然后最常用的有三种:@media
、@keyframes
、@fontface
这里我们选中了 HTML 所有的 DIV 并且给予它们一个 blue
的背景颜色。
div {
background-color: blue;
}
通过以上代码示例,我们看到一段 CSS 代码是有分为
选择器
和声明
两部分的。在我们《实战中理解浏览器原理》的文章中,我们编写我们的CSS parsor
的时候,就是把 CSS parse 成 selector 部分和 declaration 部分。我们这里也会按照这个方法来理解 CSS 规则。
div
)background-color
)blue
)+
、>
、~
、空格
类型选择器
、*
一定会在最前面,然后可以是 ID
、class
、attr
、pseudo
等选择器--main-color: #06c
color: var(--main-color)
--accent-background: linear-gradient(to top, var(--main-color), white);
var()
函数的时候是可以给默认值的,传入第二个参数就是默认值:var(--main-color, black)
--side: margin-top
然后就可以这样使用 var(--side): 20px
整型
、百分比
、浮点型
还有带维度 (Dimensions)
相对单位 (em, ex, cap, ch ... )
、视口单位 (vw, vh, vi, vb, vmin, vmax)
、绝对单位 (cm, mm, Q, in, pt, pc px)
弧度单位 (deg, grad, rad, turn)
、时间单位 (s, ms)
、频率单位 (Hz, kHz)
、分辨率单位 (dpi, dpcm, dppx)
颜色 <color>
、图片 <image>
、2D 位置 <position>
等类型计算 cal()
、最小值 min()
、最大值 max()
、范围剪切 clamp()
、切换value toogle()
、属性引用 attr()
通过上面讲到的几个部分,我们已经了解了整个 CSS 的架构。但是我们发现整个的 CSS 标准是散落在几份标准当中的,为了我们更好的阅读标准,我们想拿到一份比较完整的标准列表是需要我们做一些工作的。很多时候我们都是需要在零散的标准里面,去搜集一些共性的内容。
接下来我们来做一个小实验,通过类似爬虫的方法,在 W3C 网站上抓取标准的内容。然后我们对他进行一些处理,方便我们后续的一些工作。
首先我们打开 W3C 的标准和草稿的列表页:https://www.w3.org/TR/
这里我们可以看到所有的 W3C 的标准和草稿,但是这里我们只需要 CSS 部分的。如果我们检查元素中查看,我们可以看到其实所有的数据都已经挂载在 DOM 上了,只是前端做了筛选分页而已。
所以我们就可以用一段代码,直接复制到浏览器的 console
中运行就可以筛选出所有 CSS 相关的文章列表了。
// 获取 CSS 相关的标准列表
JSON.stringify(
Array.prototype.slice
.call(document.querySelector('#container').children)
.filter(e => e.getAttribute('data-tag').match(/css/)) // 找到有 CSS tag 的
.map(e => ({ name: e.children[1].innerText, url: e.children[1].children[0].href })) // 只获取标题名字和链接
);
最终输入的内容如下:
然后我们点击下方的 "Copy
" 即可复制,把这个 JSON 内容保存在一个 JavaScript 文件里面,并且赋予一个变量叫 standards
。在我们后面的爬虫代码中需要用到。
这里我们用一个简单的方法来获取爬取信息,就是在 W3C 原本的页面上开启一个 iframe
,这样我们就可以忽略掉跨域的问题。
let standards = [...] // 这里面的内容就是我们刚刚从 W3C 网页中爬取到的内容
let iframe = document.createElement('iframe');
document.body.innerHtml = '';
document.body.appendChild(iframe);
function happen(element, event) {
return new Promise(function (resolve) {
let handler = () => {
resolve();
element.removeEventListener(event, handler);
};
element.addEventListener(event, handler);
});
}
void (async function () {
for (let standard of standards) {
iframe.src = standard.url; // 让 Iframe 跳转到每个 standards 中的详情页面
console.log(standard.name);
await happen(iframe, 'load'); // 等待 iframe 中的页面打开完毕
}
})();
然后我们需要的信息就是属性表格中的内容:
如果我们看一下这个 table 的 HTML 代码,我们会发现这个 table 都是有一个 class 名叫 propdef
的。我们就可以用这个特性来获取这个表格中的内容了。
所以我们就可以在 await happen(iframe, 'load')
,后面添加一行代码来打印这个表格的 DOM 元素来看一下:
let iframe = document.createElement('iframe');
document.body.innerHTML = '';
document.body.appendChild(iframe);
function happen(element, event) {
return new Promise(function (resolve) {
let handler = () => {
resolve();
element.removeEventListener(event, handler);
};
element.addEventListener(event, handler);
});
}
void (async function () {
for (let standard of standards) {
iframe.src = standard.url;
console.log(standard.name);
await happen(iframe, 'load');
console.log(iframe.contentDocument.querySelectorAll('.propdef')); // 这里答应出表格的内容
}
})();