☰
H5商城静态界面开发:移动端适配与样式工程实践
2026/10/27 8:52:49 网站建设 项目流程

简介:面向移动端电商前端学习者与初级前端工程师,这套完整H5商城静态界面提供了可直接参考的整站视觉与交互实现,覆盖首页轮播与推荐位、商品详情、分类浏览、购物车、个人中心、结算支付等典型页面,适合练手复刻、课程作业或项目原型搭建。压缩包共289个文件、约98.76MB,以156张png与34张jpg商品及界面切图、16个html页面、19个css样式表、31个js脚本为主,另含15个psd设计源文件、12个map映射文件及少量说明文档,既能查看成品结构,也能回溯设计与调试过程。目前已有764人学习。借助分层清晰的样式与脚本组织,读者可对照轮播组件、商品详情、购物车、个人中心等模块理解组件拆分与响应式布局写法,并配合设计稿完成二次改造。

1. 一套完整的h5商城样式静态界面,难的不是切图

有人觉得静态页面就是照着设计稿切图,两三天能交。真做过完整的h5商城样式静态界面的人不会这么讲:首页、分类、列表、详情、购物车、订单、个人中心七个页面,每个页面还带空状态、加载态、有券无券、SKU 已选未选几种样子,页面上又挂着轮播、瀑布流、吸顶筛选、底部 TabBar 和结算栏。把这些拼起来,在 320px 到 430px 之间任意一个宽度上都不错位,才是这套东西的真实工作量。

这套方案适合两类人。一类是要交 H5 商城毕设或者课程设计,需要一个能跑起来、能截出图、答辩时讲得清结构的前端成品;另一类是从业者要给甲方先出一版静态演示,谈需求之前让对方在手机上真点一遍。下面的顺序是页面地图、适配体系、样式工程、交互细节、翻车点、验收答辩,代码和目录结构都可以直接照着搭。

2. 页面地图与目录结构:七个页面怎么拆成一份可交付清单

动手写第一行 HTML 之前,我一般先画一张页面地图。静态界面没有接口、没有数据库,唯一能约束结构的就是页面之间的关系——谁跳到谁、哪个模块在几个页面上重复、哪些状态必须做出来。这张图画清楚了,后面写样式基本是体力活;图画不清楚,写到详情页就会发现商品卡的结构和列表页对不上,返工比重写还贵。

2.1 七个页面的信息层级与跳转关系

一套完整的 H5 商城静态界面,页面数量通常落在七个左右。少于这个数撑不起"完整"两个字,多于这个数对静态演示来说是浪费。我一般按下面这张表拆:

页面文件核心模块必须处理的状态
首页index.html搜索栏、轮播、金刚区、限时秒杀、商品瀑布流有活动 / 无活动 / 图片未加载
分类category.html左侧一级分类、右侧二级分类、商品网格分类为空、左右滚动不同步
列表list.html筛选排序栏、商品卡、上拉加载无结果、加载中、加载到底
详情detail.html主图轮播、价格区、SKU 选择、评价摘要、吸底购买栏未选规格、库存不足
购物车cart.html店铺分组、勾选、数量加减、结算栏空车、全选与半选
订单确认order.html收货地址、商品清单、优惠、支付方式无地址、金额变化
个人中心user.html用户信息、订单状态入口、功能宫格未登录

跳转关系比模块更要紧。主链路只有一条:首页或分类页进入列表页,列表页进详情页,详情页加购进购物车,购物车结算进订单确认。底部 TabBar 通常挂四个入口——首页、分类、购物车、我的,秒杀、领券这类活动页不占 TabBar 位置,从首页金刚区进。

静态页面之间的跳转直接写location.href就行,但要给返回留一条后路。用户从外部链接直接打开详情页时,导航栏那个返回箭头按history.back()会直接退出页面,我的做法是加一层判断:history.length > 1才回退,否则跳到首页。这种细节不影响截图效果,但答辩演示时手一抖就露出来了,属于典型的踩坑点。

