惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
Jina AI
Jina AI
C
Check Point Blog
Apple Machine Learning Research
Apple Machine Learning Research
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
Last Week in AI
Last Week in AI
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
阮一峰的网络日志
阮一峰的网络日志
The Cloudflare Blog
M
MIT News - Artificial intelligence
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks
WordPress大学
WordPress大学
博客园 - 聂微东
月光博客
月光博客
博客园 - 司徒正美
T
The Blog of Author Tim Ferriss
大猫的无限游戏
大猫的无限游戏
Microsoft Security Blog
Microsoft Security Blog

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客
如何解决移动端的安全区域为0的问题-蚊子的前端博客
author · 2021-05-20 · via 蚊子的前端博客

从 iPhone X 开始出现了刘海和底部的黑条的区域,而 Android 系统通常也会模仿 iPhone 的一些设计,然后就是现在越来越多地新机型有了安全区域的概念。

若完全不考虑这些,可能就会出现类似这样的情况:

内容被遮挡

因此我们需要对这些区域做些特殊地处理。

1. 适配 iOS 中的安全区域 #

在大部分机型,尤其是 iOS 设备中,适配安全区域还是比较简单的,主要是 3 个步骤。

围观中

1.1 设置网页在可视窗口的布局方式 #

新增 viweport-fit 属性,使得页面内容完全覆盖整个窗口:

<meta name="viewport" content="width=device-width, viewport-fit=cover" />

只有设置了 viewport-fit=cover,才能使用 env()。

1.2 限定安全区域 #

iOS11 新增特性,Webkit 的一个 CSS 函数,用于设定安全区域与边界的距离,有四个预定义的变量:

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

这里我们只需要关注safe-area-inset-bottom这个变量,因为它对应的就是小黑条的高度。

注意:当 viewport-fit=contain 时 env() 是不起作用的,必须要配合 viewport-fit=cover 使用。对于不支持 env() 的浏览器,浏览器将会忽略它。

The env() function shipped in iOS 11 with the name constant(). Beginning with Safari Technology Preview 41 and the iOS 11.2 beta, constant() has been removed and replaced with env(). You can use the CSS fallback mechanism to support both versions, if necessary, but should prefer env() going forward.

这就意味着,之前使用的 constant() 在 iOS11.2 之后就不能使用的,但我们还是需要做向后兼容,像这样:

body {
  padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
  padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */
}

注意:env() 跟 constant() 需要同时存在,而且顺序不能换。

在竖屏的情况下,若要限定顶部的刘海区域,则要使用safe-area-inset-top;若要限定底部的区域,则要使用safe-area-inset-bottom

若需要进行计算,则可以使用calc()函数:

body {
  padding-bottom: calc(12px + constant(safe-area-inset-bottom));
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
}

2. 部分奇特的 Android 手机 #

很多 Android 手机也会按照 iOS 的标准来实现安全区域,因此上面的属性在大部分 Android 手机上也能正常使用。

但是,我们在测试的过程中发现,有几个奇特的手机,会出现下图的状况:

部分Android不支持安全区域

通过 Chrome 查看样式,发现他会识别 safe-area-inset-top 等预定义变量,但又将其解析为 0。

这就导致即使我们设置了兜底的数据,也无法使用。例如我们在不支持安全区域属性时,使用兜底的 padding-top: 25PX 样式(大写的 PX 是为了不被插件转义成 vw 或 rem),但上述的 Android 设备中,兜底样式也不会生效:

body {
  /* prettier-ignore */
  padding-top: 25PX;
  padding-top: constant(safe-area-inset-top);
  padding-top: env(safe-area-inset-top);
}

那么如何解决这个问题呢?

我养你

3. 解决方案 #

这里我们就要借助 js 来实现了。

首先我们向页面中插入一个看不见的 div,将 div 的高度设置为安全距离的高度,然后再通过 js 获取其高度,若高度为 0,则说明没有生效。

let status = 0; // 0:还没数据,-1:不支持,1:支持

/**
 * 判断当前设置是否支持constant(safe-area-inset-top)或env(safe-area-inset-top);
 * 部分Android设备,可以认识safa-area-inset-top,但会将其识别为0
 * @returns {boolean} 当前设备是否支持安全距离
 */
const supportSafeArea = (): boolean => {
  if (status !== 0) {
    // 缓存数据,只向 body 插入一次 dom 即可
    return status === 1;
  }
  const div = document.createElement('div');
  const id = 'test-check-safe-area';
  const styles = [
    'position: fixed',
    'z-index: -1',
    'height: constant(safe-area-inset-top)',
    'height: env(safe-area-inset-top)',
  ];
  div.style.cssText = styles.join(';');
  div.id = id;
  document.body.appendChild(div);
  const areaDiv = document.getElementById(id);
  if (areaDiv) {
    status = areaDiv.offsetHeight > 0 ? 1 : -1; // 该 div 的高度是否为 0
    areaDiv.parentNode?.removeChild(areaDiv);
  }
  return status === 1;
};

那么在已经设置了安全区域属性的地方,都需要额外执行下 supportSafeArea()方法:

const SignTaskDetail = () => {
  const [safaArea, setSafeArea] = useState(true); // 当前页面是否支持 safe-area-inset-top

  useEffect(() => {
    setSafeArea(supportSafeArea());
  }, []);

  return (
    <div
      className={classNames('task-page', {
        'task-page-not-safearea': !safaArea, // 不支持时需要额外设置属性
      })}
    ></div>
  );
};

4. 总结 #

设备兼容性一直我们前端在解决的问题,无论是在 PC 端还是在移动端,浏览器的多样性和编程语言的发展,必然需要解决这些问题。