问题
最近新起一个项目,直接上最新版本的 antd pro ,但是此框架封装太死,页面的框架不适合现有项目的结构(但是项目的代码结构,以及其他工具的集成还是很好用的 ),所以重写页面框架。框架很简单,其实就是上中下结构,“上”部的 header 组件需要吸顶,在吸顶的实现过程中遇到以下问题:
- 将 header 设置为
position: fixed; width:100%
- 设置 header 外层组件
min-height:1200px
, 当页面宽度收缩至 1200px 时不再收缩,出现滚动条。拖动滚动条时,header 中被浏览器隐藏掉的 头像,不随横向滚动条的移动而移动。 - 由于业务中 header 的存在可能内容较多的情况,而交互设计中没有类似于移动端的
@media
,不能适当隐藏某些组件以达到布局上的效果。
所以需要使用另外的方案来解决,达到 header 纵向不随页面滚动;横向随着页面滚动的效果。
知识储备
上述页面中使用到了 position: fixed;
,记忆中的 position
似乎不是特别清晰,让我们来回顾一下。只有巩固根基才能开支散叶,形成知识网络。
查看 MDN 的解释,如下:
static
:
该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。此时
top, right, bottom, left 和 z-index
属性无效。
简单地说就是所有元素的 position
默认值,浏览器默认行为。
relative
:相对定位
该关键字下,元素先放置在未添加定位时的位置,再在不改变页面布局的前提下调整元素位置(因此会在此元素未添加定位时所在位置留下空白)。
position:relative 对 table-*-group, table-row, table-column, table-cell, table-caption
元素无效。
图中蓝色盒子 Two ,首先定位在属于原本文档流的位置,相对于此位置进行上和左的偏移。
#two {
position: relative;
top: 20px;
left: 20px;
background: blue;
}
absolute
: 绝对定位
相对定位的元素并未脱离文档流,而绝对定位的元素则脱离了文档流。在布置文档流中其它元素时,绝对定位元素不占据空间。绝对定位元素相对于最近的非 static 祖先元素定位。当这样的祖先元素不存在时,则相对于ICB(inital container block, 初始包含块)
上段描述有两个重点:
- 脱离文档流
- 定位于最近的非 static 的元素, 如果没有那就是初始包含块,即
<html>
标签.
文档流是什么呢
可以浅显地理解为,所有页面元素有既定的默认排版顺序,依次排版,有条不紊。
脱离文档流,可以理解为在不在原本的排版规则之内,在原本的文档流切面上添加一个属于的自己的切面。不占任何原本文档流的任何位置。
fixed
: 固定定位
固定定位与绝对定位相似,但元素的包含块为 viewport 视口。该定位方式常用于创建在滚动屏幕时仍固定在相同位置的元素。
固定定位的定位方式相较于绝对定位发生了改变,基于视口定位,也可以抽象为就是浏览器的可视区域,我们之前的 header 也就是 fixed 的定位。 同样 fixed 也脱离于文档流。
sticky
: 粘性定位
元素根据正常文档流进行定位,然后相对它的最近滚动祖先(nearest scrolling ancestor)和 containing block (最近块级祖先 nearest block-level ancestor),包括 table-related 元素,基于 top, right, bottom, 和 left的值进行偏移。偏移值不会影响任何其他元素的位置。 该值总是创建一个新的层叠上下文(stacking context)。注意,一个 sticky 元素会“固定”在离它最近的一个拥有“滚动机制”的祖先上(当该祖先的 overflow 是 hidden, scroll, auto, 或 overlay 时),即便这个祖先不是真的滚动祖先。这个阻止了所有“ sticky ”行为。
这是一个 relative
和 fixed
结合体,解读一下上面官方的解释:
- 触发 relative 和 fixed 的切换需要条件。
- 须指定 top, right, bottom 或 left 四个中的至少一个作为切换条件。
- 粘性元素依托于父级的滚动,而且该父级 overflow 不能是 hidden, scroll, auto, 或 overlay 。
- 在文档流之内
解决方案
sticky 实际上有更加细致地控制。我们修改之前的代码
/*before*/
.header {
display: flex;
justify-content: space-between;
background: #fff;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.12);
align-items: center;
position: fixed;
z-index: 999;
top: 0;
width: 100%;
}
/*after*/
.header {
display: flex;
justify-content: space-between;
background: #fff;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.12);
align-items: center;
position: sticky;
z-index: 999;
top: 0;
}
将 position 改为 sticky 后马上达到了效果,可是为什么呢?
我们将触发条件提炼出来:
position: sticky
- 边界:
top: 0
,使用 top 当做触发条件。 - 当父级滚动的时候, header 离 viewport 上端超过了 0px ,header 由 raletive 变成了 fixed。
- 由于只设置在 top 方向的边界,所以在纵向滚动时没有边界,故纵向滚动时仍为 reletive,跟随页面的滚动而滚动
我们来设置一下纵向的 sticky 触发条件
/*before*/
.header {
display: flex;
justify-content: space-between;
background: #fff;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.12);
align-items: center;
position: sticky;
z-index: 999;
top: 0;
}
/*after*/
.header {
display: flex;
justify-content: space-between;
background: #fff;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.12);
align-items: center;
position: sticky;
z-index: 999;
top: 0;
right:10px;
}
但是横向滑动并没有生效。使 sticky 生效还需要一个条件,该元素必须存在对应触发条件上的宽或高。
让我们再设置一下 width :
.header {
display: flex;
justify-content: space-between;
background: #fff;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.12);
align-items: center;
position: sticky;
z-index: 999;
top: 0;
left:0;
width:800px;
}
此时 sticky 元素的行为已经和 fixed 表现一致,这也印证了 sticky 可以更细粒度的控制。
知识联想
文档流
上述文字中提到了文档流,那到底什么是文档流呢 ? 关键字在于流
官方的描述为 Normal flow,这里我们称为 文档流。
首先需要区分 html 的元素(标签)类型:块级元素、行内元素、行内-块级元素。(不展开总结)
根据标签类型可以在文档流中依次排开,自上而下、自左至右。
position 和 display 的区别
position
: 顾名思义可控制元素的位置。
而 display 分为两种类型:
- 外部显示类型定义了元素怎样参与流式布局的处理。
- 比如改变div 为 行内-块级元素。
- 内部显示类型定义了元素内子元素的布局方式。
- 比如 flex、grid ,控制元素内部的布局
display 的外部显示行为,都在文档流之中,position 可以使元素脱离于文档流
这样就清晰明了了。