CSS中的iOS安全区域:`env(safe-area-inset-*)`的适配与刘海屏布局

CSS中的iOS安全区域:env(safe-area-inset-*)的适配与刘海屏布局

大家好,今天我们来深入探讨一下CSS中用于适配iOS安全区域的env(safe-area-inset-*)属性,以及如何利用它来解决刘海屏等特殊屏幕带来的布局问题。随着全面屏手机的普及,屏幕顶部、底部和侧边常常会出现各种异形区域,比如刘海、圆角、传感器区域等。这些区域可能会遮挡网页内容,影响用户体验。env(safe-area-inset-*)应运而生,为开发者提供了一种优雅的方式来感知和利用这些安全区域,确保内容在各种设备上都能正确显示。

什么是安全区域(Safe Area)?

简单来说,安全区域是屏幕上不会被系统界面元素(例如状态栏、导航栏、Home指示器)或硬件结构(例如刘海、圆角)遮挡的区域。 在iOS设备上,安全区域由UIKit框架定义,并暴露给网页开发者,以便他们可以调整网页布局,避免关键内容被遮挡。

env(safe-area-inset-*) 属性详解

env() 是 CSS 中的一个函数,用于从浏览器环境中读取环境变量的值。在iOS Safari浏览器中,它提供了一组特殊的环境变量,用于获取安全区域的内边距(inset)。这些环境变量包括:

  • safe-area-inset-top: 顶部安全区域的内边距。
  • safe-area-inset-right: 右侧安全区域的内边距。
  • safe-area-inset-bottom: 底部安全区域的内边距。
  • safe-area-inset-left: 左侧安全区域的内边距。

这些属性的值通常以像素 (px) 为单位。 在不支持 env()safe-area-inset-* 的浏览器中,这些属性的值将默认为 0

如何使用 env(safe-area-inset-*)

使用 env(safe-area-inset-*) 的基本方法是将其与 CSS 的 paddingmargininset 属性结合使用,以调整元素的位置和大小,使其位于安全区域内。

示例 1:为页面主体添加安全区域内边距

body {
  padding-top: env(safe-area-inset-top);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
}

这段代码会根据设备的安全区域,在 body 元素的四个方向上添加相应的内边距。在没有安全区域的设备上,这些内边距将为 0,不会影响布局。

示例 2:仅为顶部导航栏添加安全区域内边距

.navbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background-color: #fff;
  padding-top: env(safe-area-inset-top);
}

这段代码会将导航栏固定在顶部,并为其添加顶部安全区域的内边距,防止内容被刘海遮挡。

示例 3:使用 inset 属性适配安全区域

.fullscreen-element {
  position: fixed;
  top: env(safe-area-inset-top);
  right: env(safe-area-inset-right);
  bottom: env(safe-area-inset-bottom);
  left: env(safe-area-inset-left);
  width: auto; /* 确保正确计算宽度 */
  height: auto; /* 确保正确计算高度 */
}

此示例展示了如何使用 inset属性(top, right, bottom, left的简写)来创建一个占据整个安全区域的固定定位元素。 注意 widthheight 设置为 auto,以便让元素根据 inset 值自动调整大小。

兼容性处理:constant()@supports

早期版本的 iOS Safari 使用 constant(safe-area-inset-*) 来表示安全区域内边距。为了兼容旧版本,我们需要同时使用 constant()env(),并使用 @supports 媒体查询来检测浏览器是否支持 env()

body {
  padding-top: constant(safe-area-inset-top); /* 兼容旧版本 iOS */
  padding-right: constant(safe-area-inset-right);
  padding-bottom: constant(safe-area-inset-bottom);
  padding-left: constant(safe-area-inset-left);
}

@supports (top: env(safe-area-inset-top)) {
  body {
    padding-top: env(safe-area-inset-top);
    padding-right: env(safe-area-inset-right);
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: env(safe-area-inset-left);
  }
}

这段代码首先使用 constant() 设置安全区域内边距,然后使用 @supports 检测浏览器是否支持 env()。如果支持,则使用 env() 覆盖 constant() 的值。这样可以确保在所有 iOS 设备上都能正确显示。

更简洁的写法:

由于constant()在现代浏览器中已经废弃,我们可以只使用env(),并配合 default 值来处理不支持env()的浏览器。

body {
  padding-top: env(safe-area-inset-top, 0);
  padding-right: env(safe-area-inset-right, 0);
  padding-bottom: env(safe-area-inset-bottom, 0);
  padding-left: env(safe-area-inset-left, 0);
}

在这个版本中,如果浏览器不支持 env(safe-area-inset-top),则 padding-top 将默认为 0。 这简化了代码,同时确保了在各种浏览器中的兼容性。

实际应用场景:刘海屏布局的适配

刘海屏是全面屏手机上常见的异形区域,位于屏幕顶部,可能会遮挡网页内容。使用 env(safe-area-inset-top) 可以轻松解决这个问题。

示例 1:固定顶部导航栏的适配

<div class="navbar">
  <h1>我的应用</h1>
</div>
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px; /* 设置导航栏高度 */
  background-color: #f0f0f0;
  padding-top: env(safe-area-inset-top); /* 添加顶部安全区域内边距 */
  box-sizing: border-box; /* 包含内边距在元素总宽度和高度内 */
}

