ionic 教程
Ionic 教程
Ionic 是一个开源移动端 UI 框架,让开发者用 Web 技术(HTML/CSS/JS)构建跨平台移动应用,一套代码可以打包成 iOS、Android 应用,也能运行在 Web 上。
本教程内容
本教程从 Ionic 的安装与项目创建讲起,系统覆盖常用组件:头部底部、按钮、列表、卡片、表单控件(toggle/checkbox/单选框/select/range)、选项卡、网格布局、图标、上拉菜单、下拉刷新、模态窗口、导航、侧栏菜单、对话框、滚动条等。
Ionic 的核心
- 跨平台:iOS / Android / Web 一套代码
- 原生体验:精心设计的移动端 UI 组件
- Web 技术栈:HTML/CSS/JS,上手门槛低
- 框架集成:支持 Angular、React、Vue
- Capacitor:调用原生设备能力(相机/定位等)
版本说明
Ionic 历经多版本迭代:1.x(基于 AngularJS)、3.x、4.x+(框架无关)。本教程以现代 Ionic(Angular 版本)为例,组件概念各版本通用。
学习建议
建议掌握 HTML/CSS/JS 基础与任一前端框架(Angular/React/Vue)。跟着教程动手创建项目、实践每个组件,快速上手移动端开发。
ionic 安装
Ionic 安装
安装 Ionic 开发环境:需要 Node.js 与 Ionic CLI,即可创建、运行和构建跨平台应用。
环境要求
# 1. Node.js 18+(LTS 推荐)
node --version
npm --version
# 2. 可选: Git
# 3. 移动端调试: Android Studio / Xcode(打包时)
安装 Ionic CLI
# 全局安装
npm install -g @ionic/cli
# 验证
ionic --version
ionic --help
# 国内加速(可选)
npm config set registry https://registry.npmmirror.com
创建新项目
# 交互式创建
ionic start myApp
# 指定模板与框架
ionic start myApp blank --type=angular
ionic start myApp tabs --type=angular
ionic start myApp sidemenu --type=angular
# 常用模板:
# blank(空白) / tabs(选项卡)
# sidemenu(侧栏菜单) / super(综合)
# 框架选项:
# --type=angular / react / vue
运行项目
# 进入项目
cd myApp
# Web 浏览器运行(开发调试)
ionic serve
# 启动后浏览器打开 localhost:8100
# 支持热更新,改代码即时生效
项目结构
myApp/
├── src/
│ ├── app/ # 应用根组件与路由
│ ├── pages/ # 页面组件
│ ├── components/ # 自定义组件
│ ├── services/ # 服务(数据/业务)
│ ├── theme/ # 全局样式
│ └── index.html
├── capacitor.config.ts # 原生构建配置
├── package.json
└── angular.json
常用 CLI 命令
# 生成页面
ionic generate page login
# 生成组件/服务
ionic generate component my-comp
ionic generate service api
# 构建
ionic build # Web 构建
ionic build --prod # 生产构建
# 添加平台
ionic capacitor add android
ionic capacitor add ios
# 打开原生项目
ionic capacitor open android
常见问题
# 1. 安装慢: 换国内镜像
# 2. 权限错误: sudo 或修复 npm 目录权限
# 3. 版本冲突: 清缓存重装
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
# 4. 端口占用: ionic serve --port 8101
环境就绪后,就可以创建应用并开始使用 Ionic 组件了。
ionic 创建 APP
Ionic 创建 APP
本节手把手创建一个完整的 Ionic 应用:项目初始化、添加页面、配置导航,跑通"创建→运行"全流程。
1. 创建项目
# 创建空白 Angular 项目
ionic start myTodo blank --type=angular
cd myTodo
# 启动开发服务器
ionic serve
# 浏览器打开 http://localhost:8100
2. 创建页面
# 生成待办页面
ionic generate page todo
# 生成后的文件:
# src/pages/todo/
# ├── todo.page.html # 页面模板
# ├── todo.page.ts # 逻辑
# ├── todo.page.scss # 样式
# └── todo.page.spec.ts # 测试
# 路由会自动注册
# src/app/app.routes.ts
3. 编写页面
<!-- todo.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>待办清单</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item *ngFor="let item of todos">
<ion-checkbox [checked]="item.done"></ion-checkbox>
<ion-label>{{ item.text }}</ion-label>
</ion-item>
</ion-list>
<ion-item>
<ion-input [(ngModel)]="newTodo"
placeholder="输入新待办"></ion-input>
<ion-button (click)="addTodo()">添加</ion-button>
</ion-item>
</ion-content>
// todo.page.ts
import { Component } from "@angular/core";
@Component({
selector: "app-todo",
templateUrl: "./todo.page.html",
})
export class TodoPage {
todos: { text: string; done: boolean }[] = [];
newTodo = "";
addTodo() {
const text = this.newTodo.trim();
if (text) {
this.todos.push({ text, done: false });
this.newTodo = "";
}
}
}
4. 配置首页跳转
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [
{ path: "", redirectTo: "todo", pathMatch: "full" },
{ path: "todo", loadChildren: () =>
import("./pages/todo/todo.page").then(
m => m.TodoPageModule) },
];
5. 运行与调试
# 浏览器
ionic serve
# 真机预览(Ionic DevApp 或浏览器设备模式)
# F12 → 设备工具栏,模拟手机
# 构建原生包(需 Capacitor + 平台工具)
ionic build
ionic capacitor add android
ionic capacitor run android
开发小技巧
# 热更新: 保存即刷新,无需手动
# 控制台: 浏览器 F12 查看错误
# 模拟数据: 先用假数据开发 UI
# 组件库: 随时查官方文档
# https://ionicframework.com/docs/components
# 常见错误:
# - 路由未配置 → 404
# - 组件未导入 → 模板报错
# - 服务未提供 → DI 错误
至此你已创建并运行了第一个 Ionic 应用。接下来逐个学习组件,丰富你的应用界面。
ionic 头部与底部
Ionic 头部与底部
头部(Header)与底部(Footer)是移动页面最常见的布局区域:头部放标题与导航,底部放操作按钮或选项卡。
基础头部
<ion-header>
<ion-toolbar>
<ion-title>页面标题</ion-title>
</ion-toolbar>
</ion-header>
头部常用内容
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
<ion-button>菜单</ion-button>
</ion-buttons>
<ion-title>我的页面</ion-title>
<ion-buttons slot="end">
<ion-button>
<ion-icon name="search"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
<!-- 第二行工具栏(搜索栏等) -->
<ion-toolbar>
<ion-searchbar></ion-searchbar>
</ion-toolbar>
</ion-header>
# slot 位置:
# start: 左侧 / end: 右侧
# 标题自动居中
底部工具栏
<ion-footer>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button>取消</ion-button>
</ion-buttons>
<ion-buttons slot="end">
<ion-button color="primary">保存</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>
样式定制
// 全局主题变量(src/theme/variables.scss)
:root {
--ion-color-primary: #3880ff;
--ion-toolbar-background: #3880ff;
--ion-toolbar-color: #ffffff;
}
// 或单独页面覆盖(页面 scss)
ion-toolbar {
--background: #f5f5f5;
--color: #333;
}
其他工具栏特性
# 1. 透明工具栏
<ion-toolbar translucent>...</ion-toolbar>
# 2. 多彩标题
<ion-title color="danger">红色标题</ion-title>
# 3. 分组按钮
<ion-buttons>
<ion-button>A</ion-button>
<ion-button>B</ion-button>
</ion-buttons>
# 4. 无障碍/固定标题
<ion-title size="large">大标题</ion-title>
完整页面骨架
<ion-header>
<ion-toolbar>
<ion-title>首页</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<!-- 页面主体内容 -->
<p>这里是内容区域</p>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-title size="small">© 2026 版权信息</ion-title>
</ion-toolbar>
</ion-footer>
头部底部是页面的"骨架"。掌握工具栏与 slot 布局,页面结构一目了然。
ionic 按钮
Ionic 按钮
按钮是用户交互的核心元素。Ionic 提供丰富的按钮样式:颜色、形状、尺寸、加载状态等。
基础按钮
<!-- 默认实心按钮 -->
<ion-button>默认按钮</ion-button>
<!-- 空心(outline) -->
<ion-button fill="outline">描边按钮</ion-button>
<!-- 透明(clear) -->
<ion-button fill="clear">文字按钮</ion-button>
<!-- 链接样式按钮 -->
<ion-button href="/home">跳转链接</ion-button>
颜色
<!-- Ionic 预定义颜色 -->
<ion-button color="primary">主色</ion-button>
<ion-button color="secondary">次色</ion-button>
<ion-button color="success">成功</ion-button>
<ion-button color="warning">警告</ion-button>
<ion-button color="danger">危险</ion-button>
<ion-button color="dark">深色</ion-button>
<ion-button color="light">浅色</ion-button>
<ion-button color="medium">中灰</ion-button>
形状与尺寸
<!-- 圆角 -->
<ion-button shape="round">圆角按钮</ion-button>
<!-- 尺寸 -->
<ion-button size="small">小</ion-button>
<ion-button size="default">默认</ion-button>
<ion-button size="large">大</ion-button>
<!-- 全宽 -->
<ion-button expand="full">全宽按钮</ion-button>
<ion-button expand="block">块级按钮</ion-button>
图标按钮
<!-- 仅图标 -->
<ion-button>
<ion-icon name="star"></ion-icon>
</ion-button>
<!-- 图标+文字 -->
<ion-button>
<ion-icon name="trash" slot="start"></ion-icon>
删除
</ion-button>
<!-- 常用图标名: -->
<!-- add / search / home / settings -->
<!-- camera / trash / heart / person -->
<!-- send / download / share -->
事件处理
<!-- Angular 事件绑定 -->
<ion-button (click)="onClick()">点击</ion-button>
<ion-button (ionFocus)="onFocus()">聚焦</ion-button>
<ion-button (ionBlur)="onBlur()">失焦</ion-button>
// TS
onClick() {
console.log("按钮被点击");
}
加载与禁用状态
<!-- 禁用 -->
<ion-button disabled>不可用</ion-button>
<ion-button [disabled]="!canSave">保存</ion-button>
<!-- 加载状态(Angular) -->
<ion-button [disabled]="loading">
<ion-spinner name="crescent" *ngIf="loading">
</ion-spinner>
<span *ngIf="!loading">提交</span>
<span *ngIf="loading">提交中...</span>
</ion-button>
自定义样式
/* 页面 scss */
.my-button {
--background: linear-gradient(135deg,
#667eea, #764ba2); /* 渐变背景 */
--border-radius: 12px; /* 圆角 */
--box-shadow: 0 4px 12px rgba(0,0,0,0.2);
--color: white; /* 文字颜色 */
font-weight: bold;
}
<ion-button class="my-button">渐变按钮</ion-button>
/* 常用 CSS 变量:
--background / --color / --border-radius
--box-shadow / --padding-start/end
--height / --font-size */
按钮组
<!-- 并排按钮 -->
<ion-buttons>
<ion-button fill="outline">取消</ion-button>
<ion-button>确认</ion-button>
</ion-buttons>
<!-- 水平排列(flex) -->
<div style="display:flex; gap:8px;">
<ion-button expand="block" fill="outline">
次要
</ion-button>
<ion-button expand="block">主要</ion-button>
</div>
按钮是界面交互的主力。掌握颜色/形状/图标/状态组合,配合事件绑定,就能做出直观的操作入口。
ionic 列表
Ionic 列表
列表是移动应用最常见的组件,用于展示数据集合:设置项、消息、商品等。Ionic 的 ion-list 与 ion-item 高度可定制。
基础列表
<ion-list>
<ion-item>
<ion-label>项目一</ion-label>
</ion-item>
<ion-item>
<ion-label>项目二</ion-label>
</ion-item>
<ion-item>
<ion-label>项目三</ion-label>
</ion-item>
</ion-list>
动态数据列表(Angular)
<ion-list>
<ion-item *ngFor="let user of users">
<ion-avatar slot="start">
<img [src]="user.avatar">
</ion-avatar>
<ion-label>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</ion-label>
</ion-item>
</ion-list>
// TS
users = [
{ name: "张三", email: "z@e.com",
avatar: "..." },
...
];
列表项布局(slot)
<ion-item>
<ion-icon name="star" slot="start"></ion-icon>
<ion-label>文字标签</ion-label>
<ion-icon name="chevron-forward"
slot="end"></ion-icon>
</ion-item>
<!-- slot 位置: -->
<!-- start(左侧) / end(右侧) -->
<!-- 可放: 图标/头像/缩略图/按钮 -->
<!-- 缩略图 -->
<ion-thumbnail slot="start">
<img src="...">
</ion-thumbnail>
可点击与滑动项
<!-- 可点击 -->
<ion-item button (click)="open(user)">
<ion-label>{{ user.name }}</ion-label>
</ion-item>
<!-- 滑动操作(删除/收藏) -->
<ion-item-sliding>
<ion-item>
<ion-label>滑动我</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger"
(click)="delete(item)">
<ion-icon name="trash" slot="start"></ion-icon>
删除
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
分组与分隔
<!-- 分组标题 -->
<ion-list>
<ion-list-header>设置</ion-list-header>
<ion-item>账号</ion-item>
<ion-item>隐私</ion-item>
</ion-list>
<!-- 插入线控制 -->
<ion-list lines="full"> <!-- full/inset/none -->
<ion-item>带完整分割线</ion-item>
</ion-list>
<!-- 单项控制 -->
<ion-item lines="none">无分割线</ion-item>
虚拟滚动(大数据列表)
<!-- 上万条数据用虚拟滚动 -->
<ion-virtual-scroll [items]="items">
<ion-item *virtualItem="let item">
<ion-label>{{ item.name }}</ion-label>
</ion-item>
</ion-virtual-scroll>
# 或 Angular 的 CDK virtual scroll
# 大数据量时性能关键,避免渲染全部 DOM
列表中的表单控件
<ion-item>
<ion-label position="stacked">用户名</ion-label>
<ion-input [(ngModel)]="name"></ion-input>
</ion-item>
<ion-item>
<ion-label>接收通知</ion-label>
<ion-toggle [(ngModel)]="notify"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>公开资料</ion-label>
<ion-checkbox [(ngModel)]="public"></ion-checkbox>
</ion-item>
列表是数据展示的骨架。掌握 item 布局、滑动操作与分组,配合 *ngFor 渲染数据,列表页轻松搞定。
ionic 卡片
Ionic 卡片
卡片(Card)用于展示独立的信息块:文章、商品、用户资料等。卡片自带圆角、阴影与内边距,视觉聚焦。
基础卡片
<ion-card>
<ion-card-header>
<ion-card-title>卡片标题</ion-card-title>
<ion-card-subtitle>副标题</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
这里是卡片的主要内容文字。
</ion-card-content>
</ion-card>
带图片的卡片(文章示例)
<ion-card>
<img src="https://example.com/cover.jpg"
alt="封面">
<ion-card-header>
<ion-card-title>Python 入门指南</ion-card-title>
<ion-card-subtitle>编程教程 · 5 分钟阅读</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
从零开始学习 Python 的完整教程...
</ion-card-content>
<ion-button fill="clear">阅读全文
<ion-icon name="arrow-forward"
slot="end"></ion-icon>
</ion-button>
</ion-card>
动态卡片列表
<!-- 商品列表 -->
<ion-card *ngFor="let product of products">
<img [src]="product.image">
<ion-card-header>
<ion-card-title>{{ product.name }}</ion-card-title>
<ion-card-subtitle>¥{{ product.price }}</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-button expand="block" color="primary"
(click)="addToCart(product)">
加入购物车
</ion-button>
</ion-card-content>
</ion-card>
卡片内的操作
<ion-card>
<ion-item>
<ion-avatar slot="start">
<img src="avatar.png">
</ion-avatar>
<ion-label>
<h2>作者名</h2>
<p>发布时间</p>
</ion-label>
</ion-item>
<img src="content.jpg">
<ion-card-content>
分享的内容文字...
</ion-card-content>
<ion-row>
<ion-col>
<ion-button fill="clear">
<ion-icon name="heart"></ion-icon> 点赞
</ion-button>
</ion-col>
<ion-col>
<ion-button fill="clear">
<ion-icon name="chatbubble"></ion-icon> 评论
</ion-button>
</ion-col>
</ion-row>
</ion-card>
样式定制
/* 页面 scss */
ion-card {
--background: #ffffff;
--color: #333;
border-radius: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
margin: 16px;
}
/* 主题变量 */
:root {
--ion-card-background: #ffffff;
--ion-card-color: #333;
}
/* 暗色模式适配 */
@media (prefers-color-scheme: dark) {
ion-card {
--background: #1e1e1e;
--color: #eee;
}
}
布局技巧
<!-- 双列卡片(瀑布流风格) -->
<ion-grid>
<ion-row>
<ion-col size="6" *ngFor="let item of items">
<ion-card>...</ion-card>
</ion-col>
</ion-row>
</ion-grid>
<!-- 卡片可点击 -->
<ion-card (click)="openDetail(item)">...</ion-card>
<!-- 卡片按钮跳转 -->
<ion-card routerLink="/detail/{{ item.id }}">
...
</ion-card>
卡片让信息"自成一体"。配合网格布局与动态渲染,商品、文章、用户卡片信手拈来。
ionic 表单和输入框
Ionic 表单和输入框
表单是用户输入的门户。Ionic 提供完善的表单控件与样式:输入框、文本域、标签、校验反馈等。
基础输入框
<!-- 带浮动标签 -->
<ion-item>
<ion-label position="floating">邮箱</ion-label>
<ion-input type="email"
[(ngModel)]="email"></ion-input>
</ion-item>
<!-- 标签位置: -->
<!-- floating(浮动) / stacked(堆叠) -->
<!-- fixed(固定) / 默认(行内) -->
输入类型
<!-- 文本 -->
<ion-input type="text"></ion-input>
<!-- 密码(可加显示切换) -->
<ion-input type="password"></ion-input>
<!-- 数字 -->
<ion-input type="number"></ion-input>
<!-- 邮箱/电话/URL -->
<ion-input type="email"></ion-input>
<ion-input type="tel"></ion-input>
<!-- 日期/时间 -->
<ion-input type="date"></ion-input>
<ion-input type="time"></ion-input>
<!-- 搜索 -->
<ion-searchbar [(ngModel)]="keyword"
placeholder="搜索"></ion-searchbar>
<!-- 多行文本 -->
<ion-textarea rows="4"
placeholder="请输入内容">
</ion-textarea>
完整登录表单
<form [formGroup]="loginForm"
(ngSubmit)="onLogin()">
<ion-item>
<ion-label position="floating">邮箱</ion-label>
<ion-input type="email"
formControlName="email">
</ion-input>
</ion-item>
<ion-text color="danger"
*ngIf="loginForm.get('email')?.invalid
&& loginForm.get('email')?.touched">
请输入有效邮箱
</ion-text>
<ion-item>
<ion-label position="floating">密码</ion-label>
<ion-input type="password"
formControlName="password">
</ion-input>
</ion-item>
<ion-button expand="block" type="submit"
[disabled]="loginForm.invalid">
登录
</ion-button>
</form>
// TS: Angular 响应式表单
import { FormGroup, FormBuilder,
Validators } from "@angular/forms";
loginForm: FormGroup;
constructor(private fb: FormBuilder) {
this.loginForm = this.fb.group({
email: ["", [Validators.required,
Validators.email]],
password: ["", [Validators.required,
Validators.minLength(6)]],
});
}
onLogin() {
console.log(this.loginForm.value);
}
输入框辅助元素
<!-- 清除按钮 -->
<ion-input clearInput placeholder="可清除">
</ion-input>
<!-- 右侧图标 -->
<ion-item>
<ion-label position="stacked">密码</ion-label>
<ion-input [type]="showPwd ? 'text' : 'password'">
</ion-input>
<ion-button fill="clear" slot="end"
(click)="showPwd = !showPwd">
<ion-icon [name]="showPwd ? 'eye-off'
: 'eye'">
</ion-icon>
</ion-button>
</ion-item>
校验反馈样式
<!-- 无效状态 -->
<ion-item class="ion-invalid ion-touched">
<ion-label position="floating">必填项</ion-label>
<ion-input required></ion-input>
</ion-item>
<!-- 自定义错误消息 -->
<ion-note color="danger">此字段为必填</ion-note>
<!-- 成功提示 -->
<ion-note color="success">✓ 输入有效</ion-note>
/* 校验成功/失败时自动应用类 */
.ion-valid { border-color: green; }
.ion-invalid { border-color: red; }
表单最佳实践
# 1. 用 Angular 响应式表单(可测试性强)
# 2. 校验规则声明式配置(Validators)
# 3. 错误消息及时反馈(blur 后显示)
# 4. 提交按钮禁用直到表单有效
# 5. 键盘适配: 输入框多时滚动
# 6. 数字输入注意键盘类型
# 键盘类型(移动端):
<ion-input type="tel" inputmode="numeric">
# 避免自动大写:
<ion-input autocomplete="off"
autocapitalize="none">
表单是应用的"数据入口"。掌握输入框类型、标签布局与响应式校验,登录、注册、设置等表单页不再是难题。
ionic toggle
Ionic Toggle
Toggle(开关)是二选一控件:开/关、启用/禁用。常见于设置页面,比复选框更直观。
基础用法
<!-- 简单开关 -->
<ion-toggle></ion-toggle>
<!-- 默认状态 -->
<ion-toggle checked></ion-toggle>
<ion-toggle [checked]="isEnabled"></ion-toggle>
<!-- 禁用 -->
<ion-toggle disabled></ion-toggle>
双向绑定(Angular)
<ion-toggle [(ngModel)]="wifiEnabled">
</ion-toggle>
<!-- 事件 -->
<ion-toggle [(ngModel)]="notify"
(ionChange)="onNotifyChange($event)">
</ion-toggle>
// TS
wifiEnabled = true;
onNotifyChange(event: any) {
console.log("通知开关:", event.detail.checked);
}
设置页典型用法
<ion-list>
<ion-item>
<ion-label>Wi-Fi</ion-label>
<ion-toggle [(ngModel)]="wifi"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>蓝牙</ion-label>
<ion-toggle [(ngModel)]="bluetooth"
color="primary">
</ion-toggle>
</ion-item>
<ion-item>
<ion-label>夜间模式</ion-label>
<ion-toggle [(ngModel)]="darkMode"
color="dark">
</ion-toggle>
</ion-item>
<ion-item>
<ion-label>省电模式</ion-label>
<ion-toggle [(ngModel)]="batterySaver"
color="success"
[disabled]="!batteryOK">
</ion-toggle>
</ion-item>
</ion-list>
颜色与样式
<!-- 颜色 -->
<ion-toggle color="primary"></ion-toggle>
<ion-toggle color="success"></ion-toggle>
<ion-toggle color="danger"></ion-toggle>
<ion-toggle color="warning"></ion-toggle>
<!-- 自定义样式 -->
ion-toggle {
--handle-background: #ffffff;
--handle-background-checked: #ffffff;
--track-background: #ccc;
--track-background-checked: #3880ff;
}
<!-- 尺寸调整 -->
ion-toggle {
--handle-width: 24px;
--handle-height: 24px;
}
响应式表单中使用
<!-- 用 formControlName 而非 ngModel -->
<form [formGroup]="settingsForm">
<ion-item>
<ion-label>自动更新</ion-label>
<ion-toggle formControlName="autoUpdate">
</ion-toggle>
</ion-item>
</form>
// TS
settingsForm = this.fb.group({
autoUpdate: [true],
notifications: [false],
});
// 读取值
this.settingsForm.value.autoUpdate
自定义开关图标(内容插槽)
<!-- 开关内显示图标 -->
<ion-toggle>
<ion-icon slot="start" name="sunny"></ion-icon>
<ion-icon slot="end" name="moon"></ion-icon>
</ion-toggle>
<!-- 或用 enableOnOffLabels 显示文字 -->
<ion-toggle enableOnOffLabels></ion-toggle>
注意事项
# 1. 与 label 搭配使用,描述开关含义
# 2. ionChange 事件获取最新值
# 3. 切换关联状态(如省电模式影响其他项)
# 4. 持久化设置(本地存储)
// 保存设置
saveSettings() {
localStorage.setItem("settings",
JSON.stringify(this.settingsForm.value));
}
Toggle 是设置页的标配控件。配合双向绑定与颜色定制,交互直观、状态清晰。
ionic checkbox
Ionic Checkbox
Checkbox(复选框)用于多项选择:勾选/取消。适合"多项选择"与"协议确认"等场景。
基础用法
<!-- 单个复选框 -->
<ion-checkbox></ion-checkbox>
<!-- 默认勾选 -->
<ion-checkbox checked></ion-checkbox>
<ion-checkbox [checked]="agree"></ion-checkbox>
<!-- 禁用 -->
<ion-checkbox disabled></ion-checkbox>
双向绑定
<ion-checkbox [(ngModel)]="acceptTerms">
</ion-checkbox>
<!-- 事件 -->
<ion-checkbox (ionChange)="onChange($event)">
</ion-checkbox>
// TS
onChange(event: any) {
console.log("选中:", event.detail.checked);
}
列表中的复选框(多选场景)
<ion-list>
<ion-item *ngFor="let item of items">
<ion-checkbox [checked]="item.selected"
(ionChange)="toggleItem(item, $event)">
</ion-checkbox>
<ion-label>{{ item.name }}</ion-label>
</ion-item>
</ion-list>
// TS
items = [
{ name: "苹果", selected: false },
{ name: "香蕉", selected: true },
{ name: "橙子", selected: false },
];
toggleItem(item: any, event: any) {
item.selected = event.detail.checked;
}
getSelected() {
return this.items
.filter(i => i.selected)
.map(i => i.name);
}
复选框组(Angular 表单)
<!-- 响应式表单数组 -->
<form [formGroup]="form">
<ion-item *ngFor="let opt of options">
<ion-label>{{ opt.label }}</ion-label>
<ion-checkbox [formControlName]="opt.key">
</ion-checkbox>
</ion-item>
</form>
// TS
form = this.fb.group({
optionA: [false],
optionB: [true],
optionC: [false],
});
颜色与样式
<ion-checkbox color="primary"></ion-checkbox>
<ion-checkbox color="success"></ion-checkbox>
<ion-checkbox color="danger"></ion-checkbox>
/* 自定义 */
ion-checkbox {
--background: #fff;
--background-checked: #3880ff;
--checkmark-color: #fff;
--border-radius: 4px;
--size: 22px;
}
<!-- 圆形 -->
<ion-checkbox shape="circle"></ion-checkbox>
使用场景
# 1. 用户协议确认
<ion-item>
<ion-checkbox [(ngModel)]="agree"></ion-checkbox>
<ion-label>我已阅读并同意<b>服务条款</b></ion-label>
</ion-item>
<ion-button [disabled]="!agree" expand="block">
注册
</ion-button>
# 2. 批量选择
# 全选/取消全选
selectAll(checked: boolean) {
this.items.forEach(i => i.selected = checked);
}
# 3. 多选筛选(筛选标签/分类)
与 Toggle 的选择
# Checkbox: 多项选择(可勾多个)
# Toggle: 单一开关(开/关)
# 单选: 用 ion-radio-group
# 场景对照:
# 多项爱好选择 → checkbox
# 是否接收通知 → toggle
# 单选支付方式 → radio
# 注意: 列表场景 checkbox 常与
# ion-item + ion-label 搭配使用
Checkbox 负责"多选"。掌握双向绑定与列表组合,配合表单数组,多选功能信手拈来。
ionic 单选框
Ionic 单选框
单选框(Radio)用于互斥选择:多选一。常与列表结合,如支付方式、性别、选项设置。
基础用法
<!-- 单选组 -->
<ion-radio-group [(ngModel)]="selected">
<ion-item>
<ion-label>选项A</ion-label>
<ion-radio value="a"></ion-radio>
</ion-item>
<ion-item>
<ion-label>选项B</ion-label>
<ion-radio value="b"></ion-radio>
</ion-item>
</ion-radio-group>
默认值与事件
<!-- 默认选中 -->
<ion-radio-group [(ngModel)]="gender">
<ion-item>
<ion-label>男</ion-label>
<ion-radio value="male"></ion-radio>
</ion-item>
<ion-item>
<ion-label>女</ion-label>
<ion-radio value="female"></ion-radio>
</ion-item>
</ion-radio-group>
// TS
gender = "male";
<!-- 变更事件 -->
<ion-radio-group [(ngModel)]="gender"
(ionChange)="onGenderChange($event)">
onGenderChange(event: any) {
console.log("选择:", event.detail.value);
}
典型场景: 支付方式
<ion-radio-group [(ngModel)]="payMethod">
<ion-item>
<ion-radio value="alipay" slot="start">
</ion-radio>
<ion-label>
<h2>支付宝</h2>
<p>支持余额与花呗</p>
</ion-label>
</ion-item>
<ion-item>
<ion-radio value="wechat" slot="start">
</ion-radio>
<ion-label>
<h2>微信支付</h2>
<p>推荐使用</p>
</ion-label>
</ion-item>
</ion-radio-group>
// TS
payMethod = "alipay";
响应式表单
<form [formGroup]="form">
<ion-radio-group formControlName="plan">
<ion-item>
<ion-label>基础版</ion-label>
<ion-radio value="basic"></ion-radio>
</ion-item>
<ion-item>
<ion-label>专业版</ion-label>
<ion-radio value="pro"></ion-radio>
</ion-item>
</ion-radio-group>
</form>
// TS
form = this.fb.group({
plan: ["pro"],
});
颜色与样式
<!-- 颜色 -->
<ion-radio color="primary"></ion-radio>
<ion-radio color="success"></ion-radio>
/* 自定义 */
ion-radio {
--color: #888; /* 未选中 */
--color-checked: #3880ff; /* 选中 */
}
<!-- 禁用 -->
<ion-radio value="x" disabled></ion-radio>
<ion-radio-group disabled>...</ion-radio-group>
<!-- 内容放置 -->
<!-- slot="start" 圆圈在左 -->
<!-- 默认圆圈在右 -->
动态选项渲染
<!-- 从数据生成 -->
<ion-radio-group [(ngModel)]="choice">
<ion-item *ngFor="let opt of options">
<ion-label>{{ opt.label }}</ion-label>
<ion-radio [value]="opt.value"
[disabled]="opt.disabled">
</ion-radio>
</ion-item>
</ion-radio-group>
// TS
options = [
{ label: "红色", value: "red" },
{ label: "绿色", value: "green" },
{ label: "蓝色", value: "blue", disabled: true },
];
choice = "red";
注意事项
# 1. radio 必须放在 radio-group 内(互斥)
# 2. value 唯一,用于标识选项
# 3. 默认值要与某选项 value 匹配
# 4. 移动端点击区域要大(item 内)
# 与 checkbox 对比:
# radio: 多选一(互斥)
# checkbox: 多选多
# toggle: 单一开/关
Radio 负责"互斥单选"。掌握 radio-group 与列表组合,支付方式、选项设置等场景轻松实现。
ionic range
Ionic Range
Range(滑块)用于在区间内选择值:音量、亮度、价格区间等。支持单滑块与双滑块(区间)。
基础用法
<!-- 基本滑块 -->
<ion-range></ion-range>
<!-- 指定范围与步长 -->
<ion-range min="0" max="100" step="10">
</ion-range>
<!-- 默认值 -->
<ion-range [value]="50"></ion-range>
<!-- 禁用 -->
<ion-range disabled></ion-range>
双向绑定
<!-- 音量调节 -->
<ion-range [(ngModel)]="volume"
min="0" max="100">
</ion-range>
<p>音量: {{ volume }}</p>
<!-- 事件 -->
<ion-range (ionChange)="onVolumeChange($event)">
</ion-range>
// TS
volume = 70;
onVolumeChange(event: any) {
console.log("音量:", event.detail.value);
}
价格区间(双滑块)
<ion-range dualKnobs="true"
min="0" max="1000" step="50"
[(ngModel)]="priceRange">
</ion-range>
<p>价格区间:
¥{{ priceRange.lower }} - ¥{{ priceRange.upper }}
</p>
// TS
priceRange = { lower: 100, upper: 800 };
带图标的滑块
<!-- 音量 -->
<ion-item>
<ion-icon name="volume-low"
slot="start"></ion-icon>
<ion-range [(ngModel)]="volume"
min="0" max="100">
</ion-range>
<ion-icon name="volume-high"
slot="end"></ion-icon>
</ion-item>
<!-- 亮度 -->
<ion-item>
<ion-icon name="sunny" slot="start"></ion-icon>
<ion-range [(ngModel)]="brightness">
</ion-range>
</ion-item>
显示数值标签
<!-- 使用 pin 显示当前值 -->
<ion-range pin="true" [(ngModel)]="value">
</ion-range>
<!-- 自定义标签内容 -->
<ion-range pin="true" [(ngModel)]="rating">
<ion-label slot="start">0</ion-label>
<ion-label slot="end">5</ion-label>
</ion-range>
<!-- 评分场景 -->
<ion-range min="0" max="5" step="1"
[(ngModel)]="rating">
</ion-range>
<p>评分: {{ rating }} / 5</p>
响应式表单
<form [formGroup]="form">
<ion-item>
<ion-label>期望薪资(万)</ion-label>
<ion-range formControlName="salary"
min="5" max="50" step="1">
</ion-range>
</ion-item>
</form>
// TS
form = this.fb.group({
salary: [15],
});
// 读取
this.form.value.salary
样式定制
/* 自定义颜色与尺寸 */
ion-range {
--bar-background: #ddd;
--bar-background-active: #3880ff;
--knob-background: #3880ff;
--knob-size: 24px;
--height: 40px;
}
/* 颜色变体 */
<ion-range color="success"></ion-range>
<ion-range color="danger"></ion-range>
/* 渐变轨道(自定义) */
ion-range::part(track) {
background: linear-gradient(90deg,
#667eea, #764ba2);
}
应用场景
# 1. 音量/亮度调节
# 2. 价格/年龄筛选区间
# 3. 评分打分
# 4. 设置数值(字体大小/透明度)
# 5. 视频播放进度(配合视频元素)
// 视频进度示例
video.currentTime = seekValue;
注意事项
# 1. min/max 默认 0-100
# 2. step 控制步进(整型值用 step=1)
# 3. dualKnobs 双滑块返回 {lower, upper}
# 4. 拖动中频繁触发 ionChange
# 需要防抖处理(性能敏感场景)
// 防抖示例
onChange(e: any) {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
// 最终值处理
}, 200);
}
Range 滑块让"区间选择"直观友好。掌握单/双滑块与双向绑定,音量、筛选、评分场景一键实现。
ionic select
Ionic Select
Select(选择器)用于从列表中选值,移动端弹出选择界面(底部弹层或弹窗)。适合枚举选择:地区、分类、语言等。
基础用法
<!-- 基本选择器 -->
<ion-select placeholder="请选择城市">
<ion-select-option value="bj">北京</ion-select-option>
<ion-select-option value="sh">上海</ion-select-option>
<ion-select-option value="gz">广州</ion-select-option>
</ion-select>
双向绑定
<ion-item>
<ion-label>所在城市</ion-label>
<ion-select [(ngModel)]="city"
placeholder="选择城市">
<ion-select-option *ngFor="let c of cities"
[value]="c.id">
{{ c.name }}
</ion-select-option>
</ion-select>
</ion-item>
// TS
cities = [
{ id: "bj", name: "北京" },
{ id: "sh", name: "上海" },
];
city = "bj";
// 事件
<ion-select (ionChange)="onCityChange($event)">
onCityChange(event: any) {
console.log("选择:", event.detail.value);
}
多选
<!-- multiple: 多选 -->
<ion-select multiple="true"
placeholder="选择标签">
<ion-select-option value="python">
Python
</ion-select-option>
<ion-select-option value="js">
JavaScript
</ion-select-option>
</ion-select>
// TS
selectedTags: string[] = [];
// 多选返回数组
onChange(event: any) {
this.selectedTags = event.detail.value;
}
<!-- 显示选中数量 -->
<ion-select multiple="true"
[interfaceOptions]="{
header: '选择标签'
}">
选择界面模式(interface)
<!-- 底部弹层(默认) -->
<ion-select interface="action-sheet">
...
</ion-select>
<!-- 弹窗 -->
<ion-select interface="alert">
...
</ion-select>
<!-- 弹出式下拉 -->
<ion-select interface="popover">
...
</ion-select>
<!-- 自定义界面选项 -->
<ion-select [interfaceOptions]="{
header: '请选择',
subHeader: '城市列表',
translucent: true
}">
响应式表单
<form [formGroup]="form">
<ion-item>
<ion-label>学历</ion-label>
<ion-select formControlName="education"
placeholder="选择学历">
<ion-select-option value="high">
高中
</ion-select-option>
<ion-select-option value="bachelor">
本科
</ion-select-option>
<ion-select-option value="master">
硕士
</ion-select-option>
</ion-select>
</ion-item>
</form>
// TS
form = this.fb.group({
education: ["bachelor"],
});
样式与提示
<!-- 颜色 -->
<ion-select color="primary"></ion-select>
<!-- 取消/确定按钮文字 -->
<ion-select [cancelText]="'取消'"
[okText]="'确定'">
<!-- 选中值显示 -->
<!-- 默认显示选项文本 -->
<!-- 多选用分隔符 -->
<ion-select multiple="true"
[selectedText]="selectedTextFn">
selectedTextFn(values: any[]) {
return values.length + " 个已选";
}
禁用与只读
<!-- 禁用 -->
<ion-select disabled></ion-select>
<!-- 条件禁用 -->
<ion-select [disabled]="!canEdit">
<!-- 隐藏箭头 -->
<ion-select [interfaceOptions]="{
cssClass: 'no-arrow'
}">
注意事项
# 1. 在 ion-item 中与 label 搭配
# 2. 选项数据量大时考虑搜索(自定义弹层)
# 3. 移动端默认底部弹层体验最佳
# 4. 级联选择(省市区)需手动处理
// 级联示例
onProvinceChange(e: any) {
this.cities = this.allData[e.detail.value] || [];
this.city = null; // 重置下级
}
Select 让枚举选择规范统一。掌握单选/多选与界面模式,配合级联逻辑,表单选择交互完整落地。
ionic tab(选项卡)
Ionic Tab(选项卡)
Tab(选项卡)是移动应用的底部导航标准:首页、发现、消息、我的。Ionic 提供完整的选项卡导航方案。
创建选项卡项目
# 用 tabs 模板创建
ionic start myApp tabs --type=angular
# 自动生成:
# src/pages/tab1/ tab2/ tab3/
# src/tabs/tabs.page.html (容器)
选项卡容器
<!-- tabs.page.html -->
<ion-tabs>
<ion-tab-bar slot="bottom">
<ion-tab-button tab="home">
<ion-icon name="home"></ion-icon>
<ion-label>首页</ion-label>
</ion-tab-button>
<ion-tab-button tab="discover">
<ion-icon name="compass"></ion-icon>
<ion-label>发现</ion-label>
</ion-tab-button>
<ion-tab-button tab="messages">
<ion-icon name="chatbubbles"></ion-icon>
<ion-label>消息</ion-label>
<ion-badge color="danger">3</ion-badge>
</ion-tab-button>
<ion-tab-button tab="profile">
<ion-icon name="person"></ion-icon>
<ion-label>我的</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
路由配置
// tabs 路由
const routes: Routes = [
{
path: "tabs",
component: TabsPage,
children: [
{
path: "home",
loadChildren: () =>
import("./pages/home/home.page")
.then(m => m.HomePageModule)
},
{ path: "", redirectTo: "home",
pathMatch: "full" },
],
},
{ path: "", redirectTo: "tabs", pathMatch: "full" },
];
徽标与角标
<!-- 未读消息角标 -->
<ion-tab-button tab="messages">
<ion-icon name="chatbubbles"></ion-icon>
<ion-label>消息</ion-label>
<ion-badge color="danger">{{ unread }}</ion-badge>
</ion-tab-button>
<!-- 小红点(无数字) -->
<ion-badge color="danger"
class="dot-badge"></ion-badge>
/* 小红点样式 */
.dot-badge {
width: 8px;
height: 8px;
border-radius: 50%;
padding: 0;
position: absolute;
top: 4px;
right: 12px;
}
顶部选项卡
<!-- slot="top" 放顶部 -->
<ion-tabs>
<ion-tab-bar slot="top">
<ion-tab-button tab="all">
<ion-label>全部</ion-label>
</ion-tab-button>
<ion-tab-button tab="unread">
<ion-label>未读</ion-label>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
<!-- 左侧选项卡 -->
<ion-tab-bar slot="start">
<!-- 竖排按钮 -->
</ion-tab-bar>
动态切换与样式
<!-- 当前选中高亮 -->
<ion-tab-button tab="home"
[selected]="currentTab === 'home'">
<!-- 自定义颜色 -->
<ion-tab-bar color="primary">...</ion-tab-bar>
<ion-tab-bar [translucent]="true">...</ion-tab-bar>
/* 选中态样式 */
ion-tab-button.tab-selected {
color: #3880ff;
}
/* 隐藏 Tab 切换时的过渡动画 */
ion-tabs { --transition-duration: 0ms; }
程序化切换
// TS: 切换到指定 tab
import { IonTabs } from "@ionic/angular";
@ViewChild(IonTabs) tabs!: IonTabs;
switchTab(name: string) {
this.tabs.select(name);
}
// 或路由导航
router.navigate(["/tabs", "profile"]);
注意事项
# 1. tab 按钮 tab 属性与路由对应
# 2. 每个 tab 是独立页面(懒加载)
# 3. 底部导航常驻(内容在中间滚动)
# 4. 隐藏某 tab 用 *ngIf 或样式
# 5. 深链接: /tabs/home 可直达
# 常见问题:
# - 图标不显示: 确认 name 有效
# - tab 点击无跳转: 检查路由配置
# - 徽标更新: 数据驱动,用服务共享状态
Tab 是 App 的"一级导航"。掌握容器/路由/角标/切换,主流 App 的底部导航结构信手拈来。
ionic 网格(Grid)
Ionic 网格(Grid)
网格(Grid)是基于 12 列的系统,用于响应式布局。Ionic 的 ion-grid/ion-row/ion-col 让页面自适应不同屏幕。
基本结构
<ion-grid>
<ion-row>
<ion-col>列 1</ion-col>
<ion-col>列 2</ion-col>
<ion-col>列 3</ion-col>
</ion-row>
</ion-grid>
<!-- 等宽三列,自动换行 -->
列宽控制(12 列系统)
<ion-grid>
<ion-row>
<ion-col size="6">占一半(6/12)</ion-col>
<ion-col size="6">占一半</ion-col>
</ion-row>
<ion-row>
<ion-col size="4">1/3</ion-col>
<ion-col size="4">1/3</ion-col>
<ion-col size="4">1/3</ion-col>
</ion-row>
<ion-row>
<ion-col size="8">2/3</ion-col>
<ion-col size="4">1/3</ion-col>
</ion-row>
</ion-grid>
响应式断点
<!-- 不同屏幕宽度不同列数 -->
<ion-grid>
<ion-row>
<!-- 手机: 整行; 平板: 半行; 桌面: 1/3 -->
<ion-col size="12"
size-sm="6"
size-md="4"
size-lg="3">
响应式卡片
</ion-col>
</ion-row>
</ion-grid>
# 断点:
# size: 默认(手机)
# size-sm: >=576px
# size-md: >=768px(平板)
# size-lg: >=992px(桌面)
# size-xl: >=1200px
商品网格案例
<!-- 商品双列布局 -->
<ion-grid>
<ion-row>
<ion-col size="6" *ngFor="let p of products">
<ion-card>
<img [src]="p.image">
<ion-card-header>
<ion-card-subtitle>
¥{{ p.price }}
</ion-card-subtitle>
<ion-card-title>
{{ p.name }}
</ion-card-title>
</ion-card-header>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
<!-- 三列布局(小图标) -->
<ion-col size="4" *ngFor="let app of apps">
<div class="app-icon">
<ion-icon [name]="app.icon"></ion-icon>
<p>{{ app.name }}</p>
</div>
</ion-col>
对齐与间距
<!-- 垂直/水平对齐 -->
<ion-row>
<ion-col size="6">左</ion-col>
<ion-col size="6" class="ion-text-end">
右
</ion-col>
</ion-row>
<!-- 垂直居中 -->
<ion-row class="ion-align-items-center">
<ion-col>文字居中</ion-col>
</ion-row>
# 工具类:
# ion-align-items-start/center/end
# ion-justify-content-center/end
# ion-text-start/center/end
# ion-text-wrap / ion-text-nowrap
<!-- 偏移与间距 -->
<ion-col size="8" offset="2">居中偏移</ion-col>
嵌套网格
<!-- 网格内嵌网格 -->
<ion-grid>
<ion-row>
<ion-col size="6">
<ion-grid>
<ion-row>
<ion-col size="12">子内容 1</ion-col>
<ion-col size="12">子内容 2</ion-col>
</ion-row>
</ion-grid>
</ion-col>
<ion-col size="6">右侧内容</ion-col>
</ion-row>
</ion-grid>
自定义间距
/* 全局或页面 scss */
ion-grid {
padding: 8px;
}
ion-col {
margin-bottom: 8px;
}
/* 卡片间距 */
ion-col ion-card {
margin: 4px;
}
/* 响应式间距 */
@media (min-width: 768px) {
ion-col {
padding: 8px;
}
}
注意事项
# 1. 12 列系统,size 和为 12 占满一行
# 2. 超出行宽自动换行
# 3. 响应式用断点前缀(size-sm 等)
# 4. 与 flexbox 类似,可互相替代
# 5. 大量列表项用虚拟滚动(网格也适用)
# 常见场景:
# - 商品/照片网格
# - 功能图标宫格
# - 统计面板多列
# - 表单双列布局
网格让布局"自适应"。掌握 12 列系统与响应式断点,配合工具类对齐,任何屏幕都优雅呈现。
ionic 颜色
Ionic 颜色
Ionic 提供一套语义化颜色系统:primary、secondary、success 等,通过 CSS 变量高度可定制,并自动支持暗色模式。
预定义颜色
<!-- 组件 color 属性 -->
<ion-button color="primary">主色</ion-button>
<ion-button color="secondary">次色</ion-button>
<ion-button color="tertiary">第三色</ion-button>
<ion-button color="success">成功</ion-button>
<ion-button color="warning">警告</ion-button>
<ion-button color="danger">危险</ion-button>
<ion-button color="light">浅色</ion-button>
<ion-button color="medium">中灰</ion-button>
<ion-button color="dark">深色</ion-button>
<!-- 几乎适用于所有组件 -->
<ion-text color="danger">错误提示</ion-text>
<ion-icon color="success" name="checkmark"></ion-icon>
<ion-badge color="warning">待处理</ion-badge>
<ion-item color="light">浅色条目</ion-item>
自定义主题色
// src/theme/variables.scss
:root {
/* 品牌主色 */
--ion-color-primary: #6c5ce7;
--ion-color-primary-rgb: 108, 92, 231;
--ion-color-primary-contrast: #ffffff;
--ion-color-primary-contrast-rgb: 255, 255, 255;
--ion-color-primary-shade: #5f51cb;
--ion-color-primary-tint: #7b6dea;
/* 自定义颜色(完整声明) */
--ion-color-brand: #ff6b6b;
--ion-color-brand-rgb: 255, 107, 107;
--ion-color-brand-contrast: #ffffff;
--ion-color-brand-contrast-rgb: 255, 255, 255;
--ion-color-brand-shade: #e05e5e;
--ion-color-brand-tint: #ff7a7a;
}
/* 注册自定义颜色类 */
.ion-color-brand {
--ion-color-base: var(--ion-color-brand);
--ion-color-base-rgb: var(--ion-color-brand-rgb);
--ion-color-contrast: var(--ion-color-brand-contrast);
--ion-color-contrast-rgb: var(--ion-color-brand-contrast-rgb);
--ion-color-shade: var(--ion-color-brand-shade);
--ion-color-tint: var(--ion-color-brand-tint);
}
/* 使用 */
<ion-button class="ion-color-brand">
品牌色按钮
</ion-button>
组件级变量
/* 每个组件有自己的颜色变量 */
ion-button {
--color: #333;
--background: #fff;
--background-hover: #f0f0f0;
}
ion-card {
--background: #ffffff;
--color: #222;
}
ion-toolbar {
--background: #3880ff;
--color: #ffffff;
}
/* 页面背景 */
ion-content {
--background: #f5f6f8;
}
暗色模式
// variables.scss 自动适配系统
@media (prefers-color-scheme: dark) {
:root {
--ion-color-primary: #8a7dff;
--ion-background-color: #121212;
--ion-background-color-rgb: 18, 18, 18;
--ion-text-color: #ffffff;
--ion-text-color-rgb: 255, 255, 255;
--ion-card-background: #1e1e1e;
--ion-item-background: #1e1e1e;
--ion-toolbar-background: #1e1e1e;
}
}
// 强制暗色(class 方式)
html.dark {
--ion-color-primary: #8a7dff;
...
}
document.body.classList.add("dark");
常用主题变量
:root {
/* 背景与文字 */
--ion-background-color: #ffffff;
--ion-text-color: #222;
/* 组件背景 */
--ion-card-background: #ffffff;
--ion-item-background: #ffffff;
--ion-toolbar-background: #ffffff;
/* 边框与分隔 */
--ion-border-color: #ddd;
--ion-item-border-color: #ddd;
/* 覆盖层 */
--ion-overlay-background-color: #fff;
/* 字体 */
--ion-font-family: -apple-system,
"PingFang SC", "Microsoft YaHei", sans-serif;
/* 圆角与阴影 */
--ion-border-radius: 8px;
--ion-box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
语义化用色建议
# 语义化颜色选择:
# primary: 主要操作/品牌
# success: 成功/完成
# warning: 警告/待处理
# danger: 错误/删除/危险
# medium: 次要/禁用感
# light/dark: 层级对比
# 避免:
# - 滥用 warning/danger(用户紧张)
# - 文字对比度不足(可读性)
# - 硬编码颜色(破坏主题切换)
# 最佳实践:
# 1. 品牌色只改 primary 变量
# 2. 状态用语义色(成功/危险)
# 3. 自定义色完整注册 5 个变量
# 4. 适配暗色模式
颜色系统让应用"有品牌感"。掌握预定义色、CSS 变量定制与暗色适配,界面风格统一且可切换。
ionic icon(图标)
Ionic Icon(图标)
图标是界面视觉语言的重要组成部分。Ionic 内置 Ionicons 图标库(1200+ 图标),也可使用自定义 SVG。
基础用法
<!-- 简单图标 -->
<ion-icon name="home"></ion-icon>
<ion-icon name="person"></ion-icon>
<!-- 尺寸 -->
<ion-icon name="star" size="small"></ion-icon>
<ion-icon name="star" size="large"></ion-icon>
<!-- 自定义像素尺寸 -->
<ion-icon name="star"
style="font-size: 32px;">
</ion-icon>
<!-- 颜色 -->
<ion-icon name="heart" color="danger">
</ion-icon>
<ion-icon name="checkmark" color="success">
</ion-icon>
图标命名
<!-- 基础图标名 -->
<ion-icon name="home"></ion-icon>
<ion-icon name="search"></ion-icon>
<ion-icon name="settings"></ion-icon>
<ion-icon name="close"></ion-icon>
<ion-icon name="add"></ion-icon>
<ion-icon name="trash"></ion-icon>
<ion-icon name="share"></ion-icon>
<ion-icon name="download"></ion-icon>
<ion-icon name="camera"></ion-icon>
<!-- 变体(实心/描边) -->
<ion-icon name="heart"></ion-icon> <!-- 描边 -->
<ion-icon name="heart-outline"></ion-icon>
<ion-icon name="heart-sharp"></ion-icon>
<!-- 平台自适应 -->
<ion-icon name="home"
md="home" ios="home-outline">
</ion-icon>
# 完整列表查官方文档:
# https://ionic.io/ionicons
图标与文字组合
<!-- 按钮图标 -->
<ion-button>
<ion-icon name="trash" slot="start"></ion-icon>
删除
</ion-button>
<!-- 列表图标 -->
<ion-item>
<ion-icon name="settings" slot="start">
</ion-icon>
<ion-label>设置</ion-label>
</ion-item>
<!-- 头像 -->
<ion-avatar>
<ion-icon name="person" size="large">
</ion-icon>
</ion-avatar>
<!-- 文字内嵌 -->
<p>
<ion-icon name="checkmark-circle"
color="success"></ion-icon>
操作成功
</p>
自定义 SVG 图标
<!-- 方式1: 内联 SVG -->
<ion-icon>
<svg viewBox="0 0 24 24">
<path d="M12 2L2 22h20L12 2z"/>
</svg>
</ion-icon>
<!-- 方式2: 自定义图标集 -->
// Angular
import { addIcons } from "ionicons";
import { logoGithub } from "ionicons/icons";
addIcons({ "logo-github": logoGithub });
// 模板中使用
<ion-icon name="logo-github"></ion-icon>
// 常用品牌图标:
// logo-github / logo-twitter / logo-facebook
// logo-apple / logo-google / logo-whatsapp
动态图标
<!-- 动态切换 -->
<ion-icon [name]="liked ? 'heart'
: 'heart-outline'"
[color]="liked ? 'danger' : 'medium'"
(click)="toggleLike()">
</ion-icon>
// TS
liked = false;
toggleLike() {
this.liked = !this.liked;
}
<!-- 加载图标(旋转) -->
<ion-icon name="refresh"
class="spin"></ion-icon>
/* 旋转动画 */
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
图标样式
/* CSS 控制 */
ion-icon {
color: #666;
font-size: 24px;
}
/* 悬浮效果 */
ion-icon:hover {
opacity: 0.8;
}
/* 图标容器 */
.icon-circle {
width: 48px;
height: 48px;
border-radius: 50%;
background: #e8f0fe;
display: flex;
align-items: center;
justify-content: center;
ion-icon { font-size: 24px; color: #3880ff; }
}
注意事项
# 1. name 必须正确(查文档确认)
# 2. 性能: 图标可打包,勿动态注入大量 SVG
# 3. 无障碍: 纯图标加 aria-label
<ion-icon name="trash"
aria-label="删除"></ion-icon>
# 4. 色彩对比: 图标颜色保证可辨识
# 5. 品牌图标单独注册(addIcons)
图标让界面"表意清晰"。掌握内置图标、变体与自定义 SVG,配合动态切换,视觉细节更专业。
ionic 上拉菜单
Ionic 上拉菜单
上拉菜单(Action Sheet)从底部弹出操作选项列表,用于"分享/删除/取消"等操作集合,是移动端常用交互。
基础用法(Angular)
import { ActionSheetController } from "@ionic/angular";
constructor(private actionSheetCtrl: ActionSheetController) {}
async showActions() {
const actionSheet = await this.actionSheetCtrl.create({
header: "选择操作",
buttons: [
{
text: "分享",
icon: "share",
handler: () => this.share()
},
{
text: "收藏",
icon: "star",
handler: () => this.favorite()
},
{
text: "删除",
icon: "trash",
role: "destructive", // 危险(红色)
handler: () => this.delete()
},
{
text: "取消",
icon: "close",
role: "cancel" // 取消
},
],
});
await actionSheet.present();
}
取消与事件
async showActions() {
const sheet = await this.actionSheetCtrl.create({
header: "更多操作",
subHeader: "请选择",
buttons: [...],
// 点击背景取消
backdropDismiss: true,
});
await sheet.present();
// 监听关闭
const { role } = await sheet.onDidDismiss();
console.log("关闭原因:", role);
// 'cancel' / 'destructive' / 自定义 role
}
样式定制
async showActions() {
const sheet = await this.actionSheetCtrl.create({
header: "操作",
cssClass: "my-action-sheet",
buttons: [
{ text: "红色操作", role: "destructive" },
{ text: "取消", role: "cancel" },
],
});
await sheet.present();
}
/* 全局 scss */
.my-action-sheet {
--background: #f8f8f8;
--backdrop-opacity: 0.4;
}
.my-action-sheet .action-sheet-button {
font-size: 16px;
}
.my-action-sheet .action-sheet-title {
color: #666;
font-weight: bold;
}
/* 暗色适配 */
@media (prefers-color-scheme: dark) {
.my-action-sheet {
--background: #1e1e1e;
}
}
分享菜单示例
async shareOptions() {
const sheet = await this.actionSheetCtrl.create({
header: "分享到",
buttons: [
{
text: "微信",
icon: "logo-wechat",
handler: () => this.shareTo("wechat")
},
{
text: "朋友圈",
icon: "logo-wechat",
handler: () => this.shareTo("moments")
},
{
text: "微博",
icon: "logo-twitter",
handler: () => this.shareTo("weibo")
},
{
text: "复制链接",
icon: "link",
handler: () => this.copyLink()
},
{
text: "取消",
icon: "close",
role: "cancel"
},
],
});
await sheet.present();
}
private shareTo(platform: string) {
console.log("分享到:", platform);
}
与 Alert 对比
# Action Sheet(上拉菜单):
# - 底部弹出,操作列表
# - 适合: 分享/删除/多项操作
# - 移动端习惯
# Alert(弹窗):
# - 居中弹窗
# - 适合: 确认/输入/单选(少于3项)
# - 打断性强
# 选择:
# 操作集合(2-6项) → Action Sheet
# 简单确认/输入 → Alert
# 复杂表单 → Modal(模态窗口)
注意事项
# 1. role="destructive" 显示危险样式(红)
# 2. role="cancel" 固定为取消(底部)
# 3. handler 返回 false 可阻止关闭
# 4. 记得 await present()
// 阻止关闭示例
{
text: "确认退出",
handler: () => {
if (!confirm) {
return false; // 不关闭
}
return true;
}
}
上拉菜单是"轻量操作选择"的标配。掌握按钮角色(取消/危险)与事件回调,交互反馈完整自然。
ionic 背景层
Ionic 背景层
背景层(Overlay/Backdrop)是覆盖在页面内容之上的半透明层,常见于弹窗、上拉菜单、模态窗口等弹出组件背后,用于聚焦内容与点击关闭。
什么是背景层
# 弹出组件(Alert/ActionSheet/Modal)显示时,
# 页面内容被半透明背景层覆盖:
# - 视觉聚焦: 突出弹出内容
# - 点击关闭: 点背景可关闭(可配置)
# - 阻止交互: 防止误操作底层内容
# 背景层默认样式:
# 半透明黑色(可定制透明度/颜色)
控制背景层行为
// Alert
const alert = await alertCtrl.create({
header: "提示",
message: "内容",
buttons: ["确定"],
backdropDismiss: false, // 禁止点背景关闭
});
await alert.present();
// ActionSheet
await this.actionSheetCtrl.create({
buttons: [...],
backdropDismiss: true, // 允许点背景关闭(默认)
}).present();
// Modal
const modal = await modalCtrl.create({
component: MyModalPage,
backdropDismiss: false, // 必须显式关闭
});
await modal.present();
自定义背景样式
// 全局 scss
ion-modal {
--backdrop-opacity: 0.5; // 透明度
--backdrop-color: #000; // 颜色
}
ion-alert {
--backdrop-opacity: 0.4;
}
/* 或组件 cssClass 定制 */
async show() {
const alert = await this.alertCtrl.create({
header: "提示",
cssClass: "custom-backdrop",
buttons: ["确定"],
});
await alert.present();
}
/* 全局 scss */
.custom-backdrop {
--backdrop-opacity: 0.6;
--backdrop-color: #333;
}
完全自定义背景层
/* 手动创建覆盖层(少用) */
<div class="overlay" *ngIf="showOverlay"
(click)="hideOverlay()">
<div class="overlay-content"
(click)="$event.stopPropagation()">
自定义弹层内容
</div>
</div>
/* scss */
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.overlay-content {
background: #fff;
padding: 24px;
border-radius: 12px;
max-width: 80%;
}
与模态窗口配合
// Modal 的半透明效果
async openModal() {
const modal = await this.modalCtrl.create({
component: FilterPage,
backdropDismiss: true,
// 底部弹出
cssClass: "bottom-modal",
});
await modal.present();
}
/* 底部弹窗样式 */
.bottom-modal {
--height: 60%;
--border-radius: 16px 16px 0 0;
align-items: flex-end;
}
/* 背景层模糊(毛玻璃) */
.bottom-modal {
--backdrop-filter: blur(4px);
}
背景层事件
// 监听背景点击关闭
const modal = await modalCtrl.create({...});
await modal.present();
const { role, data } = await modal.onWillDismiss();
// role: 'backdrop' 表示点背景关闭
if (role === "backdrop") {
console.log("用户点击背景关闭");
}
// 阻止意外关闭的场景:
// - 表单未保存确认
// - 必须完成的流程(授权)
最佳实践
# 1. 重要操作(删除确认)禁点背景关闭
# → backdropDismiss: false
# 2. 轻量选择(筛选)允许点背景关闭
# 3. 背景透明度不宜过低(聚焦)
# 4. 暗色模式下背景层自动适配
# 5. 自定义弹层注意 z-index 层级
# 常见问题:
# - 点背景关不掉: backdropDismiss=false
# - 背景太浅/太深: 调整 --backdrop-opacity
# - 弹层被遮: 检查 z-index
背景层是弹出组件的"舞台布景"。掌握 backdropDismiss 与透明度定制,弹出交互张弛有度。
ionic 下拉刷新
Ionic 下拉刷新
下拉刷新(Refresher)是移动端经典交互:下拉页面顶部触发刷新,加载最新数据。Ionic 提供 ion-refresher 组件。
基础用法
<ion-refresher slot="fixed"
(ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingIcon="arrow-down"
pullingText="下拉刷新"
refreshingSpinner="circles"
refreshingText="刷新中...">
</ion-refresher-content>
</ion-refresher>
// TS
async doRefresh(event: any) {
// 模拟请求数据
await this.loadData();
// 完成刷新(必须调用!)
event.target.complete();
}
刷新数据示例
// TS
async doRefresh(event: any) {
try {
this.items = await this.apiService
.getLatest();
console.log("刷新完成:", this.items.length);
} catch (err) {
console.error("刷新失败:", err);
} finally {
event.target.complete(); // 结束动画
}
}
// 带超时保护
async doRefresh(event: any) {
await Promise.race([
this.loadData(),
new Promise(r => setTimeout(r, 10000)),
]);
event.target.complete();
}
自定义刷新文案
<ion-refresher (ionRefresh)="doRefresh($event)">
<ion-refresher-content
pullingIcon="refresh"
pullingText="松开刷新"
refreshingSpinner="bubbles"
refreshingText="努力加载中..."
cancelPullText="取消"
></ion-refresher-content>
</ion-refresher>
# 常用 spinner 类型:
# circles / bubbles / crescent
# lines / dots
# 属性:
# pullingIcon: 下拉时的图标
# pullingText: 下拉文案
# refreshingSpinner: 刷新动画
# refreshingText: 刷新文案
与列表结合
<ion-content>
<ion-refresher slot="fixed"
(ionRefresh)="refresh($event)">
<ion-refresher-content>
</ion-refresher-content>
</ion-refresher>
<!-- 空状态 -->
<div *ngIf="items.length === 0"
class="empty-state">
<ion-icon name="cloud-offline"
size="large"></ion-icon>
<p>暂无数据,下拉刷新</p>
</div>
<!-- 数据列表 -->
<ion-list *ngIf="items.length > 0">
<ion-item *ngFor="let item of items">
<ion-label>{{ item.title }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
程序化触发
<!-- 首次进入自动刷新 -->
<ion-refresher #refresher
(ionRefresh)="refresh($event)">
<ion-refresher-content>
</ion-refresher-content>
</ion-refresher>
// TS
import { Component, ViewChild } from "@angular/core";
import { IonRefresher } from "@ionic/angular";
@ViewChild("refresher") refresher!: IonRefresher;
ngOnInit() {
// 延迟触发自动刷新
setTimeout(() => this.refresher.emit("ionRefresh"),
100);
}
禁用与关闭
<!-- 条件禁用 -->
<ion-refresher [disabled]="!canRefresh">
<!-- 手动关闭 -->
event.target.complete() // 完成
event.target.disabled = true // 禁用
// 异常时重置
async refresh(event: any) {
try {
...
} catch {
event.target.complete();
}
}
注意事项
# 1. 必须调用 complete() 结束动画!
# 2. slot="fixed" 固定(不随内容滚动)
# 3. 刷新失败也要 complete(否则一直转圈)
# 4. 页面滚动顶部才能触发
# 5. 与加载更多(上拉)配合使用
# 常见问题:
# - 刷新动画不停: 忘记 complete()
# - 无法下拉: 内容未填满或 slot 错误
# - 触发频繁: 防抖处理
下拉刷新是移动端数据更新的标准姿势。掌握组件用法与 complete 契约,配合列表与空状态,体验完整。
ionic 复选框
Ionic 复选框(Checkbox 进阶)
本节深入复选框的进阶用法:批量选择、全选/反选、表格选择、筛选标签等复杂场景。
批量选择列表
<ion-list>
<ion-item *ngFor="let item of items">
<ion-checkbox
[checked]="isSelected(item)"
(ionChange)="toggle(item, $event)">
</ion-checkbox>
<ion-label>
<h2>{{ item.name }}</h2>
<p>{{ item.desc }}</p>
</ion-label>
</ion-item>
</ion-list>
// TS
selected: any[] = [];
isSelected(item: any) {
return this.selected.includes(item);
}
toggle(item: any, event: any) {
if (event.detail.checked) {
this.selected.push(item);
} else {
this.selected = this.selected
.filter(i => i !== item);
}
}
getSelectedCount() {
return this.selected.length;
}
全选/反选
<!-- 全选控制项 -->
<ion-item>
<ion-checkbox
[checked]="isAllSelected()"
[indeterminate]="isPartial()"
(ionChange)="selectAll($event.detail.checked)">
</ion-checkbox>
<ion-label>全选</ion-label>
</ion-item>
// TS
isAllSelected() {
return this.items.length > 0
&& this.selected.length === this.items.length;
}
isPartial() {
return this.selected.length > 0
&& this.selected.length < this.items.length;
}
selectAll(checked: boolean) {
this.selected = checked
? [...this.items]
: [];
}
// indeterminate: 半选状态(部分选中)
复选框样式定制
/* 自定义 */
ion-checkbox {
--size: 22px;
--border-radius: 6px;
--border-color: #bbb;
--border-color-checked: #3880ff;
--background: #fff;
--background-checked: #3880ff;
--checkmark-color: #fff;
}
/* 圆形 */
ion-checkbox shape="circle" {
--border-radius: 50%;
}
/* 大尺寸 */
ion-checkbox {
--size: 28px;
}
筛选标签场景
<!-- 筛选标签 -->
<ion-chip *ngFor="let tag of tags"
[outline]="!selectedTags.includes(tag)"
(click)="toggleTag(tag)">
<ion-label>{{ tag }}</ion-label>
<ion-icon *ngIf="selectedTags.includes(tag)"
name="checkmark"></ion-icon>
</ion-chip>
// TS
selectedTags: string[] = [];
tags = ["Python", "AI", "前端", "后端", "运维"];
toggleTag(tag: string) {
const idx = this.selectedTags.indexOf(tag);
if (idx > -1) {
this.selectedTags.splice(idx, 1);
} else {
this.selectedTags.push(tag);
}
}
// 获取筛选结果
getFiltered() {
return this.allItems.filter(item =>
item.tags.some(t =>
this.selectedTags.includes(t)));
}
表单集成
<!-- 响应式表单数组 -->
<ion-item *ngFor="let interest of interests">
<ion-checkbox
[formControl]="getControl(interest.key)">
</ion-checkbox>
<ion-label>{{ interest.label }}</ion-label>
</ion-item>
// TS
form = this.fb.group({});
interests = [
{ key: "coding", label: "编程" },
{ key: "music", label: "音乐" },
{ key: "sports", label: "运动" },
];
constructor() {
this.interests.forEach(i =>
this.form.addControl(i.key,
new FormControl(false)));
}
getControl(key: string) {
return this.form.get(key) as FormControl;
}
与数据结构同步
// 对象数组直接绑定选中状态
interface Selectable {
id: number;
name: string;
selected: boolean;
}
toggle(item: Selectable) {
item.selected = !item.selected;
}
// 收集选中数据
getSelectedItems() {
return this.items.filter(i => i.selected);
}
// 初始化选中
markSelected(ids: number[]) {
this.items.forEach(i => {
i.selected = ids.includes(i.id);
});
}
最佳实践
# 1. 大数据列表: 用 Set 存储选中(性能)
# selected = new Set<number>()
# isSelected = (i) => this.selected.has(i.id)
# 2. 全选/反选提供 indeterminate 反馈
# 3. 批量操作(删除/移动)基于选中集
# 4. 与搜索/分页组合时选中需跨页保留
// 批量删除
async deleteSelected() {
const ids = [...this.selectedSet];
await this.api.batchDelete(ids);
this.items = this.items.filter(
i => !this.selectedSet.has(i.id));
this.selectedSet.clear();
}
复选框进阶覆盖批量操作核心。掌握全选/半选/跨页保留,再配合批量操作,数据管理界面专业完整。
ionic 单选框操作
Ionic 单选框操作(Radio 进阶)
本节深入单选框的进阶操作:动态选项、级联选择、表单集成与自定义样式。
动态选项加载
<!-- 异步加载选项 -->
<ion-radio-group [(ngModel)]="country">
<ion-item *ngFor="let c of countries">
<ion-label>{{ c.name }}</ion-label>
<ion-radio [value]="c.code"
[disabled]="c.disabled">
</ion-radio>
</ion-item>
</ion-radio-group>
// TS
countries = [];
country = "";
ngOnInit() {
this.api.getCountries().subscribe(data => {
this.countries = data;
this.country = data[0]?.code;
});
}
级联选择(省市区)
<!-- 省 → 市 -->
<ion-item>
<ion-label>省份</ion-label>
<ion-select [(ngModel)]="province"
(ionChange)="onProvinceChange()">
<ion-select-option *ngFor="let p of provinces"
[value]="p.code">
{{ p.name }}
</ion-select-option>
</ion-select>
</ion-item>
<!-- 市(随省变化) -->
<ion-item>
<ion-label>城市</ion-label>
<ion-select [(ngModel)]="city">
<ion-select-option *ngFor="let c of cities"
[value]="c.code">
{{ c.name }}
</ion-select-option>
</ion-select>
</ion-item>
// TS
provinces = data.allProvince;
cities: any[] = [];
onProvinceChange() {
this.cities = data.cities[this.province] || [];
this.city = null; // 重置下级
}
单选卡片样式
<!-- 卡片式单选(套餐选择) -->
<ion-radio-group [(ngModel)]="plan">
<ion-card *ngFor="let p of plans"
[class.selected]="plan === p.id"
(click)="plan = p.id">
<ion-card-header>
<ion-card-title>{{ p.name }}</ion-card-title>
<ion-card-subtitle>
¥{{ p.price }}/月
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
{{ p.desc }}
</ion-card-content>
</ion-card>
</ion-radio-group>
/* 选中高亮 */
ion-card.selected {
border: 2px solid #3880ff;
box-shadow: 0 4px 16px rgba(56,128,255,0.3);
}
RadioGroup 事件
<ion-radio-group [(ngModel)]="value"
(ionChange)="onChange($event)">
...
</ion-radio-group>
// TS
onChange(event: any) {
const value = event.detail.value;
console.log("选中:", value);
// 触发生成联动数据
this.loadDetail(value);
}
// 读取当前值
console.log(this.value);
// 响应式表单
form.get("choice")?.valueChanges
.subscribe(v => this.onChangeValue(v));
自定义样式
/* 圆点样式 */
ion-radio {
--color: #999;
--color-checked: #3880ff;
--border-width: 2px;
--border-style: solid;
--border-color: #ccc;
--border-color-checked: #3880ff;
--size: 24px;
}
/* 选中项文字高亮 */
ion-item {
--background-hover: #f0f4ff;
}
ion-item.ion-activated {
--background: #e8f0fe;
}
/* 隐藏圆点(自定义指示) */
ion-radio {
visibility: hidden;
position: absolute;
}
/* 按钮样式单选 */
.option-btn {
border: 1px solid #ddd;
border-radius: 8px;
padding: 8px 16px;
margin: 4px;
}
.option-btn.selected {
border-color: #3880ff;
color: #3880ff;
background: #e8f0fe;
}
表单校验
<!-- 必选校验 -->
<form [formGroup]="form">
<ion-radio-group formControlName="gender">
...
</ion-radio-group>
</form>
// TS
form = this.fb.group({
gender: ["", Validators.required],
});
// 提交校验
submit() {
if (this.form.invalid) {
alert("请选择性别");
return;
}
console.log(this.form.value);
}
// 错误提示
<ion-text color="danger" *ngIf="
form.get('gender')?.invalid &&
form.get('gender')?.touched">
此项必选
</ion-text>
最佳实践
# 1. 选项少(≤5)用 radio; 多则用 select/弹层
# 2. 选项带说明用卡片式单选
# 3. 级联选择注意重置下级
# 4. 值类型一致(数字/字符串)
# 5. 表单集成用 formControlName
单选框进阶覆盖动态与级联场景。掌握选项加载、级联联动与样式定制,选择交互专业完善。
ionic 手势事件
Ionic 手势事件
手势是移动端的核心交互:滑动、长按、缩放等。Ionic/Angular 提供多种手势处理方式,也可使用 Hammer.js 等库。
原生触摸事件
<!-- Angular 触摸事件 -->
<div (touchstart)="onTouchStart($event)"
(touchmove)="onTouchMove($event)"
(touchend)="onTouchEnd($event)">
触摸区域
</div>
// TS
onTouchStart(e: any) {
this.startX = e.touches[0].clientX;
this.startY = e.touches[0].clientY;
}
onTouchMove(e: any) {
const dx = e.touches[0].clientX - this.startX;
const dy = e.touches[0].clientY - this.startY;
// 判断方向
if (Math.abs(dx) > Math.abs(dy)) {
console.log("水平滑动", dx);
} else {
console.log("垂直滑动", dy);
}
}
onTouchEnd(e: any) {
console.log("触摸结束");
}
Ionic Gesture 控制器
import { Gesture, GestureController } from "@ionic/angular";
constructor(private gestureCtrl: GestureController) {}
ngAfterViewInit() {
const el = document.querySelector("#swipe-area")!;
const gesture: Gesture = this.gestureCtrl.create({
el: el,
gestureName: "swipe",
threshold: 10, // 触发阈值(px)
gesturePriority: 30,
onStart: () => {
console.log("手势开始");
},
onMove: (detail) => {
// detail.deltaX/deltaY 位移
console.log("移动:", detail.deltaX);
el.style.transform =
`translateX(${detail.deltaX}px)`;
},
onEnd: (detail) => {
console.log("手势结束");
el.style.transform = "";
if (detail.deltaX < -80) {
this.swipeLeft();
}
},
});
gesture.enable(true); // 启用
}
滑动删除/操作
<!-- 列表滑动操作(内置) -->
<ion-item-sliding>
<ion-item>
<ion-label>消息内容</ion-label>
</ion-item>
<ion-item-options side="start">
<ion-item-option color="success"
(click)="archive()">
归档
</ion-item-option>
</ion-item-options>
<ion-item-options side="end">
<ion-item-option color="danger"
(click)="delete()">
删除
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
Hammer.js(更丰富的手势)
# 安装
npm install hammerjs
// main.ts 引入
import "hammerjs";
// 使用
import { HammerModule } from "@angular/platform-browser";
imports: [HammerModule]
// 模板中直接用手势事件
<div (swipeleft)="onSwipeLeft()"
(swiperight)="onSwipeRight()"
(pan)="onPan($event)"
(pinch)="onPinch($event)"
(longpress)="onLongPress()">
手势区域
</div>
// TS
onSwipeLeft() {
console.log("左滑");
this.goNext();
}
onSwipeRight() {
console.log("右滑");
this.goBack();
}
onLongPress() {
console.log("长按");
}
onPinch(e: any) {
console.log("缩放:", e.scale);
}
下拉/上拉手势(自定义)
// 监听页面滚动判断方向
<ion-content (ionScroll)="onScroll($event)">
...
</ion-content>
// TS
onScroll(event: any) {
const detail = event.detail;
const scrollingDown = detail.deltaY > 0;
// 滚动到底部附近 → 加载更多
if (detail.scrollTop + detail.scrollHeight
- detail.scrollTop > ...) {
this.loadMore();
}
}
// 触底加载
onIonScroll(event: any) {
const el = event.target;
if (el.scrollTop + el.clientHeight
>= el.scrollHeight - 50) {
this.loadMore();
}
}
手势冲突处理
# 手势与滚动冲突是常见问题
# 1. 水平手势 vs 垂直滚动: 判断方向
# 2. gesturePriority: 优先级
# 3. threshold: 阈值过滤
// 只响应水平手势(让垂直滚动正常工作)
const gesture = this.gestureCtrl.create({
el,
gestureName: "h-swipe",
direction: "x", // 只监听水平
threshold: 20,
...
});
// 或手动判断
onMove(detail: any) {
if (Math.abs(detail.deltaX) <
Math.abs(detail.deltaY)) {
return; // 垂直方向,交给滚动
}
// 处理水平滑动
}
注意事项
# 1. 移动端触摸事件用 touch*,不用 click 判断滑动
# 2. 手势中修改 transform 注意性能(避免 reflow)
# 3. 长按触发系统菜单(禁用 contextmenu)
<div (contextmenu)="preventDefault()">
# 4. 手势后清除状态(防止下次误触发)
# 5. 与组件内置手势(滑动删除)共存
# 常见场景:
# - 轮播图滑动
# - 左滑返回
# - 长按删除
# - 卡片滑动操作
手势让交互"自然流畅"。掌握触摸事件、Gesture 控制器与 Hammer.js,配合方向判断,移动端交互体验专业。
ionic 头部和底部
Ionic 头部和底部(进阶)
本节深入头部与底部的进阶用法:动态标题、滚动隐藏、多彩工具栏、透明效果与多行布局。
动态标题
<!-- 动态标题 -->
<ion-header>
<ion-toolbar>
<ion-title>{{ pageTitle }}</ion-title>
</ion-toolbar>
</ion-header>
// TS
pageTitle = "首页";
// 数据加载后更新
this.api.getData().subscribe(data => {
this.pageTitle = data.title;
});
<!-- 条件显示按钮 -->
<ion-buttons slot="end" *ngIf="isEditable">
<ion-button (click)="edit()">编辑</ion-button>
</ion-buttons>
滚动隐藏头部
<!-- 向上滚动隐藏,向下显示 -->
<ion-header collapse="condense">
<ion-toolbar>
<ion-title>可折叠标题</ion-title>
</ion-toolbar>
</ion-header>
<!-- 大标题(折叠效果) -->
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">
大标题
</ion-title>
</ion-toolbar>
</ion-header>
# collapse="condense": 滚动时标题缩小
# collapse="fade": 滚动时淡出
# 注意: 需配合页面滚动内容
多彩工具栏
<!-- 渐变背景 -->
<ion-header>
<ion-toolbar class="gradient-toolbar">
<ion-title>渐变头部</ion-title>
</ion-toolbar>
</ion-header>
/* scss */
.gradient-toolbar {
--background: linear-gradient(
90deg, #667eea, #764ba2);
--color: white;
}
<!-- 图片背景 -->
<ion-header>
<ion-toolbar class="img-toolbar">
<ion-title>图片背景</ion-title>
</ion-toolbar>
</ion-header>
.img-toolbar {
--background: url("/assets/banner.jpg")
center/cover no-repeat;
--color: white;
}
<!-- 半透明(毛玻璃) -->
<ion-toolbar translucent>
<ion-title>透明工具栏</ion-title>
</ion-toolbar>
<ion-content fullscreen>...</ion-content>
多行工具栏
<!-- 头部两行: 标题 + 搜索 -->
<ion-header>
<ion-toolbar>
<ion-title>商品列表</ion-title>
</ion-toolbar>
<ion-toolbar>
<ion-searchbar placeholder="搜索商品">
</ion-searchbar>
</ion-toolbar>
</ion-header>
<!-- 底部两行: 操作 + 汇总 -->
<ion-footer>
<ion-toolbar>
<ion-title size="small">
合计: ¥{{ total }}
</ion-title>
</ion-toolbar>
<ion-toolbar>
<ion-button expand="block">
去结算
</ion-button>
</ion-toolbar>
</ion-footer>
头部内容布局
<!-- 左侧返回+标题+右侧操作 -->
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="/">
</ion-back-button>
</ion-buttons>
<ion-title>详情页</ion-title>
<ion-buttons slot="end">
<ion-button>
<ion-icon name="heart-outline">
</ion-icon>
</ion-button>
<ion-button>
<ion-icon name="ellipsis-vertical">
</ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<!-- 返回按钮自定义 -->
<ion-back-button defaultHref="/home"
text="首页"
icon="arrow-back">
</ion-back-button>
底部导航操作
<!-- 底部操作栏 -->
<ion-footer>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button fill="outline">
<ion-icon name="heart"></ion-icon>
收藏
</ion-button>
</ion-buttons>
<ion-buttons slot="end">
<ion-button color="primary">
立即购买
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>
<!-- 底部安全区适配(iPhone 等) -->
<ion-footer>
<ion-toolbar class="safe-bottom">
...
</ion-toolbar>
</ion-footer>
.safe-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
工具栏阴影与边框
/* 去除阴影 */
ion-toolbar {
--border-width: 0;
--box-shadow: none;
}
/* 自定义阴影 */
ion-toolbar {
--box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
/* 底部分割线颜色 */
ion-header {
--border-color: #eee;
}
/* 全局(Theme) */
:root {
--ion-toolbar-border-color: #eee;
--ion-toolbar-box-shadow: none;
}
最佳实践
# 1. 标题/按钮统一放工具栏(自动安全区)
# 2. 返回按钮用 ion-back-button(自带手势)
# 3. 顶部搜索用第二行工具栏
# 4. 底部操作栏避免遮挡内容
# (ion-content 自动处理 padding)
# 5. 渐变/图片头部注意对比度(白字)
# 6. 暗色模式验证两种配色
# 常见问题:
# - 工具栏背景不生效: 用 --background
# - 返回按钮不出现: 无历史页面时给 defaultHref
# - 安全区: 使用内置组件自动适配
头部与底部的进阶用法,让页面"骨架"更专业。掌握折叠、渐变与布局组合,视觉与交互双重提升。
ionic 列表操作
Ionic 列表操作
列表操作涵盖:滑动操作、拖拽排序、点击跳转、分组与空状态。本节实现一个完整的"可管理列表"。
滑动操作
<ion-item-sliding *ngFor="let item of items">
<ion-item>
<ion-label>
<h2>{{ item.title }}</h2>
<p>{{ item.date }}</p>
</ion-label>
</ion-item>
<!-- 左侧操作 -->
<ion-item-options side="start">
<ion-item-option color="success"
(click)="markRead(item)">
<ion-icon name="checkmark"></ion-icon>
已读
</ion-item-option>
</ion-item-options>
<!-- 右侧操作 -->
<ion-item-options side="end">
<ion-item-option (click)="archive(item)">
<ion-icon name="archive"></ion-icon>
归档
</ion-item-option>
<ion-item-option color="danger"
(click)="remove(item)">
<ion-icon name="trash"></ion-icon>
删除
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
点击跳转
<!-- 路由跳转 -->
<ion-item *ngFor="let item of items"
routerLink="/detail/{{ item.id }}">
<ion-label>{{ item.title }}</ion-label>
<ion-icon name="chevron-forward"
slot="end"></ion-icon>
</ion-item>
<!-- 或事件跳转 -->
<ion-item button *ngFor="let item of items"
(click)="openDetail(item)">
...
</ion-item>
// TS
openDetail(item: any) {
this.router.navigate(["/detail", item.id],
{ queryParams: { from: "list" } });
}
拖拽排序
# 安装: npm install @angular/cdk
<!-- CDK 拖拽排序 -->
<ion-list cdkDropList
(cdkDropListDropped)="onDrop($event)">
<ion-item *ngFor="let item of items"
cdkDrag>
<ion-icon name="reorder-three"
slot="start"></ion-icon>
<ion-label>{{ item.title }}</ion-label>
</ion-item>
</ion-list>
// TS
onDrop(event: any) {
const prev = event.previousIndex;
const curr = event.currentIndex;
const moved = this.items.splice(prev, 1)[0];
this.items.splice(curr, 0, moved);
// 保存新顺序
this.saveOrder(this.items);
}
分组与索引
<!-- 按组显示 -->
<ion-list *ngFor="let group of groupedItems">
<ion-list-header>
{{ group.name }}
<ion-badge slot="end">
{{ group.items.length }}
</ion-badge>
</ion-list-header>
<ion-item *ngFor="let item of group.items">
<ion-label>{{ item.name }}</ion-label>
</ion-item>
</ion-list>
// TS 分组
import { groupBy } from "lodash-es";
this.groupedItems = Object.entries(
groupBy(this.allItems, i => i.category)
).map(([name, items]) => ({ name, items }));
加载更多(上拉加载)
<!-- 触底加载更多 -->
<ion-infinite-scroll
(ionInfinite)="loadMore($event)">
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="加载中...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
// TS
page = 1;
hasMore = true;
async loadMore(event: any) {
const newItems = await this.api.fetchPage(this.page++);
if (newItems.length === 0) {
this.hasMore = false;
event.target.complete();
event.target.disabled = true; // 禁用
return;
}
this.items.push(...newItems);
event.target.complete(); // 必须调用
}
搜索与过滤
<!-- 搜索过滤 -->
<ion-searchbar [(ngModel)]="keyword"
(ionInput)="onSearch()">
</ion-searchbar>
<ion-list>
<ion-item *ngFor="let item of filtered">
<ion-label>{{ item.title }}</ion-label>
</ion-item>
</ion-list>
// TS
filtered: any[] = [];
onSearch() {
const kw = this.keyword.trim().toLowerCase();
this.filtered = this.allItems.filter(i =>
i.title.toLowerCase().includes(kw) ||
i.desc?.toLowerCase().includes(kw));
}
空状态与骨架屏
<!-- 空状态 -->
<div class="empty-state" *ngIf="items.length === 0">
<ion-icon name="file-tray-outline"
size="large"></ion-icon>
<h3>暂无数据</h3>
<p>点击刷新或添加第一条数据</p>
<ion-button (click)="load()">刷新</ion-button>
</div>
/* 骨架屏(加载占位) */
<ion-list *ngIf="loading">
<ion-item *ngFor="let _ of [].constructor(5)">
<ion-skeleton-text animated
style="width: 100%"></ion-skeleton-text>
</ion-item>
</ion-list>
最佳实践
# 1. 滑动操作放常用动作(删除/收藏)
# 2. 危险操作(删除)加确认
# 3. 大列表虚拟滚动
# 4. 触底加载 + 下拉刷新配套
# 5. 操作后更新数据与 UI(动画反馈)
# 6. 排序结果持久化
// 删除确认
async remove(item: any) {
const alert = await this.alertCtrl.create({
header: "确认删除",
message: `删除"${item.title}"?`,
buttons: [
{ text: "取消", role: "cancel" },
{
text: "删除",
role: "destructive",
handler: () => this.doDelete(item),
},
],
});
await alert.present();
}
列表操作是数据管理的核心交互。掌握滑动、拖拽、分组、加载与搜索,列表页功能完整且体验流畅。
ionic 加载动作
Ionic 加载动作(Loading)
Loading(加载指示器)在耗时操作期间显示,防止用户重复操作并反馈进度。Ionic 提供 LoadingController。
基础用法
import { LoadingController } from "@ionic/angular";
constructor(private loadingCtrl: LoadingController) {}
async showLoading() {
const loading = await this.loadingCtrl.create({
message: "加载中...",
spinner: "circles",
});
await loading.present();
return loading;
}
// 使用
async loadData() {
const loading = await this.showLoading();
try {
this.data = await this.api.fetch();
} finally {
await loading.dismiss(); // 必须关闭!
}
}
加载动画类型
# spinner 类型:
# circles / bubbles / crescent
# lines / lines-small / dots
const loading = await this.loadingCtrl.create({
message: "处理中",
spinner: "bubbles", // 动画
// spinner: null + 自定义内容
});
// 自定义内容(无 spinner)
const loading = await this.loadingCtrl.create({
message: `
<div class="custom-loading">
<img src="/assets/loading.gif">
<p>请稍候...</p>
</div>`,
spinner: null, // 关闭默认动画
});
自动关闭与时长
// 固定时长自动关闭
const loading = await this.loadingCtrl.create({
message: "上传中...",
duration: 3000, // 3秒自动关闭
});
await loading.present();
// 点击背景关闭
const loading = await this.loadingCtrl.create({
message: "处理中",
backdropDismiss: true, // 可点背景关闭
});
// 判断是否已关闭
if (!loading) return;
await loading.dismiss();
防止重复提交
// 提交按钮防抖
isSubmitting = false;
async submit() {
if (this.isSubmitting) return; // 防重复
this.isSubmitting = true;
const loading = await this.loadingCtrl.create({
message: "提交中...",
});
await loading.present();
try {
await this.api.submit(this.form.value);
} finally {
this.isSubmitting = false;
await loading.dismiss();
}
}
配合 HTTP 请求
// 请求期间显示加载
async fetchUsers() {
const loading = await this.loadingCtrl.create({
message: "获取用户...",
});
await loading.present();
try {
const data = await firstValueFrom(
this.api.getUsers());
this.users = data;
} catch (err) {
console.error(err);
this.errorMsg = "请求失败";
} finally {
await loading.dismiss();
}
}
// 或全局拦截器(统一处理)
// Angular HTTP Interceptor 中
// 请求发起显示 loading,完成关闭
样式定制
// cssClass
const loading = await this.loadingCtrl.create({
message: "加载中",
cssClass: "my-loading",
});
await loading.present();
/* 全局 scss */
.my-loading {
--background: rgba(0, 0, 0, 0.8);
--spinner-color: #fff;
color: #fff;
border-radius: 12px;
}
/* 或直接改全局 */
ion-loading {
--background: #222;
--spinner-color: #fff;
--color: #fff;
}
与进度条对比
# Loading: 不确定时长的操作(转圈)
# Progress Bar: 确定进度(上传百分比)
<!-- 进度条(上传等) -->
<ion-progress-bar [value]="uploadProgress"
color="primary">
</ion-progress-bar>
// TS
uploadProgress = 0;
// 更新进度
this.uploadProgress = event.loaded / event.total;
注意事项
# 1. present 后必须 dismiss(finally 中)
# 2. 多个 loading 叠加: 用计数器管理
# 3. 短暂操作(<200ms)可省略 loading
# 4. 关闭前判断 isPresented(避免报错)
// 安全关闭
private loading: any;
async safeDismiss() {
if (this.loading) {
await this.loading.dismiss();
this.loading = null;
}
}
// 防抖显示(操作太快不闪 loading)
let timer: any;
const show = () => {
timer = setTimeout(async () => {
this.loading = await this.loadingCtrl.create({
message: "加载中" });
await this.loading.present();
}, 200);
};
// 完成后
clearTimeout(timer);
await this.loading?.dismiss();
Loading 是耗时操作的"状态反馈"。掌握创建/关闭契约与防重复,配合请求流程,交互反馈专业。
ionic 模态窗口
Ionic 模态窗口(Modal)
模态窗口(Modal)是覆盖在当前页面上的全屏或部分屏内容,用于表单填写、详情查看、筛选等需要专注的场景。
基础用法
import { ModalController } from "@ionic/angular";
constructor(private modalCtrl: ModalController) {}
// 打开模态
async openModal() {
const modal = await this.modalCtrl.create({
component: AddItemPage, // 模态内容组件
componentProps: {
title: "新增待办",
item: { id: 1, name: "示例" },
},
});
// 组件返回数据
modal.onDidDismiss().then((data) => {
console.log("返回:", data.data, data.role);
if (data.data) {
this.refresh(data.data);
}
});
await modal.present();
}
模态组件内部
// add-item.page.ts
import { ModalController } from "@ionic/angular";
@Component({...})
export class AddItemPage {
// 接收父页面数据
title = "";
item: any;
constructor(private modalCtrl: ModalController) {}
// 关闭并返回数据
save() {
this.modalCtrl.dismiss(
{ name: "新数据" }, // data
"save" // role
);
}
// 直接关闭(无数据)
cancel() {
this.modalCtrl.dismiss(null, "cancel");
}
}
弹出方式(动画)
// 默认: 全屏滑入(类似新页面)
// 底部弹出
const modal = await this.modalCtrl.create({
component: FilterPage,
cssClass: "bottom-sheet",
});
await modal.present();
/* 全局 scss */
.bottom-sheet {
--height: 70%;
--border-radius: 20px 20px 0 0;
align-items: flex-end;
}
// 居中弹窗(小型)
.modal-center {
--width: 85%;
--height: auto;
--border-radius: 16px;
}
筛选器案例
// 父页面
async openFilter() {
const modal = await this.modalCtrl.create({
component: FilterPage,
componentProps: {
currentFilter: this.filter,
},
cssClass: "bottom-sheet",
});
modal.onDidDismiss().then(({ data }) => {
if (data) {
this.filter = data;
this.loadList();
}
});
await modal.present();
}
// 子页面(filter.page.ts)
save() {
this.modalCtrl.dismiss(this.form.value, "apply");
}
reset() {
this.modalCtrl.dismiss(null, "reset");
}
表单模态(添加/编辑)
// 新增编辑通用
async openEditor(item?: any) {
const modal = await this.modalCtrl.create({
component: ItemEditorPage,
componentProps: { item: item || null },
});
const { data, role } = await modal.onWillDismiss();
if (role === "save" && data) {
// 新增或更新
if (item) {
await this.api.update(item.id, data);
} else {
await this.api.create(data);
}
this.loadList();
}
}
导航到模态
// 模态内可以包含多个页面(导航)
<!-- modal-root.page.html -->
<ion-nav>
<ng-container *ngFor="let page of pages">
<ion-nav-link [component]="page.component"
[componentProps]="page.props">
</ion-nav-link>
</ng-container>
</ion-nav>
// 或手动管理
// 用 ion-nav 支持模态内多级导航
// 适合: 多步表单、向导流程
获取返回数据
// 方式1: onDidDismiss(关闭后)
modal.onDidDismiss().then((result) => {
const { data, role } = result;
});
// 方式2: onWillDismiss(关闭前)
modal.onWillDismiss().then((result) => {
// 可在这里拦截处理
});
// 子组件主动关闭
this.modalCtrl.dismiss(data, role);
// 返回值约定:
// data: 业务数据
// role: 'save' / 'cancel' / 'delete' 等语义
注意事项
# 1. 模态组件需要在 module 中声明
# 2. 传参用 componentProps(单向)
# 3. 返回用 dismiss(data, role)
# 4. 表单模态记得关闭时校验
# 5. 底部弹窗高度自适应内容
// 模块声明
@NgModule({
declarations: [AddItemPage],
imports: [IonicModule, CommonModule,
FormsModule],
})
export class AddItemPageModule {}
最佳实践
# 1. 复杂表单用模态(不丢页面上下文)
# 2. 轻量选择用 ActionSheet/Alert
# 3. 底部弹窗放筛选/分享
# 4. 全屏模态放编辑/详情
# 5. 返回值用 role 区分语义
# 6. 模态内表单丢失时给提示(可选)
// 防误关
const modal = await this.modalCtrl.create({
component: EditorPage,
backdropDismiss: false, // 防点背景关闭
});
模态窗口是"上下文专注"的容器。掌握传参/返回值/弹窗样式,筛选、编辑、详情页专业流畅。
ionic 导航
Ionic 导航
导航是移动应用的结构骨架。Ionic 基于 Angular Router 提供页面导航:路由跳转、参数传递、返回与历史管理。
路由配置
// app.routes.ts
import { Routes } from "@angular/router";
export const routes: Routes = [
{ path: "", redirectTo: "/home", pathMatch: "full" },
{ path: "home",
loadChildren: () =>
import("./pages/home/home.page")
.then(m => m.HomePageModule) },
{ path: "detail/:id",
loadChildren: () =>
import("./pages/detail/detail.page")
.then(m => m.DetailPageModule) },
{ path: "**", redirectTo: "/home" }, // 404
];
页面跳转方式
// 方式1: 模板 routerLink
<ion-button routerLink="/home">去首页</ion-button>
<ion-item routerLink="/detail/42">
查看详情 42
</ion-item>
// 方式2: 代码跳转
import { Router } from "@angular/router";
constructor(private router: Router) {}
goHome() {
this.router.navigate(["/home"]);
}
goDetail(id: number) {
this.router.navigate(["/detail", id]);
}
// 方式3: 带查询参数
this.router.navigate(["/list"], {
queryParams: { page: 2, keyword: "python" },
});
// URL: /list?page=2&keyword=python
接收参数
import { ActivatedRoute } from "@angular/router";
constructor(private route: ActivatedRoute) {}
// 路径参数 /detail/42
ngOnInit() {
this.id = this.route.snapshot.paramMap.get("id");
// 监听变化(同一组件复用)
this.route.paramMap.subscribe((params) => {
this.id = params.get("id");
this.loadData();
});
// 查询参数
this.route.queryParamMap.subscribe((q) => {
this.keyword = q.get("keyword");
});
}
返回与历史
// 模板返回按钮
<ion-back-button defaultHref="/home">
</ion-back-button>
// 代码返回
import { Location } from "@angular/common";
import { Router } from "@angular/router";
constructor(
private location: Location,
private router: Router,
) {}
goBack() {
this.location.back(); // 返回上一页
}
// 没有历史时的兜底
goBackSafe() {
const history = this.router
.getCurrentNavigation();
if (history?.previousNavigation) {
this.location.back();
} else {
this.router.navigate(["/home"]);
}
}
导航参数传递(复杂对象)
// 方式1: 状态传递(推荐,可序列化)
this.router.navigate(["/detail"], {
state: { item: this.currentItem },
});
// 接收
const state = this.router.getCurrentNavigation()
?.extras.state;
if (state) {
this.item = state["item"];
}
// 方式2: 服务共享(跨页面)
// data.service.ts
private itemSubject = new BehaviorSubject(null);
setItem(item) { this.itemSubject.next(item); }
getItem() { return this.itemSubject.asObservable(); }
选项卡内导航
// tabs 结构
const routes: Routes = [
{
path: "tabs",
component: TabsPage,
children: [
{ path: "home", component: HomePage },
{ path: "detail/:id", component: DetailPage },
{ path: "", redirectTo: "home", pathMatch: "full" },
],
},
];
// 跳转到 tabs 内页面
this.router.navigate(["/tabs/detail", id]);
// 切换到某个 tab
this.router.navigate(["/tabs/profile"]);
页面生命周期
// Angular 生命周期 + Ionic 生命周期
<!-- Ionic 特有: -->
// ionViewWillEnter: 每次进入页面(含返回)
// ionViewDidEnter
// ionViewWillLeave
// ionViewDidLeave
// 用法: 返回时刷新数据
ionViewWillEnter() {
this.loadData(); // 每次进入都刷新
}
// 与 ngOnInit 区别:
// ngOnInit: 只执行一次(组件创建)
// ionViewWillEnter: 每次进入都执行
导航过渡动画
// 自定义过渡(全局)
// app.module 或路由配置
import { IonicRouteStrategy } from "@ionic/angular";
// 关闭动画
providers: [
{ provide: RouteReuseStrategy,
useClass: IonicRouteStrategy },
];
/* 全局关闭转场动画 */
ion-router-outlet {
--transition-duration: 0ms;
}
// 自定义页面转场(高级)
// 实现 PageTransition 接口
// 参考 Ionic 官方文档自定义转场
最佳实践
# 1. 页面懒加载(loadChildren)
# 2. 参数用路由传递,复杂对象用 state/服务
# 3. 返回按钮给 defaultHref 兜底
# 4. 返回时刷新用 ionViewWillEnter
# 5. 深链接支持(分享/推送直达页面)
# 6. 未登录页面守卫(Router Guard)
// 路由守卫
import { CanActivate } from "@angular/router";
@Injectable({ providedIn: "root" })
export class AuthGuard implements CanActivate {
canActivate() {
return this.auth.isLoggedIn();
}
}
// 路由配置
{ path: "profile",
loadChildren: ...,
canActivate: [AuthGuard] }
导航是应用的"动脉"。掌握路由配置、参数传递与返回管理,配合守卫与生命周期,页面流转顺畅安全。
ionic 平台
Ionic 平台
平台(Platform)能力:检测运行设备(Android/iOS/Web)、屏幕信息、原生能力调用(Capacitor),实现跨平台适配。
检测平台
import { Platform } from "@ionic/angular";
constructor(private platform: Platform) {}
ngOnInit() {
console.log("是 Android:", this.platform.is("android"));
console.log("是 iOS:", this.platform.is("ios"));
console.log("是 Web:", this.platform.is("desktop"));
console.log("是手机:", this.platform.is("mobile"));
// 等待平台就绪
this.platform.ready().then(() => {
console.log("平台就绪(原生API可用)");
});
}
// 常用平台标识:
// android / ios / desktop / mobile
// phablet / tablet / hybrid / mobileweb
屏幕信息
import { Platform } from "@ionic/angular";
// 屏幕尺寸
console.log(this.platform.width()); // 像素宽
console.log(this.platform.height()); // 像素高
// 横竖屏
const isLandscape = this.platform.isLandscape();
const isPortrait = this.platform.isPortrait();
// 监听
this.platform.resize.subscribe(() => {
console.log("尺寸变化:",
this.platform.width());
});
// 设备像素比
const dpr = window.devicePixelRatio;
// 断点判断(类似 CSS)
if (this.platform.is("tablet")) {
// 平板布局
}
Capacitor 原生能力
# Capacitor 是 Ionic 的原生运行时
# 调用相机、定位、存储等设备能力
# 安装
npm install @capacitor/core @capacitor/cli
npx cap init
npm install @capacitor/camera @capacitor/geolocation
# 使用相机
import { Camera, CameraResultType } from "@capacitor/camera";
async takePhoto() {
const image = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
});
this.photoUrl = image.webPath;
}
# 使用定位
import { Geolocation } from "@capacitor/geolocation";
async getLocation() {
const pos = await Geolocation.getCurrentPosition();
console.log(pos.coords.latitude, pos.coords.longitude);
}
本地存储
# Web: localStorage(简单)
localStorage.setItem("key", "value");
const v = localStorage.getItem("key");
# 跨端安全存储(Capacitor Preferences)
import { Preferences } from "@capacitor/preferences";
// 保存
await Preferences.set({
key: "user",
value: JSON.stringify(user),
});
// 读取
const { value } = await Preferences.get({
key: "user",
});
const user = value ? JSON.parse(value) : null;
// 删除
await Preferences.remove({ key: "user" });
网络状态检测
import { Network } from "@capacitor/network";
// 当前状态
const status = await Network.getStatus();
console.log("在线:", status.connected);
console.log("类型:", status.connectionType);
// 监听变化
Network.addListener("networkStatusChange",
(status) => {
console.log("网络变化:", status.connected);
this.online = status.connected;
});
// 离线提示
<div class="offline-banner" *ngIf="!online">
网络连接已断开
</div>
深链接与推送
# 深链接(App 打开 URL)
import { App, URLOpenListenerEvent } from "@capacitor/app";
App.addListener("appUrlOpen", (event: URLOpenListenerEvent) => {
console.log("打开链接:", event.url);
const url = new URL(event.url);
const id = url.searchParams.get("id");
this.router.navigate(["/detail", id]);
});
# 推送通知(需 @capacitor/push-notifications)
import { PushNotifications } from "@capacitor/push-notifications";
// 请求权限并注册
PushNotifications.requestPermissions();
PushNotifications.register();
// 监听通知
PushNotifications.addListener("pushNotificationReceived",
(notification) => { ... });
平台条件渲染
<!-- 模板中判断 -->
<ion-button *ngIf="platform.is('android')"
(click)="scanQR()">
扫码(仅 Android)
</ion-button>
<!-- 平台特定样式 -->
<div class="app-content">...</div>
/* scss: 平台样式 */
.platform-ios .app-content {
padding-top: 44px; /* iPhone 状态栏 */
}
.platform-android .app-content {
padding-top: 24px; /* 通知栏 */
}
/* 或 CSS 媒体查询 */
@media (hover: none) and (pointer: coarse) {
/* 触屏设备 */
}
// TS 判断(平台差异逻辑)
getButtonText() {
return this.platform.is("ios") ? "iCloud 同步"
: "云同步";
}
构建与签名
# Web
ionic build
# Android
ionic build
npx cap add android
npx cap open android # Android Studio
# 生成 APK/AAB: Build → Build App Bundle
# iOS(需 macOS + Xcode)
npx cap add ios
npx cap open ios
# 签名与上架: Xcode 配置
# 更新原生项目
npx cap sync
# 调试真机
npx cap run android --device
最佳实践
# 1. 平台检测尽量少用(优先响应式)
# 2. 原生能力封装成服务(可 mock 测试)
# 3. 离线场景缓存数据
# 4. 状态栏/安全区适配
# 5. 性能: 减少 bridge 调用(原生-Web)
# 安全区适配
<ion-content>
<div style="padding-top: env(safe-area-inset-top)">
</div>
</ion-content>
# 状态栏
import { StatusBar } from "@capacitor/status-bar";
await StatusBar.setBackgroundColor({ color: "#3880ff" });
平台能力让应用"多端一致"。掌握平台检测、Capacitor 原生调用与构建流程,一次开发多端上线。
ionic 浮动框
Ionic 浮动框(Popover)
浮动框(Popover)是依附于触发元素的弹出层,类似桌面端的右键菜单/气泡。适合小菜单、快捷操作与提示。
基础用法
import { PopoverController } from "@ionic/angular";
constructor(private popoverCtrl: PopoverController) {}
// 打开浮动框
async openMenu(event: any) {
const popover = await this.popoverCtrl.create({
component: MenuPopoverPage, // 浮动内容
event: event, // 触发位置
translucent: true, // 半透明
showBackdrop: false, // 无背景层
});
await popover.present();
// 接收返回值
const { data, role } = await popover.onDidDismiss();
if (data) {
this.handleAction(data.action);
}
}
浮动框内容组件
// menu-popover.page.html
<ion-content>
<ion-list>
<ion-item button (click)="choose('edit')">
<ion-icon name="create" slot="start">
</ion-icon>
编辑
</ion-item>
<ion-item button (click)="choose('share')">
<ion-icon name="share" slot="start">
</ion-icon>
分享
</ion-item>
<ion-item button (click)="choose('delete')">
<ion-icon name="trash" slot="start"
color="danger">
</ion-icon>
<ion-label color="danger">删除</ion-label>
</ion-item>
</ion-list>
</ion-content>
// menu-popover.page.ts
import { PopoverController } from "@ionic/angular";
export class MenuPopoverPage {
constructor(private popoverCtrl: PopoverController) {}
choose(action: string) {
this.popoverCtrl.dismiss({ action }, action);
}
}
尺寸与定位
// 自定义尺寸
const popover = await this.popoverCtrl.create({
component: MenuPage,
event,
size: "auto", // auto / cover / 指定大小
// 或 cssClass 控制
cssClass: "my-popover",
});
await popover.present();
/* 全局 scss */
.my-popover {
--width: 200px;
--max-width: 80%;
--min-width: 150px;
--max-height: 60%;
}
// 定位参考
// event: 以点击位置弹出
// 无 event: 屏幕居中
// 可传 reference: 'event' | 'trigger'
快捷操作菜单
<!-- 触发按钮 -->
<ion-button (click)="openMenu($event)">
<ion-icon name="ellipsis-vertical">
</ion-icon>
</ion-button>
// TS
async openMenu(event: any) {
const popover = await this.popoverCtrl.create({
component: QuickActionsPage,
event,
showBackdrop: false,
});
await popover.present();
}
// 常见操作:
// 编辑 / 复制 / 分享 / 收藏 / 删除
// 置顶 / 移动 / 导出 / 更多
// 配合列表项
<ion-item>
<ion-label>文件.txt</ion-label>
<ion-button slot="end" fill="clear"
(click)="openFileMenu($event, file)">
<ion-icon name="ellipsis-horizontal">
</ion-icon>
</ion-button>
</ion-item>
与 ActionSheet 对比
# Popover(浮动框):
# - 出现在触发元素旁(上下文相关)
# - 适合: 小菜单、快捷操作、提示
# - 桌面/平板体验好
# ActionSheet(上拉菜单):
# - 底部弹出,大按钮
# - 适合: 移动端操作集合(分享/删除)
# - 拇指友好
# 选择:
# 点击点附近 → Popover
# 移动端底部操作 → ActionSheet
# 也可按平台: 手机用 ActionSheet,
# 平板/桌面用 Popover
传参与返回值
// 传参(componentProps)
const popover = await this.popoverCtrl.create({
component: DetailPopoverPage,
componentProps: { item: this.currentItem },
event,
});
// 子组件读取
item: any; // 自动注入 componentProps
// 返回
this.popoverCtrl.dismiss({ result: "ok" }, "action");
// 监听
const { data, role } = await popover.onWillDismiss();
console.log(data, role);
注意事项
# 1. 浮动框组件需在模块声明
# 2. 小屏设备慎用(空间不足)
# 3. showBackdrop=false 更轻量(点外部关闭)
# 4. 层级高,注意与其他弹层冲突
# 5. 动态内容高度自适应(size: auto)
# 常见问题:
# - 位置偏移: 传 event 或 trigger 引用
# - 内容不显示: 组件未声明
# - 关闭不执行: 检查 dismiss 调用
// 关闭全部浮动框
await this.popoverCtrl.dismiss();
浮动框是"上下文菜单"的利器。掌握触发定位、内容组件与返回值,快捷操作体验轻快精准。
ionic 对话框
Ionic 对话框(Alert)
对话框(Alert)是居中弹窗,用于确认、提示、输入与简单选择。打断性强,适合重要信息或必需操作。
基础提示
import { AlertController } from "@ionic/angular";
constructor(private alertCtrl: AlertController) {}
// 简单提示
async showAlert() {
const alert = await this.alertCtrl.create({
header: "提示",
message: "操作成功!",
buttons: ["确定"],
});
await alert.present();
}
// 带副标题与图标
await this.alertCtrl.create({
header: "更新可用",
subHeader: "v2.0.0",
message: "包含新功能与性能优化",
buttons: ["知道了"],
}).present();
确认对话框
// 确认操作
async confirmDelete() {
const alert = await this.alertCtrl.create({
header: "确认删除",
message: "删除后不可恢复,确定继续?",
buttons: [
{ text: "取消", role: "cancel" },
{
text: "删除",
role: "destructive",
handler: () => {
this.doDelete(); // 点击后执行
},
},
],
});
await alert.present();
}
// 处理取消
buttons: [
{
text: "取消",
role: "cancel",
handler: () => console.log("已取消"),
},
{ text: "确定", handler: () => this.doOk() },
]
输入对话框
// 单输入
async promptInput() {
const alert = await this.alertCtrl.create({
header: "重命名",
inputs: [
{
name: "name",
type: "text",
placeholder: "新名称",
value: this.currentName,
},
],
buttons: [
{ text: "取消", role: "cancel" },
{
text: "保存",
handler: (data) => {
console.log("新名称:", data.name);
this.rename(data.name);
},
},
],
});
await alert.present();
}
// 多输入(登录/注册)
inputs: [
{ name: "username", type: "text",
placeholder: "用户名" },
{ name: "password", type: "password",
placeholder: "密码" },
{ name: "email", type: "email",
placeholder: "邮箱" },
]
选择对话框
// 单选
await this.alertCtrl.create({
header: "选择排序方式",
inputs: [
{ type: "radio", label: "最新",
value: "new", checked: true },
{ type: "radio", label: "最热",
value: "hot" },
{ type: "radio", label: "价格",
value: "price" },
],
buttons: [
{ text: "取消", role: "cancel" },
{
text: "确定",
handler: (data) => console.log("选择:", data),
},
],
}).then(a => a.present());
// 多选
inputs: [
{ type: "checkbox", label: "Python",
value: "py", checked: true },
{ type: "checkbox", label: "Java",
value: "java" },
]
// handler: data 是数组
动态按钮与样式
// 动态按钮数组
const buttons: any[] = [
{ text: "取消", role: "cancel" },
];
if (this.canDelete) {
buttons.push({ text: "删除",
role: "destructive",
handler: () => this.delete() });
}
buttons.push({ text: "保存", handler: () => this.save() });
const alert = await this.alertCtrl.create({
header: "管理",
buttons,
});
await alert.present();
// 自定义样式
const alert = await this.alertCtrl.create({
header: "重要通知",
cssClass: "custom-alert",
buttons: ["确定"],
});
await alert.present();
/* 全局 scss */
.custom-alert {
--background: #fff;
--max-width: 320px;
border-radius: 16px;
}
.custom-alert .alert-title {
color: #333;
font-weight: bold;
}
配合表单校验
// 输入校验(handler 返回 false 阻止关闭)
async promptWithValidation() {
const alert = await this.alertCtrl.create({
header: "输入名称",
inputs: [
{ name: "name", type: "text", placeholder: "必填" },
],
buttons: [
{ text: "取消", role: "cancel" },
{
text: "确定",
handler: (data) => {
if (!data.name || !data.name.trim()) {
// 显示错误
const msg = alert.querySelector(".alert-message");
if (msg) msg.textContent = "名称不能为空";
return false; // 阻止关闭
}
return true;
},
},
],
});
await alert.present();
}
注意事项
# 1. handler 返回 false 阻止关闭
# 2. role: cancel/destructive 语义化
# 3. 按钮顺序: 取消在左,确认在右
# 4. 输入框用 form 校验(见上例)
# 5. 同时只能一个 Alert(管理队列)
// 自动关闭(定时)
const alert = await this.alertCtrl.create({
header: "成功",
buttons: [],
});
await alert.present();
setTimeout(() => alert.dismiss(), 1500);
# 选择控件:
# 确认/提示 → Alert
# 多项操作 → ActionSheet
# 复杂表单 → Modal
# 上下文菜单 → Popover
对话框是"打断式交互"的载体。掌握确认/输入/选择三类用法与校验,关键操作可控可感知。
ionic 滚动条
Ionic 滚动条(Scroll)
滚动是移动内容浏览的核心。Ionic 的 ion-content 提供原生滚动,配合滚动事件、滚动到指定位置与自定义滚动容器。
基础滚动
<!-- ion-content 自带滚动 -->
<ion-content>
<!-- 内容超出屏幕自动滚动 -->
<div *ngFor="let item of manyItems">
{{ item }}
</div>
</ion-content>
<!-- 滚动设置 -->
<ion-content scrollY="true"> <!-- 垂直滚动 -->
</ion-content>
<!-- 禁用滚动 -->
<ion-content scrollY="false">
</ion-content>
<!-- 滚动阻尼/惯性 -->
<ion-content [scrollEvents]="true"
(ionScroll)="onScroll($event)">
</ion-content>
滚动到指定位置
import { IonContent } from "@ionic/angular";
@ViewChild(IonContent) content!: IonContent;
// 滚动到底部
scrollToBottom() {
this.content.scrollToBottom(300); // 300ms 动画
}
// 滚动到顶部
scrollToTop() {
this.content.scrollToTop(300);
}
// 滚动到指定像素
scrollToPoint() {
this.content.scrollToPoint(0, 500, 300);
}
// 滚动到元素
scrollToElement() {
this.content.scrollToPoint(
0, 500, 300); // 或用元素 offsetTop
}
// 通过元素滚动(手动)
scrollIntoView(id: string) {
document.getElementById(id)
?.scrollIntoView({ behavior: "smooth" });
}
滚动事件
<ion-content scrollEvents="true"
(ionScrollStart)="onScrollStart()"
(ionScroll)="onScroll($event)"
(ionScrollEnd)="onScrollEnd()">
</ion-content>
// TS
onScrollStart() {
this.scrolling = true;
}
onScroll(event: any) {
const detail = event.detail;
// 滚动位置
const scrollTop = detail.scrollTop;
const maxScroll = detail.scrollHeight
- detail.clientHeight;
// 顶部/底部判断
this.atTop = scrollTop < 10;
this.atBottom = scrollTop > maxScroll - 10;
// 滚动方向
const direction = detail.deltaY > 0
? "down" : "up";
}
onScrollEnd() {
this.scrolling = false;
}
// 应用: 滚动时显示/隐藏头部
<!-- 返回顶部按钮 -->
<ion-fab *ngIf="showTopBtn"
vertical="bottom" horizontal="end"
(click)="scrollToTop()">
<ion-fab-button>
<ion-icon name="arrow-up"></ion-icon>
</ion-fab-button>
</ion-fab>
自定义滚动容器
<!-- 局部滚动区域 -->
<div class="scroll-area">
<div class="scroll-content">
<!-- 可滚动内容 -->
</div>
</div>
/* scss */
.scroll-area {
height: 300px;
overflow-y: auto; /* 关键: 滚动 */
-webkit-overflow-scrolling: touch; /* iOS */
}
/* 自定义滚动条(Web) */
.scroll-area::-webkit-scrollbar {
width: 6px;
}
.scroll-area::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
<!-- 横向滚动 -->
<div class="h-scroll">
<ion-chip *ngFor="let tag of tags">
{{ tag }}
</ion-chip>
</div>
.h-scroll {
display: flex;
overflow-x: auto;
gap: 8px;
-webkit-overflow-scrolling: touch;
}
滚动加载(触底)
<!-- 触底加载 -->
<ion-content scrollEvents="true"
(ionScroll)="onScroll($event)">
<ion-list>...</ion-list>
<!-- 加载指示 -->
<div class="load-more" *ngIf="loading">
<ion-spinner name="crescent"></ion-spinner>
加载中...
</div>
</ion-content>
// TS
onScroll(event: any) {
const el = event.target;
const nearBottom = el.scrollTop
+ el.clientHeight >= el.scrollHeight - 100;
if (nearBottom && !this.loading && this.hasMore) {
this.loadMore();
}
}
// 或用 IonInfiniteScroll(组件方案)
<ion-infinite-scroll (ionInfinite)="loadMore($event)">
<ion-infinite-scroll-content>
</ion-infinite-scroll-content>
</ion-infinite-scroll>
滚动性能优化
# 1. 大量列表用虚拟滚动
<ion-virtual-scroll [items]="items">
<ion-item *virtualItem="let item">
{{ item.name }}
</ion-item>
</ion-virtual-scroll>
# 2. 避免滚动时复杂计算
# 3. 图片懒加载
<img [src]="item.thumb" loading="lazy">
# 4. 事件节流(throttle)
import { throttleTime } from "rxjs/operators";
// 滚动事件节流(60fps 内最多触发一次)
this.content.ionScroll
.pipe(throttleTime(100))
.subscribe((e) => this.onScroll(e));
# 5. 固定高度容器(减少 reflow)
# 6. iOS 橡皮筋效果(overscroll)
ion-content {
overscroll-behavior: contain;
}
滚动位置恢复
// 返回时恢复滚动位置
import { IonContent } from "@ionic/angular";
@ViewChild(IonContent) content!: IonContent;
private savedPosition = 0;
ionViewWillEnter() {
// 恢复位置
this.content.scrollToPoint(0, this.savedPosition, 0);
}
ionViewWillLeave() {
// 保存位置
this.content.getScrollElement().then((el) => {
this.savedPosition = el.scrollTop;
});
}
// 或监听 ionScroll 持续保存
onScroll(event: any) {
this.savedPosition = event.detail.scrollTop;
}
注意事项
# 1. 滚动事件高频触发,注意性能
# 2. scrollEvents=true 才触发事件
# 3. 嵌套滚动(内层 scroll)注意手势冲突
# 4. 固定头部/底部时内容滚动区适配
# 5. iOS 回弹效果是特性,勿强行禁用
# 常见问题:
# - 事件不触发: 未开 scrollEvents
# - 滚动卡顿: 列表未虚拟化
# - 位置不准: scrollToPoint 单位像素
# - 冲突: 横向滚动容器内禁纵向手势
滚动是内容的"流动方式"。掌握事件、定位与性能优化,长页面浏览流畅舒适。
ionic 侧栏菜单
Ionic 侧栏菜单
侧栏菜单(Side Menu / Menu)从屏幕侧边滑出导航或工具面板,是抽屉式导航的标配。
基础用法
<!-- 菜单容器 -->
<ion-menu contentId="main-content">
<ion-header>
<ion-toolbar>
<ion-title>菜单</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item button (click)="goTo('home')">
<ion-icon name="home" slot="start">
</ion-icon>
首页
</ion-item>
<ion-item button (click)="goTo('profile')">
<ion-icon name="person" slot="start">
</ion-icon>
我的
</ion-item>
</ion-list>
</ion-content>
</ion-menu>
<!-- 内容区域(id 对应) -->
<ion-router-outlet id="main-content">
</ion-router-outlet>
菜单开关
import { MenuController } from "@ionic/angular";
constructor(private menuCtrl: MenuController) {}
// 打开/关闭
openMenu() { this.menuCtrl.open(); }
closeMenu() { this.menuCtrl.close(); }
toggleMenu() { this.menuCtrl.toggle(); }
// 指定菜单
this.menuCtrl.open("main-menu");
// 头部按钮开关
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
<!-- 自动关联菜单 -->
</ion-buttons>
<ion-title>首页</ion-title>
</ion-toolbar>
</ion-header>
// 手势滑动开关(默认启用)
// menu.swipeGesture = false; // 禁用滑动
侧边与布局
<!-- 左侧(默认) -->
<ion-menu side="start" contentId="main">
</ion-menu>
<!-- 右侧 -->
<ion-menu side="end" contentId="main">
</ion-menu>
<!-- 两个菜单(左右各一) -->
<ion-menu side="start" menu-id="left-menu"
contentId="main">
</ion-menu>
<ion-menu side="end" menu-id="right-menu"
contentId="main">
</ion-menu>
<!-- 菜单宽度 -->
/* scss */
ion-menu {
--width: 280px;
--max-width: 80%;
--min-width: 200px;
}
菜单内容: 用户信息
<ion-menu contentId="main">
<ion-content>
<!-- 用户信息头部 -->
<div class="menu-profile"
(click)="goProfile()">
<ion-avatar>
<img [src]="user.avatar">
</ion-avatar>
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<!-- 菜单项 -->
<ion-list>
<ion-item button *ngFor="let item of menuItems"
(click)="navigate(item.url)">
<ion-icon [name]="item.icon"
slot="start"></ion-icon>
<ion-label>{{ item.label }}</ion-label>
<ion-badge *ngIf="item.badge"
color="danger">
{{ item.badge }}
</ion-badge>
</ion-item>
</ion-list>
<!-- 底部操作 -->
<div class="menu-footer">
<ion-item button (click)="logout()">
<ion-icon name="log-out" slot="start">
</ion-icon>
退出登录
</ion-item>
</div>
</ion-content>
</ion-menu>
/* scss */
.menu-profile {
padding: 24px 16px;
background: linear-gradient(135deg,
#667eea, #764ba2);
color: white;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.menu-footer {
position: absolute;
bottom: 0;
width: 100%;
}
导航联动
// 点击菜单项: 跳转并关闭
async navigate(url: string) {
await this.menuCtrl.close(); // 先关闭
this.router.navigate([url]);
}
// 菜单高亮当前项
currentUrl = "";
ngOnInit() {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.currentUrl = event.url;
}
});
}
// 模板判断高亮
<ion-item button
[class.active]="currentUrl === item.url">
/* 高亮样式 */
ion-item.active {
--background: #e8f0fe;
color: #3880ff;
font-weight: bold;
}
事件与状态
// 菜单事件
this.menuCtrl.events().subscribe((event) => {
console.log("菜单事件:", event.type);
// menuWillOpen / menuDidOpen
// menuWillClose / menuDidClose
});
// 判断状态
const isOpen = await this.menuCtrl.isOpen();
// 禁用/启用
this.menuCtrl.enable(false, "left-menu");
// 打开时锁定内容滚动
ion-menu {
--backdrop-opacity: 0.4;
}
响应式: 桌面显示固定侧栏
// 桌面端固定侧栏(非抽屉)
// 用 CSS 媒体查询
@media (min-width: 992px) {
ion-menu {
--width: 250px;
}
// 内容推挤而非覆盖
ion-menu::part(container) {
position: relative;
}
}
// 或按平台配置
// 桌面: 菜单常驻显示
if (this.platform.is("desktop")) {
await this.menuCtrl.open();
this.menuCtrl.swipeGesture = false;
}
// 或者使用 split-pane(推荐)
<ion-split-pane when="lg">
<ion-menu contentId="main">...</ion-menu>
<ion-router-outlet id="main">
</ion-router-outlet>
</ion-split-pane>
// 大屏: 侧栏常驻; 小屏: 抽屉
注意事项
# 1. contentId 必须与内容 id 匹配
# 2. 菜单内跳转记得关闭
# 3. 多菜单用 menu-id 区分
# 4. 手势滑动与横向滚动冲突时禁用
# 5. 桌面用 split-pane 更友好
# 常见问题:
# - 菜单不显示: contentId 不匹配
# - 打不开: enable(false) 或手势禁用
# - 内容穿透: 检查 z-index/背景
侧栏菜单是"抽屉导航"的标准方案。掌握打开方式、用户信息区与导航联动,再配合 split-pane 响应式,导航体验完整。
ionic 滑动框
Ionic 滑动框(Slides/Carousel)
滑动框(轮播/幻灯片)用于展示多张图片或页面:首页 Banner、引导页、图片查看等。Ionic 集成 Swiper 实现。
基础轮播
<!-- Swiper 轮播(现代 Ionic) -->
<ion-slides> <!-- 旧版(已弃用) -->
# 新版使用 Swiper:
# 安装
npm install swiper
# 引入(angular.json 或组件中)
import { SwiperModule } from "swiper/angular";
imports: [SwiperModule]
// 模板
<swiper>
<ng-template swiperSlide *ngFor="let img of images">
<img [src]="img">
</ng-template>
</swiper>
Swiper 配置
// TS 配置
slidesPerView = 1;
pagination = true;
navigation = true;
autoplay = { delay: 3000, disableOnInteraction: false };
loop = true;
<!-- 模板绑定 -->
<swiper [slidesPerView]="slidesPerView"
[pagination]="pagination"
[autoplay]="autoplay"
[loop]="loop">
<ng-template swiperSlide *ngFor="let b of banners">
<img [src]="b.image">
</ng-template>
</swiper>
// 常见配置:
// slidesPerView: 每屏显示数
// spaceBetween: 间距
// loop: 循环
// autoplay: 自动播放
// pagination: 分页点
// speed: 动画速度
// direction: horizontal/vertical
首页 Banner 案例
<div class="banner-wrapper">
<swiper [slidesPerView]="1"
[pagination]="{ clickable: true }"
[autoplay]="{ delay: 4000 }"
[loop]="true"
(swiperSlideChange)="onSlide($event)">
<ng-template swiperSlide
*ngFor="let banner of banners">
<div class="banner-item"
(click)="openBanner(banner)">
<img [src]="banner.image">
<div class="banner-title">
{{ banner.title }}
</div>
</div>
</ng-template>
</swiper>
</div>
/* scss */
.banner-wrapper {
height: 160px;
border-radius: 12px;
overflow: hidden;
margin: 8px 0;
}
.banner-item {
position: relative;
height: 100%;
img { width: 100%; height: 100%;
object-fit: cover; }
}
.banner-title {
position: absolute;
bottom: 0; left: 0; right: 0;
padding: 24px 12px 8px;
background: linear-gradient(transparent, rgba(0,0,0,.6));
color: white;
font-size: 14px;
}
多图滑动(商品图/画廊)
<!-- 商品轮播 -->
<swiper [slidesPerView]="1"
[pagination]="{ clickable: true }">
<ng-template swiperSlide
*ngFor="let img of product.images">
<div class="product-img">
<img [src]="img">
</div>
</ng-template>
</swiper>
<!-- 多列滑动(推荐) -->
<swiper [slidesPerView]="2.5"
[spaceBetween]="8"
[freeMode]="true">
<ng-template swiperSlide
*ngFor="let item of recommendations">
<ion-card>...</ion-card>
</ng-template>
</swiper>
// freeMode: 自由滑动(无吸附)
滑动事件
<swiper (swiperSlideChange)="onSlideChange($event)"
(swiperReachBeginning)="onFirst()"
(swiperReachEnd)="onLast()">
</swiper>
// TS(通过 Swiper 实例)
onSlideChange(event: any) {
const swiper = event.detail[0];
console.log("当前页:", swiper.activeIndex);
this.currentIndex = swiper.activeIndex;
}
// 获取实例(程序控制)
import Swiper from "swiper";
@ViewChild("mySwiper") swiperRef: any;
nextSlide() {
this.swiperRef.swiper.slideNext();
}
prevSlide() {
this.swiperRef.swiper.slidePrev();
}
goToSlide(n: number) {
this.swiperRef.swiper.slideTo(n);
}
引导页案例
<!-- 首次启动引导 -->
<swiper [slidesPerView]="1">
<ng-template swiperSlide *ngFor="let page of pages">
<div class="onboarding">
<img [src]="page.image">
<h2>{{ page.title }}</h2>
<p>{{ page.desc }}</p>
</div>
</ng-template>
</swiper>
<!-- 底部按钮(最后一页显示) -->
<ion-button expand="block"
*ngIf="currentIndex === pages.length - 1"
(click)="finishOnboarding()">
开始使用
</ion-button>
// TS
pages = [
{ title: "欢迎", desc: "...", image: "..." },
{ title: "功能", desc: "...", image: "..." },
{ title: "开始", desc: "...", image: "..." },
];
finishOnboarding() {
localStorage.setItem("onboarded", "true");
this.router.navigate(["/home"]);
}
图片查看器(全屏)
<!-- 全屏图片查看 -->
<ion-modal [isOpen]="showViewer">
<ng-template>
<ion-content>
<swiper [initialSlide]="viewIndex"
[zoom]="true"> <!-- 支持缩放 -->
<ng-template swiperSlide
*ngFor="let img of images">
<div class="viewer-wrap">
<img [src]="img" (click)="close()">
</div>
</ng-template>
</swiper>
<ion-button fill="clear" color="light"
class="close-btn"
(click)="close()">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-content>
</ng-template>
</ion-modal>
/* 缩放: swiper [zoom]="true" 需引入 Zoom 模块 */
注意事项
# 1. 旧版 ion-slides 已弃用,用 swiper
# 2. swiperSlideChange 事件取实例方式
# 3. 图片懒加载(大数据量)
# 4. 自动播放注意用户交互暂停
# 5. 高度适配内容(容器固定高度)
# 常见问题:
# - 滑动不生效: 模块未导入
# - 分页点不显示: pagination 配置
# - 循环异常: loop 与 slidesPerView 配合
# - 图片变形: object-fit: cover
# 性能: 轮播图用 CDN 压缩图
# 懒加载: <img loading="lazy">
滑动框让内容"流动起来"。掌握 Swiper 配置、事件与多场景应用,轮播、画廊、引导页一次到位。
ionic 加载动画
Ionic 加载动画(Spinner/Skeleton)
加载动画反馈数据加载状态,提升体验。Ionic 提供 Spinner(转圈)、Skeleton(骨架屏)与 Progress(进度)多种形式。
Spinner 转圈
<!-- 基础 -->
<ion-spinner></ion-spinner>
<!-- 类型 -->
<ion-spinner name="circles"></ion-spinner>
<ion-spinner name="bubbles"></ion-spinner>
<ion-spinner name="crescent"></ion-spinner>
<ion-spinner name="dots"></ion-spinner>
<ion-spinner name="lines"></ion-spinner>
<ion-spinner name="lines-small"></ion-spinner>
<!-- 尺寸与颜色 -->
<ion-spinner name="crescent"
color="primary"
style="font-size: 32px;">
</ion-spinner>
/* scss 定制 */
ion-spinner {
--spinner-color: #3880ff;
}
/* 全屏居中加载 */
.loading-center {
display: flex;
justify-content: center;
align-items: center;
height: 60vh;
}
骨架屏(Skeleton)
<!-- 列表骨架屏 -->
<ion-list *ngIf="loading">
<ion-item *ngFor="let _ of [].constructor(6)">
<ion-avatar slot="start">
<ion-skeleton-text></ion-skeleton-text>
</ion-avatar>
<ion-label>
<h3>
<ion-skeleton-text
animated style="width: 60%">
</ion-skeleton-text>
</h3>
<p>
<ion-skeleton-text
animated style="width: 90%">
</ion-skeleton-text>
</p>
</ion-label>
</ion-item>
</ion-list>
<!-- 卡片骨架屏 -->
<ion-card *ngIf="loading">
<ion-skeleton-text animated
style="width: 100%; height: 180px;">
</ion-skeleton-text>
<ion-card-header>
<ion-skeleton-text animated
style="width: 70%">
</ion-skeleton-text>
</ion-card-header>
</ion-card>
// animated: 呼吸动画
// 真实数据到达后 *ngIf 切换
内容切换模式
// 骨架屏与内容切换
loading = true;
ngOnInit() {
this.loadData();
}
async loadData() {
this.loading = true; // 显示骨架
try {
this.items = await this.api.fetch();
} finally {
this.loading = false; // 显示内容
}
}
<!-- 模板: 三态切换 -->
<!-- 加载中: 骨架屏 -->
<ion-skeleton-text *ngIf="loading">...</ion-skeleton-text>
<!-- 有数据: 内容 -->
<ion-list *ngIf="!loading && items.length">...</ion-list>
<!-- 空: 空状态 -->
<div *ngIf="!loading && !items.length">...</div>
Progress 进度条
<!-- 确定进度 -->
<ion-progress-bar [value]="progress"
color="primary">
</ion-progress-bar>
<!-- 不确定进度(缓冲动画) -->
<ion-progress-bar type="indeterminate">
</ion-progress-bar>
<!-- 缓冲区 -->
<ion-progress-bar [value]="0.5"
[buffer]="0.8">
</ion-progress-bar>
// TS 更新进度
uploadProgress = 0;
onUpload(event: any) {
this.uploadProgress =
event.loaded / event.total;
}
// 颜色
<ion-progress-bar value="0.8"
color="success">
</ion-progress-bar>
/* 自定义 */
ion-progress-bar {
--progress-background: #3880ff;
--buffer-background: #e8f0fe;
height: 6px;
border-radius: 3px;
}
加载状态组合
<!-- 按钮加载 -->
<ion-button [disabled]="loading">
<ion-spinner name="crescent"
*ngIf="loading">
</ion-spinner>
<span *ngIf="!loading">提交</span>
<span *ngIf="loading">提交中...</span>
</ion-button>
<!-- 刷新指示 -->
<ion-refresher-content
refreshingSpinner="circles"
refreshingText="刷新中...">
</ion-refresher-content>
<!-- 加载更多 -->
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="加载更多...">
</ion-infinite-scroll-content>
最佳实践
# 1. 首选骨架屏(体验最好,不跳屏)
# 2. 局部加载用 spinner
# 3. 上传/下载用进度条
# 4. 加载完成平滑过渡(淡入)
# 5. 加载失败给重试按钮
// 淡入过渡
.content-loaded {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
# 6. 避免加载动画闪烁:
# 延迟 200ms 显示骨架(快速请求不闪)
# 7. 骨架屏形状尽量接近真实内容
加载动画是体验的"润滑剂"。掌握 Spinner/Skeleton/Progress 三种形态与三态切换,加载过程优雅不焦虑。
ionic 选项卡栏操作
Ionic 选项卡栏操作(Tab Bar 进阶)
本节深入选项卡栏的进阶操作:角标管理、隐藏/显示、自定义样式、中间凸起按钮与动态切换。
角标管理
<ion-tab-button tab="messages">
<ion-icon name="chatbubbles"></ion-icon>
<ion-label>消息</ion-label>
<!-- 角标 -->
<ion-badge color="danger" *ngIf="unread > 0">
{{ unread > 99 ? "99+" : unread }}
</ion-badge>
<!-- 小红点 -->
<span class="dot" *ngIf="hasNew"></span>
</ion-tab-button>
// TS 动态更新
unread = 5;
hasNew = false;
// 收到消息
onMessage() {
this.unread++;
}
// 已读
markRead() {
this.unread = 0;
this.hasNew = false;
}
/* 小红点样式 */
.dot {
position: absolute;
top: 4px;
right: 14px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #eb445a;
}
隐藏与显示
// 某些页面隐藏 Tab 栏
// 方案1: *ngIf 控制(动态)
<ion-tab-bar slot="bottom"
*ngIf="showTabBar">
</ion-tab-bar>
// TS
showTabBar = true;
// 进入隐藏页面前
ionViewWillEnter() {
this.showTabBar = !this.hideOnThisPage;
}
// 方案2: 路由配置(custom 页面)
// 把页面放在 tabs 外(独立路由)
{ path: "fullscreen", component: FullscreenPage }
// 方案3: 样式隐藏
ion-tab-bar {
display: none;
}
自定义样式
/* 全局 Tab 样式 */
ion-tab-bar {
--background: #ffffff;
--border: 1px solid #eee;
--color: #999; /* 未选中 */
--color-selected: #3880ff; /* 选中 */
height: 60px;
}
/* 选中项放大动画 */
ion-tab-button {
transition: transform 0.2s;
}
ion-tab-button.tab-selected {
transform: scale(1.1);
color: #3880ff;
}
/* 图标底色 */
ion-tab-button.tab-selected ion-icon {
background: #e8f0fe;
border-radius: 50%;
padding: 4px;
}
/* 暗色模式 */
@media (prefers-color-scheme: dark) {
ion-tab-bar {
--background: #1e1e1e;
--border: 1px solid #333;
--color: #888;
--color-selected: #8a7dff;
}
}
中间凸起按钮
<!-- 发布按钮(凸起) -->
<ion-tab-bar slot="bottom">
<ion-tab-button tab="home">...</ion-tab-button>
<!-- 中间发布 -->
<div class="center-btn"
(click)="openPublish()">
<ion-icon name="add"></ion-icon>
</div>
<ion-tab-button tab="profile">...</ion-tab-button>
</ion-tab-bar>
/* scss */
.center-btn {
width: 52px;
height: 52px;
border-radius: 50%;
background: linear-gradient(135deg,
#667eea, #764ba2);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
margin-top: -24px; /* 凸起 */
box-shadow: 0 4px 12px rgba(102,126,234,.4);
}
.center-btn ion-icon {
font-size: 28px;
}
程序化切换与刷新
import { IonTabs } from "@ionic/angular";
@ViewChild(IonTabs) tabs!: IonTabs;
// 切换到指定 tab
goToTab(name: string) {
this.tabs.select(name);
}
// 获取当前 tab
getCurrentTab() {
return this.tabs.getSelected();
}
// 事件: tab 变更
this.tabs.ionTabsDidChange.subscribe(({ tab }) => {
console.log("当前 tab:", tab);
// 切回首页刷新
if (tab === "home") {
this.refreshHome();
}
});
// 双击刷新(常见交互)
lastTap = 0;
onTabClick(name: string) {
const now = Date.now();
if (now - this.lastTap < 300
&& name === this.tabs.getSelected()) {
this.refresh(); // 双击刷新
}
this.lastTap = now;
}
Tab 与数据状态
// 跨 tab 共享状态(服务)
@Injectable({ providedIn: "root" })
export class CartService {
private count = new BehaviorSubject(0);
count$ = this.count.asObservable();
add() { this.count.next(this.count.value + 1); }
}
// 购物车角标
export class TabPage {
cartCount = 0;
constructor(private cart: CartService) {
this.cart.count$.subscribe(n => {
this.cartCount = n;
});
}
}
// 模板
<ion-badge color="danger" *ngIf="cartCount">
{{ cartCount }}
</ion-badge>
注意事项
# 1. 角标更新用服务/Subject(跨页同步)
# 2. 隐藏 Tab 时确保返回机制
# 3. 凸起按钮不占 tab 位(独立 div)
# 4. 选中样式用 tab-selected 类
# 5. 暗色模式完整适配
# 6. 多 tab 数据懒加载
# 常见问题:
# - 角标不同步: 用服务共享
# - 凸起被遮挡: 调整 z-index
# - 隐藏后无法返回: 提供关闭/返回按钮
选项卡栏是 App 的"一级导航+状态中心"。掌握角标、隐藏、凸起按钮与数据共享,导航栏专业且鲜活。
Ionic 教程
Ionic 教程总结
恭喜你完成了 Ionic 教程的学习!本节回顾核心知识,并给出从"会用组件"到"完整上架"的进阶路线。
核心知识地图
# 基础
- 安装与项目创建(CLI/模板)
- 项目结构与运行
# 布局
- 头部/底部(工具栏/slot)
- 网格(12 列响应式)
- 列表/卡片
- 侧栏菜单/选项卡
- 滚动条/滑动框
# 表单
- 输入框/文本域
- 复选框(多选)
- 单选框(互斥)
- Toggle(开关)
- Select(选择器)
- Range(滑块)
# 交互
- 按钮/图标
- 手势事件
- 下拉刷新/上拉加载
- 加载动画(Spinner/骨架屏)
- 对话框(Alert)
- 上拉菜单(ActionSheet)
- 模态窗口(Modal)
- 浮动框(Popover)
# 进阶
- 导航与路由
- 平台与原生能力(Capacitor)
- 颜色主题与暗色模式
实战能力清单
# 学完本教程,你应该能:
# 1. 创建并运行 Ionic 项目
# 2. 搭建完整的页面布局
# 3. 实现各类表单与校验
# 4. 列表增删改查与搜索
# 5. 弹窗/菜单/模态交互
# 6. 多页面导航与参数传递
# 7. 下拉刷新/上拉加载
# 8. 调用相机/定位等原生能力
# 9. 主题定制与暗色适配
# 10. 打包 Android/iOS/Web
# 自测项目:
# - 待办清单(CRUD + 本地存储)
# - 新闻阅读器(列表+详情+收藏)
# - 商城 App(商品+购物车+下单)
# - 记账应用(表单+图表+统计)
进阶学习路线
# 1. 状态管理
# - Angular: Services + RxJS(基础)
# - NgRx / Akita(大型应用)
# 2. 后端对接
# - REST API(HTTPClient/拦截器)
# - 认证(JWT/OAuth)
# - WebSocket(实时)
# 3. 原生能力
# - Capacitor 插件生态
# - 推送/支付/地图/相机
# 4. 性能优化
# - 懒加载/虚拟滚动
# - 代码分割/预渲染
# - 图片优化/缓存
# 5. 测试与发布
# - 单元测试(Jasmine/Karma)
# - E2E 测试(Cypress)
# - App Store / 应用市场发布
资源推荐
# 官方文档(权威)
# https://ionicframework.com/docs
# 官方示例
# https://github.com/ionic-team/ionic
# (ionicons、starter 模板)
# 中文社区
# CSDN / 掘金 / 思否(Ionic 标签)
# 学习建议:
# 1. 官方文档的组件页逐个过一遍
# 2. 用 starter 模板改造成自己的应用
# 3. 真机调试(Capacitor)
# 4. 阅读优秀开源 App 源码
常见误区
# 1. ❌ 只用 Web 思路写移动端
# ✅ 遵循移动端交互习惯(手势/触控区域)
# 2. ❌ 忽略安全区/状态栏适配
# ✅ 用 Ionic 内置组件自动适配
# 3. ❌ 大量原生调用不封装
# ✅ 封装成服务,便于测试与替换
# 4. ❌ 不区分平台差异
# ✅ 按平台优化(桌面/平板/手机)
# 5. ❌ 忽略性能
# ✅ 虚拟滚动/懒加载/图片优化
最后建议
# 1. 动手: 把每个组件在真项目里用一遍
# 2. 模仿: 克隆主流 App 的一个页面练手
# 3. 迭代: 从简单功能逐步完整
# 4. 上架: 完成一个小应用走全流程
# 5. 关注更新: Ionic 与 Capacitor 迭代快
# Ionic 的价值:
# 一套 Web 技能,构建多端应用
# 开发效率高,生态成熟
# 祝你用 Ionic 构建出优秀的移动应用!