《CSS揭秘》阅读笔记

我还记得国外某位大牛在一篇文章中写道,CSS is fine, it's just really hard。读完他的那篇文章,我是不信的,CSS会有多难,不过就是大量属性堆叠在一起罢了。然而等我切切实实地把写一个又一个页面的时候,我充分理解到了作者通过'really hard' 想要表达的心酸。相比于一门真正的编程语言(比如JAVA、JavaScript等),CSS以其复杂庞大的细节深深征服了我。我想我应该更加深入具体的去了解它,以至于更加得心应手的使用它。

《CSS揭秘》是图灵读者群里某位小伙伴推荐给我的,我对这本书充满了期待。

第一章 CSS编码技巧

尽量减少代码重复
1.在实践中,代码可维护性的最大要素是尽量减少改动时要编辑的地方。
2.当某些值相互依赖时,应该把它们的相互关系用代码表达出来。
3.代码易维护和代码量少不可兼得。
4.我们还得到了一个特殊的颜色关键字 currentColor ,它是从SVG 那里借鉴来的。这个关键字并没有绑定到一个固定的颜色值,而是一直被解析为 color 。实际上,这个特性让它成为了 CSS 中有史以来的第一个变量 。虽然功能很有限,但它真的是个变量。
5.inherit 可以用在任何 CSS 属性中,而且它总是绑定到父元素的计算值(对伪元素来说,则会取生成该伪元素的宿主元素)。同样在有时候对于一些用于布局的元素可以使用 padding来将元素大小撑开,而不用显式地设置 width和 height。

traingle { position: relative; }
triangle:before{
    content: "";
    position: absolute;
    top: -.4em; left: 1em;
    padding: .35em;/*这里使用了padding使得元素具有了大小*/
    background: inherit;
    border: inherit;
    border-right: 0;
    border-bottom: 0;
    transform: rotate(45deg);
}

相信你的眼睛而不是数字

人的眼睛并不是一台完美的输入设备。有时候精准的尺度看起来并不精准,而我们的设计需要顺应这种偏差。举一个在视觉设计领域广为人知的例子吧,我们的眼睛在看到一个完美垂直居中的物体时,会感觉它并不居中。实际上,我们应该把这个物体从几何学的中心点再稍微向上挪一点,才能取得理想的视觉效果。来亲身体验一下这件怪事吧(参见图 1-9)。

与此类似,在字体设计领域广为人知的是,圆形的字形(比如 0)矩形字形相比,需要稍微放大一些,因为我们倾向于把圆形感知得比其实际尺寸更小一些。

这些视觉上的错觉在任何形式的视觉设计中都普遍存在,需要我们有针对性地进行调整。一个非常常见的例子是给一个文本容器设置内边距。不论内容文本有多长,是一个单词还是几个段落,这个问题都会出现。假如我们给容器的四边指定相同的内边距,则实际效果看起来并不相等,就像图 1-11显示的那样。原因在于,字母的形状在两端都比较整齐,而顶部和底部则往往参差不齐,从而导致你的眼睛把这些参差不齐的空缺部分感知为多出来的内边距。因此,如果我们希望四边的内边距看起来是基本一致的,就需要减少顶部和底部的内边距。你可以在图 1-12 中看出这种差异。

响应式设计

媒体查询不能以一种连续的方式来修复问题。它们的工作原理基于某几个特定的阶梯(亦称“断点”),如果大部分样式代码并不是以弹性的方式来编写的,那么媒体查询能做的只是修补某个特定分辨率下的特定问题——这本质上只是把灰尘扫到地毯下面而已。媒体查询的断点不应该由具体的设备来决定,而应该根据设计自身来决定。

提示
不妨考虑在你的媒体查询中使用 em 单位取代像素单位。这能让文本缩放在必要时触发布局的变化。

