Chapter 04

样式系统

理解 rpx 单位本质,掌握跨端 Flexbox 布局,规避 nvue/uvue 样式限制

4.1 rpx 单位详解

rpx(Responsive Pixel,响应式像素)是 uni-app(以及微信小程序)中最核心的尺寸单位。它解决了一个根本问题:如何在不同屏幕尺寸的设备上保持一致的视觉效果?

rpx 的计算原理

rpx 的设计假设:无论设备屏幕多宽,其逻辑宽度均为 750rpx

这意味着:

设计师用 750px 宽的设计稿(一倍图的两倍分辨率,即 @2x),量出来多少像素,写代码时就写多少 rpx,各设备会自动等比缩放。

设计稿到代码的换算公式

设计稿标注值(px) = 代码中的 rpx 值
即:设计稿 32px → 代码写 32rpx(前提是设计稿宽度为 750px)

如果设计稿是 375px 宽(@1x),则设计稿标注值 × 2 = rpx 值
即:设计稿标注 16px → 代码写 32rpx

rpx vs px vs rem vs vw

单位跨端支持适用场景备注
rpx全平台布局尺寸、间距、字体首选,随屏幕宽度等比缩放
px全平台1px 边框、固定尺寸元素物理像素,不随屏幕缩放
upxApp 端(旧版)同 rpx旧版 App 端单位,已弃用,统一用 rpx
remH5、AppH5 端字体缩放小程序不支持
vw/vhH5、App(部分)全屏布局微信小程序已支持,但兼容性需测试
%全平台相对父元素的比例布局需要父元素有明确尺寸
1px 边框的处理

在高分屏(@2x、@3x)上,1rpx 等于 0.5px,会渲染为非常细的线,效果接近"真 1px 物理像素"。如果要实现真正的细边框,使用 1rpx 而不是 1px。这是小程序和 App 端的常见最佳实践。

4.2 Flexbox 布局

在 uni-app 中,Flexbox 是唯一推荐的布局方式。这并不是 uni-app 的限制,而是因为:

常用 Flexbox 布局模式

/* 垂直排列(默认),子元素从上到下 */
.column {
  display: flex;
  flex-direction: column;
}

/* 水平排列,子元素从左到右 */
.row {
  display: flex;
  flex-direction: row;
  align-items: center;  /* 垂直居中 */
}

/* 经典两端对齐 + 垂直居中(常用于列表项) */
.list-item {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  padding: 24rpx;
}

/* 完全居中(登录页、空状态) */
.center {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  flex: 1;
}

