ios 刘海屏,灵动岛的适配以及碰到一些坑点

饭宝  金牌会员 | 2024-11-2 02:20:35 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 798|帖子 798|积分 2394

最近再改公司内部移动端 ios 安全区域适配的问题,所谓知己知彼百战百胜,所以深入的学习了一下 安全区域适配有关的知识
  问题表象



  • 头部标题区域被灵动岛遮挡,标题被遮挡,返回按钮点不到




  • 底部被遮挡


原因

看一下官方对安全区域[1]的定义。
   视图中未被导航栏、选项卡栏、工具栏或视图控制器大概提供的其他视图覆盖的区域。
  看一下官方的图就能明白了,蓝色区域是安全区域,白色区域是大概覆盖安全区域的其他视图范围。也就是说,我们要做好适配,必须保证页面可视、可操作区域是在安全区域内。


第一次提出这个概念是 iOS11,iPhoneX 机型发布的时候。从 iPhoneX 开始推出了刘海屏,从 iPhone 14 Pro 开始推出了灵动岛。背面的讨论,均以 iPhoneX 机型为例。
处置惩罚方案

其实处置惩罚方案照旧很简单的,将 viewport-fit 指定为cover,如许我们就可以使用 env() 和 constant()这两个函数在css中获取到安全区域的值了,获取到值之后处置惩罚对应的间距就可以了。
但是使用的过程中多多少少照旧有一些坑点的,下面我来分享一些。
viewport-fit

iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签做了一个扩展,新增viewport-fit属性,用于设置网页在可视窗口的结构方式,可设置三个值:


  • contain:可视窗口完全包含网页内容(左图)
  • cover:网页内容完全覆盖可视窗口(右图)
  • auto:默认值,此值不影响初始结构视图端口,并且整个 web 页面都是可查看的。
contain 和 cover 具体区别如下图:


   注意:网页的默认值是 viewport-fit=contain,必要适配 iPhoneX 必须设置 viewport-fit=cover,这是适配的关键步骤
  1. <meta name="viewport" content="xxx(你们之前的viewport属性值),viewport-fit=cover" />
  2. <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
复制代码
更具体说明,参考文档: viewport-fit-descriptor[2]
坑点 1

viewport-fit 从 contain 修改成 cover 之后,页面大概会出现白边~~(你要是问为啥,就说明你 viewport-fit 干啥用的没看懂 (ಥ_ಥ))~~,处置惩罚方案也很简单,可以给整个页面添加一个页面主题色的背景致,如下图。


env() 和 constant()

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


  • safe-area-inset-top:安全区域距离顶部界限距离
  • safe-area-inset-bottom:安全区域距离底部界限距离
  • safe-area-inset-left:安全区域距离左边界限距离
  • safe-area-inset-right:安全区域距离右边界限距离
有一点要注意,在 IOS11.2 系统从前,可以使用 constant()函数,但是在 IOS11.2 系统以后,这个函数就被废弃了,被 env()函数替换了。所以我们在做屏幕适配时,必要如许写:
  1. .footer {
  2.   padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS 版本 < 11.2
  3.   padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS 版本 >= 11.2
  4. }
复制代码
并且env()和 constant()必要同时存在,而且顺序不能换。
使用@supports 一点点的优化

如果我们想只有在必要适配的机型上才适配的时候,我们可以使用 css @supports 语法,检测到这两个变量存在的时候,@supports 内写的 css样式才会被应用。
  1. @supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
  2.   .footer {
  3.     margin-bottom: constant(safe-area-inset-bottom);
  4.     margin-bottom: env(safe-area-inset-bottom);
  5.   }
  6. }
复制代码
如何优雅的在 js 中获取到这些变量

很多时候,我们会用 JS 做一些效果,好比页面的头部滚动到肯定程度的时候必要固定在页面顶上。这个时候就不得不使用 JS 获取到安全区域的值,去适配这个定位的元素。那么现在的问题就变成了,怎么优雅的在 JS 中获取到 safe-area-inset-xxx的值。
其实原理很简单,核心上就是 safe-area-inset-xxx 其实就是苹果为我们注入的 css 变量,我们可以自定义 css 变量,让后使用 JS 获取自定义的 css 变量。
看一段成熟的代码:
  1. function insertSafeAreaCSSVarEl() {
  2.   const cssVarStyleElVer = 1;
  3.   const cssVarStyleElId = 'beisen_mobile_utils_safe_area_vars';
  4.   const existedEl = document.getElementById(cssVarStyleElId);
  5.   if (existedEl) {
  6.     
  7.     const existedVer = parseInt(existedEl.dataset.version || '0');
  8.     if (existedVer >= cssVarStyleElVer) {
  9.       
  10.       return;
  11.     }
  12.     
  13.     existedEl.parentElement?.removeChild(existedEl);
  14.   }
  15.   
  16.   const el = document.createElement('style');
  17.   el.setAttribute('id', cssVarStyleElId);
  18.   el.dataset.version = String(cssVarStyleElVer);
  19.   el.innerHTML = `
  20. :root {
  21. --yl-sat: constant(safe-area-inset-top);
  22. --yl-sat: env(safe-area-inset-top);
  23. --yl-sar: constant(safe-area-inset-right);
  24. --yl-sar: env(safe-area-inset-right);
  25. --yl-sab: constant(safe-area-inset-bottom);
  26. --yl-sab: env(safe-area-inset-bottom);
  27. --yl-sal: constant(safe-area-inset-left);
  28. --yl-sal: env(safe-area-inset-left);
  29. }`;
  30.   document.head.append(el);
  31. }
  32. function parseRootPropertyValue(key) {
  33.   let str = getComputedStyle(document.documentElement).getPropertyValue(key);
  34.   str = str.replace('px', '');
  35.   const n = parseInt(str) || 0;
  36.   return n;
  37. }
  38.  * 获取SafeArea的尺寸。
  39.  */
  40. function getSafeAreaSize() {
  41.   insertSafeAreaCSSVarEl(); 
  42.   return {
  43.     top: parseRootPropertyValue('--yl-sat'),
  44.     right: parseRootPropertyValue('--yl-sar'),
  45.     bottom: parseRootPropertyValue('--yl-sab'),
  46.     left: parseRootPropertyValue('--yl-sal'),
  47.   };
  48. }
