微信小程序开发学习文档(三)

三、视图层 WXML 与 WXSS

3.1 WXML 模板语法基础

WXML 的全称是 WeiXin Markup Language,它类似 HTML 但不是 HTML。它的核心使命是将逻辑层的数据"翻译"成用户可见的界面。

3.1.1 数据绑定`{{}}`:单向绑定原理与限制

数据绑定是 WXML 最基础的能力,使用双大括号 `{{}}` 语法将逻辑层 `Page.data` 中的变量插入到模板的指定位置。

绑定是单向的------数据只能从逻辑层流向视图层,用户在界面上的输入不会自动反向更新 `data`。

XML 复制代码
<!-- 数据绑定的三种常见形态 -->

<view>

  <!-- 1. 内容绑定:直接显示数据 -->

  <text>{{userName}}</text>



  <!-- 2. 属性绑定:将数据赋给组件的属性,引号内不能有空格 -->

  <image src="{{avatarUrl}}" mode="aspectFill"></image>



  <!-- 3. 运算表达式:支持简单的三元运算、算术、逻辑判断 -->

  <text>{{isVip ? '尊贵会员' : '普通用户'}}</text>

  <text>{{price * quantity}}元</text>

</view>
javascript 复制代码
Page({

  data: {

    userName: '小明',

    avatarUrl: 'https://example.com/avatar.png',

    isVip: true,

    price: 29.9,

    quantity: 3

  }

})

核心限制与注意事项:

|------------------|---------------------------|----------------------------------------|
| 限制项 | 说明 | 替代方案 |
| 不支持函数调用 | `{{getFullName()}}` 会报错 | 使用计算属性思维,在 JS 中预先处理好数据 |
| 不支持复杂表达式 | 无法写 `if` 语句或 `for` 循环 | 用 `wx:if`、`wx:for` 等模板指令处理 |
| 作用域仅在 `data` 内 | 无法访问页面实例上的方法或全局变量 | 将所需值预先放入 `data` |
| 单向绑定 | 输入框的值变化不会自动更新 `data` | 手动监听 `bindinput` 事件,用 `setData` 同步 |

`{{}}` 就像在幕布上投射幻灯片的投影仪。逻辑层(电脑)里的数据更新后,投影仪会自动把新内容投射到幕布(视图)上。但幕布上的灰尘(用户输入)不会自己跑回电脑里,需要有人(`bindinput` 事件)手动把它们扫进去。这个单向流动保证了数据的来龙去脉始终清晰可追踪。

3.1.2 条件渲染:`wx:if` vs `hidden` 性能对比与使用场景

`wx:if` 和 `hidden` 都能控制元素的显示与隐藏,但实现机制完全不同。

`wx:if` 是惰性渲染,条件为假时元素根本不存在于视图树中;

`hidden` 则是始终渲染但改变可见性,类似于 CSS 的 `display: none`。

XML 复制代码
<!-- wx:if:条件为假时,这个 view 完全不会被渲染到页面上 -->

<view wx:if="{{hasError}}" class="error-msg">网络请求失败,请重试</view>



<!-- hidden:元素始终存在于 DOM 树中,只是被隐藏了 -->

<view hidden="{{!isLoading}}" class="loading">加载中...</view>

性能对比与选择决策:

|------|--------------------|-------------------------|
| 维度 | `wx:if` | `hidden` |
| 机制 | 惰性创建/销毁节点 | 始终创建,切换 CSS `display` |
| 初始开销 | 条件为假时开销极低(不创建) | 无论条件如何都要创建节点 |
| 切换开销 | 高(涉及节点的创建或销毁) | 低(仅改变样式属性) |
| 适用场景 | 条件在生命周期内基本不变,或很少切换 | 需要频繁切换的场景 |

决策法则:

如果元素在用户操作过程中会被反复切换显示/隐藏(如 loading 动画、弹窗、tab 切换),用 `hidden`;如果元素只在特定条件下出现一次且不会频繁变化(如错误提示、管理员专属按钮),用 `wx:if`。

想象你在布置一个展厅。

`wx:if` 是"根据是否有贵宾来决定是否搬一把椅子进来"------没有贵宾时椅子压根不在展厅里,省空间。

`hidden` 是"椅子始终摆在那,只是盖了一块布(hidden)或掀开布(显示)"------椅子始终占据位置,但切换速度极快。如果贵宾一天进出几十次,搬进搬出会累死(性能差),盖布掀布就轻松得多。

3.1.3 列表渲染:`wx:for`、`wx:key` 的重要性与最佳实践