一下是对响应式设计的一些建议:
1.使用百分比长度来取代固定长度。如果实在做不到这一点,也应该
尝试使用与视口相关的单位( vw 、 vh 、 vmin 和 vmax ),它们的值解
析为视口宽度或高度的百分比。
2.当你需要在较大分辨率下得到固定宽度时,使用 max-width (会覆盖width,同时本身会被min-width覆盖)而不是width ,因为它可以适应较小的分辨率(在较小的分辨率下,使用max-width可以避免出现滚动条),而无需使用媒体查询。
3.不要忘记为替换元素(比如 img 、 object 、 video 、 iframe 等)设置一个 max-width ,值为 100% 。
4.假如背景图片需要完整地铺满一个容器,不管容器的尺寸如何变化,background-size: cover 这个属性都可以做到。但是,我们也要时刻牢记——带宽并不是无限的,因此在移动网页中通过 CSS 把一张大图缩小显示往往是不太明智的。
5.当图片(或其他元素)以行列式进行布局时,让视口的宽度来决定列的数量。弹性盒布局(即 Flexbox)或者 display: inline-block
加上常规的文本折行行为,都可以实现这一点。
6.在使用多列文本时,指定 column-width (列宽)而不是指定
column-count (列数),这样它就可以在较小的屏幕上自动显示为单列布局。

总的来说,我们的思路是尽最大努力实现弹性可伸缩的布局,并在媒体查询的各个断点区间内指定相应的尺寸。

合理使用代码简写

合理使用简写是一种良好的防卫性编码方式,可以抵御未来的风险。当然,如果我们要明确地去覆盖某个具体的展开式属性并保留其他相关样式,那就需要用展开式属性。

但我们应该注意到,在同时使用简写属性和展开式属性时,我们应该将简写属性写在展开式属性的前面,特别是当你不确定简写属性是否包含这个展开式属性的时候,以防止展开式属性被简写属性覆盖。

    background: url(tr.png) no-repeat top right / 2em 2em,
    url(br.png) no-repeat bottom right / 2em 2em,
    url(bl.png) no-repeat bottom left / 2em 2em;

其实我们可以从 CSS 的“列表扩散规则”那里得到好处。它的意思是说,如果只为某个属性提供一个值,那它就会扩散并应用到列表中的每一项。

    background: url(tr.png) top right,
    url(br.png) bottom right,
    url(bl.png) bottom left;
    background-size: 2em 2em;
    background-repeat: no-repeat;

怪异的简写语法

你可能已经注意到前面那个背景属性简写的例子了:在 background 简写属性中指定 background-size时,需要同时提供一个 background-position 值(哪怕它的值就是其初始值也需要写出来),而且还要使用一个斜杠( / )作为分隔。为什么有些简写的语法如此怪异?

这通常都是为了消除歧义。在这个例子中, top right 显然是background-position ,而 2em 2em 是background-size ,不管它们的顺序如何。但是,请设想一下 50% 50% 这样的值,它到底是 background-size 还是 background-position 呢?当你在使用展开式属性时,CSS 解析器明白你的意图;而当你使用简写属性时,解析器需要在没有属性名提示的情况下弄清楚 50% 50% 到底指什么。这就是需要引入斜杠的原因。**

我应该使用预处理器吗?

在每个项目开始时使用纯 CSS,只有当代码开始变得无法保持 DRY 时,才切换到预处理器的方案。为了避免可能发生的“依赖”或“滥用”,在引入预处理器的问题上需要冷静决策,不应该在每个项目一开始时就不动脑筋顺着惯性来。

第二章 边框与背景

半透明边框

使用 background-clip属性来调整背景的大小,使其不再延伸到边框的下面。

多重边框

1.使用多个 box-shadow来创建多重边框,当偏移量和模糊量的值均为0的时候,设置第四个参数(扩张半径),就可以呈现出如同实线边框一样的“投影”。
2.使用 border和 outline来创建双层边框。但是当设置 border-radius后,outline不能完全贴合 border,这是一个bug。

这两个解决方案都要注意要预先预留出 box-shadow和 outline的位置(增加额外的 margin)。如果是 inset的 box-shadow则需要额外的padding。