复制代码
核心点是 往 :root 中插入了一些自定义的 css 变量。
坑点 2

上面提到的 getSafeAreaSize 方法有点弊端,我们是主动的往 html 上插入了一个style标签,经过测试,在插入标签的时候,env(safe-area-inset-xxx) 值是空的,等到下一帧渲染的时候才能拿到最新的值。就是说我们第一次获取值的时候是有肯定的耽误
这个问题有两个解决方案:


  • 一个是 往 :root 中插入变量的过程放到html页面中,我们哀求回来的html文件中就有这些变量了,那我们JS中只必要获取就可以了,是在不行也可以放到 index.css中,初始化样式的时候可以往 :root或者body中加自定义的css变量。
  • 第二个是我们要用之前,提前的执行一下 getSafeAreaSize() 方法。好比在组件的 useLayoutEffect 这个hooks中执行一下(我们是因为有历史原因,没有采用第一种方案,但现实上第一种方案的成本几乎没有)。
坑点 3

虽然说苹果会为我们注入 safe-area-inset-xxx变量到浏览器中,但是这玩意完满是一个黑盒,经过测试发现他是在必要注入的时候才会注入。
那什么叫做 必要注入的时候才会注入呢?
写下如下代码
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3.   <head>
  4.     <meta charset="UTF-8" />
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, minimum-scale=1, maximum-scale=1.0, user-scalable=0" />
  6.     <style>
  7.       #test123 {
  8.         background: red;
  9.         width: 100px;
  10.         height: 100px;
  11.       }
  12.     </style>
  13.     <script>
  14.       function getSafeAreaSize() {
  15.         
  16.       }
  17.       getSafeAreaSize(); 
  18.     </script>
  19.   </head>
  20.   <body>
  21.     <div id="test123"></div>
  22.     <script>
  23.       const el = document.getElementById('test123');
  24.       setTimeout(() => {
  25.         
  26.         el.innerText = JSON.stringify(getSafeAreaSize());
  27.       }, 20);
  28.     </script>
  29.   </body>
  30. </html>
复制代码
OK,上面的代码起个当地服务(可以用 Live Server 插件),用手机访问(同一个 WiFi,用 ip+端口访问)一下。
Safari 浏览器的表象如下:


我们发现显着是 14Pro 的机型,但是 top 和 bottom 的值是 0。
再看一下微信内置浏览器打开是什么样的。


我们发现 bottom 有值,但是 top 却没有值。
综上可以看出,我们的页面存在的区域被操作栏或者底部小黑条遮挡了的时候,safe-area-inset-xxx才会有值,也就是我上面说的 必要注入的时候才会注入
坑点 4

照旧微信内置浏览器,他有一个特性,就是 A 页面跳转到 B 页面的时候,B 页面下面会自带一个底部操作区。如图


出现了底部操作区的时候,我们的页面就不必要再做适配了
适配的伪代码如下
  1. let safeBottom = 0;
  2. if (isWechat && !isWechatWork) {
  3.   safeBottom = window.history.length > 1 ? 0 : 'env(safe-area-inset-bottom)';
  4. }
复制代码
处置惩罚方案 2-不推荐

在学习的过程中发现我们的系统内里还有如下的实现,使用 @media检查当前屏幕的分辨率信息,来判断对应的机型,然后做相关的适配。只做一种学习相识,不推荐这个方案实现。
  1. @media only screen and (min-device-width: 375px) and (max-device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
  2.   
  3. }
  4. @media only screen and (min-device-width: 390px) and (max-device-height: 844px) and (-webkit-device-pixel-ratio: 3) {
  5.   
  6. }
  7. @media only screen and (min-device-width: 414px) and (max-device-height: 896px) and (-webkit-device-pixel-ratio: 2) {
  8.   
  9. }
  10. @media only screen and (min-device-width: 414px) and (max-device-height: 896px) and (-webkit-device-pixel-ratio: 3) {
  11.   
  12. }
  13. @media only screen and (min-device-width: 428px) and (max-device-height: 926px) and (-webkit-device-pixel-ratio: 3) {
  14.   
  15. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

饭宝

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表