`wx:for` 用于将一个数组数据循环渲染为一组组件。`wx:key` 则为每个循环项指定唯一的标识符,是高效更新列表的关键属性。

缺失 `wx:key` 会导致框架在数据变动时采用"全量更新"策略,性能严重下降。

XML 复制代码
<!-- 基础用法:遍历商品列表 -->

<view wx:for="{{goodsList}}" wx:key="id" class="goods-item">

  <image src="{{item.cover}}"></image>

  <text>{{index}} - {{item.name}}</text>

  <text class="price">¥{{item.price}}</text>

</view>



<!-- 自定义变量名:提升可读性 -->

<view wx:for="{{goodsList}}" wx:for-item="goods" wx:for-index="rank" wx:key="id">

  <text>第{{rank + 1}}名:{{goods.name}}</text>

</view>

`wx:key` 的两种赋值方式:

|------------------|---------------------|---------------------|
| 方式 | 示例 | 适用场景 |
| 直接指定属性名 | `wx:key="id"` | 数据中已有唯一标识字段,如数据库主键 |
| 保留关键字 `*this` | `wx:key="*this"` | 数组元素是简单字符串或数字,本身即唯一 |

常见错误与后果:

不写 `wx:key` 时,控制台会输出警告。如果列表项中包含输入框等有状态组件,当数组顺序变化时,输入内容会错位到其他项上------因为框架无法识别哪个组件对应哪条数据。

javascript 复制代码
Page({

  data: {

    goodsList: [

      { id: 'g001', name: '无线蓝牙耳机', price: 199, cover: '...' },

      { id: 'g002', name: '便携充电宝', price: 89, cover: '...' },

      { id: 'g003', name: '机械键盘', price: 349, cover: '...' }

    ]

  }

})

`wx:key` 相当于给列表里每个人发了唯一的工牌。领导(框架)要调整队伍顺序时,看着工牌就知道谁是谁,只需要交换站位。如果没有工牌,领导只能认站位------第一排的人换到第二排后,领导还以为第一排还是原来那个人。

有输入框的列表尤其明显:你在第一行填了"张三",排序后"张三"留在了第一行,变成了别人的名字。

3.1.4 块级元素 `block` 的使用

`block` 是一个虚拟的包装元素,它本身不会在页面上渲染任何实际节点,仅用于组合一组子元素,使它们共享同一个逻辑指令(如 `wx:if` 或 `wx:for`)。

XML 复制代码
<!-- 需要同时控制多个元素显示/隐藏,但不想多套一层无意义的 view -->

<block wx:if="{{isLoggedIn}}">

  <view class="user-avatar">头像区域</view>

  <view class="user-menu">菜单区域</view>

  <view class="user-stats">数据统计</view>

</block>



<!-- 列表渲染中避免多余嵌套 -->

<block wx:for="{{items}}" wx:key="id">

  <view class="item-title">{{item.title}}</view>

  <view class="item-desc">{{item.desc}}</view>

</block>

`block` vs `view` 的选择:

|-------------------|-----------|------------------------|
| 场景 | 推荐元素 | 原因 |
| 纯逻辑分组,不需要样式 | `block` | 零渲染开销,不增加 DOM 层级 |
| 需要设置背景色、间距等样式 | `view` | `block` 不渲染实体,无法承载样式 |
| 作为 `flex` 布局的容器 | `view` | 需要实际节点参与布局计算 |

`block` 就像开会时用来夹文件的回形针。回形针把几页纸归在一起方便统一处理(一起传给下一个人,或者一起扔掉),但它本身不是文件内容,翻到这一页时你看不见回形针。而 `view` 则是文件夹,文件夹本身占据体积,但你可以给文件夹贴标签、盖印章(样式)。

3.2 WXML 高级语法

当页面逻辑复杂起来后,重复的结构片段和跨页面的代码复用就成了必须解决的问题。

3.2.1 模板 `template` 定义与引用

`template` 是一段可复用的 WXML 片段,通过 `name` 属性命名,通过 `is` 属性引入。

它相当于"代码片段",本身不拥有独立的逻辑,所需数据由调用方通过 `data` 属性传入。

XML 复制代码
<!-- 通常在单独的 .wxml 文件中定义,如 templates/product-card.wxml -->

<template name="productCard">

  <!-- template 内部的数据完全由外部传入的 data 对象决定 -->

  <view class="card">

    <image src="{{data.cover}}" mode="aspectFill"></image>

    <view class="info">

      <text class="title">{{data.name}}</text>

      <text class="price">¥{{data.price}}</text>

    </view>

  </view>

