前几课,我们做出了一个效率工具箱:番茄钟可以计时,待办清单支持搜索和筛选,便签能够自动保存。
功能已经能用,但还需要检查一个问题:
当页面变窄时,它是否依然方便使用?
电脑上的空间很充足,输入框、下拉框和按钮可以排成一行。到了手机上,同一行可能出现这些情况:
输入框被挤得很窄 按钮文字换行 任务内容把删除按钮推到屏幕外 页面出现横向滚动这一课,我们通过 CSS 调整布局,让工具箱根据可用宽度重新排列。
本课接着修改第六课的工具箱,也兼容第七课独立的待办页面。主要修改样式,已有 JavaScript 继续负责计时、任务操作和保存。
先明确我们希望得到的效果:
| 区域 | 较宽的页面 | 较窄的页面 |
|---|---|---|
| 页面容器 | 居中显示,限制最大宽度 | 跟随屏幕宽度,保留少量边距 |
| 新增任务 | 输入框和按钮横向排列 | 输入框与按钮上下排列 |
| 搜索与筛选 | 多个控件按空间排列 | 每个控件独占一行 |
| 任务文字 | 正常显示 | 长文字可以换行 |
| 操作按钮 | 保持合适尺寸 | 空间不足时换行 |
| 番茄钟数字 | 使用较大的字号 | 随页面宽度调整字号 |
这种根据可用空间改变排版的做法,就是响应式布局。
先打开你的 HTML 文件,检查<head>中有没有这一行:
<meta name="viewport" content="width=device-width, initial-scale=1.0" >第六、七课的代码已经包含它,保留一份即可。
它告诉移动浏览器按照设备的 CSS 像素宽度设置视口,帮助窄屏布局规则正常工作。否则,某些移动浏览器可能先用一个较宽的虚拟视口排版,再把整个页面缩小。参考:MDN viewport
这里不要添加禁止用户缩放的设置。页面排版应适应宽度,读者也应该能够放大文字。
接下来,找到原有的<style>,把下面这段 CSS追加到里面的末尾、</style>之前。
这段代码使用前几课已有的 ID 和类名。
/* 统一计算元素宽度 */ *, *::before, *::after { box-sizing: border-box; } /* 外部边距由页面容器管理 */ body { margin: 0; padding: 0; } /* 同时适配工具箱和第七课的独立页面 */ .app, .standalone { width: 100%; max-width: 900px; margin: 0 auto; padding: 24px 16px; } /* 卡片允许在窄屏内收缩 */ .panel, #tasks-panel { min-width: 0; max-width: 100%; } /* 标题和状态文字允许换行 */ .app header h1 { font-size: clamp(22px, 5vw, 28px); line-height: 1.4; } #timer-overview, #timer-status, #task-summary, #task-view, #task-status, #note-status { overflow-wrap: anywhere; } /* 主要输入控件和按钮保留操作空间 */ .app button, #tasks-panel button, .app input[type="text"], .app input[type="search"], .app input[type="number"], #tasks-panel input[type="text"], #tasks-panel input[type="search"], #tasks-panel select { min-height: 44px; font-size: 16px; } /* 文本输入框允许收缩 */ #tasks-panel #task-input, #tasks-panel #task-search, #tasks-panel .search-field { min-width: 0; max-width: 100%; } /* 导航按可用空间排列 */ #tool-nav { display: flex; flex-wrap: wrap; gap: 10px; } #tool-nav button { flex: 1 1 120px; min-width: 0; white-space: normal; overflow-wrap: anywhere; } /* 番茄钟字号随视口宽度调整 */ #clock { font-size: clamp(40px, 12vw, 64px); line-height: 1.2; } /* 长任务文字可以收缩和换行 */ #tasks-panel .task span { min-width: 0; overflow-wrap: anywhere; } /* 删除按钮保留自己的空间 */ #tasks-panel .task .danger { flex-shrink: 0; } /* 便签始终跟随容器宽度 */ #note-editor { width: 100%; min-width: 0; max-width: 100%; min-height: 240px; font-size: 16px; line-height: 1.8; } /* 当视口宽度不超过 480 CSS 像素时调整排版 */ @media (max-width: 480px) { .app, .standalone { padding: 16px 12px; } .panel, #tasks-panel { padding: 16px; border-radius: 12px; } /* 三个导航按钮尽量均分一行 */ #tool-nav { gap: 8px; } #tool-nav button { flex: 1 1 0; padding: 10px 6px; font-size: 14px; } /* 新增任务改成上下排列 */ #tasks-panel #task-form { display: flex; flex-direction: column; align-items: stretch; gap: 10px; } #tasks-panel #task-input { flex: none; width: 100%; } #tasks-panel #task-form > button { width: 100%; } /* 搜索、状态筛选和清除按钮各占一行 */ #tasks-panel .task-tools { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; align-items: stretch; } #tasks-panel .task-tools > div { min-width: 0; } #tasks-panel #task-search, #tasks-panel #task-filter, #tasks-panel #reset-view { width: 100%; } /* 任务行:勾选框、任务文字、删除按钮 */ #tasks-panel .task { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: start; gap: 10px; } #tasks-panel .task span { padding-top: 8px; } #tasks-panel .task .danger { padding: 10px; } /* 番茄钟和便签操作按钮允许换行 */ .app .actions { display: flex; flex-wrap: wrap; gap: 10px; } .app .actions > button { flex: 1 1 120px; } .app .note-actions #note-count { flex: 1 1 100%; } #clock { margin: 24px 0; } }保存文件,刷新页面,再逐渐缩小浏览器窗口。
你应该能观察到:页面宽度较大时,保留横向排列;当视口宽度不超过 480 CSS 像素时,新增任务和筛选区域变成上下排列。
480 是本例选择的布局断点,不是手机型号的判断标准。
如果在你修改后的页面里,按钮到 520 像素宽时就已经拥挤,可以把断点调整到 520。选择断点的依据,是内容什么时候需要重新排列。
接下来拆开看几个容易忽略的细节。
首先是页面容器:
.app, .standalone { width: 100%; max-width: 900px; margin: 0 auto; padding: 24px 16px; }width: 100%让容器跟随可用宽度。
max-width: 900px限制它在大屏幕上继续变宽。
margin: 0 auto让它居中。
再配合:
box-sizing: border-box;容器的边距和边框会计入指定宽度,减少“宽度已经是 100%,加上内边距后又超出”的问题。
其次是min-width: 0。
不少新手会遇到这样的现象:已经设置了弹性布局,输入框或文字区域还是不肯缩小。
在某些 Flex 和 Grid 布局中,元素的默认最小宽度会受内容影响。给需要收缩的元素设置:
min-width: 0;可以允许它缩到比内容默认最小宽度更窄,再交给换行规则处理文字。参考:MDN min-width
例如任务行里的文字:
#tasks-panel .task span { min-width: 0; overflow-wrap: anywhere; }这两行配合使用,能处理一长串没有空格的英文、路径或网址。
你可以添加下面这条任务进行检查:
检查VeryLongFileNameWithoutSpacesAndMoreCharacters1234567890也可以试试:
https://example.com/a/very/long/path/without/convenient/breaksoverflow-wrap: anywhere允许在需要时把长字符串拆到下一行,避免撑开容器。参考:MDN overflow-wrap
它适合任务说明和状态提示。如果以后展示需要保留格式的代码块,则应单独决定是否使用横向滚动。
然后看窄屏下的任务行:
grid-template-columns: 24px minmax(0, 1fr) auto;它把一行分成三部分:
勾选框 任务文字 删除按钮 固定空间 使用剩余空间 按内容占用空间中间的minmax(0, 1fr)允许文字区域收缩,长内容向下换行。
这样,删除按钮仍然留在任务右侧,不需要跟着长文字跑到屏幕外。
不过,按钮文字也会影响布局。如果以后把“删除”改成“永久删除这条任务”,右侧需要的空间会明显增加。按钮文案应与实际空间一起检查。
再看手机上的搜索区域。
上一课使用 Flex 布局,允许搜索、下拉框和按钮根据空间换行。
本课在较窄页面里明确改成一列:
#tasks-panel .task-tools { display: grid; grid-template-columns: minmax(0, 1fr); }同时让里面的控件占满这一列:
#tasks-panel #task-search, #tasks-panel #task-filter, #tasks-panel #reset-view { width: 100%; }这样,用户可以看到完整的搜索框和状态选项,不需要在很窄的控件里操作。
番茄钟数字则使用:
font-size: clamp(40px, 12vw, 64px);这表示字号在 40px 到 64px 之间,根据视口宽度取合适值。
对于这个项目,数字仍然突出,同时减少窄屏上的空间占用。
不要通过隐藏页面横向溢出,直接掩盖布局问题。
如果一发现横向滚动,就给整个页面加上:
overflow-x: hidden;超出的内容可能只是被裁掉了,按钮仍然可能留在可见范围之外。
应先找到撑开页面的元素,检查固定宽度、最小宽度、长字符串和子元素布局。
接下来用浏览器检查适配效果。
在 Windows 上的 Chrome 中打开页面,按F12打开开发者工具,再按Ctrl + Shift + M切换设备工具栏。
选择 Responsive 模式,手动调整宽度。设备模式可以模拟视口和部分移动设备行为,但不能完全替代真实手机测试。参考:Chrome 设备模式
建议检查下面几组宽度:
| 视口宽度 | 主要检查内容 |
|---|---|
| 320px | 最窄情况下,输入框和删除按钮是否仍可操作 |
| 375px | 导航文字、任务行和便签是否拥挤 |
| 480px | 窄屏规则是否生效 |
| 481px | 断点外的布局是否依然自然 |
| 768px | 横向排列是否合理 |
| 1024px | 容器是否居中、宽度是否受限 |
除了空页面,还要加入真实内容测试:
- 添加一条接近输入上限的任务。
- 添加没有空格的英文任务。
- 输入一条较长网址。
- 搜索不存在的任务,检查空结果提示。
- 在便签里输入多行文字。
- 切换三个工具,检查隐藏区域是否正常。
- 用键盘 Tab 键移动焦点,检查焦点提示是否可见。
如果页面出现横向滚动,还可以在开发者工具的 Console 中执行:
document.documentElement.scrollWidth > document.documentElement.clientWidth返回true,说明根元素的可滚动宽度大于可见宽度。它是排查线索,还需要继续找到具体元素。
可以用下面这段代码列出超出视口左右边界的元素:
const viewportWidth = document.documentElement.clientWidth; const overflowElements = [ ...document.querySelectorAll("body *") ].filter(element => { const rect = element.getBoundingClientRect(); return rect.width > 0 && ( rect.left < -1 || rect.right > viewportWidth + 1 ); }); console.log(overflowElements);这里留出 1 像素容差,减少小数尺寸造成的干扰。
输出列表里的元素不一定都是错误,例如你主动设计的滑动区域也可能超出。但对于本课工具箱,可以用它寻找需要调整的输入框、容器或任务行。
如果新增 CSS 没有效果,优先检查:
| 现象 | 检查方向 |
|---|---|
| 搜索区域仍然挤在一行 | 是否放进了正确的媒体查询,视口是否满足条件 |
| 输入框依然很窄 | 是否还有旧的flex或宽度规则覆盖它 |
| 页面整体缩小 | 是否存在正确的 viewport 标签 |
| 长任务仍然撑出屏幕 | 文字元素是否匹配到min-width和换行规则 |
| 新样式显示为被划掉 | 是否被优先级更高或更晚的规则覆盖 |
| 所有工具同时显示 | 是否仍保留第六课的[hidden]隐藏规则 |
让 AI 帮你调整时,可以把需求描述得具体一些:
我的效率工具箱包含番茄钟、待办清单和便签。
在 375px 视口下,搜索框太窄,长任务把删除按钮推到了屏幕外。
请检查容器宽度、Flex/Grid 子元素的最小宽度和文字换行。
窄屏时让新增任务和搜索筛选上下排列,并保留可见的键盘焦点。
请指出要追加或替换的 CSS,并给出检查步骤。
如果在真实手机上测试,还要留意屏幕键盘弹出后的使用情况。
另外,手机里的127.0.0.1指向手机自身,不能直接打开电脑上绑定该地址的本地服务器。现阶段可以先用浏览器设备模式检查布局;第十课会讲静态页面发布,让工具箱拥有可以访问的网页地址。
下一课,待办清单能运行,为什么还会误删、丢数据?JavaScript 状态管理与故障测试实战|第 9 课进阶篇