你不知道的CSS(上)

2,756 阅读17分钟

编写CSS样式时,其实有很多技巧可以节省HTML元素和让CSS更加的DRY,在这跟大家分享下。

文章将大量举各种示例,让同学们能更加清楚CSS的魅力所在。

文章将持续输出,增加一些新奇的或者唯美的示例。。。

背景与边框

半透明边框

半透明边框相信很多前端小伙伴都经常遇到过,UI设计师也经常性设计出这类样式。而如果对CSS不太熟悉的前端小伙伴人员来说,看似简单的东西,其实有一个小坑在里面。例:

...
<main class="wrapper">
  <div class="border">我是边框</div>
</main>
...
  
.wrapper {
  background: black;
  ...
}
.border {
  width: 100px;
  height: 100px;
  background: white;
  border: 10px solid hsla(0, 0%, 100%, 0.3);
}

border设置了0.3的透明度,那是不是我们要的半透明?让我们看看结果。

很显然不是的,background-clip属性的初始值为border-box,意味着背景默认情况下,会侵入边框的所在的范围,当属性值设置为padding-box

.border {
  background-clip: padding-box
}

这样就得到我们想要的效果图了。

多重边框


box-shadow

box-shadow我们大多数人已经用过,不太为人所知的是,它还接受第四个参数,通过设置正直或负值,可以让投影面积加大或者减小。

.border {
  box-shadow: 0 0 0 10px red;
  ...
}

使用box-shadow的好处在于,它接受逗号分隔语法,可以创建任意数量的投影

.border {
   box-shadow: 0 0 0 10px red, 0 0 0 20px yellow;
  ...
}

唯一注意的点的是,box-shadow是层层叠加的,前者会叠加在后者之上,上例中如果yellow需要宽度为10px,那么需要设置为20px

多重投影在大多数场合都可以很好的应用,但有一些注意事项。

投影行为跟边框不完全一致,因为它不会影响布局,而且也不会受box-sizing属性的影响。

投影部分并不会影响鼠标事件。

outline(描边)

如果我们只需要两层边框的话,outline(所有浏览器都支持 outline 属性)是一个不错的选择,它不会像box-shadow只能模拟实现边框。

.border {
  border: 10px solid #655;
  outline: 5px dashed deeppink;
  ...
}

描边的另一个好处在于,你可以通过outline-offset属性来控制它跟元素边缘之间的间距,这个属性甚至可以接受负值。这对于某些效果来说非常有用。举个例子

.border {
  outline: 5px solid deeppink;
  outline-offset: -79px;
}

咋一看,是不是想到了,上传文件的按钮?

使用outline也有一些需要注意的地方。

outline 并不能接受用逗号分隔的多个值。

边框不一定会贴合 border-radius属性产生的圆角,因此如果元素是圆角的,它的描边可能还是直角的。

灵活的背景定位


背景图片指定在容器某个区域,也是很常见。

background-position

.border {
  background: url('https://cdn.renqilai.com/2019_10_16/15_35_42.jpg') no-repeat white;
  background-size: 30px 30px;
  background-position: 60px 60px;
}

如果在一些不支持background-position扩展语法的浏览器上,背景图片会紧贴在左上角,看起来很奇怪,而且它会干扰到文字的可读性。提供一个回退方案也很简单,就是把老套的 bottom right 定位值写进 background 的简写属性中:

.border {
  background: url(code-pirate.svg) no-repeat bottom right #58a;
  background-position: 60px 60px;
}

background-origin

在给背景图片设置距离某个角的偏移量时,有一种情况极其常见:偏移量与容器的内边距一致。如果采用上面提到的 background-position 的扩展语法方案,代码看起来会是这样的:

padding: 10px;
background: url(code-pirate.svg) no-repeat #58a;
background-position: 80px 80px; // 分别增加20px

如你所见,它起作用了,但代码不够 DRY:每次改动内边距的值时,我们都需要在三个地方更新这个值!

我们经常使用background-position:top left,你是否有过疑惑,这个top left到底是哪个左上角?默认情况下,默认是以padding box。而我们使用background-origin默认情况下是padding-box。如果把它的 值改成 content-box (参见下面的代码),我们在 background-position 属 性中使用的边角关键字将会以内容区的边缘作为基准

background: url("code-pirate.svg") no-repeat #58a bottom right;
background-position: 70px 70px;
background-origin: content-box;

无论我们怎样更改padding值,都不会去影响图片的定位了。