灵活的背景定位

很多时候我们对容器中的背景图片进行定位时,我们可以使用background-position属性。但是,当容器尺寸不固定时,这个方案就有可能存在很大局限,借助于现代的CSS特性,我们可以拥有更好的方法。

1.background-position 扩展语法,我们可以在 position偏移量前面指定关键字。

    background: right 20px bottom 10px;

2.首先我们要明白,盒模型存在着三个矩形框(border-box、padding-box、content-box),那么 background-position是相对于哪个矩形框来说的呢?默认情况下是以 padding-box为准的,这样边框才不会遮挡住背景。如果我们把 background-origin的值改为 content-box,我们在 background-position属性中使用的关键字就会以内容框的边缘为基准了。

    padding: 10px;
    background-origin: content-box;

3.如果我们想要把图片定位到距离底边10px,距离右边20px 的位置。我们以左上角便宜的思路来考虑,其实就是希望有一个100%-20px的水平偏移量,以及100%-10px的垂直偏移量。

    background-position: calc(100% - 20px) calc(100% - 10px);
    /*注意减号左右两侧需要留一个空格*/

边框内圆角

有时候我们可以利用两个嵌套的容器来实现这个效果,给外部容器设置一个外边距,内部容器设置一个圆角。

但是加入我们只利用一个容器呢?那我们可以利用容器的 outline和 box-shadow属性,来为容器设置一个 “边框”。首先我们应该知道描边(outline)并不会跟着元素的圆角走,但 box-shadow 却是会的。

    background: tan;
    border-radius: .8em;
    padding: 1em;
    box-shadow: 0 0 0 .6em #655;
    outline: .6em solid #655;

box-shadow在这里实际上就是为了填满 outline和圆角之间存在的空隙,所以 box-shadow的扩张值不必和 outline的宽度一样大,实际扩展值为

    x=border-radius
    sqrt(x^2+x^2)-x

条纹背景

1.对于水平条纹和垂直条纹我们使用 background: linear-gradient()属性值来创建颜色渐变,以此来生成条纹。

    background: linear-gradient(red 50%,yellow 0);/*默认是垂直条纹*/
    background: linear-gradient(to right,red 50%,yellow 0);

    background: linear-gradient(red 33.3%,yellow 33.3%,yellow 66.6%,blue 100%);

    background-size: 100% 30px;

同时我们也可以生成有一定角度的条纹效果。

    background: linear-gradient(45deg,red 50%,yellow 0);
    background-size: 30px 30px;

然而你会发现这存在问题,这里所设置的45°偏转并不是相对于整个容器来说的,而是相对于这里设置的30px边长的小正方形。

因此假如我们需要给容器设置一个“偏移”,那么比较好的做法就是,将两列拆成四列。

    background: linear-gradient(45deg,red 25%,yellow 0,yellow 50%,red 0,red 75%,yellow 0);
    background-size: 30px 30px;

这里我们会发现这样确实达到了容器“偏移”的效果,但是问题是 background-size仍然设为 30px大小,我们所得到的条纹到底是多宽呢?这个问题应该不难,大家可以自行思考。

2.你可能会发现上面所提到的方法只适用于一种情况,那就是45deg,一旦我们对角度的需求更改的话,那么上述办法就失效了。幸运的是,我们还有更好的办法来解决这个问题。

一个鲜为人知的真相是:linear-gradient()和 radial-gradient()还各有一个循环加强版:repeating-linear-gradient()和 repeating-radial-gradient()。它们的工作方式与前两类相同,只不过色标是无限循环重复的,直到颜色铺满整个容器。

    background: repeating-linear-gradient(45deg,red,yellow 15px);

因此,当我们将中间的渐变区域“去掉”(将它设置为特别小)时,我们发现

    background: repeating-linear-gradient(45deg,red,red 15px,yellow 0,yellow 30px);

3.更加灵活的条纹

