☰
Flexbox实战:从零构建一个高可用登录页布局
2026/10/14 3:36:11 网站建设 项目流程

登录页大概是前端项目里看起来最不起眼、写起来却最容易翻车的一类页面。你想想,设计稿上就是一个居中卡片、两三个输入框、一个按钮,可真动手的时候,很多人的第一反应是拿起老一套:用定位算坐标,或者干脆用 JavaScript 去量窗口高度。结果窗口一拉伸、键盘一弹出来,组件全部错位。我这些年做项目,凡是遇到布局混乱的登录页,十有八九是根本没有认真过 Flex 这一关。用 Flex 去写登录页,不是炫技,而是因为它正好覆盖了居中、纵向排列、横向对齐、两端对齐这些日常布局里最高频的场景,一段干净的结构配十几行 CSS 就能跑通。这篇文章就把整个思路掰开揉碎地讲一遍,从主轴交叉轴原理讲到输入框放图标的细节,再讲到响应式和小屏上的坑,最后给一套可以直接复制的完整 Demo。

1. 登录页为什么是 Flexbox 的最佳练习场

1.1 登录页的布局模型:一个窄卡片遇上任意尺寸窗口

登录页和后台管理表格页最大的区别,在于它的内容非常克制。通常只有一个品牌 Logo、标题、账号密码输入框、按钮,顶多加一个"记住我"和"忘记密码"。这种窄而短的页面,视觉上要的是聚焦,所以整个表单区块必须在屏幕里水平垂直居中,不管你是 27 寸显示器还是 375px 宽的手机。

同时,表单内部又天然分成两种排列方向:卡片内部从上到下的纵向流,以及每个输入框内部从左到右的横向流。再加上"记住我"和"忘记密码"要左右两端对齐,登录按钮要占满卡片宽度——你会发现,登录页几乎把所有 flexbox 的高频用法都囊括了:justify-content控制主轴分布,align-items控制交叉轴对齐,flex-direction控制排列方向,gap控制间距。

为什么说它是"最佳练习场"而不是别的页面?因为它的结构足够小,你可以在十分钟内反复调整两三个属性,马上看到效果。这种即时反馈对理解 flex 模型特别重要。你不需要面对一个布满表格行、侧边栏、顶部导航的复杂系统,只需要拿一张白卡片去对抗屏幕尺寸的不确定性,思路会非常清晰。

另一个很多人没意识到的点:登录页往往要适配"任意尺寸"的视口,而 flex 的弹性伸缩设计本来就是为了解决内容与容器之间的自适应关系。所以不是"flex 恰好能写登录页",而是"登录页这种布局需求,本来就应该由 flex 这种一维弹性布局来解决"。

1.2 和传统布局方案的对比:三步定位与一行动代码

很多老项目里还在用绝对定位加transform做居中,代码大概长这样:

.login-wrap { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }

这套方案在登录场上确实能用,但有一个天然的隐患:它依赖父容器的高度。一旦页面内容超过一屏,或者移动端软键盘弹出导致视口高度变化,这个绝对定位的盒子可能直接超出屏幕范围,而且背景、滚动行为都得额外处理。遇到小屏设备,你还得再写媒体查询去修正。

用 flex 只需要在父容器上写两个属性:

.login-page { display: flex; align-items: center; justify-content: center; }

一行声明,没有定位基准,没有负 margin,不依赖固定尺寸,内容再多也能保持居中,而且子元素不会脱离文档流,滚动、留白、背景都会自然跟随。两者一比,高下立判。

再往前翻,还有float排表单的老写法,清浮动要额外加.clearfix,inline-block又会出现元素之间的空白字符缝隙。这些方案本质上都是在和"文档流规则"做对抗,你要不断处理反噬。而 flex 的思维是直接告诉浏览器"这一排元素应该如何摆放",不再依赖margin负值或clear去补漏洞,这才是符合直觉的写法。

1.3 主轴和交叉轴:先搞清楚队伍往哪边走

flexbox 里最容易让人劝退的两个概念,就是主轴和交叉轴。我用一个特别土的解释:想象一群人排队,主轴是队伍前进的方向,交叉轴是"人与人对齐"的方向。