calc()

从上述两例得知,我们无非是想得到图片距离白色右边框10px,距离白色下边框10px。使用calc,它可以完美地在background-position 属性中使用:

background-origin: padding-box;
background-position: calc(100% - 20px) calc(100% - 10px);

请不要忘记在 calc() 函数内部的 - 和 + 运算符的两侧各加一个空白符,否则会产生解析错误!这个规则如此怪异,是为了向前兼容:未来,在 calc() 内部可能会允许使用关键字,而这些关键字可能会包含连字符(即减号)

无论我们怎样更改padding值,都不会去影响图片的布局了。

边框内圆角


如果我们要实现内侧有圆角,外侧边框依旧保持着直角状态,如图:

我们一般会使用两个元素实现,如果我们需要一个元素,有没有方法实现?

在前面我们提到过outline描边,它并不会随border-radius改变边框,依旧保持着直角状态。

outline: 5px solid deeppink;
border-radius: 10px;
box-shadow: 0 0 0 5px deeppink;

这里box-shadow第四个参数值为5px,那这个值是如何得到的?是在某个浏览器测试的结果?事实上,指定一个等于描边宽度的扩张值在某些浏览器中可能会得到渲染异常,因此推荐一个稍小些的值。这里直接给出一个公式( 根号2 - 1)r,其中rborder-radis10px,另外有一个限制,为了让这个效果得以达成,扩张半径需要比描边的宽度值小,但它同时又要比 (根号2 - 1)r大。

条纹背景


线性渐变相信很多人都挺熟悉的,如何实现下图这种效果?

