资讯动态

重拾前端练习:HTML/CSS/JS基础与盒模型Flex事件委托实战

发布时间:2026/10/27 15:04:18 来源:尧图企业网站定制
重拾前端这件事我是从能把页面完整写出来这个最低标准开始的。上一次认真写 HTML 和 CSS 已经是好几年前中间隔了很长一段时间没碰重新打开编辑器的时候连 display 有几个常用值都要愣一下。所以这次我没有一上来就去啃框架、看源码而是老老实实回到最基础的练习找一套讲解细致的视频课程跟着一节一节敲敲完再关掉视频自己默写一遍。这套练习篇的定位很简单——不是让你学会多少新东西而是把已经看过、听过、以为自己会的那些知识点变成手指的肌肉记忆。练习篇最容易出的问题是把它当成看视频而不是写代码。视频里老师敲得飞快你跟着点头感觉全都懂一旦关掉视频让你从空白文件开始写一个能跑的页面手就停了。这个落差不是理解力的问题是输出量不够。我的做法是把每节课拆成三步第一遍纯看只记关键词不敲代码第二遍暂停跟敲允许照抄第三遍关掉视频默写写完再对照检查差在哪里。第三步才是真正长本事的地方也是最容易偷懒跳过的地方。下面这些内容适合两类人看一类是像我这样中断过、基础还在但生锈了的另一类是从零开始、正在找练习节奏的。整套流程围绕 HTML 结构、CSS 布局、JavaScript 交互三块地基展开中间会穿插我在练习期反复踩到的坑、调试技巧和具体的参数计算过程。所有代码都是练习级别的能直接跑、能直接改你抄走改改就能用。1. 重拾前端练习篇到底在练什么1.1 看懂了和写得出中间隔着一条鸿沟很多人重拾前端时会陷入一种知识幻觉视频里讲浮动、讲定位、讲事件冒泡每一句都听得懂于是默认自己掌握了。但真实的编码现场不是这样运作的——现场给到你的是一张设计稿或者一句需求描述没有提词器告诉你下一步该写哪个属性。这种从被动接收到主动构造的转换只能靠大量的、有反馈的重复练习来完成。我给自己定的判断标准很粗暴能不能在没有任何参考资料的情况下从空文件夹开始把一个包含头部、内容区、列表、按钮交互的页面写出来并且一次跑通不报错。第一次试的时候我用了将近两个小时中间翻了好几次文档还把一个选择器优先级搞错导致样式死活不生效。这个狼狈的过程恰恰是最有价值的因为它精确指出了你真实的知识缺口在哪里比看十遍视频都准。所以我建议的练习方式是输入—输出严格配比看一小时视频至少要配两小时的独立编码。输出的比例可以随着熟练度调整但绝不能低于 1:1。视频课程的价值在于给你一条经过设计的知识路径避免你在细枝末节上迷路但路径走完路得自己再走一遍才算数。1.2 一套能坚持下来的练习节奏练习篇最怕的是前两天激情满满写十个小时第三天开始拖延一周后彻底搁置。我试过很多种节奏最后发现真正有效的是短时长、高频次、有产出每天固定 60 到 90 分钟其中 20 分钟看课程、40 分钟写代码、剩下时间做复盘记录。周末留一个两小时的整块时间用来做完整案例因为写完整页面需要连续的上下文碎片时间做不了。阶段时长安排主要任务产出物验收标准第一周每天 60 分钟HTML 常用标签、语义化结构、表单元素3 个静态结构页不看资料能默写常见页面骨架第二周每天 90 分钟盒模型、浮动、定位、Flex 布局2 个整页还原布局不靠猜能用调试面板定位问题第三周每天 90 分钟DOM 查询、事件绑定、样式操作2 个带交互的小页面交互逻辑能独立写出报错能自己排查第四周周末集中完整案例从零重写1 个可交付的练习项目关掉视频全程独立完成这个表看起来平平无奇但执行起来的关键在于验收标准那一列。任务可以打折验收标准不能打折。比如第二周的验收标准是布局不靠猜意思是当你写完一段 CSS 发现效果不对时必须能在浏览器的调试面板里找到到底是哪条规则生效了、为什么生效而不是靠改数值蒙对。这一个习惯的建立直接决定了后面几个月的学习效率。还有一点容易被忽略每写一段代码都要在浏览器里刷新看结果。有人习惯一口气写完整个页面再看效果出问题时根本定位不到是哪一块写错了只能大面积注释掉重来。改成写完一个区块就刷新一次问题范围永远被限制在最近这几行代码里排查成本极低。这个习惯我从练习期一直保留到现在算是性价比最高的一条。2. 开工前的环境与目录约定2.1 编辑器与浏览器调试面板的极简配置工具这块不用折腾越简单越好。我用的是一个主流代码编辑器加一个主流浏览器的组合插件只装了三个实时预览、路径自动补全、代码格式化。实时预览的作用是保存即刷新省掉手动切窗口的动作路径补全解决的是写./images/时总差一个字母的问题格式化保证缩进统一练习期代码写得乱没有格式化根本看不懂自己昨天写的东西。真正值得花时间研究的是浏览器的调试面板这是练习期最重要的反馈工具。三个面板我几乎每分钟都在用元素面板看 DOM 结构和你写的 HTML 是否一致检查某个元素上到底挂了哪些类名。我经常在这里发现自己少写了一个闭合标签导致后面的元素全部变成了嵌套关系布局自然全乱。样式面板看每条 CSS 规则的生效状态。被划掉的属性说明被更高优先级的规则覆盖了这一条能解释掉大约七成的样式不生效问题。右侧的计算样式标签更关键它显示的是浏览器最终算出来的值比如你设了width: 100%实际算出来是327.5px那问题就在父容器身上。控制台除了看报错我更常用它做临时验证。选中某个元素后输入$0就能拿到这个元素的引用直接测试$0.getBoundingClientRect()看它的实际尺寸和位置比盯着屏幕猜快得多。提示调试面板里改样式是临时的刷新就没了。这个特性要善用——它允许你大胆尝试改坏了刷新一下恢复原样完全不用害怕。2.2 练习仓库的目录结构约定练习期的项目都很小但目录一定要有规矩否则一周之后你自己都找不到东西在哪。我的约定是每个练习一个独立文件夹文件夹内部固定四层结构practice-01-goods-list/ ├── index.html ├── css/ │ ├── base.css # 重置样式、公共变量、通用类 │ └── index.css # 当前页面的专属样式 ├── js/ │ └── index.js └── images/ └── ...拆分base.css和index.css的理由很实际重置样式margin、padding归零、box-sizing统一几乎每个项目都要写抽成独立文件后可以直接复制到下一个练习里省掉重复劳动也避免每次都重新纠结要不要用通配符重置。样式表引入顺序也会影响结果base.css必须在前面因为后面的index.css需要能覆盖基础设置。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title练习页 - 商品列表/title link relstylesheet href./css/base.css link relstylesheet href./css/index.css /head body !-- 页面结构 -- script src./js/index.js/script /body /html这里有两个细节值得说一下。第一script放在body末尾是因为脚本里要查询 DOM 元素如果放在head里脚本执行的时候下面的元素还没解析出来querySelector会直接返回null。这是新手期最常见的报错来源之一我在练习第二天就撞了一次。第二meta viewport那一行如果漏掉在手机上打开会发现整个页面被缩小了所有文字小得看不清因为移动端浏览器默认按 980px 左右的宽度渲染再缩放。加上这一行页面才会按设备实际宽度布局。box-sizing: border-box我建议从第一行代码就设成全局别等踩坑了再改* { margin: 0; padding: 0; box-sizing: border-box; }默认的content-box模式下你设width: 200px再加上padding: 20px和border: 1px实际占位变成 242px两个并排的元素就换行了。这个计算过程我一开始完全没概念还以为是浏览器算错了。切成border-box后宽度就是最终占位宽度padding和border往里挤数学关系直观得多布局时的心智负担小一大截。3. 三块地基的专项练习拆解3.1 HTML先把结构写对后面样式才不打架HTML 的练习重点不在标签数量在于结构层级是否反映内容逻辑。我刚重拾的时候有个坏习惯为了让样式好写动不动就套div一个列表项里能塞四个div加两个span。这样写样式确实顺手但结构完全失去了语义后面想加交互或者做适配的时候根本分不清哪个div是干什么的。现在的练习方法是写结构之前先用一句话描述每块内容是什么然后选择最贴切的标签。比如一个商品列表外层是列表就用ul每一项是li标题用h3价格用p配一个span包住数字方便单独调字体按钮用button而不是a。这么写完之后类名会自然变少因为标签本身已经携带了语义信息。内容类型该用的标签常见错误写法影响一组同类项目ulli外层 div 里堆 div无障碍读屏无法识别为列表点击后执行动作buttona加空 href键盘无法触发语义混乱商品标题、文章标题h2或h3div加字体加粗页面标题层级丢失独立成篇的内容块section或article一律用div结构扁平维护困难练习期我会刻意做一个动作写完之后打开调试面板用无障碍检查功能看页面的标题层级和列表结构。如果检查结果里冒出一堆无标题的区块说明结构写得还是太糊。这个反馈比视觉上看起来对不对更严格也更能逼着你把结构想清楚。另外图片一定要写alt。练习期容易觉得反正是自己看的不写无所谓但养成习惯之后alt会提醒你每次插入图片时想一想这张图承担的是什么信息——是内容本身还是纯装饰。纯装饰的图应该用 CSS 背景图内容图才用img这个区分在以后做性能优化时会被反复用到。3.2 CSS盒模型和你绕不过的四个经典坑CSS 是重拾阶段最容易挫败的部分因为它不像编程语言那样有明确的报错样式不对的时候浏览器什么都不说全靠你自己找。我把练习期踩过的坑整理了一遍反复出现的基本就是下面这几类每一个都值得单独练一遍。第一坑margin 塌陷。父元素没有padding和border的时候子元素的margin-top会穿透到父元素身上表现为父元素整体往下跑而子元素贴在上面。我第一次遇到时以为是浏览器 bug折腾了半小时。解决方式有三种任选给父元素加overflow: hidden触发块级格式化上下文给父元素加padding-top替代子元素的margin-top或者给父元素加border。练习期我会故意写一次塌陷然后用三种方式各修一遍把这个感觉刻进脑子。第二坑浮动之后父元素高度为 0。浮动元素脱离了常规文档流父元素计算高度时不会把浮动的子元素算进去于是背景色、边框全都消失了后面的元素也会顶上来。清除浮动的方法我用的是伪元素方案兼容性好、不影响结构.clearfix::after { content: ; display: block; clear: both; height: 0; visibility: hidden; }不过要说明的是现在做布局我会优先用 Flexible Box浮动更多是用在文字环绕图片这种场景。练习期还是要专门练一遍浮动清除因为你在已有的老项目里一定会遇到它看得懂比会写更重要。第三坑行内元素的宽高设置无效。span、a这些行内元素设width和height是不生效的margin-top和margin-bottom也不生效。要么把display改成inline-block或block要么就用line-height和内边距来控制。这个问题在给按钮里的文字做垂直居中时特别容易出现我第一次给a标签设高度怎么都不对最后才发现是display的问题。第四坑图片底部莫名其妙多出几像素空白。图片是行内元素它的基线对齐方式和文字一样底部会给字母的下伸部分留出空间于是图片底下就多了一条缝。解决办法是给图片设display: block或者vertical-align: middle。这个坑极其隐蔽页面放大到 200% 才看得出来但它会让你的布局高度对不上设计稿。注意上面这些坑不要在脑子里过一遍就算学会。我建议每一个都亲手复现一次看着问题出现再动手修好这个过程形成的记忆比看十篇总结都牢固。3.3 JavaScript从查到改把页面动起来JS 部分的练习重点只有两个动作查询元素和修改元素。绝大多数页面交互都能拆成这两步的组合——找到按钮绑定点击点击后找到对应的内容区改它的类名或者文字。练习期不要一上来就搞复杂逻辑先把这两个动作练到不假思索。查询元素我统一用document.querySelector和querySelectorAll前者返回第一个匹配元素或者null后者返回一个静态的节点列表。之所以不用getElementsByClassName是因为它返回的是动态集合你在循环里增删元素时集合会实时变化很容易写出死循环或者漏遍历。这个差异我在练习时专门验证过一次拿到集合之后往页面里再加一个同类元素动态集合的长度会立刻变静态列表不会。理解了这个后面处理列表渲染就不会莫名其妙出错。修改元素的最小闭环是这样的var btn document.querySelector(.btn-buy); var list document.querySelector(.list); list.addEventListener(click, function (e) { var target e.target.closest(.btn-buy); if (!target) return; var id target.dataset.id; var item target.closest(.item); item.classList.add(is-selected); target.textContent 已加入; target.disabled true; console.log(选中的商品编号, id); });这段代码里有三个练习期必须掌握的点。第一是事件委托把监听器绑在外层列表上而不是给每个按钮单独绑。理由是列表项数量可能变化绑在父级上永远有效而且一个监听器比 N 个监听器省内存。第二是closest方法从触发元素往上找最近的匹配祖先能有效解决点到按钮里的文字而不是按钮本身的问题。第三是dataset读取自定义属性>(function () { var designWidth 750; var docEl document.documentElement; function setRem() { var clientWidth docEl.clientWidth; if (clientWidth designWidth) { clientWidth designWidth; } docEl.style.fontSize (clientWidth / designWidth) * 100 px; } setRem(); window.addEventListener(resize, setRem); })();验算一下在 375px 宽的手机上clientWidth是 375根字号 375 / 750 × 100 50px。前面算的卡片宽 6.9rem实际渲染就是 6.9 × 50 345px正好等于 375 − 30 345px左右各留 15px。这个算式对上了说明换算逻辑没错。如果是 414px 的屏幕根字号变成 55.2px卡片变成 380.88px左右留白 16.56px比例依然一致。当屏幕超过 750px 时我把宽度锁死在 750避免在大屏上元素被拉得过于夸张。结构部分div classpage header classhd商品清单/header ul classlist idlist li classitem>:root { --main: #ff5000; --text: #333; --gap: 0.24rem; } body { font-size: 0.28rem; color: var(--text); background: #f5f5f5; font-family: system-ui, -apple-system, Helvetica Neue, sans-serif; } .page { display: flex; flex-direction: column; min-height: 100vh; max-width: 7.5rem; margin: 0 auto; background: #fff; } .hd { display: flex; align-items: center; justify-content: center; height: 0.88rem; font-size: 0.32rem; font-weight: 600; border-bottom: 1px solid #eee; } .list { display: flex; flex-wrap: wrap; gap: var(--gap); padding: var(--gap); list-style: none; } .item { width: calc(50% - var(--gap) / 2); padding: 0.16rem; box-sizing: border-box; border: 1px solid #eee; border-radius: 0.16rem; } .thumb { display: block; width: 100%; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 0.12rem; background: #f2f2f2; } .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; } .title { margin: 0.16rem 0 0.08rem; font-size: 0.28rem; font-weight: 400; line-height: 1.4; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .price { color: var(--main); font-size: 0.24rem; } .price span { font-size: 0.32rem; font-weight: 600; } .btn-buy { width: 100%; height: 0.6rem; margin-top: 0.12rem; border: 0; border-radius: 0.3rem; background: var(--main); color: #fff; font-size: 0.26rem; } .btn-buy:disabled { background: #ccc; } .item.is-selected { border-color: var(--main); } .ft { margin-top: auto; padding: 0.2rem; text-align: center; font-size: 0.26rem; color: #666; border-top: 1px solid #eee; }几个值得说明的设计决策。width: calc(50% - var(--gap) / 2)是为了让两列卡片加中间间隙刚好填满一行如果直接写50%再用gap两列加间隙会超出容器宽度导致换行卡片就变成一列了。aspect-ratio: 1 / 1让图片容器保持正方形配合object-fit: cover让图片裁剪填充而不变形这比老式用padding-top: 100%的写法直观太多。标题的两行截断用的是-webkit-line-clamp虽然带前缀但主流浏览器都支持练习期用它完全够。margin-top: auto放在底部条上配合父级flex-direction: column能把底部栏推到页面最下方不需要算高度。交互部分document.addEventListener(DOMContentLoaded, function () { var list document.getElementById(list); var countEl document.getElementById(count); var selected []; list.addEventListener(click, function (e) { var btn e.target.closest(.btn-buy); if (!btn || btn.disabled) return; var item btn.closest(.item); var id item.dataset.id; if (selected.indexOf(id) -1) { selected.push(id); } item.classList.add(is-selected); btn.textContent 已加入; btn.disabled true; countEl.textContent selected.length; }); });这段代码里selected数组承担的是数据源的角色界面上的数字永远从数组长度算出来而不是用countEl.textContent自增。这个习惯一开始会觉得绕但它能避免一类经典 bug状态和数据不同步。练习期养成这个思路以后接触数据驱动的写法时过渡会非常自然。4.3 默写检验与二次重构代码跑通只是第一步真正的验收在第二天。我的做法是关掉视频、新建一个空文件夹从index.html的第一个字符开始重写只允许查文档不允许看自己昨天的代码。写完对比一下通常会发现两类问题一类是语法记错比如calc里减号两边忘了空格导致计算无效-webkit-line-clamp忘了配display: -webkit-box另一类是结构差异比如我第一遍是把间距写在每个卡片上第二遍才意识到用父级gap更简洁。第二遍重写的时候我会故意换个方案实现同一个效果比如两列布局第一遍用width: calc()第二遍用flex: 1加负边距或者干脆换成网格布局。同一个效果用不同方案各写一遍你对每个方案边界的理解会清晰很多——什么时候用gap更省事什么时候必须靠calc心里就有数了。这一步很多人会跳过觉得能跑就行但练习篇的价值恰恰在这一步。提示默写检验时不要求一次跑通。出现报错反而是好事它把你不牢靠的地方标出来了。我第二次默写时在querySelector上栽了跟头忘了加类名前面的点控制台返回null我当时已经忘了null是什么意思查了半天才反应过来。这个跟头之后选择器的写法我再没出过错。5. 踩坑实录与排查技巧5.1 样式不生效按这个顺序查样式问题占了练习期八成的时间。我总结了一个固定的排查顺序按顺序走基本能在两分钟内定位第一看元素面板里这个元素上的类名和你 CSS 里写的选择器是否完全一致。拼写错误、多一个空格、连字符写成下划线是最常见的原因而且调试面板不会提示你只能肉眼比。第二在样式面板里找到你写的规则看它是否被划掉了。被划掉说明有更高优先级的规则覆盖了它常见于你自己后面又写了一条同名规则或者用了!important而你没注意。第三看计算样式确认最终的数值是多少和你的预期差在哪里这能区分是宽度问题还是定位问题。第四检查样式表有没有被正确引入路径对不对文件名大小写是否一致——某些系统对文件名大小写敏感Index.css和index.css是两个文件。还有一个隐蔽的原因元素自身尺寸为 0。给一个空的div设margin当然看不到效果因为它没有高度。这种情况用元素面板把鼠标移上去浏览器会高亮出元素的实际区域一眼就能看出它是没有尺寸还是位置不对。5.2 JS 报错的高频原因练习期的 JS 报错翻来覆去就那么几种认识清楚之后排查速度会快很多。Uncaught TypeError: Cannot read properties of null (reading xxx)是最典型的一个意思是querySelector没找到元素返回了null你又在这个null上取属性。原因通常有两个选择器写错或者脚本执行时元素还没渲染出来。前者改选择器后者把脚本放body末尾或者用DOMContentLoaded包一层。xxx is not a function一般是方法名拼错或者调用对象的类型不对。比如对querySelectorAll的返回值直接调addEventListener它是个节点列表不是单个元素必须先遍历或者取下标。这类错误看控制台的行号直接跳过去就行改起来很快。逻辑错但不报错是最麻烦的一类。比如点击按钮没反应代码也没报错那大概率是事件没绑上选择器问题或者绑上了但被别的元素挡住了层级问题。我的排查方法是先在监听器里console.log一句确认事件是否触发触发了就是逻辑问题没触发就是绑定问题。这个二分法能省掉大量瞎猜的时间。5.3 问题速查表现象最可能的原因快速验证方法解决方向两个并排元素换行了宽度 padding border 超过容器在计算样式里看实际宽度全局设border-box或减小宽度父元素高度为 0子元素浮动未清除元素面板看父元素高亮区域清除浮动或改用 Flex子元素 margin 顶穿父元素margin 塌陷给父元素临时加 border 观察加 padding 或触发格式化上下文图片下面有缝隙行内元素基线对齐放大页面观察底部图片设display: block点击没反应脚本在元素之前执行控制台查是否null脚本移到末尾或用加载事件修改了样式没变化缓存或选择器优先级强制刷新再看样式面板清缓存或提高选择器权重移动端字体特别小缺 viewport 元信息手机上打开看缩放比例补上meta viewportrem 换算对不上根字号未设置或设置时机不对控制台看documentElement计算字号检查适配脚本执行顺序这张表我贴在练习文件夹里遇到问题先扫一眼命中率大概能到七成。剩下的三成通常是几个问题叠加在一起需要一条一条排除这时候耐心比技巧更重要。6. 练习期我踩出来的几条经验6.1 宁可写得慢也别抄得快练习期最大的诱惑是先把效果做出来再说于是遇到不会写的样式就打开调试面板从别的页面复制一段遇到不会写的逻辑就去搜一段能跑的代码贴进来。这么做的结果是页面确实做出来了但你什么都没学会下次遇到同样的需求还是不会。我给自己定了个规矩任何一段代码如果不能在不看资料的情况下解释清楚每一行的作用就不许留在文件里。要么搞懂要么删掉重写。这条规矩让我的进度慢了不少第一周的练习量大概只有别人的一半。但一个月之后差距就反过来了——别人还在到处找现成代码改我已经能从零开始构造。练习篇的目的从来不是产出多少页面而是把知识变成能力速度是这个过程中最不重要的指标。6.2 笔记只记两类东西练习期容易陷入另一种极端把视频里讲的每个知识点都抄一遍笔记抄完厚厚一本回头一次都没翻过。我现在的笔记只记两类内容一类是踩过的坑和它的解法格式固定为现象—原因—解决用我自己的话写不抄原文另一类是能复用的代码片段比如清除浮动的写法、rem 适配的脚本、事件委托的模板配上两三行注释说明什么场景用。这两类内容有个共同点它们都是下次还会用到的。知识点本身在文档里随时能查到但你踩坑时的那个具体情境、那段调了半小时才对的代码文档里没有。笔记的价值在于节省重复劳动不在于把知识搬一遍家。我的笔记到现在也就几十条但每一条都被翻过好几次。提示练习文件夹不要删。三个月后回头看自己第一周写的代码那种这写的什么玩意儿的羞耻感是最直观的进步证据也是继续下去的动力。最后分享一个我在练习后期一直在用的小方法每完成一个案例就把它改成另一个主题换个配色、换个内容、调整一下布局结构重新写一遍。同一个骨架做三次第一次照着视频第二次关掉视频默写第三次换皮改造。三次之后这套结构就真的长在手上了后面再遇到类似的页面几乎不用思考就能写出来。这个方法笨但有效而且不需要任何额外资料一个案例就能榨出三倍的练习量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