</template>

使用模板:

XML 复制代码
<!-- 某业务页面中 -->

<import src="../../templates/product-card.wxml" />



<view class="product-list">

  <!-- 循环中使用模板,将每项数据包装为 data 对象传入 -->

  <template is="productCard" wx:for="{{products}}" wx:key="id" data="{{data: item}}" />

</view>

`template` 像是印刷好的表格模板。你在一个地方设计好"姓名___ 年龄___ 电话___"的格式,然后填表的人各自把自己的信息(`data`)填进去。表格本身不会自动知道填什么,每张表都需要发的人主动提供数据。它纯粹是格式复用,没有自己的"思想"。

3.2.2 文件引用:`import` vs `include` 区别与注意事项

`import` 和 `include` 都能在当前 WXML 中引入外部文件,但作用域和引用深度有本质区别。

`import` 引入的是模板定义,且不传递引用链;

`include` 则是全文替换,将目标文件的所有内容原样复制到当前位置。

|------|----------------------------------------|----------------------------------|
| 对比维度 | `import` | `include` |
| 引入内容 | 仅引入 `<template>` 定义 | 引入整个文件所有内容(除 `<template>` 外) |
| 引用链 | 不传递:A import B,B import C,A 无法使用 C 的模板 | 不涉及,直接文本替换 |
| 适用场景 | 按需引入特定模板,配合 `is` 使用 | 复用页面的公共结构片段,如页头、页脚 |
| 作用范围 | 仅对当前文件可见 | 内容成为当前文件的一部分 |

XML 复制代码
<!-- import 示例:只引入模板定义 -->

<import src="../../templates/product-card.wxml" />

<template is="productCard" data="{{...}}" />



<!-- include 示例:引入完整结构片段 -->

<include src="../../partials/header.wxml" />

<!-- header.wxml 的内容会直接替换这行代码 -->

`import` 不传递引用的坑:

如果 `a.wxml` import 了 `b.wxml`,`b.wxml` 又 import 了 `c.wxml`,在 `a.wxml` 中无法使用 `c.wxml` 中定义的模板。

解决方案是在 `a.wxml` 中显式 import `c.wxml`,或改用 `include` 全量引入。

3.2.3 2025 新增语法:`wx:elif` 链式条件、`wx:for` 嵌套优化

从基础库 3.0+ 开始,WXML 引入了多项语法优化,让条件判断和列表渲染的写法更简洁、性能更好。

`wx:elif` 链式条件判断:

在旧版本中,多分支条件只能嵌套书写,导致层级深、可读性差:

XML 复制代码
<!-- 旧写法:多层嵌套,缩进地狱 -->

<view wx:if="{{type === 'A'}}">类型A</view>

<view wx:elif="{{type === 'B'}}">类型B</view>

<view wx:elif="{{type === 'C'}}">类型C</view>

<view wx:else>其他类型</view>

`wx:elif` 让多分支判断和 `if...else if...else` 一样自然,所有判断在同一层级展开:

XML 复制代码
<!-- 新写法:扁平化条件链,逻辑一目了然 -->

<view wx:if="{{score >= 90}}">优秀</view>

<view wx:elif="{{score >= 70}}">良好</view>

<view wx:elif="{{score >= 60}}">及格</view>

<view wx:else>需要努力</view>

`wx:for` 嵌套优化:

当一个 `wx:for` 内部还嵌套另一个 `wx:for` 时,内层循环默认可以访问外层的 `item` 和 `index`,容易造成变量名冲突。新版本允许通过编译优化自动检测嵌套变量遮蔽问题,并给出明确警告。

最佳实践是始终使用 `wx:for-item` 和 `wx:for-index` 显式命名,避免任何歧义:

XML 复制代码
<view wx:for="{{categories}}" wx:for-item="category" wx:key="id">

  <text>{{category.name}}</text>

  <!-- 内层循环显式命名,绝不依赖默认的 item/index -->

  <view wx:for="{{category.products}}" wx:for-item="product" wx:key="id">

    <text>{{product.name}}</text>

  </view>

</view>

3.3 WXSS 样式体系

WXSS 在 CSS 基础上做了扩展和约束。理解它的尺寸单位和选择器支持情况,是写出精准样式的关键。

3.3.1 尺寸单位 `rpx` 原理与多端适配方案

