Uniapp 一键适配多端:H5 / 小程序 / APP 统一适配方案
作为移动端适配系列的收官之作,这篇讲透 Uniapp 如何用一套代码搞定所有端。
一、Uniapp 为什么是多端适配的最优解
做移动端开发,最头疼的不是写业务,而是适配。H5 要考虑浏览器兼容,小程序要处理各个平台差异,APP 还要区分 iOS 和 Android。维护三四套代码,bug 都能给你写出一本书来。
Uniapp 就是来解决这个问题的。
核心优势
一套代码 → 编译输出 → H5 + 微信小程序 + 支付宝小程序 + 抖音小程序 + APP (iOS/Android)| Uniapp | ||||
Uniapp 适合的场景:
业务驱动的项目,追求快速迭代
不想维护多套代码
主要用户在小程序端,需要快速扩展到 H5 和 APP
团队熟悉 Vue 技术栈
二、Uniapp 适配核心概念
upx 单位:Uniapp 的私有神器
upx 是 Uniapp 的核心适配单位,类似于 rem,但更简单。
核心规则:
设计稿宽度默认 750upx(对应 iPhone 6 的 375 逻辑像素)
upx 会自动转换为各端适配的单位
upx 转换比例 = 屏幕宽度 / 750实际换算示例:┌─────────────┬──────────────┬────────────┐│ 屏幕宽度 │ 1upx = ? │ 100upx = │├─────────────┼──────────────┼────────────┤│ 375px │ 0.5px │ 50px ││ 414px │ 0.552px │ 55.2px ││ 375px │ 0.5rem │ 50px │└─────────────┴──────────────┴────────────┘
为什么设计稿用 750upx?
历史原因:早期 iPhone 屏幕宽度 640,用 750 方便换算
750 是偶数,除以 2 就是 375,方便
现在的 upx 公式是动态的,不用死记硬背
使用方式:
/* 直接写数值,Uniapp 会自动转换 */.box {width: 750upx; /* 占满整个屏幕宽度 */height: 200upx;padding: 20upx;font-size: 28upx;}
三、统一适配配置(核心章节)
pages.json 全局配置
这是所有适配的起点,配置好这个就成功了一半。
{"globalStyle": {"navigationBarTextStyle": "black","navigationBarTitleText": "项目名称","navigationBarBackgroundColor": "#ffffff","backgroundColor": "#f5f5f5","app-plus": {"titleNView": {"autoBackButton": true,"buttonsWithSefArea": true},"safeArea": "auto"}}}
安全区域配置
各平台安全区域配置存在差异,需要分别处理:
{"app-plus": {"titleNView": {"buttonsWithSefArea": true},"safeArea": "auto"},"mp-weixin": {"appid": "your-appid","setting": {"style": "v2"},"usingComponents": true},"mp-alipay": {"defaultTitle": "项目名称"}}
四、H5 端适配
H5 特殊处理
// App.vueexport default {onLaunch() {// #ifdef H5if (process.env.VUE_APP_PLATFORM === 'h5') {document.addEventListener('DOMContentLoaded', () => {document.documentElement.style.fontSize = 'calc(100vw / 7.5)px'})}// #endif}}
H5 下 rem 方案(可选用)
/* App.vue 或全局样式 *//* H5 端用 rem,Uniapp 默认 upx */page {font-size: calc(100vw / 7.5); /* 设计稿 750upx 基准 */background-color: #f5f5f5;}
⚠️ 注意:100vw 包含滚动条,需要减去滚动条宽度:
page {font-size: calc((100vw - 17px) / 7.5); /* 17px 是滚动条宽度 */}
五、小程序端适配
微信小程序安全区域配置
// pages.json{"mp-weixin": {"appid": "your-appid","setting": {"style": "v2","postcss": true},"usingComponents": true,"permission": {"scope.userLocation": {"desc": "你的位置信息将用于小程序定位"}}}}
动态获取安全区域
onLoad() {// #ifdef MP-WEIXINconst systemInfo = uni.getSystemInfoSync()const safeArea = systemInfo.safeAreathis.safeAreaInsets = {top: safeArea.top,bottom: safeArea.bottom,left: safeArea.left,right: safeArea.right}// #endif}
完整示例:带安全区域的页面模板
<template><view class="page"><!-- 顶部安全区域 --><view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view><!-- 页面内容 --><view class="content">内容</view><!-- 底部安全区域 --><view class="tabbar" :style="{ paddingBottom: safeAreaInsets.bottom + 'px' }">TabBar</view></view></template><script>export default {data() {return {statusBarHeight: 0,safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 0 }}},onLoad() {this.initSafeArea()},methods: {initSafeArea() {const systemInfo = uni.getSystemInfoSync()this.statusBarHeight = systemInfo.statusBarHeight || 20if (systemInfo.safeArea) {this.safeAreaInsets = systemInfo.safeArea} else {// 兼容旧版this.safeAreaInsets = {top: systemInfo.statusBarHeight || 20,bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,left: 0,right: systemInfo.screenWidth}}}}}</script>
条件编译:处理各平台差异
// 条件编译,处理平台差异// #ifdef H5const platform = 'h5'// #endif// #ifdef MP-WEIXINconst platform = 'weixin'// #endif// #ifdef APP-PLUSconst platform = 'app'// #endifswitch (platform) {case 'h5':// H5 端处理breakcase 'weixin':// 微信小程序处理breakcase 'app':// APP 端处理break}
六、APP 端适配
iOS 安全区域
// App.vueexport default {onLaunch() {// #ifdef APP-PLUSconst systemInfo = uni.getSystemInfoSync()if (systemInfo.platform === 'ios') {console.log('iOS 平台')}// #endif}}
APP 状态栏配置
// pages.json{"pages": [{"path": "pages/index/index","style": {"navigationBarTitleText": "首页","navigationBarBackgroundColor": "#ffffff","navigationBarTextStyle": "black","enablePullDownRefresh": true,"app-plus": {"titleNView": {"type": "transparent","buttonsWithSefArea": true}}}}]}
APP 刘海屏适配(CSS)
/* App.vue 或全局样式 *//* iOS 安全区域 */page {padding-top: constant(safe-area-inset-top);padding-bottom: constant(safe-area-inset-bottom);}/* Android 安全区域 */page {padding-top: env(safe-area-inset-top);padding-bottom: env(safe-area-inset-bottom);}
七、统一适配工具函数
封装一个跨端适配 hook,哪里用到哪里引入:
// utils/safeArea.jsexport function useSafeArea() {const systemInfo = uni.getSystemInfoSync()const isH5 = process.env.VUE_APP_PLATFORM === 'h5'const isMp = ['mp-weixin', 'mp-alipay', 'mp-baidu', 'mp-toutiao','mp-lark', 'mp-qq', 'mp-kuaishou'].includes(systemInfo.platform)// 获取安全区域const safeArea = systemInfo.safeArea || {top: systemInfo.statusBarHeight || 20,bottom: systemInfo.windowHeight - (systemInfo.screenHeight - systemInfo.windowHeight),left: 0,right: systemInfo.screenWidth}// 获取状态栏高度const statusBarHeight = systemInfo.statusBarHeight || 20// 获取平台const platform = systemInfo.platform // ios / androidreturn {safeArea,statusBarHeight,platform,isH5,isMp,screenWidth: systemInfo.screenWidth,screenHeight: systemInfo.screenHeight,windowWidth: systemInfo.windowWidth,windowHeight: systemInfo.windowHeight}}
使用方式:
<template><view class="page" :style="{ paddingTop: safeArea.top + 'px' }"><view class="header">顶部</view><view class="content">内容</view><view class="footer" :style="{ paddingBottom: safeArea.bottom + 'px' }">底部</view></view></template><script>import { useSafeArea } from '@/utils/safeArea'export default {setup() {const { safeArea, statusBarHeight, platform } = useSafeArea()return {safeArea,statusBarHeight,platform}}}</script>
八、常见坑与避坑指南
🔴 坑一:upx 在 H5 端不生效
/* 解决方案:H5 端单独处理 */page {font-size: calc(100vw / 7.5);}
🔴 坑二:安全区域在 APP 端不准
const safeArea = systemInfo.safeArea || {top: systemInfo.statusBarHeight || 20,bottom: 34, // fallback 值left: 0,right: systemInfo.screenWidth}
🔴 坑三:小程序端样式不生效
/* 小程序不支持 hover,用其他方式处理 */🔴 坑四:多端样式差异
/* app.vue 中使用条件编译 *//* #ifdef H5 */@import './styles/h5.css'/* #endif *//* #ifdef MP-WEIXIN */@import './styles/weixin.css'/* #endif *//* #ifdef APP-PLUS */@import './styles/app.css'/* #endif */
🔴 坑五:APP 端键盘弹起问题
<!-- 解决方案:使用 adjust-position 属性 --><inputadjust-position:cursor-spacing="100"@focus="onInputFocus"@blur="onInputBlur"/>
九、Uniapp 多端适配检查清单
✅ 开发前检查这些配置,开发后对照验收
app-plus.safeArea = "auto" | ||
uni.getSystemInfoSync().statusBarHeight | ||
safeArea.bottom | ||
#ifdef 处理 | ||
transform: scaleY(0.5) | ||
adjust-position | ||
🚀 快速开发模板
<template><view class="page"><!-- 自动适配状态栏 --><view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view><!-- 固定顶部导航 --><view class="nav-bar">导航</view><!-- 页面内容 --><scroll-view class="scroll-view" scroll-y>内容区域</scroll-view><!-- 固定底部按钮 --><view class="fixed-bottom" :style="{ paddingBottom: safeArea.bottom + 'px' }">提交按钮</view></view></template><script>export default {data() {return {statusBarHeight: 20,safeArea: { top: 0, bottom: 0 }}},onLoad() {this.initDevice()},methods: {initDevice() {const info = uni.getSystemInfoSync()this.statusBarHeight = info.statusBarHeight || 20if (info.safeArea) {this.safeArea = info.safeArea}}}}</script>
总结
Uniapp 的适配核心就三点:
upx 单位 - 设计稿 750upx,直接写数值,全端自动适配
安全区域 - 用
getSystemInfoSync()获取,动态设置 padding条件编译 - 平台差异用
#ifdef处理,一套代码多端运行
记住这个公式:
成功适配 = 750upx + safeArea + 条件编译把这三板斧用好,多端适配就是手到擒来的事。
夜雨聆风