前端重构范式之 position
本文旨在让你更深入地了解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
有了一个不一样的认识,接下来可以尝试着思考以下几个问题
position
的副作用是什么?position
的应用场景又是什么?
不知道?没关系,或许以下的文章能够给你一点提示
副作用
首先我们需要讨论的是position
的副作用,原因很简单,你手中有一个锤子,你总得知道这个锤子能干什么,不能干什么。我们只有像如来佛祖了解悟空那样了解position
,才能发挥出悟空(position
)的真正威力!
要分析position
的副作用,我们或许可以采用这样的思路:
以下的组件均指开启了
position
定位的元素,这里把它称为组件
- 把该组件以及内部的元素作为一个整体,分析它对外界或者外界对它有什么影响。简称对外的影响
- 单独分析该组件,不考虑对外的影响,分析该
position
属性对元素内部的元素会带来什么影响。简称对内的影响 - 除了单独分析每个定位属性的副作用,我们还需要考虑不同的定位属性之间的组合会带来什么样的副作用,例如最为典型的
relative
和absolute
顺着这个思路,接下来我们来思考不同的position
属性都会带来什么影响或副作用
副作用absolute
和 relative
relative
和absolute
是我们再熟悉不过的一对欢喜冤家,它们两个经常是成对出现,产生的副作用也更多地来自于它们之间的组合。因此在这里将它们放在一起分析。
特性与副作用往往是相伴而生的。我们先来回顾一下,这两个属性各有什么特性?
position:absolute
元素具备的特性如下:
- 相对于最近的设置了非static定位的祖先元素进行定位,如果没有,则相对于浏览器窗口进行定位
- 脱离了文档流,对文档流不可见
- 脱离了文档流,改变了元素性质,内联元素和块元素统一变性成为脱离文档流之后的元素,具体表现为类似
inline-block
的效果。 - 使元素提升一个层级,层级要比浮动和文档流元素要高
鉴于本人的经验限制,下面的副作用例子展示的也只是其中的一部分,如有更好的补充,欢迎在下方评论区中与大家一起分享
position:relative
元素具备的特性如下:
- 参考坐标系以该元素在文档中的原有位置作为参考点
- 不脱离文档流,因此,元素本身性质不改变,块元素还是块元素,内联元素依然是内联元素。
- 元素在原始文档中占有的位置依然存在,因此在执行
relative
定位之后,基本不会影响其他布局 - 元素层级提升一级,元素层级高于浮动元素和文档流中的元素,也就是说会覆盖普通文档流和浮动的元素,可以使用
z-index
来提升元素的层级 - 开启了相对定位的元素,若不设置任何偏移量,将不会发生任何变化(不考虑定位元素内部的变化)
深度relative
如果我们单独分析absolute
的副作用,我们会把它简单地归结为脱离文档流,从而导致两个最为明显的副作用:改变布局和覆盖。
但是我认为这属于它本身具备的特性,并不是它的副作用。我觉得在现实应用中真正存在的副作用在于relative
和absolute
的共同应用所造成的问题,在这里我把它称作「深度relative
」。
所谓的「深度
relative
」,指的是relative
包裹的组件处于较深的层级,也就是被包裹的比较深,导致其中的absolute
组件要想不被其他元素覆盖,需要一级一级地往f父元素设置z-index
或者排查overflow
属性。这个问题最关键的核心就是relative
组件层级太深。
这里讲得可能有点抽象,让我们一起来看一个例子:
这是我在实际项目开发中遇到的一个典型的例子。简单来讲,就是点击重选按钮,在指定的位置跳出图标选择框,并且在窗口滚动时依然有效。具体效果如下图所示:
就是这么一个简单的例子,当然以上是已经实现好的效果。如果你没有遇到过类似的问题,你会觉得这个问题很简单,使用relative
和absolute
就能轻松解决。但是,在这里先卖个关子,以上的效果是使用fixed
实现的。
我们首先使用我们典型的relative
和absolute
来实现以上的效果,我们可以得到如下的效果:
首先你会发现一个最为明显的问题,就是图标选择框被其他元素盖住了,这个时候的原因就很多样了,或许是因为它所在的祖先元素的元素层级没有人家高,又或许是因为祖先元素设置了overflow:hidden
等等。这个时候如果我们依然要坚持使用relative
和absolute
的解决方案,那我们通常会采取的解决方案就是,一级一级的网上排查overflow:hidden
又或者是设置z-index
,直到解决问题为止。
z-index
说到z-index
,这里可以简单下使用方法。它仅在positon
属性不为static
时起作用。从position
从基础可以了解到absolute
和fixed
会让元素脱离文档流,这两个修饰的元素默认层级自然比文档流的要高,然而relative
则不会脱离文档流,但当它设置了 TRBL 时它也会覆盖文档流,其默认层级自然也是比后者要高的,脱离文档流和层级的概念不能混淆。
同级原则
所谓同级,顾名思义,也就是同级间层级的比较,一个元素position
不为static
后,如果不给它的z-index
设定值,默认为0,由下图可以看到,A 元素设定了z-index
为0,B元素未设定,E 元素设定了z-index
为负数,按照顺序 B 覆盖住了 A,而E则被A盖住。往后则是z-index
大的把小的覆盖。
在此仅展示结构,css 代码则省去。
1 | <div id="1" style="position: absolute;z-index: 0">A</div> |
多级原则
看到这个标题,你可能会问,这是啥意思?其实很简单,它的意思是,要比较的元素不再是简单的同级概念,而是其中一个或两者都有祖先元素。看看下面这个代码:
1 | <div id="1" style="position:relative;z-index:2;"> |
效果图:
可见,A、B父元素的层级影响了相应的子元素的层级,就算 B 的z-index
设的再大,它的父元素的z-index
总是小于 A 的父元素的z-index
值,这时候不论 A 的z-index
怎么变化,元素 A 就会像图中一样一直压着 B,这就是从父原则。不过这里要划一下重点,这里的父元素不一定要同级,换句话说,两个元素间的层级比较,是相应的的同级祖先元素各自往下找到第一个position
不为static
的的两个元素之间的比较。举个栗子,结构改成下面这个结构,实现效果是一样的。
1 | <div id="1" style="position:relative;z-index:2;"> |
以上讲了这么多,你或许觉得怎么这么繁琐,要考虑这儿考虑那儿的,也会发现即使你用这种方法达到了你想要的效果,它也并不能从根本上解决问题。我一个简单的小组件,你却需要我去影响那么多父级元素,谁知道会造成什么意想之外的情况。除了这个,设置过多的z-index
会导致页面层级管理的混乱,使得页面很难管理,所有有一个原则,要么对z-index
做明文的规定,要么就少用层级过深的z-index
。
我这里有一种较好的解决方案,就是「fixed
组件」。如果你有更好的解决方案,欢迎在下方评论区中进行分享。
fixed组件
我们都知道当元素设置了position:fixed
之后,该元素将具备以下的特性:
- 永远相对于浏览器窗口进行定位
- 固定在浏览器窗口的某个位置,不随滚动条滚动
- 脱离文档流
以上是我们众所周知的一些特性,其实根据以上的特性,我们还可以推出以下这些特性:
- 单独使用
fixed
属性,不需要再开启父元素的定位,使得,减少了很多的z-index属性的设置
在下面的例子中,我们只对「图标组件」设置了
position:fixed
定位,页面中的其他大部分都是position:static
组件,因此可以很轻松的解决原来的覆盖问题,也不会影响其他布局,更不会造成z-index
管理混乱的问题。
- 使用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
范式,学才疏浅,请不要见怪
- 相对于具体元素的小组件可以采用
relative
+absolute
或者「fixed
组件」的解决方案。如果组件层级较深,推荐使用「fixed
组件」的方式来进行定位;具体可以参照上面提到过的图标选择框的例子。 - 相对于浏览器窗口固定的小组件,推荐是用
fixed
定位,例如右下角的小弹窗,固定的底部导航 - 要尽量减少
z-index
的嵌套使用 - 在使用
fixed
定位时,要留意transform
属性对它的影响