`rpx`(responsive pixel,响应式像素)是小程序独有的尺寸单位,它根据屏幕物理宽度动态计算实际像素值。规定所有屏幕宽度统一为 750rpx,框架在渲染时自动将其换算为设备对应的实际像素。

换算公式:

`实际 px = (rpx 值 / 750) × 屏幕物理像素宽度`

|-------------------|-------|------------|--------------|
| 设备 | 屏幕宽度 | 1rpx 对应 px | 375rpx 对应 px |
| iPhone SE | 375px | 0.5px | 187.5px(半屏) |
| iPhone 14 Pro | 393px | 0.524px | 196.5px |
| iPhone 14 Pro Max | 430px | 0.573px | 215px |

设计稿适配规范:

设计师通常以 iPhone 6(375px 物理宽度)为基准,此时 1px 设计稿 = 2rpx。若设计稿宽度为 375px,只需将标注值乘以 2 即得 rpx 值。若设计稿为 750px,则标注值直接等于 rpx 值。

css 复制代码
/* 设计稿标注 20px → 代码中写 40rpx */

.container {

  padding: 40rpx;       /* 相当于 20px 在 375 基准下的比例 */

  font-size: 28rpx;     /* 最小字号建议不低于 24rpx,否则部分手机看不清 */

  width: 690rpx;        /* 750 - 30*2,左右各留 30rpx 安全边距 */

}

把 `rpx` 想象成将手机屏幕横向切成 750 份等分。无论你拿的是 4 英寸的小屏还是 6.7 英寸的大屏,这个单位始终说"我要占 375 份"(即一半屏幕)。它天然解决了"同一个尺寸在不同屏幕上看起来不一样大"的问题。而传统 `px` 则是"我要 375 个物理点",在小屏上可能占满整个宽度,在大屏上反而只占一小半。

3.3.2 全局样式与局部样式优先级

WXSS 的样式作用域遵循"全局基础,局部覆盖"原则。`app.wxss` 中的样式作用于所有页面,页面级 `.wxss` 中定义的同名规则会覆盖全局样式。页面之间的样式默认是隔离的。

优先级排序(从低到高):

app.wxss 全局样式 < 页面 .wxss 样式 < 内联 style 样式

样式隔离的例外:

自定义组件默认启用样式隔离,其内部样式不会影响页面,页面样式也不会穿透组件。需要修改此行为时,在组件 JS 中配置 `styleIsolation` 选项:

javascript 复制代码
Component({

  options: {

    styleIsolation: 'shared'  /* 允许页面样式影响组件内部 */

  }

})

3.3.3 支持的选择器与不支持的选择器

WXSS 支持 CSS 选择器的一个子集,部分 Web 中常用的选择器在小程序中不可用。了解边界能避免调试时才发现选择器不生效的挫败感。

|--------------------------|--------------------------------------|--------|-----------------------------------|
| 选择器 | 示例 | 支持情况 | 说明 |
| 类选择器 | `.card` | ✅ 支持 | 最常用,推荐优先使用 |
| ID 选择器 | `header` | ✅ 支持 | 可用但不推荐滥用,ID 应留给逻辑层 |
| 标签选择器 | `view`、`text` | ✅ 支持 | 全局重置时常用 |
| 属性选择器 | `type="primary"` | ✅ 支持 | 可用于选择特定属性的组件 |
| 后代选择器 | `.card .title` | ✅ 支持 | 用空格分隔,选择所有匹配的后代 |
| 子元素选择器 | `.card > .title` | ✅ 支持 | 仅选择直接子元素 |
| 伪类选择器 | `:first-child`、`:last-child` | ✅ 部分支持 | 常用伪类基本可用 |
| `:before` / `:after` | ------ | ✅ 支持 | 可用于装饰性伪元素 |
| 属性通配选择器 | `class\^="btn-"` | 不支持 | 无法使用 `^=`、`$=`、`*=` 等通配匹配 |
| 同级兄弟选择器 | `.item + .item`、`.item ~ .item` | 不支持 | 无法选择相邻或后续兄弟元素 |

实践建议:

构建样式体系时,优先采用类选择器 + BEM 命名规范(如 `.card__title--active`),既能保证可维护性,又能规避选择器兼容性问题。

3.3.4 样式导入 `@import`

`@import` 语句用于在一个 WXSS 文件中导入另一个 WXSS 文件的内容,实现样式代码的模块化拆分。

css 复制代码
/* pages/order/detail.wxss */

@import "../../styles/common.wxss";        /* 导入公共样式库 */

@import "../../styles/components/button.wxss"; /* 导入特定组件样式 */