状态设计里最容易被漏掉的是空状态。购物车空、搜索无结果、地址列表为空,这三张占位图一定要做,而且要做得像那么回事——一张灰色插画加一句"购物车还是空的,去逛逛",比一个白屏强太多。答辩时被问"没有数据的时候长什么样",能直接翻出来给老师看的,和现场卡壳的,评分档次不一样。

2.2 目录分层:样式按 base / layout / module 切三层

静态项目最容易出现的结构问题是所有 CSS 塞进一个style.css,写到第三个页面就变成两千行,改一个圆角要在里面翻五分钟。我一般按职责切三层,页面级样式单独放:

h5-mall/ ├── index.html # 首页:搜索 + 轮播 + 金刚区 + 秒杀 + 瀑布流 ├── category.html # 分类:左侧一级 + 右侧二级 + 商品网格 ├── list.html # 列表/搜索结果:筛选排序 + 商品卡 + 上拉加载 ├── detail.html # 详情:主图轮播 + 价格 + SKU + 评价 + 吸底栏 ├── cart.html # 购物车:店铺分组 + 勾选 + 数量 + 结算 ├── order.html # 订单确认:地址 + 清单 + 优惠 + 支付方式 ├── user.html # 个人中心:订单状态入口 + 功能宫格 ├── css/ │ ├── base.css # 重置样式、CSS 变量、通用原子类 │ ├── layout.css # 导航栏、TabBar、安全区、栅格容器 │ ├── module.css # 商品卡、轮播、SKU 面板、步进器等组件 │ └── page/ │ ├── index.css # 只放首页独有样式 │ └── detail.css # 只放详情页独有样式 ├── js/ │ ├── common.js # TabBar 高亮、返回、Toast、金额格式化 │ └── page/ # 每页一个交互脚本,与 css/page 一一对应 ├── img/ │ ├── icon/ # 图标,SVG 雪碧或字体图标 │ └── goods/ # 商品图,全部按二倍图切 └── README.md # 页面清单 + 设计稿对照 + 适配说明

三层各管一件事:base.css管变量和重置,layout.css管骨架和固定定位,module.css管可复用组件。这么切的好处很直接——甲方说主色从橙色改成红色,只动base.css里一个变量;说底部导航太高,只动layout.css;商品卡在首页和列表页长得不一样,改module.css里的修饰类,两个页面同时生效。

css/page/这一层是防污染的。首页的轮播容器可能叫.banner,详情页的主图容器也可能想叫.banner,放在一个文件里就必须起两个名字,放到页面目录里就互不干扰。js/page/与css/page/一一对应,改哪个页面就打开对应的一对文件,新人接手也能找得到。

README.md不是可选项。里面写清页面清单、每个页面对应哪一屏设计稿、适配方案是什么、哪些位置是留给真实接口的数据位。这份文件本身就是交付物的一部分,静态界面能讲清楚"这里将来接什么",价值立刻上一个台阶。

2.3 一套可复用的页面骨架

七个页面的<head>和<body>结构几乎一致,我一般先把骨架定死,再往里填内容。骨架里每一个 meta 都是踩过坑才留下的:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <!-- viewport-fit=cover 是刘海屏安全区生效的前提,漏了就白写 env() --> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> <!-- 关掉电话号码自动识别,否则价格里的数字会被染成蓝色下划线 --> <meta name="format-detection" content="telephone=no,email=no,address=no"> <title>首页 - H5 商城</title> <link rel="stylesheet" href="./css/base.css"> <link rel="stylesheet" href="./css/layout.css"> <link rel="stylesheet" href="./css/module.css"> <link rel="stylesheet" href="./css/page/index.css"> </head> <body class="page-index"> <!-- 页面容器:统一控制内容区最大宽度与左右留白 --> <div class="app"> <header class="app__header">导航栏 / 搜索栏</header> <main class="app__main">页面内容</main> </div> <!-- TabBar 放在滚动容器外面,避免被页面滚动带走 --> <nav class="tabbar">首页 / 分类 / 购物车 / 我的</nav> <script src="./js/common.js"></script> <script src="./js/page/index.js"></script> </body> </html>

