uniapp全面屏安全区域适配:从iPhone刘海屏到安卓挖孔屏的完整实践

2026-08-09 0 810

前阵子用 uniapp 做了一款扫码点餐应用,开发的时候用的安卓测试机,感觉一切正常。结果一发布到 iOS 上,用户反馈底部按钮被 Home 条挡住了一半,点也点不中。我赶紧拿了台 iPhone 14 Pro 模拟器看了一眼,确实惨不忍睹。最讽刺的是我用的还是系统自带 tabbar,怎么也会踩到安全区域?后来查了下,是因为自定义样式的 tabbar 没有适配底部安全区。

仔细研究后发现,uniapp 这种多端框架,想一套代码吃遍所有平台,安全区域这块绝对绕不开。今天就拿我自己平时的适配方案从原理到代码完整写一遍,希望能帮你少走几个我踩过的坑。

先从“安全区域”这词说起

手机屏幕现在不再是完整的矩形。iPhone 有刘海和底部 Home 条,安卓机有挖孔屏、水滴屏。系统为了保证内容不被这些结构遮挡,专门划分出一个“安全区域”,意思是你的界面元素最好放在这个区域里,否则就可能被挡住。

在 CSS 里,我们可以用 env(safe-area-inset-bottom) 这样的变量拿到安全区域的值,前提是页面的 viewport 要设置成 viewport-fit=cover。而在 uniapp 里,如果你只跑 H5,那直接在 index.html 里改 meta 标签就行。

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

但等你跑到小程序或者 App 端时,这条路就不一定那么顺畅了。小程序有它自己的环境,App 端又要看 WebView 的实现。我自己的感受是:千万别只依赖 CSS env,最好用 JS 把具体数值算出来,再手动加给元素。

官方给的属性:safeAreaInsets

uniapp 接口里有一个很关键的参数:safeAreaInsets。你可以通过 uni.getSystemInfoSync() 拿到它:

const info = uni.getSystemInfoSync()
console.log(info.safeAreaInsets)

在 iPhone 14 Pro 上,我打印出来的结果是这样的:

{left: 0, right: 0, top: 47, bottom: 34}

在普通国产安卓机上,结果通常是:

{left: 0, right: 0, top: 0, bottom: 0}

这就很直观了:bottom 就是底部 Home 条的高度,top 是刘海区域的高度。leftright 在横屏时可能会有值。

底部按钮适配:最常遇到的需求

很多页面底部都会放一个固定定位的“确认支付”或“提交订单”按钮,如果不处理,iPhone 的 Home 条会直接盖住按钮文字。

我推荐的做法是:在页面里先把安全区域的 bottom 值存下来,然后给底部按钮加一个动态 padding。

完整代码大概长这样(Vue3 组合式写法):

<template>
  <view class="page">
    <view class="content">
      <!-- 页面主要内容 -->
    </view>

    <view class="footer" :style="{ paddingBottom: bottomSafe + 'px' }">
      <button class="submit-btn">确认支付</button>
    </view>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'

const bottomSafe = ref(0)

onLoad(() => {
  // 低版本安卓机器可能没有 safeAreaInsets,所以给个默认值
  const { safeAreaInsets } = uni.getSystemInfoSync()
  bottomSafe.value = (safeAreaInsets && safeAreaInsets.bottom) || 0
})
</script>

然后在样式文件里写上常规的底部高度就行:

.footer {
  width: 100%;
  background: #fff;
  box-sizing: border-box;
  padding-bottom: 12px; /* 这个 12px 是给原本元素的间距,动态 padding 会额外加上 */
}

.submit-btn {
  width: 90%;
  margin: 0 auto;
  height: 48px;
  background: #07c160;
  color: white;
  border-radius: 24px;
  line-height: 48px;
  text-align: center;
  font-size: 16px;
  border: none;
}

这段代码在 App 端和小程序端都可以工作。为什么不用 CSS 的 env()?因为小程序端虽然有支持,但安卓一些低版本 WebView 表现不稳定,而且 App 端如果你使用的是 nvue 页面,处理方式又不一样。用 JS 赋值是最简单粗暴且统一的方式。

自定义导航栏:顶部刘海适配

当你的页面使用自定义导航栏时,就需要考虑顶部状态栏的高度。最典型的场景是做一个沉浸式大图页面,标题栏浮在图片上。

获取顶部高度也有专用的属性:statusBarHeight

const info = uni.getSystemInfoSync()
console.log(info.statusBarHeight)

在 iPhone 上,我习惯把状态栏高度和标题栏拼在一起,动态设置导航栏的高度:

<template>
  <view class="custom-nav" :style="{ height: navHeight + 'px' }">
    <view class="nav-title">商品详情</view>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'

const navHeight = ref(44)

