当前位置: 首页 > article >正文

【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》新版封面

《CSS in Depth》新版封面

6.3 相对定位 Relative positioning

相对定位可能是最不被理解的定位类型。元素首次设置 position: relative 后,通常页面上看不到任何变化。被相对定位的元素及其周围的所有元素,都还保持着原来的位置(尽管某些元素可能会跑到另一些元素前面,这个问题稍后讨论)。

如果再加上与 inset 相关的属性,元素就会从原位置移走,但周围元素的位置却保持不变。如图 6.4 所示的四个行内块级(inline-block)元素,若给第二个元素指定三个额外属性:position: relative; top: 1em; left: 2em,该元素将从初始位置移走,而其余元素则不受影响。它们依旧遵循着常规文档流的行为模式,围绕在被移走元素的初始位置周围。

图 6.4 第二个元素在相对定位的作用下发生移动

图 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 下拉菜单

图 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
        }
      }
    }
  }
});

上述代码的 firstItemlastItem 放到 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 版高分译文专栏,全网首发,精译精校,持续更新,计划今年内完成全书翻译,敬请期待!!!


http://www.kler.cn/a/324669.html

相关文章:

  • rk3399开发环境使用Android 10初体验蓝牙功能
  • 控制器ThinkPHP6
  • Windows下使用adb实现在模拟器中ping
  • 基于麒麟服务器操作系统V10版本,部署Nginx服务、MySql服务搭建PHP环境,实现静态网站平台的搭建。
  • Hybird和WebView
  • 03-axios常用的请求方法、axios错误处理
  • WPF一个控件根据另一个控件的某种状态的改变从而改变自身某种状态
  • 机械键盘驱动调光DIY--【DAREU】
  • Makefile编程:4种赋值差异
  • Python爬虫lxml模块安装导入和xpath基本语法
  • 计算机毕业设计 校运会管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • ssm模糊知识点整合
  • Java | Leetcode Java题解之第442题数组中重复的数据
  • 滚雪球学MySQL[3.1讲]: 高级SQL查询
  • leetcode_015_三数之和解析
  • Python集成测试详解
  • 工业边缘计算网关和普通网关的区别-天拓四方
  • python基础语法--顺序结构
  • SpringCloud源码:客户端分析(一)- SpringBootApplication注解类加载流程
  • 工业缺陷检测——Windows 10本地部署AnomalyGPT工业缺陷检测大模型
  • naocs注册中心,配置管理,openfeign在idea中实现模块间的调用,getway的使用
  • Python爬虫bs4的基本使用
  • Android平台如何获取CPU占用率和电池电量信息
  • Unity 与虚幻引擎对比:两大游戏开发引擎的优劣分析
  • 【工具变量】无废城市试点DID数据集(2000-2023)
  • 【C++笔记】八、结构体 [ 4 ]