{
    "componentChunkName": "component---src-templates-blog-tsx",
    "path": "/blog/2022/SafeArea",
    "result": {"data":{"markdownRemark":{"frontmatter":{"author":"lhzbxx","classify":"大前端","date":"2022 年 01 月 08 日","title":"编写 Safe Area 组件约束用户界面"},"html":"<p>如今在移动设备领域，消费者随身携带的显示器呈现出了多样化的设计，不再限制于简单的矩形块。面对尺寸不一、形状各异的设备，开发者们如何确保提供给用户的内容和交互不受影响呢？</p>\n<!-- endexcerpt -->\n<h3>由 iPhone X 说起</h3>\n<p>Apple 推出了以 iPhone X 为代表的异形屏方案后，提供了<a href=\"https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/#:~:text=NSLayoutConstraint%20and%20UITraitCollection.-,Layout%20Guides%20and%20Safe%20Areas,-A%20layout%20guide\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">相应的视觉设计指南</a>。其中提到了 Safe Area 的定义，我们用一张图片来演示 Safe Area 在 iPhone 上的应用：</p>\n<p><span\n      class=\"gatsby-resp-image-wrapper\"\n      style=\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1536px; \"\n    >\n      <a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/fce425a6506675c386b2c3c24ae537ce/e6b01/SafeArea.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-background-image\"\n    style=\"padding-bottom: 65.10416666666666%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIEAf/EABUBAQEAAAAAAAAAAAAAAAAAAAID/9oADAMBAAIQAxAAAAG122LCoqf/xAAbEAEBAAEFAAAAAAAAAAAAAAABABECAxITIv/aAAgBAQABBQIzyIbr1Ft+4MH/xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAwEBPwGH/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERAv/aAAgBAgEBPwGaoj//xAAdEAABBAIDAAAAAAAAAAAAAAAAAQIRMhASIVGR/9oACAEBAAY/Ap3f6WUuLL+OoFx//8QAHBABAAICAwEAAAAAAAAAAAAAAQARITFBUYHR/9oACAEBAAE/IaFac4cIut/LcbueRchSNfWZrVqHRQ+T/9oADAMBAAIAAwAAABAgD//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAwEBPxDcSl//xAAYEQEAAwEAAAAAAAAAAAAAAAABABEhUf/aAAgBAgEBPxDNb2BCp//EAB4QAQEAAgICAwAAAAAAAAAAAAERAEEhMVFhgeHx/9oACAEBAAE/EAfbK4mj0TWsjSaFpU16+sAKiWhynB+/OIMxCXpnb34xMYQHV3lYR52z/9k='); background-size: cover; display: block;\"\n  ></span>\n  <img\n        class=\"gatsby-resp-image-image\"\n        alt=\"基于 iPhone 的 Safe Area 演示\"\n        title=\"基于 iPhone 的 Safe Area 演示\"\n        src=\"/static/fce425a6506675c386b2c3c24ae537ce/ac99c/SafeArea.jpg\"\n        srcset=\"/static/fce425a6506675c386b2c3c24ae537ce/4ecad/SafeArea.jpg 384w,\n/static/fce425a6506675c386b2c3c24ae537ce/212bf/SafeArea.jpg 768w,\n/static/fce425a6506675c386b2c3c24ae537ce/ac99c/SafeArea.jpg 1536w,\n/static/fce425a6506675c386b2c3c24ae537ce/ae289/SafeArea.jpg 2304w,\n/static/fce425a6506675c386b2c3c24ae537ce/67226/SafeArea.jpg 3072w,\n/static/fce425a6506675c386b2c3c24ae537ce/e6b01/SafeArea.jpg 3840w\"\n        sizes=\"(max-width: 1536px) 100vw, 1536px\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;\"\n        loading=\"lazy\"\n        decoding=\"async\"\n      />\n  </a>\n    </span></p>\n<p>其实 Safe Area 不是一个新鲜的概念，它最早源自于电视生产领域，用于描述在电视屏幕上看到的区域。</p>\n<p>简单地说，<strong>安全区域 ≈ 主要视觉区域 + 实际交互区域</strong>。如 iPhone X 的 Home 指示条的设计，虽然在视觉上是隐藏的，但由于它会响应系统的滑动手势，不能把控制功能放在此处。</p>\n<h3>结论</h3>\n<p>这给我们的启示是：<strong>除了极少数的场景，我们应该把渲染出来的内容放在一个名为 SafeArea 的组件内</strong>。</p>\n<p>类似在 Flutter 中，</p>\n<div class=\"gatsby-highlight\" data-language=\"dart\"><pre class=\"language-dart\"><code class=\"language-dart\"><span class=\"token class-name\">Widget</span> <span class=\"token function\">build</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">BuildContext</span> context<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token class-name\">SafeArea</span><span class=\"token punctuation\">(</span>child<span class=\"token punctuation\">:</span> <span class=\"token class-name\">Container</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>就确保内容 <code class=\"language-text\">Container()</code> 放置在了 Safe Area 内。</p>\n<h3>构建 &#x3C;SafeArea /></h3>\n<p>构建 <code class=\"language-text\">&lt;SafeArea /></code> 组件的原理其实很简单：通过操作系统提供的接口获取显示边距。</p>\n<div class=\"gatsby-highlight\" data-language=\"dart\"><pre class=\"language-dart\"><code class=\"language-dart\"><span class=\"token class-name\">Widget</span> <span class=\"token function\">build</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">BuildContext</span> context<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">assert</span><span class=\"token punctuation\">(</span><span class=\"token function\">debugCheckHasMediaQuery</span><span class=\"token punctuation\">(</span>context<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">final</span> <span class=\"token class-name\">MediaQueryData</span> data <span class=\"token operator\">=</span> <span class=\"token class-name\">MediaQuery</span><span class=\"token punctuation\">.</span><span class=\"token function\">of</span><span class=\"token punctuation\">(</span>context<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token class-name\">EdgeInsets</span> padding <span class=\"token operator\">=</span> data<span class=\"token punctuation\">.</span>padding<span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Bottom padding has been consumed - i.e. by the keyboard</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>data<span class=\"token punctuation\">.</span>padding<span class=\"token punctuation\">.</span>bottom <span class=\"token operator\">==</span> <span class=\"token number\">0.0</span> <span class=\"token operator\">&amp;&amp;</span> data<span class=\"token punctuation\">.</span>viewInsets<span class=\"token punctuation\">.</span>bottom <span class=\"token operator\">!=</span> <span class=\"token number\">0.0</span> <span class=\"token operator\">&amp;&amp;</span> maintainBottomViewPadding<span class=\"token punctuation\">)</span>\n    padding <span class=\"token operator\">=</span> padding<span class=\"token punctuation\">.</span><span class=\"token function\">copyWith</span><span class=\"token punctuation\">(</span>bottom<span class=\"token punctuation\">:</span> data<span class=\"token punctuation\">.</span>viewPadding<span class=\"token punctuation\">.</span>bottom<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token class-name\">Padding</span><span class=\"token punctuation\">(</span>\n    padding<span class=\"token punctuation\">:</span> <span class=\"token class-name\">EdgeInsets</span><span class=\"token punctuation\">.</span><span class=\"token function\">only</span><span class=\"token punctuation\">(</span>\n      left<span class=\"token punctuation\">:</span> math<span class=\"token punctuation\">.</span><span class=\"token function\">max</span><span class=\"token punctuation\">(</span>left <span class=\"token operator\">?</span> padding<span class=\"token punctuation\">.</span>left <span class=\"token punctuation\">:</span> <span class=\"token number\">0.0</span><span class=\"token punctuation\">,</span> minimum<span class=\"token punctuation\">.</span>left<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      top<span class=\"token punctuation\">:</span> math<span class=\"token punctuation\">.</span><span class=\"token function\">max</span><span class=\"token punctuation\">(</span>top <span class=\"token operator\">?</span> padding<span class=\"token punctuation\">.</span>top <span class=\"token punctuation\">:</span> <span class=\"token number\">0.0</span><span class=\"token punctuation\">,</span> minimum<span class=\"token punctuation\">.</span>top<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      right<span class=\"token punctuation\">:</span> math<span class=\"token punctuation\">.</span><span class=\"token function\">max</span><span class=\"token punctuation\">(</span>right <span class=\"token operator\">?</span> padding<span class=\"token punctuation\">.</span>right <span class=\"token punctuation\">:</span> <span class=\"token number\">0.0</span><span class=\"token punctuation\">,</span> minimum<span class=\"token punctuation\">.</span>right<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      bottom<span class=\"token punctuation\">:</span> math<span class=\"token punctuation\">.</span><span class=\"token function\">max</span><span class=\"token punctuation\">(</span>bottom <span class=\"token operator\">?</span> padding<span class=\"token punctuation\">.</span>bottom <span class=\"token punctuation\">:</span> <span class=\"token number\">0.0</span><span class=\"token punctuation\">,</span> minimum<span class=\"token punctuation\">.</span>bottom<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    child<span class=\"token punctuation\">:</span> <span class=\"token class-name\">MediaQuery</span><span class=\"token punctuation\">.</span><span class=\"token function\">removePadding</span><span class=\"token punctuation\">(</span>\n      context<span class=\"token punctuation\">:</span> context<span class=\"token punctuation\">,</span>\n      removeLeft<span class=\"token punctuation\">:</span> left<span class=\"token punctuation\">,</span>\n      removeTop<span class=\"token punctuation\">:</span> top<span class=\"token punctuation\">,</span>\n      removeRight<span class=\"token punctuation\">:</span> right<span class=\"token punctuation\">,</span>\n      removeBottom<span class=\"token punctuation\">:</span> bottom<span class=\"token punctuation\">,</span>\n      child<span class=\"token punctuation\">:</span> child<span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3>一个例子：小程序</h3>\n<p>当然，我们并不总是在操作系统层面上去实现应用。典型的如小程序运行环境，我们无法直接拿到 ViewInsets 的数据。</p>\n<p>其实在 iPhone X 推出前夕，WebKit 就发布了<a href=\"https://webkit.org/blog/7929/designing-websites-for-iphone-x/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">网站的适配指南</a>。</p>\n<p>简单的两步：</p>\n<p>一、设置视口的布局方式（小程序不需要设置）</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\">&lt;meta name=<span class=\"token string\">\"viewport\"</span> content=<span class=\"token string\">\"... viewport-fit=cover\"</span>></code></pre></div>\n<p>二、设置主体元素的内边距</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token selector\">.SafeArea</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">padding-top</span><span class=\"token punctuation\">:</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-top<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding-right</span><span class=\"token punctuation\">:</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-right<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding-bottom</span><span class=\"token punctuation\">:</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-bottom<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding-left</span><span class=\"token punctuation\">:</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-left<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>其中 <code class=\"language-text\">safe-area-inset-*</code> 属性是由系统代理的环境变量，也可以通过加入自定义的 var，</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token selector\">.SafeArea</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token property\">padding-top</span><span class=\"token punctuation\">:</span> <span class=\"token function\">max</span><span class=\"token punctuation\">(</span>--minimum-top<span class=\"token punctuation\">,</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-top<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding-right</span><span class=\"token punctuation\">:</span> <span class=\"token function\">max</span><span class=\"token punctuation\">(</span>--minimum-right<span class=\"token punctuation\">,</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-right<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding-bottom</span><span class=\"token punctuation\">:</span> <span class=\"token function\">max</span><span class=\"token punctuation\">(</span>--minimum-bottom<span class=\"token punctuation\">,</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-bottom<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token property\">padding-left</span><span class=\"token punctuation\">:</span> <span class=\"token function\">max</span><span class=\"token punctuation\">(</span>--minimum-left<span class=\"token punctuation\">,</span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span>safe-area-inset-left<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>瞧，是不是跟 Flutter 官方的 SafeArea 组件形式一致了。</p>"}},"pageContext":{"slug":"/blog/2022/SafeArea"}},
    "staticQueryHashes": ["844979529"]}