很多时候我们所需要的条纹并不是差异巨大的两个不同颜色,而是两个色系相同或者相近的颜色。我们可以采取将深色作为背景,而把半透明白色条纹覆盖在上面来显示出同色系的条纹效果。

复杂的背景图案

1.线性

如果我们把垂直的渐变和水平的渐变以某种规律重合在一起的话,那么可以设计出令人惊讶的效果。

甚至我们还可以将两幅图案重合起来。

2.波点

除了线性渐变,径向渐变也非常有用,可以利用径向渐变来创建圆和椭圆。

    background: red radial-gradient(dan 30%,transparent 0);
    background-size: 30px 30px;

但是,这个图案看起来并不十分实用。那么,下面这个可能让你大吃一惊。

    background: red;
    background-image: radial-gradient(tan 30%,transparent 0),radial-gradient(tan 30%,transparent 0); 
    background-position: 0 0,15px 15px;
    background-size: 30px 30px;

3.棋盘

使用直角三角形拼成正方形,一共需要四个直角三角形。

    background: red;
    background-image: linear-gradient(45deg,yellow 25%,transparent 0),linear-gradient(45deg,transparent 75%,yellow 0),linear-gradient(45deg,yellow 25%,transparent 0),linear-gradient(45deg,transparent 75%,yellow 0);
    background-position: 0 0,15px 15px,15px 15px,30px 0;
    background-size: 30px 30px;

当然还有更加简洁的写法,那就是把前两次渐变以及后两次渐变分别合成一次渐变。

    background: red;
    background-image: linear-gradient(45deg,rgba(0,0,0,0.25) 25%,transparent 0,transparent 75%,rgba(0,0,0,0.25) 0),linear-gradient(45deg,rgba(0,0,0,0.25) 25%,transparent 0,transparent 75%,rgba(0,0,0,0.25) 0);
    background-position: 0 0,15px 15px;
    background-size: 30px 30px;

最佳的解决方案还是SVG图案,对于棋盘来说,在以后有机会再学习。

伪随机模型

在上面的背景中你会轻易的发现,我们一直是在重复平铺几何图案。其实自然界中的事物都不是以无限平铺的方式存在的。即使重复,也往往伴随着多样性和随机性。

我们可以利用“蝉原则”来创建具有一定随机性的背景图案。

假设我们需要模拟四种颜色条纹的随机性,那么我们可能想到,把这组条纹拆分成多个图层:一种颜色做底色,另三种颜色作为条纹,然后再让条纹以不同的颜色进行重复平铺(对不同颜色条纹设置不同的background-size)。虽然这可以让条纹出现局部的随机性,但是一旦平铺区间大小超过三种 background-size的最小公倍数时,我们发现条纹开始重复了。

那么这里为了使得 background-size的最小公倍数比较大,在设置background-size时,选择质数会比较好,因为质数跟其他任意数字都是相对质数。

连续的图像边框

如果我们想要把一张图片应用为边框,并且还希望这个元素的尺寸在扩大或缩小时,这幅图片都可以自动延伸并覆盖完整的边框区域,那么首先可能想到的是使用 border-image属性。但是,如果我们详细了解过 border-image属性的工作原理后,那么你可能发现 border-image可能难以完成这个任务。

border-image是将一张图片切割成九宫格的形式,然后将这九个部分分别对应为盒模型的 border的九个部分,这是非常死板的做法,一旦内容大小的改变,会对局部产生难以预期的影响。

一个非常容易想到的解决方案就是,在内容外部设置一个较大的容器,将图片设为外部大容器的背景图片,同时将内容背景设置为纯白色以覆盖内容部分展示出的背景图片。但这个做法显然是不太优雅的,它不仅引入了多余的容器,而且并没有将结构和表现分离。

1.比较好的做法是,就是利用 background-clip和 background-origin(这里要将 background-origin设为 border-box)以及白色的线性(这里利用了渐变是作为 background-image二不是作为 color属性)渐变来创建这样一个容器。但这里要注意到的是,在使用 background简写属性的时候,background-origin是在 background-clip属性前面的。