background: linear-gradient(#fb3 50%, #58a 50%);
background-size: 100% 30px;

如果我想将黄色部分的宽度减少,我需要去修改两个值,这种也不够DRY

background: linear-gradient(#fb3 50%, #58a 0);
background-size: 100% 30px;

将后者修改为0,那它的位置就总是会被浏览器调整为前一个色标的位置值,即可显示出相同的效果。

斜向条纹

在完成了水平之后,我们来尝试下倾斜渐变的效果。

background: linear-gradient(45deg,#fb3 50%, #58a 0);
background-size: 30px 30px;

很显然,这并不是我们希望的效果。原因在于我们只是把每个贴片内部渐变旋转45度,而不是把整个重复的背景都旋转了。实际上,如果想让整个背景看起来都旋转了45度,需要单个贴片包含了四条条纹,而不是两条,只有这样才有可能做到无缝拼接。

background: linear-gradient(45deg,#fb3 25%, #58a 0, 
  #58a 50%,#fb3 0, #fb3 75%, #58a 0);
background-size: 30px 30px;

效果出来了,并且是正确的,那如果我想要60度的?会是怎样?

background: linear-gradient(60deg,#fb3 50%, #58a 0);

很糟糕,并不是我们想得到的一个结果。幸运的是,我们还有更好的解决方案,一个循环式的加强版: repeating-linear-gradient()(radial-gradient()也有),色标是无限循环重复的,直到填满整个背景。

background: repeating-linear-gradient(60deg,#fb3, #fb3 15px, #58a 0, #58a 30px);
// background-size: 30px 30px;

上例几个例子中,我们设置同色标的开始位置和结束位置时,使用了逗号分隔,其实这种也是可以简化的,如:

background: white repeating-linear-gradient(60deg, 
    blue 0 15px, 
    hsla(0, 0%, 0%) 0 30px, 
    red 0 45px);

色标后有两个参数值,第一个为开始位置,第二个为结束位置。

如果我们将第一个参数不为0会是怎样的状态?

 background: white repeating-linear-gradient(60deg, 
      blue 10px 15px, 
      black 20px 30px, 
      red 0 45px);

我们将blue第一个参数值为10pxblack第一个参数值为20pxred保持不变。

我们注意到,左下角不再是以蓝色开头,而是红色开头。为什么?原因在于blue色标起始位置为10px,所以在左下角开始到10px会被重复的渐变叠加到。

而色标blueblack之间有一层渐变的过程,因为blue结束为15px,而black开始为20px,他们之间存在着5px的渐变过程。

灵活的同色系条纹

大多数情况下,如果我们想要的条纹图案色差不是差异极大的情况下,只是明度有着轻微的差异。举个例子,我们来看看这个条纹图案:

background: repeating-linear-gradient(30deg,
#79b, #79b 15px, #58a 0, #58a 30px);

条纹是由一个主色调( #58a)和它的浅色变体所组成的。但是,这两种颜色之间的关系在代码中并没有体现出来。此外,如果我们想要改变这个条纹的主色调,甚至需要修改四处!

幸运的是,还有一种更好的方法:不再为每种条纹单独指定颜色,而是 把最深的颜色指定为背景色,同时把半透明白色的条纹叠加在背景色之上来 得到浅色条纹:

background: #58a;
background-image: repeating-linear-gradient(30deg,hsla(0,0%,100%,.1),
  hsla(0,0%,100%,.1) 15px,transparent 0, transparent 30px);

复杂的背景图案


前面我们举了各种简单的例子,接下来我们尝试下稍微复杂点的情况。

网格

background-image:linear-gradient(white 1px, transparent 0),
  linear-gradient(90deg, white 1px, transparent 0);
background-size: 30px 30px;

我们甚至可以把两幅不同线宽、不同颜色的网格图案叠加起来,得到一个更加逼真的蓝图网格。

background-image: linear-gradient(white 2px, transparent 0),
  linear-gradient(90deg, white 2px, transparent 0),
  linear-gradient(hsla(0,0%,100%,.3) 1px,transparent 0),
  linear-gradient(90deg, hsla(0,0%,100%,.3) 1px,transparent 0);
background-size: 75px 75px, 75px 75px,15px 15px, 15px 15px;

波点

background: #655;
background-image: radial-gradient(tan 30%, transparent 0);
background-size: 30px 30px;

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

棋盘

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

波点,棋盘最主要利用了偏移,使用background-position来偏移第二个渐变,使用交差的方式,实现。

伪随机背景

自然界中,因为没有规律而美,而平时我们使用渐变总是能查到一定的规律,那如何增加随机性?

background-image:
      linear-gradient(90deg, #fb3 11px, transparent 0),
      linear-gradient(90deg, #ab4 23px, transparent 0),
      linear-gradient(90deg, #655 41px, transparent 0);
background-size: 41px 100%, 61px 100%, 83px 100%;

这个组合图案中第一个贴片的终点,就是各层背景图像以不同间距重复数次后再次统一对齐的 点像这种,看起来并没有遵循一定的规律,而这种是如何实现的?代码段中,background-size分别为41, 61, 83如果细心的同学可能会注意到它们都为质数,因为我们需要随机性更加真实,我们得把贴片的尺寸最大化,为了让最小公倍数最大化,这些数字最好是“相对质数” 平铺贴片的尺寸现在是 41×61×83=207 583 像素,比任何我们所 能想像出的屏幕分辨率都要大!

请注意这个方法不仅适用于背景,还可以用于其他涉及有规律重复的情况。

在照片图库中,为每幅图片应用细微的伪随机旋转效果时,可以使用多个 :nth-child(a) 选择符,且让 a 是质数。

如果要生成一个动画,而且想让它看起来不是按照明显的规律在循环时,我们可以应用多个时长为质数的动画。

连续的图像边框

有时我们想把一幅图案或图片应用为边框,而不是背景。可能会有人说使用border-image,它的原理基本上就是九宫格伸缩法:把图片切割成九块,然后把它们应用到 元素边框相应的边和角。但是我们希望出现在拐角出的图片区域是随着元素宽高和边框厚度的变化而变化的。用border-image是不可能做到的。

在前面,我们使用了很多的渐变背景,那我们能不能使用渐变来解决这个难题?

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;

需要注意的是,background第一个白色背景的background-clip是在padding-box上的,这样防止白色背景覆盖边框和第二个背景。

蚂蚁行军,当鼠标点击某个元素时,我们能看到边框能"行走",这是如何实现的?

@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 / .6em .6em;
animation: ants 12s linear infinite;

卡卷

.voucher {
   width: 150px;
   height: 80px;
   line-height: 5;
   background: radial-gradient(circle at 100% 50%, transparent 9px, #fff 0) 0 0 / 100% 100% no-repeat;
   filter: drop-shadow(3px 3px 2px rgba(0,0,0,.2)); 
}

三角形

...
width: 0;
height: 0;
border-width: 0 25px 40px 25px;
border-style: solid;
border-color: transparent transparent rgb(245, 129, 127) transparent;

这是利用什么原理实现?

盒子有marginborderpaddingcontent,下左下右边框交界处出呈现平滑的斜线。

我们可以利用这个特点, 通过设置不同的上下左右边框宽度或者颜色可以得到小三角等。

调整宽度大小可以调节三角形形状。

利用这个特点,我们来看看其他的形状

height:20px;
width:20px;
border-color: red blue yellow orange;
border-style:solid;
border-width:20px;

感兴趣的同学,可以利用这个特点,去捣鼓其他的形状,这里就不再举例啦。

形状

自适应的椭圆

场景:当我们要实现一个根据内容区的大小,自动适应的椭圆。如:

当内容区宽高相等,则为圆形。

当内容区宽度大于高度,则为椭圆。

border-radius可以帮我们实现这一点,但是如果我们使用px单位的话,并不能达到我们想要的效果。

而它可以接受另一个单位值,%百分比值。

border-radius ,有一个鲜为人知的真相:它可以单独指定水平垂直半径,只要用一个斜杠( / )分隔这两个值即可。

<main class="wrapper">
    <div class="voucher">我是一个形状</div>
</main>

.voucher {
  border-radius: 50% / 50%;
  padding: 10px;
  background: orange;
}

由于斜杠前后的两个值现在是一致的,所以可以简化为:

...
border-radius: 50%;

到这里大家能发现,border-radius其实是一个简写属性,在水平方向上,它可以接受4个值,垂直也是。这四个值分别从左上角开始以顺时针顺序应用到元素的拐角。说到这,我们直接举两个例子来看看效果吧。

半椭圆

...
border-radius: 50% / 100% 100% 0 0;
background: orange;

四分之一

...
border-radius: 100% 0 0 / 100% 0 0;

平行四边形


平时四边形形状,相信也是很常用的UI设计。而如何只用一个元素实现?可能我们脑子立马会想到一个skew()变形,这个想法是对的。但是,如果你不做一些处理的话,内容也会跟着倾斜。那如何使内容不变化呢?

.voucher {
  position: relative;
  padding: 10px;
  z-index: 0;
}
.voucher::after {
  content: '';
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  background: orange;
  transform: skew(-45deg);
}

这一实例,巧用伪元素,来实现我们想要的效果。

菱形图片


菱形图片的制作,我们大概能想出,使用两个元素,然后旋转剪切啥的来实现。而我们是利用clip-path来实现我们的功能。

注意 cli-path有兼容性问题,如果项目考虑到兼容低版本的,需要注意。

<main class="wrapper">
    <img src="https://cdn.renqilai.com/2020_05_27/11_58_26.png" alt="我失败了">
</main>

img {
  width: 100px;
  height: 100px;
  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%);
}

我们不仅做出了棱形,顺带还让它动起来,使得不会那么枯燥。

clip-path可以绘制各种各样的形状,不过绘制点比较麻烦,快捷绘制出形状点我

顺带的也给出一位牛人使用clip-path做出的各类好玩的东西点我

饼图


<div class="pie" style="animation-delay: -30s;"></div> // (3)
<div class="pie" style="animation-delay: -70s;"></div>


...
.pie {
  position: relative;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: #735f5f;
  background-image: linear-gradient(to right, transparent 50%,  #c43737 0);
  overflow: hidden;
}
.pie::after {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  width: 50%;
  background-color: inherit;
  transform-origin: left;
  animation: spin 50s linear infinite,
    bg 100s step-end infinite;
  animation-play-state: paused; // (2)
  animation-delay: inherit; // (4)
}
@keyframes spin {
  to { transform: rotate(.5turn); } // (1)
}
@keyframes bg {
  50% { background: #c43737; }
}

注意点(1),.5turn代表(0.5 × 360 = 180)180deg。(2),将动画处于停止状态,为何要停止,请看注意点(3)。(3),

一个负的延时值是合法的。与 0s 的延时类似,它意味着动画会立即开始播放,但会自动前进到延时值的绝对值处,就好像动画在过去已经播放了指定的时间一样。因此实际效果就是动画跳过指定时间而从中间开始播放了。

因为我们的动画是暂停的,所以动画的第一帧(由负的animation-delay值定义)将是唯一显示出的那一帧。在饼图上显出的比率就是我们的animation-delay 值在总的动画持续时间中所占的比率

而我们使用内联方式指定动画延时时间,并且在(4)使用inherit继承其时间。这样我们就能控制进度了。

30%

70%

到这,文章就结束啦,希望同学们能get到新的知识点。


亲,分享不易额,喜欢的话一定别忘了点💖!!!

只关注不点💖的都是耍流氓,只收藏也不点💖的也一样是耍流氓。