/* 等分布局(底部导航、Tab 栏) */
.tab-bar {
  display: flex;
  flex-direction: row;
}
.tab-item {
  flex: 1;  /* 等分剩余空间 */
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* 换行瀑布流(商品列表,每行两个) */
.goods-list {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 20rpx;
}
.goods-item {
  width: 345rpx;  /* (750 - 40 - 20) / 2 */
  margin-bottom: 20rpx;
}

flex 属性详解

flexflex-growflex-shrinkflex-basis 的简写:

flex: 1
等同于 flex: 1 1 0%。子元素均分剩余空间,这是最常用的写法,实现等分布局。
flex-grow
定义当容器有剩余空间时,该元素的扩张比例。flex-grow: 2 的元素会获得 flex-grow: 1 元素两倍的剩余空间。
flex-shrink
定义当容器空间不足时,该元素的收缩比例。默认为 1(允许收缩)。设为 0 则不允许收缩。
flex-basis
元素在主轴上的初始大小。auto 表示使用元素自身 width/height,0 表示从零开始分配空间。

4.3 nvue 与 uvue 的样式限制

当使用原生渲染模式(nvue 文件用于传统 uni-app,uvue 文件用于 uni-app x)时,CSS 支持范围有所限制,因为底层不再是 WebView,而是原生视图系统。

nvue/uvue 不支持的 CSS 特性

在原生渲染页面中不可用

nvue/uvue 必须使用的样式规范

/* ✅ 正确:单位用 px(nvue 中没有 rpx,需要用 uni.getWindowInfo 换算)*/
.box {
  width: 200px;
  height: 100px;
  background-color: #ffffff;
  border-radius: 8px;
  display: flex;
  flex-direction: column;
}

/* ✅ 正确:所有样式必须写在 <style> 中,不支持内联复杂样式 */
/* ❌ 错误:不支持 scoped(nvue 页面级别作用域天然隔离) */

/* ✅ 推荐:线性渐变(部分支持)*/
.gradient {
  background-image: linear-gradient(135deg, #0ea5e9, #0284c7);
}

/* ❌ 不支持:box-shadow(uvue 中有替代方案)*/
/* ❌ 不支持:text-shadow */

uni-app x uvue 的改进

uni-app x 的 uvue 相比 nvue 有了很大的样式能力提升:

4.4 样式作用域与 scoped

<!-- .vue 文件中的样式默认是全局的 -->
<style>
.title { color: red; }  /* 影响所有页面 */
</style>

<!-- 添加 scoped 后,样式只影响当前组件 -->
<style scoped>
.title { color: red; }  /* 仅影响当前组件内的 .title */
</style>

<!-- 深度选择器:修改子组件内部的样式 -->
<style scoped>
/* Vue 3 的深度选择器语法 */
:deep(.child-class) {
  color: blue;
}
</style>

4.5 全局样式与主题

uni-app 支持通过 App.vue<style> 块定义全局样式,这里定义的样式会作用于所有页面:

/* App.vue */
<style>
/* CSS 变量定义(主题色系)*/
page {  /* 小程序中等同于 body */
  --primary: #0ea5e9;
  --primary-dark: #0284c7;
  --text: #292524;
  --text-2: #57534e;
  --bg: #f5f5f0;
  --radius: 16rpx;
  font-size: 32rpx;
  background-color: var(--bg);
}

/* 通用工具类 */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }

.mt-2 { margin-top: 16rpx; }
.mt-4 { margin-top: 32rpx; }
.p-4 { padding: 32rpx; }

.text-primary { color: var(--primary); }
.text-sm { font-size: 28rpx; }
.text-lg { font-size: 36rpx; }
.font-bold { font-weight: 700; }
</style>

4.6 安全区域适配

现代 iPhone 和 Android 设备有刘海、圆角、底部 Home Indicator 等设计,内容需要避开这些区域:

/* 适配 iOS 安全区域(刘海屏、底部 Home 条)*/
.header {
  padding-top: constant(safe-area-inset-top);    /* iOS 11 */
  padding-top: env(safe-area-inset-top);          /* iOS 11.2+ */
}

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

在 uni-app 中,可以通过 API 获取安全区域信息并动态设置:

const windowInfo = uni.getWindowInfo()
const statusBarHeight = windowInfo.statusBarHeight    // 状态栏高度(px)
const safeAreaInsets = windowInfo.safeArea            // 安全区域

// 通常自定义导航栏的高度 = 状态栏高度 + 44px(内容区)
const navBarHeight = statusBarHeight + 44  // 单位 px

4.7 多端样式兼容策略

面对 H5、小程序、App 的样式差异,以下是推荐的兼容策略:

策略一:优先使用 rpx + Flexbox(通吃所有平台)

这是成本最低的策略。避免使用任何平台特有的 CSS,只用 rpx 单位和 Flexbox 布局,确保在所有平台表现一致。

策略二:条件编译特殊样式

<style>
.nav-bar {
  height: 88rpx;
}

/* #ifdef H5 */
.nav-bar {
  height: 100px;
  position: sticky;
  top: 0;
}
/* #endif */

/* #ifdef APP */
.nav-bar {
  /* App 端加上状态栏高度 */
  padding-top: 44px;
}
/* #endif */
</style>

策略三:动态样式(JavaScript 计算)

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const navStyle = ref({})

onMounted(() => {
  const info = uni.getWindowInfo()
  navStyle.value = {
    paddingTop: `${info.statusBarHeight}px`,
    height: `${info.statusBarHeight + 44}px`
  }
})
</script>

<template>
  <view class="nav-bar" :style="navStyle">
    <text>自定义导航栏</text>
  </view>
</template>

4.8 小结