2.同样除了应用图片,单纯使用渐变也可以设计出善心悦目的边框(千万不要忽略渐变其实也是图片!)

    padding: 1em;
    border: 1em solid transparent;
    background: linear-gradient(white, white) padding-box,repeating-linear-gradient(-45deg,red 0, red 12.5%,transparent 0, transparent 25%,#58a 0, #58a 37.5%,transparent 0, transparent 50%) 0 / 5em 5em;;

使用border-image同样可以实现

    padding: 1em;
    border: 16px solid transparent;
    border-image: 16 repeating-linear-gradient(-45deg,red 0, red 1em,transparent 0, transparent 2em,#58a 0, #58a 3em,transparent 0, transparent 4em);

3.我们可以利用动画和渐变来制作一个蚂蚁行军图。

    @keyframes ants{to {background-position: 100% }}
    padding: 1em;
    border: 1px solid transparent;
    background: linear-gradient(white,white) padding-box,repeating-linear-gradient(-45deg,black 0,black 25%,white 0,white 50%) 0/ 0.6em 0.6em;
    animation: ants 12s linear infinite;

(这里的动画是对于每一个 background-image来说的)
background-position如果只有一个值被指定,则这个值就会默认设置背景图片位置中的水平方向,与此同时垂直方向的默认值被设置成50%。

我们也可以利用 border-image来创建一些特殊的边框,比如顶边边框被裁剪的效果

    border-top: 0.2em solid transparent;
    border-image: 100% 0 0 linear-gradient(to right,currentcolor 4em,transparent 0);
    padding-top: 1em;

第三章 形状

自适应的椭圆

我们日常会使用 border-radius来生成圆,比如

    background: red;
    width: 200px;
    height: 200px;
    border-radius: 100px;

这里有一个问题,就是当你指定大于100px的半径的值的时候,仍然可以生成一个圆。规范特别指出了原因

    当任意两个相邻圆角的半径之和超过 border box 的尺寸时,用户代理必须按比例减小各个
边框半径所使用的值,直到它们不会相互重叠为止。

而要得到一个自适应的椭圆也很简单,只需要将 border-radius的值设置为50%,这样不管容器的大小、长宽都可以生成一个圆或者椭圆。

半椭圆
四分之一椭圆

这里我们需要明白 border-raius是一个简写值,那么实际展开值就是 border-top-left-radius、border-top-right-radius、border-bottom-left-radius、border-bottom-right-radius四个值。而如何简写这四个值呢?如果我们可以向它一次性提供用空格分开的多个值。如果我们传给它四个值,这四个值就会从左上角开始以顺时针顺序应用到四个角,如果我们传给它三个值,那么第二和第四个值相同,如果传给它两个值,那么第一和第三个值相同。同时,我们甚至可以提供完全不同的垂直和水平半径。举例来说,当 border-radius的值为 10px/ 5px 20px时,那么就相当于10px 10px 10px 10px/ 5px 20px 5px 20px。

平行四边形

假设我们希望创建一个平行四边形的按钮,那么首先我们可能想到使用2D变形 skew(),但是这会造成整个按钮包括内容倾斜,使的内容难以辨认。但,如果在内容上再嵌套一个容器,在按钮 skew()的情况下,对内容容器进行相反方向的 skew()就能解决这个问题。但是这同样犯了结构没有与表现分离的错误。

这里可以利用伪元素来实现这样一个效果。我们不对元素本身进行变形,而是对伪元素进行变形。

    .button {
    position: relative;
    /* 其他的文字颜色、内边距等样式…… */
}
    .button::before {
    content: ''; /* 用伪元素来生成一个矩形 */
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    z-index: -1;
    background: #58a;
    transform: skew(45deg);
}

这里给我的感觉这并不是一个好方法,相对于上面添加额外的容器来说,操作限制太多,一旦文字内容太多(或者字号太大)超出了伪元素区域,看起来就很不协调。

菱形图形

日常开发中将图片裁切为菱形是常见的设计手法。

1.基于变换的手法,就像前一篇提到的,给内容增加一个额外的容器,内外进行相反的rotate()变换。

<div class="picture">
    ![](adam-catlace.jpg)
</div>

.picture {
    width: 400px;
    transform: rotate(45deg);
    overflow: hidden;
}
.picture > img {
    max-width: 100%;
    transform: rotate(-45deg);
}

但是这并不是我们想要的效果,图形被裁剪成了一个八边形。那么问题出在什么地方呢?仔细观察我们想要得到的效果,你就会发现,我们需要的是图片的宽度等于容器的对角线的长度才行。所以我们需要将图片放大。将 max-width设置为1.414倍(应该明白1.414怎么来的?),但是还有更好的解决方法,就是 scale()进行缩放。

.picture {
    width: 400px;
    transform: rotate(45deg);
    overflow: hidden;
}
.picture > img {
    max-width: 100%;
    transform: rotate(-45deg) scale(1.42);
}

2.真正的裁剪路劲方案,你会发现上述的方法存在一个问题,就是当图片不是正方形的时候,就无能为力了。

这里我们要介绍一个新属性 clip-path,clip-path借鉴于 SVG语法具有很强的功能性。

    clip-path: polygon(50% 0,100% 50%,50% 100%,0 50%);

同时我们还可以利用这个属性产生动画

img {
    clip-path: polygon(50% 0, 100% 50%,50% 100%, 0 50%);
    transition: 1s clip-path;
}
img:hover {
    clip-path: polygon(0 0, 100% 0,100% 100%, 0 100%);
}

但是这个功能仍然还在实验中。

切角效果

1.解决方案一是利用渐变,设置一个45度的渐变就可以了。而如果我们需要切除两个角,使用一个渐变会使得相互覆盖。所以这里就需要将限制渐变的大小,使得我们裁切的角不会被覆盖。同时注意到一旦设置了 background-size,就要注意背景是否会重复。

    background: linear-gradient(45deg,white 15px,red 0) left/50% 100% no-repeat,linear-gradient(-45deg,white 15px,red 0) right/50% 100% no-repeat;

上述代码切割了两个底角。

如果是需要切割四个角那么你可以用四层渐变,但还有一个更好的办法那就是之前介绍的 clip-path。

    background: yellowgreen;
    clip-path: polygon(20% 0,80% 0,100% 20%,100% 80%,80% 100%,20% 100%,0 80%,0 20%);

弧形切角

1.这里仍然使用渐变,不过是径向渐变,就可以设计出内凹圆角的效果了

    background: radial-gradient(circle at top left,transparent 15px,red 0) top left,radial-gradient(circle at top right,transparent 15px,red 0) top right,radial-gradient(circle at bottom left,transparent 15px,red 0) bottom left,radial-gradient(circle at bottom right,transparent 15px,red 0) bottom right;
    background-size: 50% 50%;
    background-repeat: no-repeat;

2.另一个比较好的方案就是内联SVG和 border-image方案,由于我还不会SVG矢量图,所以这里略过。

3.最后一个仍然是 clip-path方案,但需要注意的是这是个仍在试验阶段的属性。

推荐阅读更多精彩内容

  • 选择qi:是表达式 标签选择器 类选择器 属性选择器 继承属性: color,font,text-align,li...
    wzhiq896阅读 649评论 0 2
  • 选择qi:是表达式 标签选择器 类选择器 属性选择器 继承属性: color,font,text-align,li...
    love2013阅读 710评论 0 9
  • 1、属性选择器:id选择器 # 通过id 来选择类名选择器 . 通过类名来选择属性选择器 ...
    圆圆和方方阅读 495评论 0 7
  • CSS背景 background-color:red background-image:url('paper.gi...
    专注寒冰三千岁阅读 108评论 0 2
  • 70后的伙伴们大约都还记得小学一年级老师教过的那首儿歌 :我在马路边,捡到一分钱,把它交到警察叔叔手里边…… 歌词...
    燕子_1422阅读 124评论 0 0