默认情况下,flex-direction: row,队伍从左往右排,主轴是水平方向,交叉轴是垂直方向。此时justify-content控制的是水平方向怎么排,align-items控制的是垂直方向怎么对齐。

如果你把flex-direction改成column,队伍就变成了从上往下排队,主轴变成垂直方向,交叉轴变成水平方向。这时候justify-content管的是上下分布,align-items管的是左右对齐。

登录页最常见的情况就是外层主轴按 column 走,让卡片里的标题、输入框、按钮从上往下排;而输入框内部用 row 走,让图标、输入控件、密码切换按钮站成一行。每次换方向,两个属性对应的方向也会互换,这个点我见过太多人搞混。我的经验是:写布局前先在注释里标一句"当前容器主轴方向是什么",能少踩一半坑。

属性作用对象row(水平主轴)column(垂直主轴)
justify-content主轴方向上的分布控制水平间距控制垂直间距
align-items交叉轴方向上的对齐控制垂直对齐控制水平对齐

理解这张表之后,登录页里绝大多数的布局问题都能直接翻译成一句"哪个容器的主轴是什么方向,我要让它的子元素怎么分布"。

2. 登录页骨架搭建:三步搞定整体居中

2.1 最外层容器:min-height 100vh 比 height 100vh 更稳

先看一个常见错误写法:有人为了让登录页铺满全屏,把外层容器写成height: 100vh,然后配display: flex; align-items: center; justify-content: center;。单看没什么问题,但一旦卡片内容超过了视口高度——比如小屏手机上键盘弹出、或者你加了多语言导致文案变长——容器会被内容撑破,但背景和滚动区域都有可能出问题。

更稳的做法是min-height: 100vh:

.login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; box-sizing: border-box; }

min-height的意思是"至少满一屏,但如果内容更多,容器继续变大"。配合padding: 24px,可以保证在极窄屏幕上卡片不会直接贴到边缘。这里还要多说一句,box-sizing: border-box虽然在全局 reset 里已经很常见,但如果你的项目里没有写,这个padding会让外层容器超出一屏,因为这 24px 会被算进高度里去。

之前也提过,背景渐变建议直接写在这个最外层容器上,不要单独用一个伪元素或者背景层去实现。flex 布局下,父容器的背景天然覆盖整个可视区域,不需要额外定位一个背景 div,代码更干净。

2.2 卡片尺寸:max-width 与 width 组合的奥妙

登录卡片是整页的视觉核心,宽度通常设计在 360px 到 440px 之间。我见过不少代码直接写死width: 420px,这样在桌面端没问题,到 375px 的小屏手机上就横向溢出了。正确做法是组合使用:

.login-card { width: 100%; max-width: 420px; background: #fff; border-radius: 16px; padding: 40px 32px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; }

width: 100%是为了让卡片在小屏上自适应,max-width: 420px是为了防止它在桌面上被无限拉宽。这个组合比单独写width: 420px或者max-width: 420px都更可靠,因为它同时覆盖了两种极端情况。box-sizing: border-box也必须生效,否则 420px 加上 64px 的左右内边距,卡片实际占的宽度会比预期更大。

关于内边距,我习惯用40px 32px这个比例:垂直方向 40px 让视觉重心稳定,水平方向 32px 给输入框留足呼吸感。间距太小会显得拥挤,比如输入框里的文字几乎贴着卡片边缘;太大又会撑爆整个卡片,在 320px 宽的旧手机上,左右各 32px 再加输入框内边距会导致输入区域只剩 200px 左右,体验很差。

2.3 用 gap 管理间距:彻底告别 first-child 特判

早年间写表单间距,最痛苦的一点是每个元素之间的 margin 要手动控制,比如第一个输入框要设margin-top: 32px,后面的每个字段之间又要margin-bottom: 20px,然后你可能还要写:first-child { margin-top: 0 }这种规则。一套组合拳下来,CSS 里全是间距 hack。

flex 提供gap之后,这种问题直接消失了:

.login-form { margin-top: 32px; display: flex; flex-direction: column; gap: 20px; }

这一个 gap 会作用于 form 内部所有直接子元素之间,无论你后续是删除一个字段还是新增一个字段,间距体系都自动维护。我自己对 gap 的依赖程度已经高到,几乎所有 flex 容器里都不再用 margin 去控制兄弟间距,只有需要和容器外部产生距离感时才用 margin 或 padding。

要提醒一点:gap只在现代浏览器里被支持,旧版本 Safari 14 之前、Edge 18 之前会有兼容问题。如果你的项目还要适配很老的机型和浏览器,只能退回 margin 方案。不过以现在的用户环境来看,绝大多数场景下 gap 已经可以放心用,真见客户反馈老系统打不开,再单独抽一个兼容层也不迟。

3. 表单元素排布的核心细节:输入框、图标与按钮

3.1 把图标和输入框焊成一个整体:input-wrap 的 flex 方案

登录页设计稿里,输入框前面基本都会放一个小图标。如果为了省事直接写成:

<input type="text" placeholder="请输入账号" />

然后期望用一个 background 图片去实现图标,你会发现图标位置、大小、点击区域都很难控制,尤其是聚焦状态还要变颜色,开发成本一下子就上去了。

更干净的做法是,用一个 div 把图标和 input 包起来,让这个外层 div 成为 flex 容器,把输入框和图标"并排焊死":

<div class="input-wrap"> <svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <!-- 这里放用户头像图标路径 --> </svg> <input type="text" name="username" placeholder="请输入账号" autocomplete="username" /> </div>

对应的 CSS 是:

.input-wrap { display: flex; align-items: center; gap: 10px; background: #fafafa; border: 1px solid #ddd; border-radius: 12px; padding: 0 14px; transition: border-color 0.2s, box-shadow 0.2s; } .input-wrap:focus-within { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); background: #fff; } .input-icon { width: 20px; height: 20px; flex-shrink: 0; color: #999; } .input-wrap input { flex: 1; min-width: 0; height: 48px; border: none; outline: none; background: transparent; font-size: 16px; color: #333; }

这里有几个细节值得单独拿出来讲。第一,align-items: center让图标、文字、光标在垂直方向都对齐到中线,这个属性比你用 line-height 去微调稳定得多。第二,input必须设flex: 1,它的意思不是"宽度撑满",而是"剩余空间全部给我",这样图标固定占据左侧,输入框吃掉剩下的所有宽度。第三,也是最容易被忽略的,是min-width: 0。input 默认有个最小宽度约束,如果 placeholder 文字很长,它就会把整个 wrap 撑破。设了min-width: 0之后,输入框内容超出时会在内部压缩,布局不会崩。

focus-within这个伪类是整个方案的亮点:它监听的是"自身或内部子元素是否聚焦",所以输入框一聚焦,整个外层 div 的边框和阴影都会联动变化。传统做法是要用 JS 监听 input 的 focus/blur,再加 class,现在一行 CSS 就搞定了,而且视觉反馈更准确,因为没有额外 JS 参与。

3.2 按钮内部也是一个 flex 容器

主登录按钮往往要占满卡片宽度,同时文字在按钮内居中。如果按钮里只有纯文字,那用 text-align 就够了;一旦要加 loading 转圈、加一个箭头图标,你又会陷入手写对齐的泥潭。

把按钮本身也变成一个 flex 容器,是最简单直接的解法:

.login-btn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 48px; border: none; border-radius: 12px; background: #667eea; color: #fff; font-size: 16px; cursor: pointer; transition: background 0.2s; }

之后你想加任何东西,比如加载图标、右侧箭头,只要放进按钮内部,flex 会自动帮你排好。justify-content: center让它水平居中,align-items: center让它垂直居中,gap: 8px控制文字和图标之间的间隔。

顺便说一句,按钮在 column 方向的 flex 容器里设置width: 100%是完全没有问题的。很多人学 flex 时会以为子元素的尺寸只能靠flex-basis控制,其实交叉轴方向上的width、height依然有效,这里的按钮横向占满父容器,就是通过显式 width 实现的。你也可以写成align-self: stretch,效果类似,但 width 更明确,别人看代码时一眼就懂。

3.3 两端对齐:记住我与忘记密码的经典玩法

表单底部那一行,左边是"记住我",右边是"忘记密码",这是 flex 里justify-content: space-between用得最多的场景:

<div class="actions"> <label class="remember"> <input type="checkbox" />记住我 </label> <a href="#" class="forgot">忘记密码</a> </div>
.actions { display: flex; justify-content: space-between; align-items: center; font-size: 14px; }

space-between会把第一个子元素放在起点,最后一个子元素放在终点,中间多余的自动空间均匀分到两者之间。你不需要为了"让忘记密码靠右"而给它加 margin-left: auto,也不需要放一个空的占位 div,这大概是最能体现 flex 优势的案例之一了。

还有个顺手的小技巧:原生 checkbox 在现在的主流浏览器里可以用accent-color换主题色,不用再写一堆隐藏 checkbox、用 label 画伪元素的操作:

.remember input { width: 16px; height: 16px; accent-color: #667eea; }

另外,"记住我"这个 label 的可点击区域最好同步扩大,否则用户得精确点到那个 16px 的小方框上。给 label 加上display: flex; align-items: center; gap: 6px; padding: 4px 0;可以既兼容图标对齐,又扩大触控范围。

3.4 输入框高度:用 padding 撑出来远比写死 height 稳定

我见过很多新手喜欢给输入框写height: 48px,这个数值主要来自设计稿。但你如果把 height 固定死,再配合line-height或者内部 icon 的垂直对齐,稍不留神文字就偏离视觉中心。因为height决定的是整个输入框区域的高度,而文字本身有自己的行高和基线,两者之间还需要额外计算;不同字体甚至同一字体的不同字重,都会影响显示效果。

更稳的做法是,给 input 设padding,让它自己撑出高度:

.input-wrap { padding: 0 14px; } .input-wrap input { padding: 14px 0; }

外层加左右内边距,内层加上下内边距,最终输入框的实际高度就是 14px 顶 + 14px 底 + 一行文字高度。配合align-items: center,内部图标和文字的垂直对齐永远处在正确的位置,因为 flex 容器的高度本来就是由内容撑出来的,不需要我再额外猜一个 line-height 值。

这样还有一个附加好处:当用户使用系统字体缩放、浏览器页面缩放时,文字高度变化,容器高度会自动跟着变,不会出现"两个字打架"的局促感。

4. 响应式适配与常见踩坑:从小屏到桌面

4.1 一行放不下的场景:flex-wrap 不是摆设

登录页大部分内容是纵向排列,好像用不到换行。但有些场景总会冒出来:第三方登录按钮组"微信 / 支付宝 / 邮箱图标"排成一排,或者页面顶部放"登录 / 注册 / 找回密码"三个 Tab,再或者表单里出现手机号验证码需要"输入框 + 按钮"的横向组合,都需要处理好换行。

默认情况下,flex 容器里的子元素即便压缩也不会换行,只会不断收缩。如果你的 Tab 文案较长,在小屏上会被压得很难看,这时候用flex-wrap: wrap是唯一正确解法:

.third-party { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }

gap在 wrap 容器里的表现同样可靠,它会同时控制换行后行与行之间的垂直间距,以及同一行内的水平间距。这个特性在你动手写媒体查询之前就提前解决了一半问题,因为不管内容被折到几行,间距都是统一的。

4.2 移动端登录页的隐藏规则:缩放、点击区域、安全区

移动端是登录页的主战场,但很多人只把"响应式"理解成"宽度改小一点",忽略了三个更隐蔽的坑。

第一个坑是输入框聚焦时的自动缩放。iOS Safari 在 input 的font-size小于 16px 时,会在聚焦时自动放大页面,相当于强制用户去手动缩小回来。所以移动端登录框的字体大小一律不要小于16px,桌面端可以保持一样的值,虽然视觉上偏大一点,但换来的是稳定体验。

第二个坑是点击区域。小屏上误触率本来就高,如果你的"记住我"、密码切换按钮只有 16px 大小,用户很容易点到隔壁输入框上。我一般会把可点击元素的最小宽高定位到 44px,也就是苹果人机交互指南里建议的舒适点击尺寸,记得给 label 和按钮加合适的 padding。

第三个坑是安全区。iPhone 全面屏底部有 home indicator,如果登录按钮正好卡在页面最底部,会被遮挡或显得很局促。处理方式是给最外层容器的padding-bottom额外加上env(safe-area-inset-bottom):

.login-page { padding-bottom: calc(24px + env(safe-area-inset-bottom)); }

这里env()只在支持安全区环境变量时会生效,不支持的环境会正常使用 24px,所以不用担心兼容性。

4.3 align-items 与 align-content:一字之差,行为天差地别

这是登录页开发里我见过面试现场也常被问到的考点,实际项目里也很容易踩。先记住一句话:align-items控制单行容器内子元素在交叉轴上的对齐,align-content控制多行容器中"行整体"的分布。

什么情况下会出现"行"?当容器设置了flex-wrap: wrap且子元素被折成多行后,每行自身就算一个整体,align-content决定这些行整体是居中、靠上、靠下还是均匀分布。

我之前就踩过一次:登录页底部放一排第三方图标,设置了flex-wrap: wrap和align-items: center,结果图标被折成两行后,两行之间缝隙很怪,怎么调都不对。后来把align-items: center改成align-content: center才解决,就是因为:一旦出现多行,align-items管的是"每一行内部"的垂直对齐,而两行作为一个整体在容器里的位置,需要align-content去管。

用一句话概括:没有flex-wrap、只有单行,就放心用align-items;出现多行,优先考虑align-content。两者可以同时写,各管各的。

4.4 避坑清单:登录页里最容易翻车的几个点

下面这张表整理了我实际写登录页时反复遇到、或者帮别人 review 时看到的典型问题。每一条都值得你在写完布局后自查一遍。

现象根本原因解法
输入框内容过长把 wrap 撑破flex 子项默认 min-width: auto给 input 加 min-width: 0
卡片在小屏上溢出只写固定 width 或 max-width使用 width: 100% + max-width
聚焦时外层框不变色只用了 input:focus 而非外层 :focus-within外层容器用 :focus-within 触发
移动端点击输入框页面放大input 字号小于 16px设置 font-size: 16px 或更大
内容超过一屏后背景截断外层用 height: 100vh改用 min-height: 100vh
flex-wrap 换行后两行间距乱只用了 align-items,没处理 align-content按需用 align-content 控制行整体分布
老浏览器不显示间距flex 容器里的 gap 兼容性不足用 margin + 父子选择器做兼容方案

这些坑都不是什么高深问题,但每个都能让一次简单的页面调试拖上半小时。把表存下来,写代码的时候对照一遍,能省不少时间。

5. 完整实战:一个可复制的登录页与打磨细节

5.1 HTML 结构:先搭骨,再上妆

正式开写之前,先给一个完整的、语义化的 HTML 结构。只要结构清晰,后面无论怎么调 CSS 都有底气:

<main class="login-page"> <section class="login-card"> <h1>欢迎回来</h1> <p class="subtitle">登录你的账号以继续</p> <form class="login-form"> <div class="field"> <label class="field-label" for="username">账号</label> <div class="input-wrap"> <svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> <circle cx="12" cy="7" r="4" /> </svg> <input id="username" name="username" type="text" placeholder="请输入账号" autocomplete="username" /> </div> </div> <div class="field"> <label class="field-label" for="password">密码</label> <div class="input-wrap"> <svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect> <path d="M7 11V7a5 5 0 0 1 10 0v4" /> </svg> <input id="password" name="password" type="password" placeholder="请输入密码" autocomplete="current-password" /> <button class="toggle-password" type="button">显示</button> </div> </div> <div class="actions"> <label class="remember"> <input type="checkbox" name="remember" />记住我 </label> <a href="#" class="forgot">忘记密码</a> </div> <button class="login-btn" type="submit"> <span>登录</span> </button> </form> </section> </main>

这里有几个注意点:第一个是 label 的for属性要和输入框id对应,点击文字时输入框才能聚焦,这个细节能提升表单可用性。第二个是密码框右侧的"显示"按钮,我用type="button"避免它触发表单提交,在 flex 容器里它可以自然地和图标、输入框排成一行,这是传统 input 不好模拟的交互。第三个是登录按钮用button type="submit"包裹文字,方便后面扩展 loading 状态。

5.2 CSS 完整代码:直接可跑的最小实现

把前面讨论过的所有方案整合成一套完整 CSS,粘贴到项目里可以直接运行:

* { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; } .login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; } .login-card { width: 100%; max-width: 420px; background: #fff; border-radius: 16px; padding: 40px 32px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; } .login-card h1 { font-size: 26px; text-align: center; } .subtitle { margin-top: 8px; text-align: center; color: #888; font-size: 14px; } .login-form { margin-top: 32px; display: flex; flex-direction: column; gap: 20px; } .field-label { display: block; margin-bottom: 6px; font-size: 14px; color: #333; } .input-wrap { display: flex; align-items: center; gap: 10px; background: #fafafa; border: 1px solid #ddd; border-radius: 12px; padding: 0 14px; transition: border-color 0.2s, box-shadow 0.2s; } .input-wrap:focus-within { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); background: #fff; } .input-icon { width: 20px; height: 20px; flex-shrink: 0; color: #999; } .input-wrap input { flex: 1; min-width: 0; padding: 14px 0; border: none; outline: none; background: transparent; font-size: 16px; color: #333; } .input-wrap input::placeholder { color: #bbb; } .toggle-password { border: none; background: none; color: #667eea; cursor: pointer; font-size: 14px; padding: 8px 0; flex-shrink: 0; } .actions { display: flex; justify-content: space-between; align-items: center; font-size: 14px; } .remember { display: flex; align-items: center; gap: 6px; padding: 4px 0; cursor: pointer; } .remember input { width: 16px; height: 16px; accent-color: #667eea; } .forgot { color: #667eea; text-decoration: none; } .login-btn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 48px; border: none; border-radius: 12px; background: #667eea; color: #fff; font-size: 16px; font-weight: 500; cursor: pointer; transition: background 0.2s; } .login-btn:hover { background: #5a6fd6; } .login-btn:disabled { background: #a5b1f0; cursor: not-allowed; }

这套样式里,几乎所有组件都是 flex 容器或 flex 子项。你把浏览器窗口从宽屏慢慢缩到手机宽度,卡片始终居中,输入框始终撑满,按钮始终占满,不需要额外引入任何 JavaScript。focus-within、gap、accent-color这些特性也让代码比十年前的老写法短了不少。

5.3 打磨细节:autofill、小屏微调、loading 反馈

演示版能跑起来之后,要做的事就是把它从"能看"磨到"好用"。第一个值得处理的是 Chrome 等浏览器给输入框自动填充时,默认会覆盖一层淡蓝色背景。你想要保留白色背景,可以加一段:

.input-wrap input:-webkit-autofill { -webkit-box-shadow: 0 0 0 1000px #fff inset; -webkit-text-fill-color: #333; }

这段 CSS 的原理是用一个巨大的内阴影盖住背景色,同时用-webkit-text-fill-color强制文字颜色。它不会影响 flex 布局,因为输入框本身仍然是一个 flex 子项,只是视觉背景被修正了。

第二个是 420px 宽度在小屏上的进一步收缩。虽然width: 100%已经让卡片不会溢出,但在 375px 屏幕上横向留白只有 24px 时,卡片圆角几乎贴到屏幕边缘,观感很差。可以在小屏媒体查询里进一步压缩:

@media (max-width: 480px) { .login-card { padding: 32px 20px; } }

八个字总结这个改动:宽度让出去,内边距省回来。卡片在小屏上依然有呼吸感。

第三个是提交按钮的 loading 反馈。登录动作大概率是异步的,用户点完按钮以后如果没有任何反应,会下意识再点一次,造成重复提交。给按钮加一个简单 loading 状态,可以在点击后改变按钮内容:

<button class="login-btn" type="submit"> <span class="spinner" style="display: none"></span> <span class="btn-text">登录</span> </button>

按钮的display: flex已经准备好了文字的居中对齐,只需要在点击时让 spinner 显示、文字换成"登录中..."即可。用 CSS 写一个带旋转动画的圆形小环也不复杂,关键是这份结构已经为它铺好了路,这就是在没考虑交互前就把按钮组件用 flex 封装的好处。

我自己现在拿到任何一个登录页设计稿,已经不急着写完整 CSS 了。会先花两三分钟确认几个问题:最外层容器的主轴方向是什么,卡片内部有哪些横向排列的组合,间距是统一用 gap 维护还是局部 margin。这几条理清楚,写起来非常快,而且基本不需要返工。Flex 这个东西,学的时候觉得概念多,一旦用登录页这种小而全的场景反复练几次,后面再处理列表、弹窗、导航栏的布局,你会发现核心思路都是同一套:先定主轴,再定对齐,然后排间距。这个习惯,值得从一个小登录页开始养成。

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

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

立即咨询