CSS – Position
前言
定位是 CSS 里蛮重要的一课.
图片黑影 (overlay), back to top button, header, footer 紧贴在屏幕上下方等效果都是靠 position 完成的.
参考:
Static
position 一共有 5 种, 默认是 static.
假设有 5 个 box
<div class="container"> <div class="box1">box1</div> <div class="box2">box2</div> <div class="box3">box3</div> <div class="box4">box4</div> <div class="box5">box5</div> </div>
它的效果就是一个一个往下放. 彼此是不重叠的.
fixed
fixed 的使用场景是在 scrolling 的时候, 我们想让某个元素一直保持在一个位置上. 比如 back to top button.
.box3 { position: fixed; }
效果
当设置 fixed 以后, box3 发生了几个变化.
1.飘起来了
即便没有 scroll, 一开始 box3 就和 box4 重叠了. 这是因为 box3 飘起来了.
这个过程类似把 box3 从布局中抽走, 整个布局变成 box1, 2, 4, 5.
2. top, right, bottom, left, inset
通常 fixed 一定是搭配 offset 属性一起用的. 它的玩法是这样:
有 2 个对象
第 1 个是外面的 offset 对标框 (fixed 对标框就是 viewport 屏幕框)
第 2 个是里面的定位元素咯
白色区域就是整个 viewport (屏幕框)
inset 是 top, right, bottom, left 的 shorthand, 和 padding, margin 用法完全一样.
inset: 0 相等于 4 边都是 0, inset 10px 20px 则是 top,bottom: 10px, left, right: 20px
3. offset 的默认值
当 box3 没有设置 top 偏移时, 游览器的默认行为是把它定位到它原本的位置上, 所以效果就是和 box4 发生了重叠.
如果设置 top: 60px 的话
box3 不在和 box4 重叠, 而是和 box1,2 重叠.
注: 很少会用默认的, 而且默认的 "原地" 是依赖排版方式的, 如果是用 margin 来布局, 或者 Flex, 游览器很有可能无法正确的计算到 "原地".
absolute
offset parent
absolute 和 fixed 很像. 只有 2 个点不同, 第一是它们的 offset 对标框不同.
fixed 的 offset 对标框是 viewport. 而 absolute 的 offset 对标框是不固定的.
它有个术语叫 offset parent.
通过 JS 可以拿到哦 (注: chrome position fixed offsetParent will be null. 不清楚为什么 chrome 那么特别...哈哈)
document.querySelector(".box3").offsetParent;
从 absolute element 往 parent 走, 第一个 position 不是 static 的 element 就是它的 offset parent.
虽然红框是第一个 parent 但它是 static, 所以继续往上找, 蓝框才是第一个不是 static 的 element, 所以它成为了 offset parent.
所有 offset 位置都基于它来计算.
fixed 则是不管什么 parent, 它就对着 viewport
absolute 不被 scroll 影响
第二个不同是 absolute 不会被 scroll 影响. fixed 对标的是 viewport 所以才会随着 scroll 而改变.
box3 是 absolute; top: 0; rigth: 0; 如果要它随着 scroll 改变, 可以用 stikcy (下面会讲到)
border 不算在内
还有一个点是, top, right, bottom, left 是从 padding 算起的, border 不算在内.
top left: 50px
黑线不算哦
冷知识 – Position absolute 导致 parent overflow
冷知识 – 当 position: absolute 遇上 grid container
relative
relation 和 ablsolute 就有差别了.
第 1 它没有被抽出布局的概念.
.box3 { position: relative; top: -50px; }
效果
先不管 top: -50px 的逻辑. 当 box3 relation 以后, box2, box4 并没有连在一起. 中间依然空了一个 box3 的距离.
这就类似灵魂出窍一样.
第 2 它的 offset 计算不是对着 offset parent 也不是对着 viewport.
而是对着元素原本的位置.
过程类似, 在元素原本的位置画一个虚拟框作为它的 offset parent. 然后依据框做偏移.
上面例子中 top: -50px
红框就是元素本来的位置, -50px 往上偏移, 所以最终 box3 和 box2 重叠了.
小总结:
static: 默认 position
fixed: 会飘起来, 抽离原先的布局. 会导致原本的布局不一样. 始终和 viewport 保持固定的偏移.
absolute: 会飘起来, 抽离原先的布局. 会导致原本的布局不一样, 始终和 offset parent (第一个不是 static 的 parent) 保持固定的偏移.
最常用的手法就是给 parent position: relation 让它变成 offset parent. 因为 relation 不会对原本的布局有影响. 同时它又不是 static, 就可以成为 offset parent 了.
relation: 会飘起来, 但是不会抽离原先布局, 对原本的布局没有影响. 原地偏移.
sticky
参考:Position: stuck; — and a way to fix it
以前写过 Angular 学习笔记 (Material table sticky 原理)
重要概念:
1. sticky scroll container
sticky element 会找到第一个 overflow hidden, scroll, auto, overlay 的 parent 作为它的 sticky scroll container (不管是 vertical 还是 horizontal, 只要有那些 overflow 就是 sticky scroll container).
注:overflow overlay 是 auto 的前生,已经废弃了。overflow visible 和 clip 就不算是 sticky scroll container,clip 比较冷门,我不熟。
2. sticky max area container
sticky 的第一个 parent 就是 max area container, 没有任何要求, 第一个 parent 就是了, sticky 的可移动空间就看这个 max area container.
3. sticky top, right, bottom, left
position sticky 一定要要配上 offset 这些会 start working.
它的计算和 absolute 是不同的. absolute 不 cover padding, 但是 sticky cover.
scroll container 的 border 和 padding 会保留, sticky element 会在 padding 下面.
absolution 的话 element 是会盖掉 padding 的.
什么时候会 stick ?
中间长方形是 viewport, 当 scroll 的时候,红色的 sticky top 会不会触发, 取决于红色 element 是否在 viewport 的前面.
蓝色的 sticky bottom 会不会触发取决于它是否在 viewport 的下面.
比如黄色在中间, 那么它是没有任何 sticky 的. 上下都不粘.
它并不完美
上面说的 1, 2 条件 sticky scroll container 和 sticky max area container 大大限制了使用的场景.
sticky 的 first overflow 很有可能是 horizontal 但是需求是更上一层的 vertical 才是 scroll container. 这就不能用了.
first parent 是 max area 更恐怖. 比如需求要做一个 animation, 你 wrap 它起来就 gg.com 了.
它适合的场景是, first parent 刚好是 max area container 同时也是 scroll container. 这样才比较顺.
可以通过 JS 实现突破这些局限. 以前没有 position: sticky 的时候大家都是这样干的. 确保性能 ok 就可以了. 它的基本原理就是做计算, 然后配上 relative or absolute 都可以 (只要定位就可以了, 其它的就是计算偏移量问题而已).
细节看这篇: CSS & JS Effect – Simulation Position Sticky
当 sticky 遇上 <table>
上面我们有提到 sticky max area container 的概念,sticky element 的 first parent 就是 sticky 可移动的 max area。
但这个概念不适用于 native table 里的 tr td。
这是一个用 div flex layout 做的 table
<div class="table-container"> <div class="table"> <div class="tr"> <div class="th" style="position: sticky; top: 0; left: 0; background-color: red; color: white;">First Name</div> <div class="th">Last Name</div> <div class="th">Age</div> <div class="th">Address</div> <div class="th">Email</div> </div> <div class="tr"> <div class="td">John</div> <div class="td">Doe</div> <div class="td">30</div> <div class="td">123 Main St</div> <div class="td">john.doe@example.com</div> </div> <div class="tr"> <div class="td">Jane</div> <div class="td">Smith</div> <div class="td">25</div> <div class="td">456 Elm St</div> <div class="td">jane.smith@example.com</div> </div> <div class="tr"> <div class="td">Michael</div> <div class="td">Johnson</div> <div class="td">35</div> <div class="td">789 Oak St</div> <div class="td">michael.johnson@example.com</div> </div> <div class="tr"> <div class="td">Sarah</div> <div class="td">Williams</div> <div class="td">28</div> <div class="td">321 Pine St</div> <div class="td">sarah.williams@example.com</div> </div> <div class="tr"> <div class="td">David</div> <div class="td">Brown</div> <div class="td">40</div> <div class="td">654 Cedar St</div> <div class="td">david.brown@example.com</div> </div> <div class="tr"> <div class="td">Emily</div> <div class="td">Miller</div> <div class="td">33</div> <div class="td">987 Maple St</div> <div class="td">emily.miller@example.com</div> </div> <div class="tr"> <div class="td">James</div> <div class="td">Wilson</div> <div class="td">27</div> <div class="td">753 Walnut St</div> <div class="td">james.wilson@example.com</div> </div> <div class="tr"> <div class="td">Emma</div> <div class="td">Anderson</div> <div class="td">29</div> <div class="td">159 Birch St</div> <div class="td">emma.anderson@example.com</div> </div> </div> </div>
CSS Styles
.table-container { margin-top: 128px; margin-inline: auto; max-width: 512px; max-height: 360px; overflow: auto; } .table { width: 1024px; } .table .tr { display: flex; min-width: max-content; } .table .tr .th, .table .tr .td { padding: 16px; flex: 1; }
效果
vertical sticky 无效是因为 div.td 的 parent (max area) 是 div.tr,而它没有多余的高度。
我们把它换成 <table> <tr> <td> 结构
<div class="table-container"> <table> <thead> <tr> <th style="position: sticky; top: 0; left: 0; background-color: red; color: white;">First Name</th> <th>Last Name</th> <th>Age</th> <th>Address</th> <th>Email</th> <th>Phone</th> <th>City</th> <th>Country</th> <th>Occupation</th> <th>Salary</th> </tr> </thead> <tbody> <tr> <td>John</td> <td>Doe</td> <td>30</td> <td>123 Main St</td> <td>john.doe@example.com</td> <td>123-456-7890</td> <td>New York</td> <td>USA</td> <td>Software Engineer</td> <td>$80,000</td> </tr> <tr> <td>Jane</td> <td>Smith</td> <td>25</td> <td>456 Elm St</td> <td>jane.smith@example.com</td> <td>987-654-3210</td> <td>Los Angeles</td> <td>USA</td> <td>Graphic Designer</td> <td>$60,000</td> </tr> <tr> <td>Michael</td> <td>Johnson</td> <td>35</td> <td>789 Oak St</td> <td>michael.johnson@example.com</td> <td>456-789-0123</td> <td>Chicago</td> <td>USA</td> <td>Teacher</td> <td>$50,000</td> </tr> <tr> <td>Sarah</td> <td>Williams</td> <td>28</td> <td>321 Pine St</td> <td>sarah.williams@example.com</td> <td>789-012-3456</td> <td>Miami</td> <td>USA</td> <td>Accountant</td> <td>$70,000</td> </tr> <tr> <td>David</td> <td>Brown</td> <td>40</td> <td>654 Cedar St</td> <td>david.brown@example.com</td> <td>210-987-6543</td> <td>Houston</td> <td>USA</td> <td>Engineer</td> <td>$90,000</td> </tr> <tr> <td>Emily</td> <td>Miller</td> <td>33</td> <td>987 Maple St</td> <td>emily.miller@example.com</td> <td>567-890-1234</td> <td>Seattle</td> <td>USA</td> <td>Manager</td> <td>$100,000</td> </tr> <tr> <td>James</td> <td>Wilson</td> <td>27</td> <td>753 Walnut St</td> <td>james.wilson@example.com</td> <td>890-123-4567</td> <td>San Francisco</td> <td>USA</td> <td>Marketing Specialist</td> <td>$75,000</td> </tr> <tr> <td>Emma</td> <td>Anderson</td> <td>29</td> <td>159 Birch St</td> <td>emma.anderson@example.com</td> <td>234-567-8901</td> <td>Boston</td> <td>USA</td> <td>Consultant</td> <td>$85,000</td> </tr> </tbody> </table> </div>
CSS Styles
table td { padding: 16px; } .table-container { margin-top: 128px; margin-inline: auto; max-width: 512px; max-height: 360px; overflow: auto; }
效果
照理说效果应该和 flex table 一样,但很奇怪,td sticky 竟然也可以 sticky vertical。
我是在用 Angular Material Table 发现的,估计是游览器动了手脚。参考:【前端】position:sticky解析 这次应该大结局了
当 absolute / fixed 遇上 width / height auto
参考
stackoverflow – width:auto and fixed position
有时候当我们修改 div 的 position 之后会发现它变小了.
div block element width: auto 相等于 100% 对标 parent. 但是经过 position absolute 以后变成了 hug content.
在 MDN 有一段就是声明这个的.
如果希望保留原本的效果可以设置 left:0; right:0. 或者不要使用 width: auto 改成 100%.
常见的 overlay 写法
3个写法是等价的, 通常会写第 3 种. 因为它最短嘛.