.order-detail {

  padding: 30rpx;

}

与 CSS `@import` 的差异:

小程序中的 `@import` 会在编译阶段将文件内容合并,不会产生额外的网络请求。

因此不必担心 Web 开发中"`@import` 影响加载速度"的问题。这与 Web 端的运行时加载有本质区别。

组织建议:

将设计令牌(颜色、字号、间距变量)和工具类(`.flex-center`、`.text-ellipsis`)抽离到独立文件,通过 `@import` 在每个页面按需引入,避免把所有内容堆在 `app.wxss` 中导致全局污染。

3.3.5 内联样式与动态样式绑定

WXML 支持直接在元素上写 `style` 属性,接收一个由 JS 数据构成的样式对象,实现样式的动态控制。这种方式适用于样式值在编译时无法确定的场景。

XML 复制代码
<!-- 静态内联样式:直接写死值 -->

<view style="margin-top: 20rpx; color: 333;">静态样式</view>



<!-- 动态样式绑定:style 值来自 data 中的对象 -->

<view style="{{dynamicStyle}}">动态样式1</view>



<!-- 组合写法:部分固定 + 部分动态 -->

<view style="padding: 20rpx; {{progressStyle}}">动态进度条</view>
javascript 复制代码
Page({

  data: {

    /* 对象中的属性名使用驼峰命名,框架会自动转为短横线 */

    dynamicStyle: 'background-color: 07c160; border-radius: 8rpx;',

    progressStyle: ''

  },



  onLoad() {

    const percent = 75

    this.setData({

      /* 根据数据计算出进度条宽度,动态拼成样式字符串 */

      progressStyle: `width: ${percent}%; height: 10rpx; background: 07c160;`

    })

  }

})

`style` 与 `class` 的职责分工:

  • `class` 负责预设的、静态的样式规则,写在 WXSS 中,性能更好。

  • `style` 负责运行时动态变化的样式值(如进度条宽度、拖动位置、动画参数),直接写在元素上。

`class` 是服装店里的固定尺码------S、M、L,适合大多数人。`style` 是裁缝现场量体裁衣------胸围精确到厘米、袖长按手臂实测。固定尺码效率高,现成就能穿;量体裁衣慢一点,但能应对特殊身材。大多数场景用固定尺码足够,只有数据驱动的动态效果才需要请裁缝。

3.4 布局技术

小程序的界面布局,经历了从 Flex 一家独大,到 Flex 与 Grid 各司其职的演变。

3.4.1 Flex 弹性布局:小程序首选布局方案

Flex 布局(Flexible Box Layout,弹性盒子布局)是一种一维布局模型,通过将容器内的子元素沿主轴或交叉轴进行排列、对齐和分配空间,轻松实现居中、等分、两端对齐等常见布局需求。

css 复制代码
/* 高频 Flex 布局模式 */



/* 1. 水平居中 + 垂直居中(页面中最常用的组合) */

.center-box {

  display: flex;

  justify-content: center;  /* 主轴(默认水平)居中 */

  align-items: center;      /* 交叉轴(默认垂直)居中 */

}

/* 2. 两端对齐(导航栏、列表项的标准布局) */

.space-between {

  display: flex;

  justify-content: space-between;  /* 首尾贴边,中间均分 */

  align-items: center;

}



/* 3. 等分网格(如九宫格图标区) */

.grid-row {

  display: flex;

}

.grid-item {

  flex: 1;            /* 每个子元素均分剩余空间 */

  text-align: center;

}

核心属性速查:

|---------------------|------|-----------------------------------------------|-------------|
| 属性 | 作用对象 | 常用值 | 效果 |
| `justify-content` | 容器 | `center`/`space-between`/`space-around` | 控制主轴方向的对齐 |
| `align-items` | 容器 | `center`/`flex-start`/`stretch` | 控制交叉轴方向的对齐 |
| `flex-direction` | 容器 | `row`/`column` | 决定主轴是水平还是垂直 |
| `flex` | 子项 | 数字(如 `1`) | 分配剩余空间的比例 |
| `flex-wrap` | 容器 | `wrap` | 子项超出时是否换行 |

把 Flex 容器想象成一个桌面台球的开球框。

`justify-content` 决定了球在框里是挤在左边、右边还是均匀散开;

`align-items` 决定了球在垂直方向是贴桌面、悬浮还是贴天花板;

`flex-direction` 决定了框是横着放(`row`)还是竖着放(`column`)。