几个参数的用意值得单独说。viewport-fit=cover决定了env(safe-area-inset-bottom)有没有值,缺了它,iPhone 全面屏上底部栏一定被小黑条压住。user-scalable=no是防止用户双击放大后整页布局错位,代价是牺牲了一部分可访问性,做交付型静态界面时这个取舍是划算的,但要在 README 里写一句说明。

body上的page-index类是全站的命名入口。所有页面级选择器都挂在这个类下面,比如.page-index .banner { ... },天然形成作用域,不用上 CSS Modules 那套构建流程也能避免样式外泄。

TabBar 提出滚动容器之外是个小动作,作用不小。放在容器里,页面滚动时它跟着走;提出来之后用position: fixed定在底部,滚动区域只剩main,滚动条的位置也干净。这套骨架复制七份,改标题和引入的页面样式就能开工,前面省下来的时间够把详情页的 SKU 面板做扎实。

3. 移动端适配体系:从 750 设计稿到 320px 屏幕的一次换算

H5 商城最玄学的地方就是适配。同一份代码,在模拟器里完美,到真机上可能底部被挡、分割线变粗、字号忽大忽小。适配方案选错了,后面每一个页面都在替它还债。我一般把方案定在一次换算上:设计稿按 750px 宽出,代码里只写 rem,根字号交给 vw,一劳永逸。

3.1 三套适配方案的取舍:固定 px、媒体查询、vw + rem

市面上常见的做法有三类,各自的毛病都很明确:

方案写法优点主要问题
固定 pxwidth: 375px所见即所得换个宽度就横向滚动,完全不能交付
媒体查询断点@media (min-width: 375px)可控断点之间是真空地带,360 和 390 之间所有机型都没人管
vw + remfont-size: 13.3333vw+rem连续缩放,一套代码通吃需要换算,第三方库的 px 要单独处理

固定 px 在毕设里出现频率最高,也最容易被老师一句话问倒:"你这个在 iPhone SE 上看看?"媒体查询看起来稳妥,实际上安卓机型宽度太碎,360、384、393、412 全都有,断点根本写不完。

vw + rem 的思路是把"缩放"这件事交给浏览器:根字号跟着视口宽度线性变化,页面里所有尺寸都用 rem 表达,于是整页等比缩放。它唯一的成本是换算,而换算可以用一个 Sass 函数解决,写两天就形成肌肉记忆了。

3.2 根字号与视口:一行 CSS 定基准

我习惯让 1rem 等于设计稿上的 100px。这个基准是刻意的——设计稿标注 24px,代码里写 0.24rem,心算一步到位;如果按 1rem = 16px 那套,每个数值都要除一遍,写一天就烦了。

/* 设计稿宽 750px,令 1rem = 100px 设计稿宽度 */ html { /* 100 / 750 * 100vw ≈ 13.3333vw */ font-size: 13.3333vw; } /* 大屏封顶:超过 560px 不再继续放大,避免平板上一屏只有一个商品 */ @media (min-width: 560px) { html { font-size: 74.6667px; /* 560 * 13.3333 / 100 */ } } /* 防止横屏或系统字号设置把文字放大 */ body { -webkit-text-size-adjust: 100%; font-size: 0.28rem; /* 设计稿 28px */ line-height: 1.5; color: var(--c-text-1); }

13.3333vw这个数的来历要记牢:设计稿 750 宽对应 1rem = 100px,那么 1rem 占视口宽度的比例就是 100/750,乘 100 得到 vw 值。换成 375 设计稿就是 26.6667vw,换成 640 就是 15.625vw,公式只有一个。