onLoad(() => {
  const { statusBarHeight } = uni.getSystemInfoSync()
  // 48 是导航栏自身的高度,你可以按设计稿调整
  navHeight.value = (statusBarHeight || 0) + 48
})
</script>

这样在 iPhone 上导航栏高度大约是 47 + 48 = 95px,在普通安卓上大约是 24 + 48 = 72px。不同机型有不同高度,但至少不会被状态栏遮挡。

横屏适配:别忘了左右安全区

有些项目需要支持横屏,比如扫码枪配对、视频横播放。一旦转横屏,安全区域就跑到屏幕左右两侧了。

实测在 iPhone 横屏时,safeAreaInsets 可能是这样:

{left: 47, right: 47, top: 0, bottom: 21}

所以如果你有一个左右滑动的设计,最好根据这个值给容器加左右 padding。

const { safeAreaInsets } = uni.getSystemInfoSync()
const horizontalPadding = Math.max(
  safeAreaInsets?.left || 0,
  safeAreaInsets?.right || 0
)

然后把它用到滚动区域里。注意在横屏下,底部那条 Home 条仍然存在,bottom 也会有一个小值,所以底部按钮的适配依然不能忘。

再说说 CSS env 的正确使用姿势

虽然我前面说要靠 JS,但 CSS 方案在某些轻量场景下还是很方便。比如你想让一个整页背景图全屏铺开,不被刘海遮挡,同时保持内容在安全区内,可以这样写:

.banner {
  padding-top: constant(safe-area-inset-top);
  padding-top: env(safe-area-inset-top);
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

但这里有一个前提:页面所在的环境必须支持这两个 CSS 变量。H5 端只要你加了 viewport-fit=cover 就行。小程序端在微信开发者工具里也能识别。App 端则需要 WebView 内核完整支持。反正我试过几台安卓老机器,样式根本没生效。

所以我的最终建议是:能用 JS 拿数值就用 JS,CSS env 只用来做“增强效果”,不要依赖它作为唯一方案。

TabBar 适配:最容易翻车的地方

再用 tabbar 举例。uniapp 自带的 tabbar 在 iOS 上会自动处理安全区,但如果用自定义 tabbar(custom-tab-bar),你就得自己动手。

自定义 tabbar 的结构一般就这样:

<view class="tabbar">
  <view class="tabbar-item">首页</view>
  <view class="tabbar-item">订单</view>
  <view class="tabbar-item">我的</view>
</view>

你可以在 tabbar 这个组件里也加上安全区的动态 padding。但要知道,自定义 tabbar 最后会单独编译成一个组件,然后插入到页面里。在 App 端和微信小程序端,这个组件同样可以调用 uni.getSystemInfoSync()

在 tabbar 组件中加一个计算属性:

computed: {
  tabbarPadding() {
    return (this.safeAreaInsets && this.safeAreaInsets.bottom) + 'px'
  }
}

然后在模板里给整个 tabbar 根节点绑定样式:

<view class="tabbar" :style="{ paddingBottom: tabbarPadding }">

这个方法虽然麻烦,但起码能保证不管在哪台手机上,都不会出现 Home 条把 tabbar 文字顶掉的情况。

实践中的几个小教训

最后分享几个我踩过的坑。

第一,safeAreaInsets 在某些安卓机器上永远都是 0,不要因此以为没有安全区。很多安卓机的挖孔屏比较靠近屏幕顶部,但系统并没有上报 safeArea,这时就需要用 statusBarHeight 来辅助判断。

第二,在 Vue3 的 uniapp 项目里,onLoad 需要从 @dcloudio/uni-app 里导入,不是全局自动注入。我第一次写的时候没注意,结果 onLoad 直接未定义。

第三,如果某个页面同时用了自定义导航栏和底部按钮,别把 top 和 bottom 混在一起。最好在 onLoad 里一次性取好 statusBarHeight 和 safeAreaInsets,存成两个变量,后面各用各的。

第四,App 端 nvue 页面的安全区处理不太一样。因为 nvue 用的是原生渲染,safeAreaInsets 仍然可用,但 CSS 的 env 在 nvue 里不一定支持。所以 nvue 页面更得用 JS 动态设置。

写在最后

安全区域适配本身不难,难的是不同端各自为政。uniapp 已经帮我们做了很多封装,但这些东西还是得自己懂。我这点经验分享出来,如果你正准备做自定义 tabbar 或者底部固定按钮,希望可以省下一些百度的时间。

最后记住一句话:先拿 safeAreaInsets,再写样式;能动态就动态,别硬编码 44px 或者 34px。这样才能让你的 app 在各种奇怪屏幕面前挺住。

uniapp全面屏安全区域适配:从iPhone刘海屏到安卓挖孔屏的完整实践
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp uniapp全面屏安全区域适配:从iPhone刘海屏到安卓挖孔屏的完整实践 https://www.taomawang.com/web/uniapp/2514.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务