【CSS in Depth 2 精译_039】6.3 CSS 定位技术之:相对定位(上)
当前内容所在位置(可进入专栏查看其他译好的章节内容)
- 第一章 层叠、优先级与继承(已完结)
- 第二章 相对单位(已完结)
- 第三章 文档流与盒模型(已完结)
- 第四章 Flexbox 布局(已完结)
- 第五章 网格布局(已完结)
- 【第六章 定位与堆叠上下文】 ✔️
- 6.1 固定定位
- 6.1.1 创建一个固定定位的模态对话框
- 6.1.2 在模态对话框打开时防止屏幕滚动
- 6.1.3 控制定位元素的大小
- 6.2 绝对定位
- 6.2.1 关闭按钮的绝对定位
- 6.2.2 伪元素的定位问题
- 6.3 相对定位 ✔️
- 6.3.1 创建下拉菜单(上) ✔️
- 6.3.2 创建 CSS 三角形(下)(精译中 ⏳)
文章目录
- 6.3 相对定位 Relative positioning
- 6.3.1 创建下拉菜单 Creating a dropdown menu
《CSS in Depth》新版封面
6.3 相对定位 Relative positioning
相对定位可能是最不被理解的定位类型。元素首次设置 position: relative
后,通常页面上看不到任何变化。被相对定位的元素及其周围的所有元素,都还保持着原来的位置(尽管某些元素可能会跑到另一些元素前面,这个问题稍后讨论)。
如果再加上与 inset
相关的属性,元素就会从原位置移走,但周围元素的位置却保持不变。如图 6.4 所示的四个行内块级(inline-block)元素,若给第二个元素指定三个额外属性:position: relative; top: 1em; left: 2em
,该元素将从初始位置移走,而其余元素则不受影响。它们依旧遵循着常规文档流的行为模式,围绕在被移走元素的初始位置周围。
图 6.4 第二个元素在相对定位的作用下发生移动
声明 top: 1em
将元素从原来的顶部边缘下移了 1em
;而 left: 2em
则将元素从最初的左边缘右移了 2em
。这可能导致该元素与其下方或旁边的其他元素发生重叠。元素定位时,属性值也可以取负值,如 bottom: -1em
可以令元素下移 1em
,效果上与 top: 1em
相同。
注意
与固定定位和绝对定位不同,设置了相对定位的元素,无法使用
inset
相关的属性来控制其大小。这些值只能让元素上下左右移动。可以用top
或者bottom
,但它们不能一起使用(否则bottom
将被忽略);同理,可以用left
或者right
,但它们也不能一起使用(否则right
将被忽略)。
使用这些属性来调整相对元素(relative element)的位置偶尔也很管用,比如对摆放位置进行微调。但这并非相对定位最主要的用法;更常见的使用场景,是通过设置 position: relative
来为其内部的绝对定位元素(absolutely positioned element)创建一个包含块(containing block)。
6.3.1 创建下拉菜单 Creating a dropdown menu
接下来将用相对定位与绝对定位来创建一个下拉菜单。初始状态下,该菜单是一个普通的矩形按钮;当用户点击它时,则会弹出一个链接列表,如图 6.5 所示。
图 6.5 下拉菜单效果图
该菜单的 HTML 标记如代码清单 6.5 所示。将属于菜单的部分添加到示例页 <main class="container">
元素的开头。该代码包含了一个容器元素,用来设置里面的内容居中对齐,并与页面顶部横幅中的内容左对齐。而菜单下面的 <h1>
元素则是为了演示下拉菜单的弹框(popup)将出现在页面其他内容前方的效果。
代码清单 6.7 添加下拉菜单的 HTML 标记
<main class="container">
<nav>
<div class="dropdown" id="dropdown"><!-- 下拉菜单的容器 -->
<!-- 切换按钮始终保持可见 -->
<button type="button" class="dropdown-toggle"
id="dropdown-toggle">Main Menu</button>
<!-- 随菜单的打开/关闭而显示/隐藏的 div 元素 -->
<div class="dropdown-menu">
<ul class="submenu">
<li><a href="/">Home</a></li>
<li><a href="/coffees">Coffees</a></li>
<li><a href="/brewers">Brewers</a></li>
<li><a href="/specials">Specials</a></li>
<li><a href="/about">About us</a></li>
</ul>
</div>
</div>
</nav>
<h1>Wombat Coffee Roasters</h1>
</main>
下拉菜单的容器包含两个子元素:一个始终显示的切换按钮,以及一个会随下拉框的开合而显示与隐藏的下拉菜单。由于下拉菜单会设置绝对定位,因此它的出现并不会影响最初的页面布局。换言之,下拉菜单将出现在其他内容的前方。
而通过按下按钮来切换菜单的展开与关闭的逻辑,则需要一些 JavaScript 脚本的配合,如代码清单 6.8 所示。将它们更新到页面,实现这一功能。
代码清单 6.8 控制下拉菜单打开与关闭的 JavaScript 代码
<script type="text/javascript">
const dropdownToggle = document.getElementById("dropdown-toggle");
const dropdown = document.getElementById("dropdown");
dropdownToggle.addEventListener("click", function (event) {
dropdown.classList.toggle("is-open");
});
</script>
这段代码与前面构建的模态对话框有些类似,通过按钮的点击实现在下拉菜单中添加或删除一个 is-open
类;基于该类的 CSS 代码则会同步切换对应的效果。
接着再给下拉菜单的容器设置相对定位,为内部绝对定位的菜单建立包含块。根据代码清单 6.9 更新页面样式。
代码清单 6.9 展示呈打开状态的下拉菜单样式
.dropdown {
display: inline-block;
position: relative; /* 创建包含块 */
}
.dropdown-toggle {
padding: 0.5em 1.5em;
border: 1px solid #ccc;
background-color: #eee;
border-radius: 0;
}
.dropdown-menu {
display: none; /* 设置菜单初始隐藏 */
position: absolute;
left: 0;
top: 2.1em; /* 将菜单放到下拉菜单按钮的下方 */
inline-size: max-content;
min-inline-size: 100%;
background-color: #eee;
}
.dropdown.is-open .dropdown-menu {
display: block; /* 菜单可用时予以显示 */
}
.submenu {
padding-inline-start: 0;
margin: 0;
list-style-type: none;
border: 1px solid #999;
}
.submenu > li + li {
border-top: 1px solid #999;
}
.submenu > li > a {
display: block;
padding: 0.5em 1.5em;
background-color: #eee;
color: #369;
text-decoration: none;
}
.submenu > li > a:hover {
background-color: #fff;
}
这样,当点击主菜单的切换按钮时,其下方就会弹出下拉菜单;再次点击切换按钮,下拉菜单就关闭了。值得一提的是,类似功能也可以通过伪类 :hover
来实现,但该方案对键盘导航及触屏设备不太友好(如果此时打开模态对话框,您可能会看到模态框莫名其妙地跑到下拉菜单后面去了。不要紧,这个问题很快就会解决。)。
给绝对定位的 dropdown-menu
设置 left: 0
,实现了其左边与整个容器左边对齐;而 top: 2.1em
则将其上边缘放置到按钮的下方(算上内边距与边框,切换按钮大约高 2.1em
)。将 min-inline-size
的值设为 100%
,确保了其宽度至少与下拉菜单的容器宽度相当(该容器宽度则由其内部元素宽度、即切换按钮的实际宽度决定)。最后通过 submenu
类指定下拉菜单的具体样式。
注意
当下拉菜单位于屏幕右边缘时,通常会使用
right: 0
而非left: 0
来进行定位。因为这样一来,宽度超过容器的菜单项则会紧贴按钮右边缘,同时向左延展至切换按钮的左侧,而不是溢出视口的右边缘。
构建页面时,可访问性是一个重要的考量因素,对于下拉菜单这样的交互元素(interactive elements)而言更是至关重要。即使再不济,至少也要确保用户能完全通过键盘实现页面交互。本例中的下拉菜单可以使用 Tab 健定位到切换按钮,并通过 Space 空格键切换菜单的打开与关闭。当菜单打开时,您还可以继续使用 Tab 健循环浏览菜单项。(而在苹果操作系统 Mac OS 上,可能还需要再系统偏好设置中打开键盘导航(enable keyboard navigation))。关于构建考虑页面可访问性的下拉菜单的更多信息,详见:https://www.webaxe.org/accessible-custom-select-dropdowns/
DIY 发散
最后讨论页面可访问性的问题时,作者顺便提到了一个小功能:下拉菜单打开时,可以通过 Tab 健循环浏览菜单项。然而该功能并没有在新版示例页中有所体现。根据提供的示例页
listing-6.09.html
,用 Tab 下翻到最后一个菜单项或用 Shift + Tab 上翻到首个菜单项时,页面并不会自动循环浏览。要实现上述功能,还应该监听页面当前被激活的元素在菜单项中的位置:顺序浏览到最后一个时,下一次 Tab 让第一个菜单项得焦点;逆序浏览至首个菜单项时,则下一次 Tab 让最后一个子项得焦点。根据这个思路,其中一个经实测的 JS 版本如下:// ... keep the same for handling the toggle of 'is-open' class // use Tab to cycle through the menu items const firstItem = dropdown.querySelector('li:first-of-type > a'); const lastItem = dropdown.querySelector('li:last-of-type > a'); document.addEventListener("keydown", function (event) { const {key, shiftKey} = event; if (dropdown.classList.contains('is-open')) { if(key === 'Tab'){ const {activeElement: active} = document; if (shiftKey) { // Shift + Tab if (active === firstItem) { event.preventDefault(); lastItem.focus(); // Focus the last item } } else { // Tab if (active === lastItem) { event.preventDefault(); firstItem.focus(); // Focus the first item } } } } });
上述代码的
firstItem
与lastItem
放到keydown
事件逻辑的外面,是基于下拉菜单项数量和内容均固定的情况,即静态页面下的处理。如果下拉菜单是动态构建的(如从后端查询数据并在前端动态渲染),则最好放到第 7 行到第 9 行之间。作为必要的妥协,改到里面后,页面每次触发keydown
事件都会执行 DOM 查询,性能上估计会差一点点,尤其是在频繁触发的情况下。适合放到哪里,得根据具体情况而定。
关于《CSS in Depth》(中译本书名《深入解析 CSS》)
第 1 版 | 第 2 版 | |
---|---|---|
读者评分 | 原版:4.7(亚马逊);中文版:9.3(豆瓣) | 原版:5.0(亚马逊);中文版:暂无,待出版 |
出版时间 | 原版:2018 年 3 月;中文版:2020 年 4 月 | 原版:2024 年 7 月;中文版:暂无,待出版 |
原价 | 原版:$44.99;中文版:¥139.00 | 原版:$59.99;中文版:暂无,待出版 |
现价 | 原版:$36.49;中文版:¥52.54 起步 | 原版:$52.09;中文版:暂无,待出版 |
原版国内预订 | 起步价 ¥461.00 | 起步价 ¥750.00 |
本专栏为该书第 2 版高分译文专栏,全网首发,精译精校,持续更新,计划今年内完成全书翻译,敬请期待!!!