封顶那条媒体查询是很多模板里没有的。不加的话,在 iPad 上 1rem 会被算到 90 多 px,商品卡撑得像海报,一屏放不下两个。加了之后,超过 560px 就按固定根字号渲染,配合容器max-width,平板上是居中的一块区域,看起来是设计过的,而不是失控的。

写样式时全程用 Sass 函数转换,别手敲小数:

// 设计稿 px 转 rem,$base 与根字号定义保持一致 @function r($px) { @return ($px / 100) * 1rem; } .goods-card { padding: r(24); border-radius: r(12); margin-bottom: r(20); &__title { font-size: r(28); line-height: r(40); } }

r(24)编译出来是0.24rem。这里有个值得提前说清的边界:小于 1px 的 rem 值(比如0.005rem)在部分浏览器里会被取整成 0,所以 1px 边框不要指望用 rem 写,后面 3.3 会单独处理。

3.3 安全区、1px 边框与点击态:真机上最容易露馅的三处

这三处问题在模拟器里一个都看不出来,一到真机全部现形,属于典型的血泪经验。

/* 1. 底部 TabBar / 结算栏:给全面屏手势条留出高度 */ .tabbar { position: fixed; left: 0; right: 0; bottom: 0; height: calc(1rem + env(safe-area-inset-bottom)); padding-bottom: constant(safe-area-inset-bottom); /* iOS 11.0-11.2 */ padding-bottom: env(safe-area-inset-bottom); /* iOS 11.2+ */ background: #fff; z-index: 100; } /* 2. 1px 分割线:缩放 0.5 倍,二倍屏上才是真正的 1 物理像素 */ .hairline-b { position: relative; } .hairline-b::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: var(--c-line); transform: scaleY(0.5); transform-origin: 0 100%; } /* 3. 点击态:去掉系统灰块,自己给反馈 */ * { -webkit-tap-highlight-color: transparent; } .btn:active, .goods-card:active { opacity: 0.85; }

安全区那段的坑在于constant()和env()必须写两遍,且顺序不能反,老版本 iOS 不认识env()会整条丢弃,新版本不认识constant()也一样。高度用calc()而不是写死,是因为内容区高度要加上手势条那 34px 左右的空间,写死的话在小屏机型上会把价格文字挤掉。

1px 边框那段,本质是 CSS 像素和物理像素的区别。设计稿上一条 1px 的线,在 2 倍屏上如果按 CSS 像素渲染,实际占 2 个物理像素,看起来就是"粗了一倍"。用伪元素加scaleY(0.5)缩一半,视觉上才和设计稿一致。同理还有左右分割线用scaleX(0.5),四边全框的用transform: scale(0.5)配width: 200%。

点击态是最容易被忽略的体验点。移动端浏览器默认会给可点元素加一层灰色高亮,颜色深浅由系统决定,和设计稿完全不搭。全局清掉之后必须自己补:active,否则用户点下去毫无反馈,会以为页面卡了,然后连点三下——这就是后面 5.2 要讲的重复跳转的由来。

3.4 图片与文字:二倍图、占位与最小字号

商品图的处理直接决定页面稳不稳。移动端全部按二倍图切,img的 CSS 宽度写设计稿一半,否则在高清屏上会糊。

/* 商品图容器:先占位,图片没下载完也不会把下面的内容顶下去 */ .goods-card__pic { position: relative; width: 100%; aspect-ratio: 1 / 1; /* 一比一占位,瀑布流高度才稳定 */ background: #f2f2f2; /* 灰色占位底,避免白块闪一下 */ overflow: hidden; border-radius: var(--radius-m) var(--radius-m) 0 0; } .goods-card__pic img { display: block; /* 去掉 inline 元素底部的基线空隙 */ width: 100%; height: 100%; object-fit: cover; /* 裁切填充,商品图比例不一时不拉伸 */ } /* 不支持 aspect-ratio 的老内核兜底 */ @supports not (aspect-ratio: 1 / 1) { .goods-card__pic { height: 0; padding-top: 100%; } .goods-card__pic img { position: absolute; inset: 0; } }

display: block这一行不是可选的。img默认是 inline 元素,下面会留 3 到 4px 的基线空隙,一个页面几十张图,累计起来就是页面底部莫名其妙多出一条白边,查半天查不出来。

文字这边有两个经验值:一是正文不要小于 12px,部分安卓浏览器和 Chrome 对中文有最小字号限制,写 10px 会被强制拉到 12px,导致设计稿上的紧凑排版失效;二是需要视觉上更小的文字(比如价格符号、角标),用transform: scale(0.9)配合transform-origin缩小,而不是继续压font-size。规格说明、活动标签这类文字,我一般直接按 22px 到 24px 设计稿走,宁可稍微大一点,也别在真机上被系统接管。

4. 样式工程与关键模块实现:把变量、组件、层级一次定好

适配解决的是"尺寸对不对",样式工程解决的是"改起来疼不疼"。静态界面看着简单,实际上甲方改稿频率比带接口的项目还高——反正是静态的,说改就改。变量和组件结构不提前定好,改到第五版基本等于重写。

4.1 CSS 变量 + BEM 命名:颜色和间距只改一个地方

我把所有会变的东西收进:root,页面里一个色值都不硬编码:

:root { /* 主色和价格色分开:活动期换主色,价格色不用跟着动 */ --c-primary: #ff5000; --c-price: #ff2d2d; --c-text-1: #1a1a1a; --c-text-2: #666666; --c-text-3: #999999; --c-line: #ebebeb; --c-bg: #f5f5f5; --c-white: #ffffff; --radius-s: 0.08rem; --radius-m: 0.16rem; --radius-l: 0.24rem; --gap-s: 0.16rem; --gap-m: 0.24rem; --gap-l: 0.32rem; --tabbar-h: 1rem; --buybar-h: 1.08rem; } /* 活动主题:只覆盖变量,一行选择器解决,不动任何组件样式 */ .theme-618 { --c-primary: #e93b3b; --c-price: #e93b3b; }

用 CSS 变量而不是 Sass 变量的理由很实际:Sass 变量在编译期就没了,运行时切换不了;CSS 变量留着,给body加一个类,整站配色立刻换掉,静态演示时可以做"看看另一种风格"的效果,甲方会很吃这一套。

命名上我坚持 BEM,但只用到两级,不写三层以上。块是组件名,元素用双下划线,状态用双连字符:

.goods-card { } /* 块 */ .goods-card__pic { } /* 元素 */ .goods-card__title { } /* 元素 */ .goods-card--horizontal { } /* 修饰:横向排布,用于购物车 */ .goods-card__price--disabled { } /* 状态:失效价 */

为什么不直接上原子类框架?静态页面的类名总量就那么多,引一个工具库进来,构建配置、按需引入、团队约定三件事都要额外维护,收益不成正比。BEM 的类名长一点,但搜得到、改得动、看得出归属关系,交付给下一个人不用写文档说明。

4.2 商品卡:一份结构撑起瀑布流和双列网格

商品卡是全站复用率最高的组件,首页瀑布流、列表页双列、购物车横向、订单确认紧凑版,四个地方都在用。结构写一份,靠修饰类切换排布:

<article class="goods-card"> <a class="goods-card__pic" href="./detail.html"> <img src="./img/goods/1001.jpg" alt="商品主图" loading="lazy"> <span class="goods-card__badge">秒杀</span> </a> <div class="goods-card__body"> <h3 class="goods-card__title">商品标题最多两行,超出部分省略号截断</h3> <p class="goods-card__sales">已售 2.3 万件</p> <p class="goods-card__price"> <i>¥</i>129<i>.00</i> <del>¥199</del> </p> </div> </article>
.goods-card { background: var(--c-white); border-radius: var(--radius-m); overflow: hidden; } .goods-card__title { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; /* 行数写死,卡片高度才一致 */ overflow: hidden; font-size: 0.28rem; line-height: 0.4rem; height: 0.8rem; /* 两行行高之和,保险起见再写一次 */ } .goods-card__price { font-size: 0.36rem; color: var(--c-price); font-weight: 600; } .goods-card__price i { font-style: normal; font-size: 0.24rem; /* 货币符号小一号,价格数字更突出 */ } .goods-card__price del { margin-left: var(--gap-s); font-size: 0.24rem; color: var(--c-text-3); font-weight: 400; }

-webkit-line-clamp那几行必须成套写:display: -webkit-box、-webkit-box-orient: vertical、-webkit-line-clamp: 2缺一不可,只写最后一行不生效。截断行数写死的意义在瀑布流里才看得出来——两列商品如果标题一行两行混着,卡片高度参差,视觉上就是没对齐。宁可多截几个字,也要让卡片等高。

loading="lazy"是原生的图片懒加载,静态页面直接用它,不需要再引一个懒加载库。它的行为和占位容器配合得很好:容器先撑出高度,图片进入视口才下载,首屏渲染速度肉眼可见地快。

横向版本只在购物车和订单页用,覆盖几个属性即可:

.goods-card--horizontal { display: flex; gap: var(--gap-m); border-radius: 0; background: transparent; } .goods-card--horizontal .goods-card__pic { width: 1.6rem; flex: 0 0 1.6rem; aspect-ratio: 1 / 1; border-radius: var(--radius-s); } .goods-card--horizontal .goods-card__body { flex: 1; min-width: 0; /* 关键:不加这行,长标题会把 flex 子项撑破 */ }

min-width: 0这一行是 flex 布局的经典卡点,不加的话子项的最小宽度由内容决定,长标题会把整个卡片撑出屏幕,右边出现横向滚动条。

4.3 商品详情:SKU 面板与吸底购买栏

详情页是整站信息密度最高的一页,也是最容易做塌的一页。SKU 选择面板的规格按钮要区分三种状态——可选、已选、不可选,视觉上必须一眼分得清:

.sku__option { display: inline-flex; align-items: center; justify-content: center; min-width: 1.2rem; height: 0.56rem; padding: 0 0.24rem; margin: 0 var(--gap-s) var(--gap-s) 0; font-size: 0.24rem; color: var(--c-text-1); background: #f7f7f7; border: 1px solid transparent; border-radius: 0.28rem; } /* 已选:主色描边 + 主色文字 + 淡色底 */ .sku__option--active { color: var(--c-primary); background: #fff5f0; border-color: var(--c-primary); } /* 不可选:置灰加删除线,同时禁用点击 */ .sku__option--disabled { color: var(--c-text-3); background: #fafafa; text-decoration: line-through; pointer-events: none; }

pointer-events: none比在 JS 里判断要不要拦截更省事,样式层直接断掉交互,不会出现"看着是禁用的其实还能点"这种事故。

吸底购买栏的关键不在样式,在层级和滚动锁定。层级我一般按固定四档来:页面内容 1,TabBar 和吸底栏 100,弹层遮罩 200,Toast 999。数值不要随手写 9999,写多了后面想插一层都没空间。

/* 吸底购买栏:高度加上安全区,内容区留出同等 padding-bottom */ .buy-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 100; display: flex; align-items: center; height: calc(var(--buybar-h) + env(safe-area-inset-bottom)); padding-bottom: env(safe-area-inset-bottom); background: var(--c-white); box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); } .detail-main { padding-bottom: calc(var(--buybar-h) + env(safe-area-inset-bottom)); } /* 弹层打开时锁住页面滚动,否则手指一滑背景跟着跑 */ body.is-lock { position: fixed; width: 100%; overflow: hidden; }

is-lock这个类很值得单独拎出来说。SKU 弹层、优惠券弹层、地址选择弹层打开时,如果不锁滚动,用户在弹层里滑动,底下的商品详情会一起滚,关掉弹层后位置全乱。加position: fixed锁住 body 是成本最低的解法,代价是锁定的瞬间页面会跳回顶部,所以在加锁前要记一下window.scrollY,解锁时再滚回去,两行代码的事。

4.4 购物车:勾选、步进器与结算栏的静态结构

购物车的难点不在样式,在于它同时有列表、选择、计数、合计四套结构,还全都得是静态可点的:

<section class="cart-shop"> <div class="cart-shop__head"> <label class="check"><input type="checkbox" checked><i></i></label> <span class="cart-shop__name">模拟旗舰店</span> </div> <div class="cart-item"> <label class="check"><input type="checkbox" checked><i></i></label> <img class="cart-item__pic" src="./img/goods/1001.jpg" alt=""> <div class="cart-item__info"> <p class="cart-item__title">商品标题最多两行</p> <p class="cart-item__spec">规格:黑色 / 128G</p> <div class="cart-item__bottom"> <span class="cart-item__price">¥129.00</span> <div class="stepper"> <button class="stepper__btn" type="button" aria-label="减少">-</button> <input class="stepper__num" type="text" value="1" readonly> <button class="stepper__btn" type="button" aria-label="增加">+</button> </div> </div> </div> </div> </section>
/* 步进器:按钮触控区不小于 0.44rem,约等于 44px */ .stepper { display: inline-flex; align-items: center; border: 1px solid var(--c-line); border-radius: var(--radius-s); overflow: hidden; } .stepper__btn { width: 0.56rem; height: 0.56rem; font-size: 0.32rem; line-height: 1; color: var(--c-text-1); background: #fafafa; } .stepper__btn:disabled { color: #cccccc; /* 数量为 1 时减号置灰,比弹提示更友好 */ } .stepper__num { width: 0.8rem; height: 0.56rem; text-align: center; font-size: 0.26rem; border: none; border-left: 1px solid var(--c-line); border-right: 1px solid var(--c-line); background: var(--c-white); }

步进器按钮 0.56rem 是按 375 宽度换算后约 21px,实际可点区域靠外层 padding 补到 44px 左右。手指点击的准确度远低于鼠标,触控区太小是移动端最常见的体验缺陷,验收时值得专门点一遍。

input加readonly是静态页面的必要处理。不加的话用户点进去会弹起键盘,而键盘一弹,底部结算栏就飞到半空——也就是 5.5 要讲的那个翻车现场。静态界面里数量只能靠加减按钮改,输入框纯展示,加个readonly就把这条路堵死了。

结算栏固定在底部,结构和购买栏一致,左侧全选,中间合计,右侧结算按钮。合计金额用 JS 现算是可以的,静态页面照样能跑这段逻辑,但要注意金额计算不能直接用浮点相加,0.1 + 0.2 = 0.30000000000000004这种问题在价格上特别刺眼,统一转成整数分再算,展示时除以 100。

5. 避坑与常见问题:五个真机上最容易翻车的点

本地跑通不等于交付,静态界面尤其如此。下面五个问题,每一个我都在真机上见过,而且每一个在模拟器里都复现不出来。按"现象 → 原因 → 解决"记下来,遇到时能五分钟定位。

5.1 底部按钮被全面屏手势条压住

现象:iPhone 全面屏机型上,购物车结算栏、详情页购买栏的按钮文字被系统手势条压掉一半,或者干脆点不到。

原因:只写了bottom: 0,没给安全区留高度;更常见的是viewport里漏了viewport-fit=cover,导致env(safe-area-inset-bottom)恒等于 0,写了也白写。

解决:先检查 viewport 有没有cover,再给固定定位元素补padding-bottom: env(safe-area-inset-bottom),高度用calc()把这段加上去。同时别忘了给对应的

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询