CSS有个样式叫position
,它决定了浏览器应当如何排版所有元素。
首先介绍的是这个样式的默认值,即position: static
: 它表浏览器将以默认的,从左到右,从上到下,从内到外的方式去排列所有元素。比如我们如下正常写三个排排坐的<div>
<div class="size-20 bg-red-300"></div>
<div class="size-20 bg-yellow-300"></div>
<div class="size-20 bg-cyan-300"></div>
由于div是块元素,自己默认独占一行,所以视觉上三个方块是竖向排列的,浏览器渲染结果如下图所示:
再来看第二个值:position:relative
,它的效果其实是在static
的基础上,根据样式中给出的top/bottom/right/left/z-index
再去调整元素的视觉位置。
比如我们对上面的代码做如下修改:
<div class="size-20 bg-red-300"></div>
- <div class="size-20 bg-yellow-300"></div>
+ <div class="size-20 bg-yellow-300" style="position: relative; top: 20px; left: 20px;"></div>
<div class="size-20 bg-cyan-300"></div>
浏览器的渲染结果就会变成下面这样:
你可以简单的把position: relative
的排版过程理解为两步走:
position: relative
的元素,然后按照样式中的top/bottom/right/left/z-index
对各个元素的位置进行调整第三个是值 position: absolute
,它的排版过程可以理解为以下三步走:
position: absolute
的元素先扔出去,它们不参与这一步的排版,后续另安排position: absolute
元素了,但在安排之前,浏览器要先看看元素的爹,爷爷,祖宗,一路向上找,看看能不能找到一个position
样式不是static
的先人,如果实在没有,一路都找不到,你就可以把这个先人理解为<body>
元素top/bottom/right/left/z-index
的值,把元素放在正确的位置上 <div class="size-20 bg-red-300"></div>
- <div class="size-20 bg-yellow-300" style="position: relative; top: 20px; left: 20px;"></div>
+ <div style="position:relative; top: 100px; left: 100px;">
+ <div class="size-20 bg-yellow-300" style="position: absolute; top: 20px; left: 20px;"></div>
+ </div>
<div class="size-20 bg-cyan-300"></div>
这个例子的渲染结果如下所示:
我来给你解释一下浏览器是怎么渲染上面这段代码的:
static
进行一次基本排版,上面也说了,在基本排版过程中,会把黄色的<div>
给扔掉。而tricky的事情是,黄色<div>
的父亲是没有显式写上宽高尺寸的,而黄色被扔掉后,黄色的父亲相当于内部没有内容了,所以宽高就变成了0。<div>
不参与基本排版,但黄色<div>
的父亲是参与的,因为父亲是position:relative
<div>
正常排版,左上角的屏幕坐标是0, 0
,宽高都是20个tailwind基本单位,即80像素<div>
的父亲也正常排版,左上角的屏幕坐标是0, 80px
,但黄色的父亲在排版中被认为是一个空<div>
,所以没有实际尺寸<div>
正常排版,左上角的屏幕坐标依然是0, 80px
: 因为黄色的<div>
是没有尺寸的0 * 0
的一个div
<div>
的宽度并不是0,而是浏览器的最大宽度<div>
的父亲,我们上面也说了,position: relative
会在初步排版结束后再调整位置,于是黄色<div>
的父亲的左上角坐标被调整成了(0, 80px) + (top: 100px; left: 100px)
,即100px, 180px
<div>
:它以它爹的左上角坐标为基准,再加上自己的样式,得出它的左上角坐标是(100px, 180px) + (top: 20px; left:20px)
即120px, 200px
理解了这三个position
样式值,浏览器的排版过程里最基本的逻辑你就理解了,再介绍两个另外的值
第一个是position: fixed
,它和absolute
很相似,不同的是在排版过程中这不找任何先人做基准,而是直接就以浏览器根元素的左上角坐标为基准点,是纯粹的绝对定位
<div class="size-20 bg-red-300"></div>
<div style="position:relative; top: 100px; left: 100px;">
- <div class="size-20 bg-yellow-300" style="position: absolute; top: 20px; left: 20px;"></div>
+ <div class="size-20 bg-yellow-300" style="position: fixed; top: 20px; left: 20px;"></div>
</div>
<div class="size-20 bg-cyan-300"></div>
第二个是position: sticky
,它的行为就比较难描述一点,它是用来在滚动过程中将元素固定在滚动框内的。下面是一个例子:
<div class="size-20 bg-red-300"></div>
<div class="size-40 bg-blue-300" style="overflow:auto">
<div class="size-20 bg-yellow-300" style="position: sticky; top: 20px; left: 20px;"></div>
<!-- 这里用lorem.100生成100个单词 -->
</div>
<div class="size-20 bg-cyan-300"></div>
效果如下:
sticky
的逻辑和relative
有点像,或者说是加强版的relative
,可以这样去理解:
position
和top/left/right/bottom
四个样式都不存在一样position:sticky
元素的祖上,有没有先人有滚动条:即overflow
属性的值为auto
, scroll
或hidden
top/left/bottom/right
四个属性中,只有left
在某些情况下会生效,如果存在的话,转下一步position:sticky
的时候,父级元素中一定存在一个overflow:auto/scroll/hidden
overflow:auto
或overflow:scroll
的情况下,浏览器会按top/bottom/right/left
的值,对元素位置进行调整,然后重点来了:在先人存在滚动条且滚动的过程中,这个sticky
元素和先人的相对位置,不会改变overflow:hidden
的情况下,浏览器也会按照top/bottom/right/left
的值对元素位置进行调整,但由于此时没有滚动条,所以看起来效果和relative
是差不多的以上这些样式在tailwind中是这样写的:
CSS样式 | tailwind类名 |
---|---|
position:static |
static |
position:relative |
relative |
position:absolute |
absolute |
position:fixed |
fixed |
position:sticky |
sticky |
overflow:visible |
overflow-visible |
overflow:auto |
overflow-auto |
overflow:scroll |
overflow-scroll |
overflow:hidden |
overflow-hidden |
top:10px |
top-[10px] |
top:calc(3/5 * 100%) |
top-3/5 |
top:-20px |
-top-5 |
top:100% |
top-full |
其实CSS样式overflow
还有一些其它知识点,包括:
overflow:clip
这个值overflow
其实是两个CSS属性:overflow-x
和overflow-y
的简写,也就是说,在横向和竖向两个方向上,可以分别设置滚动样式。这个了解一下就可以,使用到的时候查文档就可以除了一些特殊的组件(比如用sticky
实现的固定在视口的漂浮条啊等),这部分知识在实际工作中,用的最频繁的,其实是position:relative
和position:absolute
的组合。
典型的就是画重叠元素的时候,就会用到这种技巧,来直接看例子(这回就直接用tailwind了):
<div class="m-5 size-50 outline-1 outline-red-300"></div>
<div class="m-5 size-50 outline-1 outline-purple-300 relative">
<div class="size-30 top-3 left-3 bg-blue-300 absolute"></div>
<div class="size-30 top-6 left-6 bg-cyan-300 absolute"></div>
<div class="size-30 top-9 left-9 bg-yellow-300 absolute"></div>
</div>
<div class="m-5 size-50 outline-1 outline-red-300"></div>
效果如下:
首先看三个空心框:三个框样式基本一样,只有一点特殊:
relative
,但没有设置任何top/left/bottom/right
属性。用意并不是要对第二个框的位置做什么调整,而是使第二个框成为其子元素absolute
时的先人,以第二个框自己的左上角位置为原点去定位。
然后在第二个框内部,三个实心的框,就可以直接使用absolute
配合top/left
来定位了
来看下面这个稍微复杂一点的例子:
<div class="m-5 size-50 outline-1 outline-black relative">
<div id="blue_div" class="size-30 bg-blue-300 absolute top-5 left-5 "></div>
<div id="red_div" class="size-30 bg-red-300 absolute top-15 left-15 "></div>
</div>
<div class="m-5 size-50 outline-1 outline-black relative transform-3d">
<div id="blue_div_in_3d" class="size-30 bg-blue-300 absolute top-5 left-5 "></div>
<div id="red_div_in_3d" class="size-30 bg-red-300 absolute top-15 left-15 "></div>
</div>
<div class="flex flex-col">
<div>
<label for="blue_div_rotate_x">Blue DIV Rotate X(0~180)</label>
<input id="blue_div_rotate_x" type="range" min="0" max="180" step="1" value="0" onchange="UpdateRotation()">
</div>
<div>
<label for="blue_div_rotate_y">Blue DIV Rotate Y(0~180)</label>
<input id="blue_div_rotate_y" type="range" min="0" max="180" step="1" value="0" onchange="UpdateRotation()">
</div>
<div>
<label for="blue_div_rotate_z">Blue DIV Rotate Y(0~180)</label>
<input id="blue_div_rotate_z" type="range" min="0" max="180" step="1" value="0" onchange="UpdateRotation()">
</div>
<div>
<label for="red_div_rotate_x">Red DIV Rotate X(0~180)</label>
<input id="red_div_rotate_x" type="range" min="0" max="180" step="1" value="0" onchange="UpdateRotation()">
</div>
<div>
<label for="red_div_rotate_y">Red DIV Rotate Y(0~180)</label>
<input id="red_div_rotate_y" type="range" min="0" max="180" step="1" value="0" onchange="UpdateRotation()">
</div>
<div>
<label for="red_div_rotate_z">Red DIV Rotate Y(0~180)</label>
<input id="red_div_rotate_z" type="range" min="0" max="180" step="1" value="0" onchange="UpdateRotation()">
</div>
</div>
<script>
function RemoveRotateClasses(element) {
const classesToBeRemove = [];
element.classList.forEach(className => {
if(className.startsWith('rotate-x') || className.startsWith('rotate-y') || className.startsWith('rotate-z')) {
classesToBeRemove.push(className);
}
});
classesToBeRemove.forEach(className => {
element.classList.remove(className);
});
}
function AddRotateClasses(element, x, y, z) {
element.classList.add(`rotate-x-${x}`);
element.classList.add(`rotate-y-${y}`);
element.classList.add(`rotate-z-${z}`);
}
function UpdateRotation() {
const blueDiv = document.getElementById('blue_div');
const blueDivIn3d = document.getElementById('blue_div_in_3d');
const blueDivRotateX = document.getElementById('blue_div_rotate_x');
const blueDivRotateY = document.getElementById('blue_div_rotate_y');
const blueDivRotateZ = document.getElementById('blue_div_rotate_z');
const redDiv = document.getElementById('red_div');
const redDivIn3d = document.getElementById('red_div_in_3d');
const redDivRotateX = document.getElementById('red_div_rotate_x');
const redDivRotateY = document.getElementById('red_div_rotate_y');
const redDivRotateZ = document.getElementById('red_div_rotate_z');
RemoveRotateClasses(blueDiv);
RemoveRotateClasses(blueDivIn3d);
AddRotateClasses(blueDiv, blueDivRotateX.value, blueDivRotateY.value, blueDivRotateZ.value);
AddRotateClasses(blueDivIn3d, blueDivRotateX.value, blueDivRotateY.value, blueDivRotateZ.value);
RemoveRotateClasses(redDiv);
RemoveRotateClasses(redDivIn3d);
AddRotateClasses(redDiv, redDivRotateX.value, redDivRotateY.value, redDivRotateZ.value);
AddRotateClasses(redDivIn3d, redDivRotateX.value, redDivRotateY.value, redDivRotateZ.value);
}
</script>
首先是HTML部分的DIV,首先是第一段:
<div class="m-5 size-50 outline-1 outline-black relative">
<div id="blue_div" class="size-30 bg-blue-300 absolute top-5 left-5 "></div>
<div id="red_div" class="size-30 bg-red-300 absolute top-15 left-15 "></div>
</div>
有了定位知识后,这段代码很容易理解:
画出来长下面这样:
然后把这个图形又画了一遍,不同的是在父元素上加了一个新的样式类,叫transform-3d
,也改了两个子元素的ID以便在JS代码中区分
- <div class="m-5 size-50 outline-1 outline-black relative">
+ <div class="m-5 size-50 outline-1 outline-black relative transform-3d">
...
</div>
然后在下面写了六个input:range
控件,每个都有自己的ID以便在JS代码中区分,取值范围都是0~180
,步进为1
,默认值为0
现在页面长下面这样:
再接下来就是JS代码部分了,其实非常容易理解:每次改变控件的值的时候,都会按照取的值,去给两个蓝色或两个红色div修改对应的旋转属性。
唯一的新鲜知识就是,我们在2D平面变换的时候,只讲过rotate
可以有rotate-x
和rotate-y
,但现在多了rotate-z
,有什么作用,把这个例子跑起来,你一眼就看明白了:
这个例子很直观的向我们展示了以下几点:
rotate/scale/translate
都有对应的Z轴可以用
skew
没有对应的Z轴版本可用z-index
一个个的投影到父元素平面上去,所以视觉上是没有3D效果的transform-3d
,它对应的CSS样式是transform-style: preserve-3d
flat
和preserve-3d
,其中flat
是默认值,默认值也有对应的tailwindcss类:transform-flat
,但鉴于这是默认值,所以一般用处不多平常用不到的一些属性,不提了,查文档的话查perspective-origin
和perspective