.navbar h1 {
  margin: 0;
  line-height: 60px; /* 垂直居中标题 */
  text-align: center;
}

在这个例子中,我们为导航栏添加了顶部安全区域内边距,确保导航栏内容不会被刘海遮挡。box-sizing: border-box 确保padding不会增加元素的总高度。

示例 2:全屏图片或视频的适配

<div class="fullscreen-image">
  <img src="image.jpg" alt="全屏图片">
</div>
.fullscreen-image {
  position: fixed;
  top: env(safe-area-inset-top);
  right: env(safe-area-inset-right);
  bottom: env(safe-area-inset-bottom);
  left: env(safe-area-inset-left);
  width: 100%;
  height: 100%;
  object-fit: cover; /* 保持图片宽高比并填充整个容器 */
}

这段代码会将图片设置为全屏显示,并使用安全区域内边距来避免内容被遮挡。 object-fit: cover 确保图片填充整个容器,同时保持宽高比,避免变形。

viewport-fit=cover 的重要性

要使 env(safe-area-inset-*) 生效,需要在 <meta> 标签中设置 viewport-fit=cover

<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">

viewport-fit=cover 指示浏览器将网页内容扩展到整个视口,包括安全区域。如果没有设置这个属性,env(safe-area-inset-*) 的值将为 0

viewport-fit 的其他值:

  • auto: 默认值。网页内容将适应视口,但不包括安全区域。
  • contain: 网页内容将完全包含在视口内,可能会出现留白。

使用JavaScript获取安全区域信息

虽然CSS提供了方便的方式来适配安全区域,但有时我们需要在JavaScript中获取安全区域的信息,以便进行更复杂的处理。

function getSafeAreaInsets() {
  const top = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--safe-area-inset-top') || 0);
  const right = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--safe-area-inset-right') || 0);
  const bottom = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--safe-area-inset-bottom') || 0);
  const left = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--safe-area-inset-left') || 0);

  return { top, right, bottom, left };
}

const safeArea = getSafeAreaInsets();
console.log(safeArea);

说明:

  1. 我们定义了一个名为 getSafeAreaInsets 的函数。
  2. 在函数内部,我们使用 getComputedStyle 获取根元素(document.documentElement)的计算样式。
  3. 然后,我们使用 getPropertyValue 获取名为 --safe-area-inset-top--safe-area-inset-right--safe-area-inset-bottom--safe-area-inset-left 的 CSS 变量的值。
  4. 使用parseInt将获取的字符串转换为整数。
  5. 如果获取不到值(例如,在不支持 env() 的浏览器中),则使用 || 0 提供一个默认值 0
  6. 最后,函数返回一个包含四个安全区域内边距的对象。

配合CSS变量使用:

为了使JavaScript代码能够获取安全区域信息,我们需要在CSS中将 env() 的值赋给 CSS 变量。

:root {
  --safe-area-inset-top: env(safe-area-inset-top);
  --safe-area-inset-right: env(safe-area-inset-right);
  --safe-area-inset-bottom: env(safe-area-inset-bottom);
  --safe-area-inset-left: env(safe-area-inset-left);
}

现在,我们就可以在JavaScript中使用getComputedStyle来获取这些CSS变量的值,从而得到安全区域的信息。

注意:

  • 这种方法依赖于 CSS 变量,因此需要确保浏览器支持 CSS 变量。
  • 在某些情况下,可能需要在页面加载完成后等待一段时间,才能获取到正确的安全区域信息。

适配横屏模式

在横屏模式下,安全区域的位置和大小可能会发生变化。因此,我们需要使用媒体查询来针对横屏模式进行适配。

@media (orientation: landscape) {
  body {
    padding-top: env(safe-area-inset-top);
    padding-right: env(safe-area-inset-right);
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: env(safe-area-inset-left);
  }
}

这段代码会在设备处于横屏模式时,重新设置 body 元素的内边距,以适应新的安全区域。

常见问题和注意事项

  • 确保设置 viewport-fit=cover: 这是使用 env(safe-area-inset-*) 的前提条件。
  • 使用 box-sizing: border-box: 这可以避免内边距增加元素的总宽度和高度。
  • 考虑横屏模式: 使用媒体查询来针对横屏模式进行适配。
  • 测试不同设备: 在不同的 iOS 设备上进行测试,以确保布局在各种屏幕尺寸和异形区域上都能正确显示。
  • 避免过度依赖安全区域: 不要过度依赖安全区域,尽量设计灵活的布局,使其在没有安全区域的设备上也能正常显示。
  • 模拟器调试: 使用 Xcode 模拟器可以方便地模拟各种 iOS 设备和屏幕尺寸,方便调试安全区域适配。

总结安全区域适配要点

env(safe-area-inset-*) 属性是适配 iOS 安全区域的关键工具,它允许开发者感知和利用安全区域的内边距,确保网页内容在各种设备上都能正确显示。 配合 viewport-fit=coverbox-sizing: border-box、媒体查询以及 JavaScript,开发者可以构建出适应各种屏幕尺寸和异形区域的优秀用户体验。

更多IT精英技术系列讲座,到智猿学院

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注