本文旨在让你更深入地了解position,并为你提供一套使用position的范式,为你使用position提供一点建议和参考。

在此之前先让我们来看看 learncss 中文文档中对position的定义

描述
absolute 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
fixed 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。
relative 生成相对定位的元素,相对于其正常位置进行定位。因此,”left:20” 会向元素的 LEFT 位置添加 20 像素。
static 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
inherit 规定应该从父元素继承 position 属性的值。

以上是较为官方的定义,你可以从中获取到关于position的一些基本操作,接下来让我们试图从另一个角度来理解position.

position是一种描述物体相对位置的艺术,它的核心是「参考坐标系」的选择

正如我们在现实生活中看到的那样,我们需要使用各种不同的「参考坐标系」,来更方便准确地描述我们身边各种物体的相对位置。因为,我们已经知道,如果单纯依靠一个坐标系来描述这个世界上的各个位置,这个世界将变得异常复杂。我们需要使用不同的坐标系,来帮住我们简化对位置的描述。

在我们的网页布局中也是一样,我们不能只采用一个坐标系来对网页中的元素进行定位,那将使得我们的网页变得异常复杂且不易维护。我们需要多个不同的坐标系,来帮助我们对网页进行更好地布局。css中的 position,就是我们俗称的「定位」,就是定义了这么一套规则,使得我们可以应用不同的坐标系,来对页面中的元素进行更好地布局和管理。

反应在具体的规则当中,就是我们熟知的static,relative,absolute,fixed。而它们最本质的区别就在于「参考坐标系」的不同。其他的一系列问题,例如典型的元素层级,都可以归结为position 所带来的副作用。

到这里,你或许对position有了一个不一样的认识,接下来可以尝试着思考以下几个问题

  1. position的副作用是什么?
  2. position的应用场景又是什么?

不知道?没关系,或许以下的文章能够给你一点提示

副作用

首先我们需要讨论的是position的副作用,原因很简单,你手中有一个锤子,你总得知道这个锤子能干什么,不能干什么。我们只有像如来佛祖了解悟空那样了解position,才能发挥出悟空(position)的真正威力!

要分析position的副作用,我们或许可以采用这样的思路:

以下的组件均指开启了position定位的元素,这里把它称为组件

  1. 把该组件以及内部的元素作为一个整体,分析它对外界或者外界对它有什么影响。简称对外的影响
  2. 单独分析该组件,不考虑对外的影响,分析该position属性对元素内部的元素会带来什么影响。简称对内的影响
  3. 除了单独分析每个定位属性的副作用,我们还需要考虑不同的定位属性之间的组合会带来什么样的副作用,例如最为典型的relativeabsolute

顺着这个思路,接下来我们来思考不同的position属性都会带来什么影响或副作用

副作用absoluterelative

relativeabsolute是我们再熟悉不过的一对欢喜冤家,它们两个经常是成对出现,产生的副作用也更多地来自于它们之间的组合。因此在这里将它们放在一起分析。

特性与副作用往往是相伴而生的。我们先来回顾一下,这两个属性各有什么特性?

position:absolute元素具备的特性如下:

  1. 相对于最近的设置了非static定位的祖先元素进行定位,如果没有,则相对于浏览器窗口进行定位
  2. 脱离了文档流,对文档流不可见
  3. 脱离了文档流,改变了元素性质,内联元素和块元素统一变性成为脱离文档流之后的元素,具体表现为类似inline-block的效果。
  4. 使元素提升一个层级,层级要比浮动和文档流元素要高

鉴于本人的经验限制,下面的副作用例子展示的也只是其中的一部分,如有更好的补充,欢迎在下方评论区中与大家一起分享

position:relative元素具备的特性如下:

  1. 参考坐标系以该元素在文档中的原有位置作为参考点
  2. 不脱离文档流,因此,元素本身性质不改变,块元素还是块元素,内联元素依然是内联元素。
  3. 元素在原始文档中占有的位置依然存在,因此在执行relative定位之后,基本不会影响其他布局
  4. 元素层级提升一级,元素层级高于浮动元素和文档流中的元素,也就是说会覆盖普通文档流和浮动的元素,可以使用z-index来提升元素的层级
  5. 开启了相对定位的元素,若不设置任何偏移量,将不会发生任何变化(不考虑定位元素内部的变化)

深度relative

如果我们单独分析absolute的副作用,我们会把它简单地归结为脱离文档流,从而导致两个最为明显的副作用:改变布局和覆盖。

但是我认为这属于它本身具备的特性,并不是它的副作用。我觉得在现实应用中真正存在的副作用在于relativeabsolute的共同应用所造成的问题,在这里我把它称作「深度relative」。

所谓的「深度relative」,指的是relative包裹的组件处于较深的层级,也就是被包裹的比较深,导致其中的absolute组件要想不被其他元素覆盖,需要一级一级地往f父元素设置z-index或者排查overflow属性。这个问题最关键的核心就是relative组件层级太深。

这里讲得可能有点抽象,让我们一起来看一个例子:

这是我在实际项目开发中遇到的一个典型的例子。简单来讲,就是点击重选按钮,在指定的位置跳出图标选择框,并且在窗口滚动时依然有效。具体效果如下图所示:

就是这么一个简单的例子,当然以上是已经实现好的效果。如果你没有遇到过类似的问题,你会觉得这个问题很简单,使用relativeabsolute就能轻松解决。但是,在这里先卖个关子,以上的效果是使用fixed实现的。

我们首先使用我们典型的relativeabsolute来实现以上的效果,我们可以得到如下的效果:

首先你会发现一个最为明显的问题,就是图标选择框被其他元素盖住了,这个时候的原因就很多样了,或许是因为它所在的祖先元素的元素层级没有人家高,又或许是因为祖先元素设置了overflow:hidden等等。这个时候如果我们依然要坚持使用relativeabsolute的解决方案,那我们通常会采取的解决方案就是,一级一级的网上排查overflow:hidden又或者是设置z-index,直到解决问题为止。

z-index

说到z-index,这里可以简单下使用方法。它仅在positon属性不为static时起作用。从position从基础可以了解到absolutefixed会让元素脱离文档流,这两个修饰的元素默认层级自然比文档流的要高,然而relative则不会脱离文档流,但当它设置了 TRBL 时它也会覆盖文档流,其默认层级自然也是比后者要高的,脱离文档流和层级的概念不能混淆

同级原则

所谓同级,顾名思义,也就是同级间层级的比较,一个元素position不为static后,如果不给它的z-index设定值,默认为0,由下图可以看到,A 元素设定了z-index为0,B元素未设定,E 元素设定了z-index为负数,按照顺序 B 覆盖住了 A,而E则被A盖住。往后则是z-index大的把小的覆盖。

image-20180917213219642

在此仅展示结构,css 代码则省去。

1
2
3
4
5
<div id="1" style="position: absolute;z-index: 0">A</div>
<div id="2" style="position: absolute">B</div>
<div id="3" style="position: absolute;z-index: 1">C</div>
<div id="4" style="position: absolute;z-index: 2">D</div>
<div id="5" style="position: absolute;z-index: -1">E</div>
多级原则

看到这个标题,你可能会问,这是啥意思?其实很简单,它的意思是,要比较的元素不再是简单的同级概念,而是其中一个或两者都有祖先元素。看看下面这个代码:

1
2
3
4
5
6
7
<div id="1" style="position:relative;z-index:2;">
<div id="1-1" style="position:relative;z-index:1;">A</div>
</div>
<div id="2" style="position:relative;z-index:1;">
<div id="2-1" style="position:relative;z-index:999;">B</div>
</div>

效果图:

image-20180917214330725

可见,A、B父元素的层级影响了相应的子元素的层级,就算 B 的z-index设的再大,它的父元素的z-index总是小于 A 的父元素的z-index值,这时候不论 A 的z-index怎么变化,元素 A 就会像图中一样一直压着 B,这就是从父原则。不过这里要划一下重点,这里的父元素不一定要同级,换句话说,两个元素间的层级比较,是相应的的同级祖先元素各自往下找到第一个position不为static的的两个元素之间的比较。举个栗子,结构改成下面这个结构,实现效果是一样的。

1
2
3
4
5
6
7
8
9
<div id="1" style="position:relative;z-index:2;">
<div id="1-1" style="position:relative;z-index:1;">A</div>
</div>
<div>
<div id="2" style="position:relative;z-index:1;">
<div id="2-1" style="position:relative;z-index:999;">B</div>
</div>
</div>

以上讲了这么多,你或许觉得怎么这么繁琐,要考虑这儿考虑那儿的,也会发现即使你用这种方法达到了你想要的效果,它也并不能从根本上解决问题。我一个简单的小组件,你却需要我去影响那么多父级元素,谁知道会造成什么意想之外的情况。除了这个,设置过多的z-index会导致页面层级管理的混乱,使得页面很难管理,所有有一个原则,要么对z-index做明文的规定,要么就少用层级过深的z-index

我这里有一种较好的解决方案,就是「fixed组件」。如果你有更好的解决方案,欢迎在下方评论区中进行分享。

fixed组件

我们都知道当元素设置了position:fixed之后,该元素将具备以下的特性:

  1. 永远相对于浏览器窗口进行定位
  2. 固定在浏览器窗口的某个位置,不随滚动条滚动
  3. 脱离文档流

以上是我们众所周知的一些特性,其实根据以上的特性,我们还可以推出以下这些特性:

  1. 单独使用fixed属性,不需要再开启父元素的定位,使得,减少了很多的z-index属性的设置

在下面的例子中,我们只对「图标组件」设置了position:fixed定位,页面中的其他大部分都是position:static组件,因此可以很轻松的解决原来的覆盖问题,也不会影响其他布局,更不会造成z-index管理混乱的问题。

  1. 使用js控制fixed组件的margin,我们可以实现,fixed组件相对于父元素进行定位

通常我们在应用fixed组件的时候,都要解决fixed组件的滚动问题。通常fixed组件不会随着滚动条的滚动而滚动,但是我们拥有强大的js,我们可以通过为fixed组件设置一个固定的margin-top,然后通过js动态的控制这个值,使得它可以相对父元素进行滚动。因为我们都知道,margin属性调节的是box之间的距离,fixed组件脱了文档流,但它依然是一个box,因此我们可以使用margin来调节fixed组件与父元素之间的相对位置。

就拿上面的例子而言,具体代码如下所示:

副作用fixed

fixed 失效

在固定定位元素的父元素上应用transform属性,固定定位的元素会相对于父元素来定位

具体可参考以下这篇博文,简单来说就是transform属性会对fixed组件造成影响

Eric’s Archived Thoughts: Un-fixing Fixed Elements with CSS Transforms

范式

讲了这个多关于position的副作用,我想到这里你或许也早已经有了一套自己的范式,其实里面的很多细节我们都已经在前面提到过。在这里我将分享一套属于我自己的position范式,学才疏浅,请不要见怪

  1. 相对于具体元素的小组件可以采用relative+absolute或者「fixed组件」的解决方案。如果组件层级较深,推荐使用「fixed组件」的方式来进行定位;具体可以参照上面提到过的图标选择框的例子。
  2. 相对于浏览器窗口固定的小组件,推荐是用fixed定位,例如右下角的小弹窗,固定的底部导航
  3. 要尽量减少z-index的嵌套使用
  4. 在使用fixed定位时,要留意transform属性对它的影响