`flex: 1` 的子元素则是"弹性球",会自动膨胀填满框里剩下的所有空隙。

3.4.2 Grid 网格布局

Grid 布局是一种二维布局模型,同时控制行和列,适合构建复杂的页面骨架、仪表盘、商品橱窗等多行多列且对齐要求高的场景。基础库 3.0+ 全面支持 Grid,所有主流微信版本均可使用。

css 复制代码
/* Grid 布局典型用法:响应式商品网格 */

.product-grid {

  display: grid;

  grid-template-columns: repeat(2, 1fr);  /* 2 列等宽 */

  gap: 20rpx;                              /* 行列间距统一为 20rpx */

  padding: 20rpx;

}



/* 不规则布局:头图横跨两列 */

.featured {

  grid-column: span 2;  /* 该元素占满两列 */

}

Flex 与 Grid 的选择边界:

|---------------|----------------------|---------------------------------|
| 场景 | 推荐方案 | 理由 |
| 单行排列(导航栏、工具栏) | Flex | 一维排列最简洁 |
| 列表项 + 内容自适应 | Flex | `flex: 1` 处理弹性空间更自然 |
| 规则的多行多列网格 | Grid | 行列对齐代码更少,`gap` 统一间距 |
| 行与列都有明确对齐要求 | Grid | `grid-template-rows` 精确控制每行高度 |
| 不确定列数、需自动换行 | Flex + `flex-wrap` | Grid 需要定义列模板 |

3.4.3 响应式布局与多设备适配

小程序跑在手机上,屏幕宽度差异相对 Web 小得多,但折叠屏设备和平板微信的普及,让响应式适配变得有价值。

适配策略:

css 复制代码
/* 1. 利用 rpx 作为基础单位,覆盖 90% 的适配需求 */

.container {

  width: 690rpx;

  padding: 30rpx;

}



/* 2. 通过媒体查询应对极端尺寸(折叠屏展开、平板) */

@media (min-width: 600px) {

  /* 屏幕宽度超过 600px 时,采用多列布局 */

  .product-grid {

    grid-template-columns: repeat(3, 1fr);

  }

}



@media (min-width: 900px) {

  .product-grid {

    grid-template-columns: repeat(4, 1fr);

    max-width: 1200rpx;

    margin: 0 auto;  /* 超宽屏居中显示 */

  }

}



/* 3. 使用 CSS 函数动态计算 */

.sidebar {

  width: min(300rpx, 30vw);  /* 取较小值,避免侧边栏过宽 */

}

3.4.4 安全区域适配:`safe-area-inset-*`

全面屏手机(特别是 iPhone X 及后续机型)的圆角、刘海和底部横条(Home Indicator)会遮挡页面内容。CSS 环境变量 `safe-area-inset-*` 提供了这些危险区域的准确尺寸,让开发者能将关键内容放入"安全区"内。

css 复制代码
/* 底部安全区适配:为固定底部的按钮栏留出空间 */

.bottom-bar {

  position: fixed;

  bottom: 0;

  left: 0;

  right: 0;

  /* 底部 padding = 基础值 20rpx + 系统安全区高度 */

  padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));  /* iOS 11.0-11.2 兼容 */

  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));       /* 标准写法 */

  background: ffffff;

  box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);

}



/* 顶部安全区适配:自定义导航栏时使用 */

.custom-nav {

  padding-top: constant(safe-area-inset-top);

  padding-top: env(safe-area-inset-top);

  height: 88rpx;  /* 导航栏内容高度 */

}

四个安全区变量:

|----------------------------|----------|--------------------|
| 变量 | 含义 | 典型值(iPhone 14 Pro) |
| `safe-area-inset-top` | 顶部危险区高度 | 59px(包含状态栏和灵动岛) |
| `safe-area-inset-bottom` | 底部横条区域高度 | 34px |
| `safe-area-inset-left` | 左侧安全间距 | 0px(竖屏时通常为 0) |
| `safe-area-inset-right` | 右侧安全间距 | 0px(竖屏时通常为 0) |

需要适配的典型场景:

底部固定按钮(提交订单、加入购物车)、全屏自定义导航栏、沉浸式视频播放页、启动页全屏背景图。

安全区适配就像设计海报时避开折叠线和出血线。全面屏手机的刘海和底部横条就是"折叠线",如果把关键文字或按钮放在这些位置,会被硬件遮挡或误触。

`safe-area-inset` 就是印刷厂告诉你的"这里不能印字"的区域尺寸,提前预留出来就能保证所有内容清晰可见、点击顺畅。