《uni-app跨平台开发完全指南》- 04 - 页面布局与样式基础
uni-app:掌握页面布局与样式
新手刚接触uni-app布局可能会遇到以下困惑:明明在模拟器上完美显示的页面,到了真机上就面目全非;iOS上对齐的元素,到Android上就错位几个像素,相信很多开发者都经历过。今天就带大家摸清了uni-app布局样式的门道,把这些经验毫无保留地分享给大家,让你少走弯路。
一、Flex布局
1.1 为什么Flex布局是移动端首选?
传统布局的痛点:
/* 传统方式实现垂直居中 */
.container {
position: relative;
height: 400px;
}
.center {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 100px;
margin-top: -50px; /* 需要计算 */
margin-left: -100px; /* 需要计算 */
}
Flex布局:
/* Flex布局实现垂直居中 */
.container {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
.center {
width: 200px;
height: 100px;
}
从对比中不难看出,Flex布局用更少的代码、更清晰的逻辑解决了复杂的布局问题。
1.2 Flex布局的核心概念
为了更好地理解Flex布局,我们先来看一下它的基本模型:
Flex容器 (display: flex)
├─────────────────────────────────┤
│ 主轴方向 (flex-direction) → │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ 元素1 │ │ 元素2 │ │ 元素3 │ ← Flex元素
│ └─────────┘ └─────────┘ └─────────┘
│ │
│ ↑ │
│ 交叉轴方向 │
└─────────────────────────────────┘
Flex布局的两大核心:
-
容器:设置
display: flex的元素,控制内部项目的布局 - 元素:容器的直接子元素,受容器属性控制
1.3 容器属性
1.3.1 flex-direction:布局方向
这个属性决定了元素的排列方向,是Flex布局的基础:
.container {
/* 水平方向,从左到右(默认) */
flex-direction: row;
/* 水平方向,从右到左 */
flex-direction: row-reverse;
/* 垂直方向,从上到下 */
flex-direction: column;
/* 垂直方向,从下到上 */
flex-direction: column-reverse;
}
实际应用场景分析:
| 属性值 | 适用场景 |
|---|---|
row |
水平导航、卡片列表 |
column |
表单布局、设置页面 |
row-reverse |
阿拉伯语等从右向左语言 |
column-reverse |
聊天界面(最新消息在底部) |
1.3.2 justify-content:主轴对齐
这个属性控制元素在主轴上的对齐方式,使用频率非常高:
.container {
display: flex;
/* 起始位置对齐 */
justify-content: flex-start;
/* 末尾位置对齐 */
justify-content: flex-end;
/* 居中对齐 */
justify-content: center;
/* 两端对齐,项目间隔相等 */
justify-content: space-between;
/* 每个项目两侧间隔相等 */
justify-content: space-around;
/* 均匀分布,包括两端 */
justify-content: space-evenly;
}
空间分布对比关系:
- start - 从头开始
- end - 从尾开始
- center - 居中对齐
- between - 元素"之间"有间隔
- around - 每个元素"周围"有空间
- evenly - 所有空间"均匀"分布
1.3.3 align-items:交叉轴对齐
控制元素在交叉轴上的对齐方式:
.container {
display: flex;
height: 300rpx; /* 需要明确高度 */
/* 交叉轴起点对齐 */
align-items: flex-start;
/* 交叉轴终点对齐 */
align-items: flex-end;
/* 交叉轴中点对齐 */
align-items: center;
/* 基线对齐(文本相关) */
align-items: baseline;
/* 拉伸填充(默认) */
align-items: stretch;
}
温馨提示:align-items的效果与flex-direction密切相关:
- 当
flex-direction: row时,交叉轴是垂直方向 - 当
flex-direction: column时,交叉轴是水平方向
1.4 元素属性
1.4.1 flex-grow
控制元素放大比例,默认0(不放大):
.item {
flex-grow: <number>; /* 默认0 */
}
计算原理:
总剩余空间 = 容器宽度 - 所有元素宽度总和
每个元素分配空间 = (元素的flex-grow / 所有元素flex-grow总和) × 总剩余空间
示例分析:
.container {
width: 750rpx;
display: flex;
}
.item1 { width: 100rpx; flex-grow: 1; }
.item2 { width: 100rpx; flex-grow: 2; }
.item3 { width: 100rpx; flex-grow: 1; }
/* 计算过程:
剩余空间 = 750 - (100+100+100) = 450rpx
flex-grow总和 = 1+2+1 = 4
item1分配 = (1/4)×450 = 112.5rpx → 最终宽度212.5rpx
item2分配 = (2/4)×450 = 225rpx → 最终宽度325rpx
item3分配 = (1/4)×450 = 112.5rpx → 最终宽度212.5rpx
*/
1.4.2 flex-shrink
控制元素缩小比例,默认1(空间不足时缩小):
.item {
flex-shrink: <number>; /* 默认1 */
}
小技巧:设置flex-shrink: 0可以防止元素被压缩,常用于固定宽度的元素。
1.4.3 flex-basis
定义元素在分配多余空间之前的初始大小:
.item {
flex-basis: auto | <length>; /* 默认auto */
}
1.4.4 flex
flex是flex-grow、flex-shrink和flex-basis的简写:
.item {
/* 等价于 flex: 0 1 auto */
flex: none;
/* 等价于 flex: 1 1 0% */
flex: 1;
/* 等价于 flex: 1 1 auto */
flex: auto;
/* 自定义 */
flex: 2 1 200rpx;
}
1.5 完整页面布局实现
让我们用Flex布局实现一个典型的移动端页面:
<view class="page-container">
<!-- 顶部导航 -->
<view class="header">
<view class="nav-back">←</view>
<view class="nav-title">商品详情</view>
<view class="nav-actions">···</view>
</view>
<!-- 内容区域 -->
<view class="content">
<!-- 商品图 -->
<view class="product-image">
<image src="/static/product.jpg" mode="aspectFit"></image>
</view>
<!-- 商品信息 -->
<view class="product-info">
<view class="product-name">高端智能手机 8GB+256GB</view>
<view class="product-price">
<text class="current-price">¥3999</text>
<text class="original-price">¥4999</text>
</view>
<view class="product-tags">
<text class="tag">限时优惠</text>
<text class="tag">分期免息</text>
<text class="tag">赠品</text>
</view>
</view>
<!-- 规格选择 -->
<view class="spec-section">
<view class="section-title">选择规格</view>
<view class="spec-options">
<view class="spec-option active">8GB+256GB</view>
<view class="spec-option">12GB+512GB</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="footer">
<view class="footer-actions">
<view class="action-btn cart">购物车</view>
<view class="action-btn buy-now">立即购买</view>
</view>
</view>
</view>
.page-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
/* 头部导航 */
.header {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 32rpx;
background: white;
border-bottom: 1rpx solid #eee;
}
.nav-back, .nav-actions {
width: 60rpx;
text-align: center;
font-size: 36rpx;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: bold;
}
/* 内容区域 */
.content {
flex: 1;
overflow-y: auto;
}
.product-image {
height: 750rpx;
background: white;
}
.product-image image {
width: 100%;
height: 100%;
}
.product-info {
padding: 32rpx;
background: white;
margin-bottom: 20rpx;
}
.product-name {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 20rpx;
line-height: 1.4;
}
.product-price {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.current-price {
font-size: 48rpx;
color: #ff5000;
font-weight: bold;
margin-right: 20rpx;
}
.original-price {
font-size: 28rpx;
color: #999;
text-decoration: line-through;
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
}
.tag {
padding: 8rpx 20rpx;
background: #fff2f2;
color: #ff5000;
font-size: 24rpx;
border-radius: 8rpx;
}
/* 规格选择 */
.spec-section {
background: white;
padding: 32rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 24rpx;
}
.spec-options {
display: flex;
gap: 20rpx;
}
.spec-option {
padding: 20rpx 40rpx;
border: 2rpx solid #e0e0e0;
border-radius: 12rpx;
font-size: 28rpx;
}
.spec-option.active {
border-color: #007AFF;
background: #f0f8ff;
color: #007AFF;
}
/* 底部操作栏 */
.footer {
background: white;
border-top: 1rpx solid #eee;
padding: 20rpx 32rpx;
}
.footer-actions {
display: flex;
gap: 20rpx;
}
.action-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: bold;
}
.cart {
background: #fff2f2;
color: #ff5000;
border: 2rpx solid #ff5000;
}
.buy-now {
background: #ff5000;
color: white;
}
这个例子展示了如何用Flex布局构建复杂的页面结构,包含了水平布局、垂直布局、空间分配等各种技巧。
二、跨端适配:rpx单位系统
2.1 像素密度
要理解rpx的价值,首先要明白移动端面临的问题:
设备现状:
设备A: 4.7英寸, 750×1334像素, 326ppi
设备B: 6.1英寸, 828×1792像素, 326ppi
设备C: 6.7英寸, 1284×2778像素, 458ppi
同样的CSS像素在不同设备上的物理尺寸不同,这就是我们需要响应式单位的原因。
2.2 rpx的工作原理
rpx的核心思想很简单:以屏幕宽度为基准的相对单位
rpx计算原理:
1rpx = (屏幕宽度 / 750) 物理像素
不同设备上的表现:
| 设备宽度 | 1rpx对应的物理像素 | 计算过程 |
|---|---|---|
| 750px | 1px | 750/750 = 1 |
| 375px | 0.5px | 375/750 = 0.5 |
| 1125px | 1.5px | 1125/750 = 1.5 |
2.3 rpx与其他单位的对比分析
为了更好地理解rpx,我们把它和其他常用单位做个对比:
/* 不同单位的对比示例 */
.element {
width: 750rpx; /* 总是占满屏幕宽度 */
width: 100%; /* 占满父容器宽度 */
width: 375px; /* 固定像素值 */
width: 50vw; /* 视窗宽度的50% */
}
2.4 rpx实际应用与问题排查
2.4.1 设计稿转换
情况一:750px设计稿(推荐)
设计稿测量值 = 直接写rpx值
设计稿200px → width: 200rpx
情况二:375px设计稿
rpx值 = (设计稿测量值 ÷ 375) × 750
设计稿200px → (200÷375)×750 = 400rpx
情况三:任意尺寸设计稿
// 通用转换公式
function pxToRpx(px, designWidth = 750) {
return (px / designWidth) * 750;
}
// 使用示例
const buttonWidth = pxToRpx(200, 375); // 返回400
2.4.2 rpx常见问题
问题1:边框模糊
/* 不推荐 - 可能在不同设备上模糊 */
.element {
border: 1rpx solid #e0e0e0;
}
/* 推荐 - 使用px保证清晰度 */
.element {
border: 1px solid #e0e0e0;
}
问题2:大屏设备显示过大
.container {
width: 750rpx; /* 在小屏上合适,大屏上可能太大 */
}
/* 解决方案:媒体查询限制最大宽度 */
@media (min-width: 768px) {
.container {
width: 100%;
max-width: 500px;
margin: 0 auto;
}
}
2.5 响应式网格布局案例
<view class="product-grid">
<view class="product-card" v-for="item in 8" :key="item">
<image class="product-img" src="/static/product.jpg"></image>
<view class="product-info">
<text class="product-name">商品标题{{item}}</text>
<text class="product-desc">商品描述信息</text>
<view class="product-bottom">
<text class="product-price">¥199</text>
<text class="product-sales">销量: 1.2万</text>
</view>
</view>
</view>
</view>
.product-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
gap: 20rpx; /* 间隙,需要确认平台支持 */
}
.product-card {
width: calc((100% - 20rpx) / 2); /* 2列布局 */
background: white;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
}
/* 兼容不支持gap的方案 */
.product-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx;
justify-content: space-between;
}
.product-card {
width: 345rpx; /* (750-20*2-20)/2 = 345 */
margin-bottom: 20rpx;
}
.product-img {
width: 100%;
height: 345rpx;
display: block;
}
.product-info {
padding: 20rpx;
}
.product-name {
display: block;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 10rpx;
line-height: 1.4;
}
.product-desc {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 20rpx;
line-height: 1.4;
}
.product-bottom {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
font-size: 32rpx;
color: #ff5000;
font-weight: bold;
}
.product-sales {
font-size: 22rpx;
color: #999;
}
/* 平板适配 */
@media (min-width: 768px) {
.product-card {
width: calc((100% - 40rpx) / 3); /* 3列布局 */
}
}
/* 大屏适配 */
@media (min-width: 1024px) {
.product-grid {
max-width: 1200px;
margin: 0 auto;
}
.product-card {
width: calc((100% - 60rpx) / 4); /* 4列布局 */
}
}
这个网格布局会在不同设备上自动调整列数,真正实现"一次编写,到处运行"。
三、样式作用域
3.1 全局样式
全局样式是整个应用的样式基石,应该在App.vue中统一定义:
/* App.vue - 全局样式体系 */
<style>
/* CSS变量定义 */
:root {
/* 颜色 */
--color-primary: #007AFF;
--color-success: #4CD964;
--color-warning: #FF9500;
--color-error: #FF3B30;
--color-text-primary: #333333;
--color-text-secondary: #666666;
--color-text-tertiary: #999999;
/* 间距 */
--spacing-xs: 10rpx;
--spacing-sm: 20rpx;
--spacing-md: 30rpx;
--spacing-lg: 40rpx;
--spacing-xl: 60rpx;
/* 圆角 */
--border-radius-sm: 8rpx;
--border-radius-md: 12rpx;
--border-radius-lg: 16rpx;
--border-radius-xl: 24rpx;
/* 字体 */
--font-size-xs: 20rpx;
--font-size-sm: 24rpx;
--font-size-md: 28rpx;
--font-size-lg: 32rpx;
--font-size-xl: 36rpx;
/* 阴影 */
--shadow-sm: 0 2rpx 8rpx rgba(0,0,0,0.1);
--shadow-md: 0 4rpx 20rpx rgba(0,0,0,0.12);
--shadow-lg: 0 8rpx 40rpx rgba(0,0,0,0.15);
}
/* 全局重置样式 */
page {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica,
'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
SimSun, sans-serif;
background-color: #F8F8F8;
color: var(--color-text-primary);
font-size: var(--font-size-md);
line-height: 1.6;
}
/* 工具类 - 原子CSS */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.flex { display: flex; }
.flex-column { flex-direction: column; }
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.m-10 { margin: 10rpx; }
.m-20 { margin: 20rpx; }
.p-10 { padding: 10rpx; }
.p-20 { padding: 20rpx; }
/* 通用组件样式 */
.uni-button {
padding: 24rpx 48rpx;
border-radius: var(--border-radius-md);
font-size: var(--font-size-lg);
border: none;
background-color: var(--color-primary);
color: white;
transition: all 0.3s ease;
}
.uni-button:active {
opacity: 0.8;
transform: scale(0.98);
}
</style>
3.2 局部样式
局部样式通过scoped属性实现样式隔离,避免样式污染:
scoped样式原理:
<!-- 编译前 -->
<template>
<view class="container">
<text class="title">标题</text>
</view>
</template>
<style scoped>
.container {
padding: 32rpx;
}
.title {
color: #007AFF;
font-size: 36rpx;
}
</style>
<!-- 编译后 -->
<template>
<view class="container" data-v-f3f3eg9>
<text class="title" data-v-f3f3eg9>标题</text>
</view>
</template>
<style>
.container[data-v-f3f3eg9] {
padding: 32rpx;
}
.title[data-v-f3f3eg9] {
color: #007AFF;
font-size: 36rpx;
}
</style>
3.3 样式穿透
当需要修改子组件样式时,使用深度选择器:
/* 修改uni-ui组件样式 */
.custom-card ::v-deep .uni-card {
border-radius: 24rpx;
box-shadow: var(--shadow-lg);
}
.custom-card ::v-deep .uni-card__header {
padding: 32rpx 32rpx 0;
border-bottom: none;
}
/* 兼容不同平台的写法 */
.custom-card /deep/ .uni-card__content {
padding: 32rpx;
}
3.4 条件编译
uni-app的条件编译可以针对不同平台编写特定样式:
/* 通用基础样式 */
.button {
padding: 24rpx 48rpx;
border-radius: 12rpx;
font-size: 32rpx;
}
/* 微信小程序特有样式 */
/* #ifdef MP-WEIXIN */
.button {
border-radius: 8rpx;
}
/* #endif */
/* H5平台特有样式 */
/* #ifdef H5 */
.button {
cursor: pointer;
transition: all 0.3s ease;
}
.button:hover {
opacity: 0.9;
transform: translateY(-2rpx);
}
/* #endif */
/* App平台特有样式 */
/* #ifdef APP-PLUS */
.button {
border-radius: 16rpx;
}
/* #endif */
3.5 样式架构
推荐的项目样式结构:
styles/
├── variables.css # CSS变量定义
├── reset.css # 重置样式
├── mixins.css # 混合宏
├── components/ # 组件样式
│ ├── button.css
│ ├── card.css
│ └── form.css
├── pages/ # 页面样式
│ ├── home.css
│ ├── profile.css
│ └── ...
└── utils.css # 工具类
在App.vue中导入:
<style>
/* 导入样式文件 */
@import './styles/variables.css';
@import './styles/reset.css';
@import './styles/utils.css';
@import './styles/components/button.css';
</style>
四、CSS3高级特性
4.1 渐变与阴影
4.1.1 渐变
/* 线性渐变 */
.gradient-bg {
/* 基础渐变 */
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* 多色渐变 */
background: linear-gradient(90deg,
#FF6B6B 0%,
#4ECDC4 33%,
#45B7D1 66%,
#96CEB4 100%);
/* 透明渐变 - 遮罩效果 */
background: linear-gradient(
to bottom,
rgba(0,0,0,0.8) 0%,
rgba(0,0,0,0) 100%
);
}
/* 文字渐变效果 */
.gradient-text {
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
4.1.2 阴影
/* 基础阴影层级 */
.shadow-layer-1 {
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.shadow-layer-2 {
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.12);
}
.shadow-layer-3 {
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.15);
}
/* 内阴影 */
.shadow-inner {
box-shadow: inset 0 2rpx 4rpx rgba(0, 0, 0, 0.06);
}
/* 多重阴影 */
.shadow-multi {
box-shadow:
0 2rpx 4rpx rgba(0, 0, 0, 0.1),
0 8rpx 16rpx rgba(0, 0, 0, 0.1);
}
/* 悬浮效果 */
.card {
transition: all 0.3s ease;
box-shadow: var(--shadow-md);
}
.card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-4rpx);
}
4.2 变换与动画
4.2.1 变换
/* 2D变换 */
.transform-2d {
/* 平移 */
transform: translate(100rpx, 50rpx);
/* 缩放 */
transform: scale(1.1);
/* 旋转 */
transform: rotate(45deg);
/* 倾斜 */
transform: skew(15deg, 5deg);
/* 组合变换 */
transform: translateX(50rpx) rotate(15deg) scale(1.05);
}
/* 3D变换 */
.card-3d {
perspective: 1000rpx; /* 透视点 */
}
.card-inner {
transition: transform 0.6s;
transform-style: preserve-3d; /* 保持3D空间 */
}
.card-3d:hover .card-inner {
transform: rotateY(180deg);
}
.card-front, .card-back {
backface-visibility: hidden; /* 隐藏背面 */
}
.card-back {
transform: rotateY(180deg);
}
4.2.2 动画
/* 关键帧动画 */
@keyframes slideIn {
0% {
opacity: 0;
transform: translateY(60rpx) scale(0.9);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20rpx);
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* 动画类 */
.slide-in {
animation: slideIn 0.6s ease-out;
}
.bounce {
animation: bounce 0.6s ease-in-out;
}
.pulse {
animation: pulse 2s infinite;
}
/* 交互动画 */
.interactive-btn {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.interactive-btn:active {
transform: scale(0.95);
opacity: 0.8;
}
4.3 高级交互动效
<template>
<view class="interactive-demo">
<!-- 悬浮操作按钮 -->
<view class="fab" :class="{ active: menuOpen }" @click="toggleMenu">
<text class="fab-icon">+</text>
</view>
<!-- 悬浮菜单 -->
<view class="fab-menu" :class="{ active: menuOpen }">
<view class="fab-item" @click="handleAction('share')"
:style="{ transitionDelay: '0.1s' }">
<text class="fab-icon">📤</text>
<text class="fab-text">分享</text>
</view>
<view class="fab-item" @click="handleAction('favorite')"
:style="{ transitionDelay: '0.2s' }">
<text class="fab-icon">❤️</text>
<text class="fab-text">收藏</text>
</view>
<view class="fab-item" @click="handleAction('download')"
:style="{ transitionDelay: '0.3s' }">
<text class="fab-icon">📥</text>
<text class="fab-text">下载</text>
</view>
</view>
<!-- 动画卡片网格 -->
<view class="animated-grid">
<view class="grid-item" v-for="(item, index) in gridItems"
:key="index"
:style="{
animationDelay: `${index * 0.1}s`,
background: item.color
}"
@click="animateItem(index)">
<text class="item-text">{{ item.text }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
menuOpen: false,
gridItems: [
{ text: '卡片1', color: 'linear-gradient(135deg, #667eea, #764ba2)' },
{ text: '卡片2', color: 'linear-gradient(135deg, #f093fb, #f5576c)' },
{ text: '卡片3', color: 'linear-gradient(135deg, #4facfe, #00f2fe)' },
{ text: '卡片4', color: 'linear-gradient(135deg, #43e97b, #38f9d7)' },
{ text: '卡片5', color: 'linear-gradient(135deg, #fa709a, #fee140)' },
{ text: '卡片6', color: 'linear-gradient(135deg, #a8edea, #fed6e3)' }
]
}
},
methods: {
toggleMenu() {
this.menuOpen = !this.menuOpen
},
handleAction(action) {
uni.showToast({
title: `执行: ${action}`,
icon: 'none'
})
this.menuOpen = false
},
animateItem(index) {
// 可以添加更复杂的动画逻辑
console.log('点击卡片:', index)
}
}
}
</script>
<style scoped>
.interactive-demo {
padding: 40rpx;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* 悬浮操作按钮 */
.fab {
position: fixed;
bottom: 80rpx;
right: 40rpx;
width: 120rpx;
height: 120rpx;
background: #FF3B30;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 32rpx rgba(255, 59, 48, 0.4);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
z-index: 1000;
cursor: pointer;
}
.fab-icon {
font-size: 48rpx;
color: white;
transition: transform 0.4s ease;
}
.fab.active {
transform: rotate(135deg);
background: #007AFF;
}
/* 悬浮菜单 */
.fab-menu {
position: fixed;
bottom: 220rpx;
right: 70rpx;
opacity: 0;
visibility: hidden;
transform: translateY(40rpx) scale(0.8);
transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.fab-menu.active {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
.fab-item {
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
padding: 24rpx 32rpx;
margin-bottom: 20rpx;
border-radius: 50rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
transform: translateX(60rpx);
opacity: 0;
transition: all 0.4s ease;
}
.fab-menu.active .fab-item {
transform: translateX(0);
opacity: 1;
}
.fab-text {
font-size: 28rpx;
color: #333;
margin-left: 16rpx;
white-space: nowrap;
}
/* 动画网格 */
.animated-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 30rpx;
margin-top: 40rpx;
}
.grid-item {
height: 200rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
animation: cardEntrance 0.6s ease-out both;
transition: all 0.3s ease;
cursor: pointer;
}
.grid-item:active {
transform: scale(0.95);
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
}
.item-text {
color: white;
font-size: 32rpx;
font-weight: bold;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
/* 入场动画 */
@keyframes cardEntrance {
from {
opacity: 0;
transform: translateY(60rpx) scale(0.9) rotateX(45deg);
}
to {
opacity: 1;
transform: translateY(0) scale(1) rotateX(0);
}
}
/* 响应式调整 */
@media (max-width: 750px) {
.animated-grid {
grid-template-columns: 1fr;
}
}
@media (min-width: 751px) and (max-width: 1200px) {
.animated-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1201px) {
.animated-grid {
grid-template-columns: repeat(4, 1fr);
max-width: 1200px;
margin: 40rpx auto;
}
}
</style>
五、性能优化
5.1 样式性能优化
5.1.1 选择器性能
/* 不推荐 - 性能差 */
.container .list .item .title .text {
color: red;
}
/* 推荐 - 性能好 */
.item-text {
color: red;
}
/* 不推荐 - 通用选择器性能差 */
* {
margin: 0;
padding: 0;
}
/* 推荐 - 明确指定元素 */
view, text, image {
margin: 0;
padding: 0;
}
5.1.2 动画性能优化
/* 不推荐 - 触发重排的属性 */
.animate-slow {
animation: changeWidth 1s infinite;
}
@keyframes changeWidth {
0% { width: 100rpx; }
100% { width: 200rpx; }
}
/* 推荐 - 只触发重绘的属性 */
.animate-fast {
animation: changeOpacity 1s infinite;
}
@keyframes changeOpacity {
0% { opacity: 1; }
100% { opacity: 0.5; }
}
/* 启用GPU加速 */
.gpu-accelerated {
transform: translateZ(0);
will-change: transform;
}
5.2 维护性
5.2.1 BEM命名规范
/* Block - 块 */
.product-card { }
/* Element - 元素 */
.product-card__image { }
.product-card__title { }
.product-card__price { }
/* Modifier - 修饰符 */
.product-card--featured { }
.product-card__price--discount { }
5.2.2 样式组织架构
styles/
├── base/ # 基础样式
│ ├── variables.css
│ ├── reset.css
│ └── typography.css
├── components/ # 组件样式
│ ├── buttons.css
│ ├── forms.css
│ └── cards.css
├── layouts/ # 布局样式
│ ├── header.css
│ ├── footer.css
│ └── grid.css
├── utils/ # 工具类
│ ├── spacing.css
│ ├── display.css
│ └── text.css
└── themes/ # 主题样式
├── light.css
└── dark.css
通过本节的学习,我们掌握了:Flex布局 、rpx单位、样式设计、css3高级特性,欢迎在评论区留言,我会及时解答。
版权声明:本文内容基于实战经验总结,欢迎分享交流,但请注明出处。禁止商业用途转载。