前端基础入门


目录:

整体课时规划

模块 课时数 核心目标
课前准备 2 认识前端、搭建极简学习环境
HTML(骨架) 18 掌握网页结构搭建的所有核心
CSS(样式) 25 搞定美化+布局,做出美观网页
JS(交互) 30 实现网页动态效果,会做简单交互
综合实战 5 整合知识,完成2个入门级项目

第一部分:课前准备(2课时)

课时1:认识Web前端——到底学什么?

学习目标

知道前端是做什么的,分清HTML/CSS/JS的分工,建立学习框架。

通俗讲解

前端 = 网页/APP的「用户看得见的部分」,比如淘宝首页、微信公众号页面;

  • HTML:网页的「骨架」(决定有什么,比如标题、图片、按钮);
  • CSS:网页的「装修」(决定长什么样,比如颜色、大小、位置);
  • JS:网页的「功能」(决定能做什么,比如点击按钮弹窗、输入内容实时显示)。

img

核心认知

  1. 前端开发工具:只用「记事本/VS Code + 浏览器(Chrome)」,不用装复杂软件;
  2. 学习路径:先搭骨架(HTML)→ 再装修(CSS)→ 最后加功能(JS);
  3. 核心原则:先实现「能用」,再优化「好看/好用」。

课后小练习

打开Chrome浏览器,随便打开一个网页(比如百度),右键→「检查」,看看Elements面板里的HTML/CSS代码(不用懂,只需要知道“网页是由这些代码组成的”)。

课时2:搭建极简学习环境

学习目标

会安装VS Code,能新建/保存/运行HTML文件。

通俗讲解

VS Code是「前端专用记事本」,比系统记事本好用,支持代码高亮、自动补全,新手只需要掌握最基础的操作。

实操步骤

  1. 下载安装VS Code:官网(https://code.visualstudio.com/)下载,一路点「下一步」安装;

image-20251230185630113

2.安装中文插件:打开VS Code→左侧「扩展」→搜「Chinese」→安装「Chinese (Simplified) Language Pack」→重启VS Code;

image-20251230185859924

安装Live Server插件,代码更新,浏览器自动更新页面

image-20251231104217549

设置自动保存,每次编辑自动保存文件

image-20251231104318260

3.新建第一个文件:

  • 新建文件夹(比如「前端学习」);
  • VS Code→打开文件夹→右键「新建文件」→命名为01-测试.html(后缀必须是.html);
  • 输入<!DOCTYPE html><body>你好,前端!</body></html>,按Ctrl+S保存;

  • 运行文件:右键文件→「在默认浏览器中打开」,能看到页面显示“你好,前端!”即可。

image-20251230185821434

易错点提醒

  • ❌ 文件后缀写错:把.html写成.htm/.txt,浏览器打开不是网页;
  • ❌ 保存路径有中文/空格:比如「我的 前端/测试.html」,部分场景会出问题,建议用英文/数字命名(如web-study/01-test.html)。

课后小练习

参考01-测试.html代码,创建02-VSCode.html,编辑“我的第一个VS Code网页”,保存后刷新浏览器,看文字变化。

VScode快捷键(了解):

功能 Windows/Linux Mac 前端使用场景
复制当前行/选中内容 Ctrl + C Cmd + C 复制代码片段(如CSS样式、JS函数)
剪切当前行/选中内容 Ctrl + X Cmd + X 快速移动代码行(如调整HTML标签顺序)
粘贴 Ctrl + V Cmd + V 粘贴复制的代码
撤销 Ctrl + Z Cmd + Z 回退错误操作(如误删代码)
重做 Ctrl + Y Cmd + Shift + Z 恢复撤销的操作
保存文件 Ctrl + S Cmd + S 前端开发必高频用(避免代码丢失)
全选 Ctrl + A Cmd + A 选中整段代码(如替换整块CSS样式)
格式化代码(核心) Shift + Alt + F Shift + Option + F 一键整理HTML/CSS/JS格式,解决缩进混乱
注释行(单行/多行) Ctrl + / Cmd + / 快速注释调试代码(前端调试必备)
复制当前行到下一行 Shift + Alt + ↓ Shift + Option + ↓ 快速复制重复代码(如多个li标签、CSS属性)
移动当前行上下 Alt + ↓/↑ Option + ↓/↑ 调整代码行顺序(如调整JS逻辑、CSS属性)
快速查找 Ctrl + F Cmd + F 在当前文件搜索关键词(如找CSS类名、JS变量)
查找并替换 Ctrl + H Cmd + H 批量替换(如修改类名、变量名)

VSCode内置Emmet语法,输入缩写后按Tab键即可快速生成代码,是前端写HTML/CSS的效率神器:

输入缩写 按Tab生成效果
! 生成HTML5完整骨架(DOCTYPE、html/head/body等)
div.container <div class="container"></div>
ul>li*3 生成包含3个li的ul列表 <ul><li></li><li></li><li></li></ul>
a[href="#"] <a href="#"></a>
p{这是文本} <p>这是文本</p>
css: w100px width: 100px;(CSS缩写,支持h、m、p等属性,如h50pxheight: 50px;
.box>h2{标题}+p{内容} <div class="box"><h2>标题</h2><p>内容</p></div>

JavaScript专属快捷键

功能 Windows/Linux Mac 前端使用场景
快速打开终端 Ctrl + | Cmd + 运行npm命令、启动本地服务(如live-server)
跳转到变量/函数定义 F12 F12 查看JS函数/变量的定义位置(调试必备)
快速重命名变量/函数 F2 F2 批量修改变量名(如把num改为count
折叠/展开代码块 Ctrl + -/+ Cmd + -/+ 折叠冗长的JS函数、CSS样式块
查看代码提示/补全 Ctrl + 空格 Cmd + 空格 触发VSCode代码补全(如JS方法、CSS属性)

窗口/文件管理(前端多文件开发)

功能 Windows/Linux Mac 前端使用场景
新建文件 Ctrl + N Cmd + N 新建HTML/CSS/JS文件
打开文件 Ctrl + O Cmd + O 打开本地前端项目文件
切换已打开的文件 Ctrl + Tab Cmd + Tab 在HTML/CSS/JS文件间快速切换
分屏编辑(左右) Ctrl + \ Cmd + \ 同时查看HTML和CSS文件(前端布局必备)
关闭当前文件 Ctrl + W Cmd + W 关闭无用文件,保持编辑器整洁

总结

  1. 前端开发最高频快捷键Shift + Alt + F(格式化代码)、Ctrl + /(注释)、Ctrl + S(保存),以及Emmet缩写+Tab(快速写HTML/CSS);
  2. Emmet语法是前端提效核心,记住!(HTML骨架)、>(子元素)、*(重复)、.(类名)这几个核心规则即可覆盖80%场景;
  3. 若快捷键无效,可检查VSCode是否开启「自动格式化」(设置中搜索Format On Save),保存时自动整理代码,减少手动操作。

你可根据自己的系统(Windows/Mac)记忆对应快捷键,先掌握前3类核心快捷键,后续再逐步熟悉其他功能。


第二部分:HTML(网页骨架)

课时3:HTML基础语法——标签和属性

学习目标

认识HTML标签的写法,知道属性的作用。

通俗讲解

HTML标签就像「作文的格式符号」:<h1>是标题标记,<p>是段落标记,标签必须成对(除了少数单标签);属性是标签的「补充说明」,比如<img>标签的src属性告诉浏览器“图片在哪”。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>标签和属性</title>
</head>
<body>
    <!-- 双标签:<标签>内容</标签> -->
    <h1>这是一级标题</h1>
    <p>这是段落文字</p>
    <!-- 单标签:<标签 属性="值"> -->
    <img src="https://picsum.photos/200/100" alt="风景图">
</body>
</html>

image-20251230190410163

实操步骤

  1. 新建03-html基础.html,粘贴代码;
  2. 运行后看页面显示,修改<h1>里的文字、<img>src地址,看效果;
  3. 尝试删除</h1>,刷新页面,看浏览器是否自动补全(观察即可,知道双标签要成对)。

易错点提醒

  • ❌ 标签大小写混用:比如<H1>标题</h1>,虽然浏览器兼容,但规范写法是小写;
  • ❌ 属性值漏加引号:比如<img src=https://picsum.photos/200/100>,虽然部分场景生效,但必须加引号(单/双引号都可以)。

课后小练习

添加<h3>标签写二级标题,添加<br>单标签给段落换行。

课时4:HTML文本标签——标题、段落、换行

学习目标

掌握h1-h6、p、br、strong/em的用法。

通俗讲解

  • h1-h6:标题层级,h1最大(一个页面只写1个),h6最小;
  • p:段落,自动换行且和其他段落有间距;
  • br:强制换行(无间距);
  • strong:文字加粗,em:文字斜体。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文本标签</title>
</head>
<body>
    <h1>网页主标题</h1>
    <h3>二级小标题</h3>
    <p>这是第一段文字,<strong>加粗部分</strong>很重要,<em>斜体部分</em>是注释</p>
    <p>这是第二段文字<br>这行是强制换行的内容</p>
</body>
</html>

image-20251230190508268

实操步骤

  1. 新建04-文本标签.html,粘贴代码;
  2. 修改标题层级、加粗/斜体的位置,看显示效果;
  3. 对比p标签和br标签的换行差异(p有间距,br无)。

易错点提醒

  • ❌ 用多个br代替p:比如<br><br>文字,可读性差,段落用p,强制换行才用br;
  • ❌ 一个页面写多个h1:不符合SEO规范,一个页面只保留1个h1。

课后小练习

写一段自我介绍,包含h1(我的自我介绍)、h3(基本信息)、p(姓名/年龄/爱好),给重点内容加粗。

课时5:HTML图片标签——插入本地/网络图片

学习目标

会插入网络图片和本地图片,掌握img核心属性。

通俗讲解

img标签是「网页的图片框」,src是图片地址(网络地址/本地路径),alt是图片加载失败时的提示文字(必须加),width/height控制图片大小(只设一个会等比例缩放)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>图片标签</title>
</head>
<body>
    <!-- 网络图片 -->
    <img src="https://picsum.photos/300/200" alt="风景图" width="200">
    <!-- 本地图片:把图片和html文件放同一文件夹,写文件名即可 -->
    <img src="1.jpeg" alt="本地图片" height="100">
</body>
</html>

image-20251230190812321

实操步骤

  1. 新建05-图片标签.html,粘贴网络图片代码,运行看效果;
  2. 找一张本地图片(比如截图),重命名为我的图片.jpg,和html文件放同一文件夹,补充本地图片代码,运行看是否显示;
  3. 只改width,看图片是否等比例缩放。

易错点提醒

  • ❌ 本地图片路径错:比如图片在「图片文件夹」里,src要写图片文件夹/我的图片.jpg
  • ❌ 同时设width和height导致图片变形:只设一个,让浏览器自动适配比例。

课后小练习

插入一张自己的头像图片(本地),设置宽度为150px,添加alt="我的头像"。

课时6:HTML链接标签——跳转网页/锚点

学习目标

会做网页跳转链接,知道锚点链接的用法。

通俗讲解

a标签是「网页的跳转按钮」,href是跳转地址,target="_blank"是新窗口打开(不覆盖当前页面);锚点链接是「跳转到页面内指定位置」,比如长页面的“回到顶部”。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>链接标签</title>
</head>
<body>
    <!-- 跳转到外部网页 -->
    <a href="https://www.baidu.com" target="_blank">打开百度(新窗口)</a>
    <a href="https://www.taobao.com">打开淘宝(当前窗口)</a>

    <!-- 锚点链接:先定义锚点,再跳转 -->
    <a href="#bottom">跳转到页面底部</a>
    <p style="height: 1000px;">占位文字,让页面变长</p>
    <div id="bottom">我是页面底部</div>
</body>
</html>

image-20251230190931054

实操步骤

  1. 新建06-链接标签.html,粘贴代码;
  2. 点击两个外部链接,看跳转方式差异;
  3. 点击锚点链接,看是否跳转到页面底部。

易错点提醒

  • ❌ href漏写https://:比如<a href="www.baidu.com">,会跳转到本地路径,不是百度;
  • ❌ 锚点id写错:比如href="#bot",但锚点id是bottom,跳转无效。

课后小练习

添加“回到顶部”锚点链接,跳转到页面最上方(给h1加id="top",链接href="#top")。

课时7:HTML无序列表——ul+li

学习目标

掌握无序列表的写法,用于非顺序类内容(比如购物清单)。

通俗讲解

无序列表是「没有数字的清单」,用<ul>包裹<li>,默认显示圆点,适合列爱好、导航栏等不需要排序的内容。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>无序列表</title>
</head>
<body>
    <h3>我的爱好</h3>
    <ul>
        <li>学习前端</li>
        <li>看电影</li>
        <li>跑步</li>
    </ul>
</body>
</html>

image-20251230191001323

实操步骤

  1. 新建07-无序列表.html,粘贴代码;
  2. 新增2个li标签,添加更多爱好;
  3. 尝试把ul改成ol,看显示差异(提前感知有序列表)。

易错点提醒

  • ❌ 直接在ul里写文字:比如<ul>学习前端</ul>,不会显示列表样式,必须包在li里;
  • ❌ li标签不成对:比如<li>跑步,浏览器会自动补全,但要养成成对写的习惯。

课后小练习

用无序列表做一个“早餐清单”,包含3-5个菜品。

课时8:HTML有序列表——ol+li

学习目标

掌握有序列表的写法,用于顺序类内容(比如步骤说明)。

通俗讲解

有序列表是「带数字的步骤」,用<ol>包裹<li>,默认显示1、2、3…,适合写教程步骤、排行榜等需要排序的内容。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>有序列表</title>
</head>
<body>
    <h3>学习前端的步骤</h3>
    <ol>
        <li>学HTML搭建骨架</li>
        <li>学CSS美化样式</li>
        <li>学JS实现交互</li>
    </ol>
</body>
</html>

image-20251230191126573

实操步骤

  1. 新建08-有序列表.html,粘贴代码;
  2. 修改li的顺序,看数字是否自动调整;
  3. 尝试在li里嵌套无序列表(比如步骤2里加“学Flex布局、学盒子模型”)。

易错点提醒

  • ❌ 嵌套列表层级混乱:比如<ol><li>步骤1<ul>子项</li></ul></ol>,要保证标签嵌套正确(ul/ol里只能放li);
  • ❌ 用有序列表做导航:导航不需要排序,优先用无序列表。

课后小练习

用有序列表写“煮泡面的步骤”,包含4-5个步骤。

课时9:HTML表格——table+tr+td

学习目标

会做简单表格,用于展示结构化数据(比如课程表)。

通俗讲解

表格是「行+列的格子」:

  • <table>:表格容器;
  • <tr>:表格的一行;
  • <td>:一行里的一个单元格;
  • <th>:表头单元格(加粗居中)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>表格标签</title>
</head>
<body>
    <h3>简易课程表</h3>
    <table border="1"> <!-- border=1 加边框,方便看 -->
        <tr>
            <th>时间</th>
            <th>课程</th>
        </tr>
        <tr>
            <td>周一上午</td>
            <td>HTML</td>
        </tr>
        <tr>
            <td>周一下午</td>
            <td>CSS</td>
        </tr>
    </table>
</body>
</html>

image-20251230191206644

实操步骤

  1. 新建09-表格标签.html,粘贴代码;
  2. 新增一行(复制...),添加“周二上午”和“JS”;
  3. 尝试合并单元格(简单版:给td加colspan="2",比如<td colspan="2">午休</td>)。

易错点提醒

  • ❌ 表格没有border:新手开发时加border="1",方便看结构,上线后再去掉;
  • ❌ tr/td数量不匹配:比如一行有2个td,另一行有3个,表格会错位。

课后小练习

做一个“个人信息表”,包含姓名、年龄、性别、爱好4列,1行数据。

课时10:HTML表单——form+input(文本框/密码框)

学习目标

会做基础表单,用于收集用户输入(比如登录框)。

通俗讲解

表单是「用户的输入界面」,<form>是表单容器,<input>是核心输入元素,不同type对应不同输入类型:

  • type="text":普通文本框;
  • type="password":密码框(输入隐藏)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>表单-文本框/密码框</title>
</head>
<body>
    <h3>登录表单</h3>
    <form>
        <p>用户名:<input type="text" placeholder="请输入用户名"></p>
        <p>密码:<input type="password" placeholder="请输入密码"></p>
    </form>
</body>
</html>

image-20251230191438748

实操步骤

  1. 新建10-表单-文本框.html,粘贴代码;
  2. 在文本框/密码框输入内容,看密码框是否隐藏;
  3. 修改placeholder提示文字,看显示效果。

易错点提醒

  • ❌ 密码框用type="text":输入内容能直接看到,必须用type="password";
  • ❌ 忘记加placeholder:用户不知道该输入什么,体验差。

课后小练习

添加“手机号”文本框,placeholder写“请输入11位手机号”。

课时11:HTML表单——单选框/复选框

学习目标

掌握单选框(radio)、复选框(checkbox)的用法。

通俗讲解

  • 单选框:只能选一个(比如性别),必须给同一组单选框加相同的name
  • 复选框:可以选多个(比如爱好),同一组复选框加相同的name

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>表单-单选/复选框</title>
</head>
<body>
    <h3>注册表单</h3>
    <form>
        <!-- 单选框:name相同为一组 -->
        <p>性别:
            <input type="radio" name="gender" id="male"> <label for="male"></label>
            <input type="radio" name="gender" id="female"> <label for="female"></label>
        </p>
        <!-- 复选框:name相同为一组 -->
        <p>爱好:
            <input type="checkbox" name="hobby" id="read"> <label for="read">阅读</label>
            <input type="checkbox" name="hobby" id="sport"> <label for="sport">运动</label>
        </p>
    </form>
</body>
</html>

image-20251230191501239

实操步骤

  1. 新建11-表单-单选复选框.html,粘贴代码;
  2. 点击单选框,看是否只能选一个;点击复选框,看是否能选多个;
  3. 给单选框加checked属性(比如<input type="radio" checked>),看默认选中效果。

易错点提醒

  • ❌ 单选框没加name:多个单选框能同时选中,失去单选意义;
  • ❌ label的for和id不匹配:点击label文字不能选中输入框,体验差。

课后小练习

新增“是否同意协议”复选框,默认选中(加checked)。

课时12:HTML表单——下拉框/提交按钮

学习目标

掌握下拉框(select)和提交按钮(submit)的用法。

通俗讲解

  • 下拉框:用<select>+<option>实现,适合选项多的场景(比如城市选择);
  • 提交按钮:type="submit",点击后提交表单(新手暂时不用管提交后的逻辑)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>表单-下拉框/提交按钮</title>
</head>
<body>
    <h3>注册表单</h3>
    <form>
        <!-- 下拉框 -->
        <p>所在城市:
            <select>
                <option value="">请选择</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
            </select>
        </p>
        <!-- 提交按钮 -->
        <p><input type="submit" value="注册"></p>
    </form>
</body>
</html>

image-20251230191528805

实操步骤

  1. 新建12-表单-下拉框.html,粘贴代码;
  2. 选择下拉框的选项,点击提交按钮(会触发表单提交,暂时不用管跳转);
  3. 给下拉框的某个option加selected属性,设置默认选中。

易错点提醒

  • ❌ 下拉框没加空选项:默认选中第一个,用户可能误选,建议加<option value="">请选择</option>
  • ❌ 提交按钮用type="button":不会触发表单提交,新手先记type="submit"。

课后小练习

添加“学历”下拉框,包含小学、初中、高中、大学选项。

课时13:HTML分区标签——div和span

学习目标

掌握div(块级)和span(行内)的用法,用于布局分组。

通俗讲解

  • div:「块级容器」,默认占一行,用来划分网页大区域(比如头部、主体、底部);
  • span:「行内容器」,和文字同行,用来包裹小段文字(比如给某几个字改颜色)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>div和span</title>
</head>
<body>
    <!-- div:块级,占一行 -->
    <div style="background-color: #eee;">我是第一个div,占一行</div>
    <div style="background-color: #ddd;">我是第二个div,占一行</div>

    <!-- span:行内,和文字同行 -->
    <p>我是段落文字,<span style="color: red;">这段文字变红</span>,其他文字不变</p>
</body>
</html>

image-20251230191553178

实操步骤

  1. 新建13-div和span.html,粘贴代码;
  2. 观察div和span的显示差异(div换行,span不换行);
  3. 用div包裹之前学的列表/表单,做简单分组。

易错点提醒

  • ❌ 用span做大区域布局:span是行内元素,不能设置宽高,布局优先用div;
  • ❌ 嵌套div层级过多:新手先保持2-3层,避免混乱。

课后小练习

用div划分“头部、主体、底部”3个区域,头部放标题,主体放表单,底部放版权文字。

课时14:HTML语义化标签——header/nav/main/footer

学习目标

认识语义化标签,替代div让代码更易读。

通俗讲解

语义化标签是「有含义的div」,比如<header>就是网页头部,<nav>是导航栏,浏览器显示效果和div一样,但代码更易读,对SEO更友好。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>语义化标签</title>
</head>
<body>
    <!-- 头部 -->
    <header>
        <h1>我的网页</h1>
    </header>
    <!-- 导航 -->
    <nav>
        <ul>
            <li><a href="#">首页</a></li>
            <li><a href="#">关于我</a></li>
        </ul>
    </nav>
    <!-- 主体 -->
    <main>
        <p>网页的主要内容</p>
    </main>
    <!-- 底部 -->
    <footer>
        <p>版权所有 © 2025</p>
    </footer>
</body>
</html>

image-20251230191621067

实操步骤

  1. 新建14-语义化标签.html,粘贴代码;
  2. 运行后看显示效果(和div一样),理解每个标签的含义;
  3. 把之前的div布局改成语义化标签。

易错点提醒

  • ❌ 滥用语义化标签:比如用header包裹表单,语义不符,按“头部/导航/主体/底部”对应使用;
  • ❌ 认为语义化标签有特殊样式:默认样式和div一致,样式需要自己用CSS改。

课后小练习

用语义化标签重构“个人主页”的HTML结构。

课时15-18:HTML综合练习

课时15:综合练习1——搭建个人主页骨架

学习目标

整合HTML标签,搭建包含“头部、导航、主体、底部”的个人主页骨架。

实操任务
  1. 新建15-个人主页骨架.html
  2. 用语义化标签划分结构:
  3. header:放头像+姓名;
  4. nav:放导航链接(首页、我的爱好、我的经历、留言板);
  5. main:放我的爱好(无序列表)、我的经历(有序列表);
  6. footer:放版权信息。
  7. 确保所有标签成对,结构清晰。

课时16:综合练习2——给个人主页加表单

学习目标

在个人主页添加“留言板”表单,包含姓名、留言内容、提交按钮。

实操任务
  1. 在main里新增留言板区域;
  2. 表单包含:
  3. 姓名文本框;
  4. 留言内容文本域(<textarea>);
  5. 提交按钮。
  6. 测试表单显示是否正常。

课时17:综合练习3——优化个人主页结构

学习目标

调整标签嵌套,修复布局混乱问题,规范属性写法。

实操任务
  1. 检查所有img标签是否加了alt属性;
  2. 检查表单的单选/复选框是否加了label;
  3. 给所有区域加div/语义化标签分组,避免标签嵌套混乱。

参考代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的个人主页</title>
</head>
<body>
    <!-- 头部:头像 + 姓名(语义化标签header) -->
    <header>
        <!-- img标签添加alt属性,优化可访问性 -->
        <img src="avatar.jpg" alt="我的头像">
        <h1>张三</h1>
    </header>

    <!-- 导航栏:导航链接(语义化标签nav) -->
    <nav>
        <a href="#home">首页</a>
        <a href="#hobby">我的爱好</a>
        <a href="#experience">我的经历</a>
        <a href="#message">留言板</a>
    </nav>

    <!-- 主体内容:爱好、经历、留言板(语义化标签main) -->
    <main>
        <!-- 我的爱好:无序列表 -->
        <section id="hobby" class="section">
            <h2>我的爱好</h2>
            <ul>
                <li>阅读(偏爱技术类书籍)</li>
                <li>编程(HTML/CSS/JavaScript)</li>
                <li>运动(跑步、羽毛球)</li>
                <li>摄影(记录生活点滴)</li>
            </ul>
        </section>

        <!-- 我的经历:有序列表 -->
        <section id="experience" class="section">
            <h2>我的经历</h2>
            <ol>
                <li>2018-2022:XX大学 计算机专业 本科</li>
                <li>2022-2023:XX科技公司 前端实习生</li>
                <li>2023-至今:XX互联网公司 前端开发工程师</li>
            </ol>
        </section>

        <!-- 留言板:表单(新增区域,优化label关联) -->
        <section id="message" class="section">
            <h2>留言板</h2>
            <form action="#" method="post">
                <div class="form-item">
                    <!-- label通过for关联input的id,提升可访问性 -->
                    <label for="username">姓名:</label>
                    <input type="text" id="username" name="username" placeholder="请输入你的姓名">
                </div>
                <div class="form-item">
                    <label for="content">留言内容:</label>
                    <textarea id="content" name="content" placeholder="请输入你的留言..."></textarea>
                </div>
                <button type="submit">提交留言</button>
            </form>
        </section>
    </main>

    <!-- 底部:版权信息(语义化标签footer) -->
    <footer>
        <p>© 2025 张三的个人主页 | 版权所有</p>
    </footer>
</body>
</html>

image-20251230192059275

课时18:HTML模块总结与查漏补缺

学习目标

回顾HTML所有核心知识点,解决学习中遇到的问题。

核心回顾
  1. HTML核心:标签(双标签/单标签)+ 属性(src/alt/href等);
  2. 常用标签分类:
  3. 文本:h1-h6、p、br、strong/em;
  4. 媒体:img;
  5. 链接:a;
  6. 列表:ul/ol+li;
  7. 表格:table+tr+td/th;
  8. 表单:form+input/select/textarea;
  9. 布局:div/span、语义化标签。
查漏补缺

针对前17课时的易错点,逐一检查自己的代码,比如:

  • 标签是否成对;
  • 属性值是否加引号;
  • 表单的name/for/id是否匹配。

第三部分:CSS(样式)

课时19:CSS引入方式——行内样式

学习目标

掌握最简单的CSS引入方式(行内样式),给元素改样式。

通俗讲解

行内样式是「直接给标签加style属性」,比如<div style="color: red;">,优点是简单,缺点是不能复用,适合临时改样式。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>行内样式</title>
</head>
<body>
    <h1 style="color: red; font-size: 28px;">红色大号标题</h1>
    <p style="color: #666; line-height: 2;">深灰色段落,行高2倍</p>
</body>
</html>

image-20251230192156975

实操步骤

  1. 新建19-行内样式.html,粘贴代码;
  2. 修改style里的属性值(比如color改成green,font-size改成32px);
  3. 给div加行内样式(width: 200px; height: 100px; background-color: #eee;)。

易错点提醒

  • ❌ style里漏写分号:比如style="color: red font-size:28px",后面的样式失效;
  • ❌ 样式名写错:比如style="fontsize:28px"(正确是font-size)。

课后小练习

给个人主页的header加行内样式,设置背景颜色为#f5f5f5。

课时20:CSS引入方式——内嵌样式

学习目标

掌握内嵌样式(style标签),批量修改元素样式,避免重复写行内样式。

通俗讲解

内嵌样式是「把样式写在head里的style标签里」,用选择器选中元素,批量改样式,比行内样式高效,适合单页面样式。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>内嵌样式</title>
    <!-- 内嵌样式:style标签写在head里 -->
    <style>
        /* 标签选择器:选中所有h1标签 */
        h1 {
            color: green;
            font-size: 28px;
        }
        /* 标签选择器:选中所有p标签 */
        p {
            color: #666;
            line-height: 2;
        }
    </style>
</head>
<body>
    <h1>绿色标题</h1>
    <p>第一个段落</p>
    <p>第二个段落(和第一个样式一样)</p>
</body>
</html>

image-20251230192225571

实操步骤

  1. 新建20-内嵌样式.html,粘贴代码;
  2. 新增一个div标签,在style里加div选择器,设置宽高和背景色;
  3. 对比行内样式和内嵌样式的差异(内嵌样式批量生效)。

易错点提醒

  • ❌ style标签放body里:样式不生效,必须放head里;
  • ❌ 选择器和样式块之间漏写空格:比如h1{color:red;}(虽然生效,但规范写法是h1 { ... })。

课后小练习

把个人主页的行内样式改成内嵌样式,批量设置相同元素的样式。

课时21:CSS选择器——标签选择器

学习目标

掌握标签选择器的用法,选中页面中所有同类型标签。

通俗讲解

标签选择器是「直接写标签名」,比如p { ... }选中所有p标签,优点是批量生效,缺点是不能精准控制单个元素。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>标签选择器</title>
    <style>
        /* 选中所有ul标签 */
        ul {
            list-style: none; /* 去掉默认圆点 */
            padding: 0; /* 去掉默认内边距 */
        }
        /* 选中所有li标签 */
        li {
            line-height: 1.8;
            color: #333;
        }
    </style>
</head>
<body>
    <ul>
        <li>学习HTML</li>
        <li>学习CSS</li>
        <li>学习JS</li>
    </ul>
</body>
</html>

image-20251230192249975

实操步骤

  1. 新建21-标签选择器.html,粘贴代码;
  2. 观察ul的圆点是否消失,li的行高是否变化;
  3. 给table标签加选择器,设置border-collapse: collapse;(合并单元格边框)。

易错点提醒

  • ❌ 用标签选择器改单个元素:比如页面有2个h1,只想改其中1个,标签选择器会全改,需要用类选择器;
  • ❌ 选择器大小写:比如P { ... },虽然生效,但规范写小写。

课后小练习

用标签选择器给个人主页的所有a标签设置颜色为#007bff,去掉下划线(text-decoration: none;)。

课时22:CSS选择器——类选择器(核心)

学习目标

掌握类选择器,精准控制指定元素的样式,实现样式复用。

通俗讲解

类选择器是「给元素贴标签」,比如给某个div加class="box",然后用.box { ... }选中它,优点是精准、可复用,是CSS最常用的选择器。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>类选择器</title>
    <style>
        /* 类选择器:.类名 */
        .red-text {
            color: red;
        }
        .big-text {
            font-size: 20px;
        }
        .bg-gray {
            background-color: #eee;
            padding: 5px;
        }
    </style>
</head>
<body>
    <!-- 一个元素可以加多个类,空格分隔 -->
    <p class="red-text">只变红</p>
    <p class="red-text big-text">变红+变大</p>
    <div class="bg-gray">灰色背景</div>
</body>
</html>

image-20251230192317236

实操步骤

  1. 新建22-类选择器.html,粘贴代码;
  2. 新增一个.center-text类(text-align: center;),给h1加这个类,看文字是否居中;
  3. 尝试把类名写错(比如red-text写成redtext),看样式是否失效。

易错点提醒

  • ❌ 类选择器漏写点:比如red-text { ... },样式不生效,必须加.
  • ❌ 类名有空格:比如class="red text",实际是两个类(red和text);
  • ❌ 类名以数字开头:比如.1box { ... },语法错误。

课后小练习

给个人主页的“留言板”区域加类选择器,设置宽600px、背景色#f8f8f8、内边距20px。

课时23:CSS选择器——ID选择器

学习目标

掌握ID选择器,选中页面中唯一的元素。

通俗讲解

ID选择器是「给元素加唯一标识」,比如<div id="header">,用#header { ... }选中它,一个页面中ID只能出现一次,适合选唯一元素(比如页面头部、底部)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>ID选择器</title>
    <style>
        /* ID选择器:#ID名 */
        #title {
            color: blue;
            font-size: 32px;
        }
    </style>
</head>
<body>
    <h1 id="title">唯一的标题</h1>
    <h1>普通标题(不受ID选择器影响)</h1>
</body>
</html>

image-20251230192337465

实操步骤

  1. 新建23-ID选择器.html,粘贴代码;
  2. 尝试给两个h1加相同的ID,看浏览器是否报错(不会报错,但不符合规范);
  3. 对比类选择器和ID选择器的差异(类可复用,ID唯一)。

易错点提醒

  • ❌ 重复使用ID:一个页面中ID只能有一个,重复使用会导致JS获取元素出错;
  • ❌ 用ID选择器复用样式:ID是唯一的,复用样式优先用类选择器。

课后小练习

给个人主页的footer加ID选择器,设置文字居中、颜色#999。

课时24:CSS文字样式——颜色、大小、粗细

学习目标

掌握文字的核心样式:color、font-size、font-weight、font-family。

通俗讲解

  • color:文字颜色(英文/十六进制/RGB);
  • font-size:文字大小(单位px,默认16px);
  • font-weight:文字粗细(normal/400 正常,bold/700 加粗);
  • font-family:字体(比如“微软雅黑”“宋体”)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文字样式</title>
    <style>
        .text-style {
            color: #333; /* 深灰色 */
            font-size: 16px;
            font-weight: 500; /* 半加粗 */
            font-family: "Microsoft Yahei", sans-serif; /* 微软雅黑,无衬线字体兜底 */
        }
    </style>
</head>
<body>
    <p class="text-style">这是设置了字体样式的文字</p>
</body>
</html>

image-20251230192412906

实操步骤

  1. 新建24-文字样式.html,粘贴代码;
  2. 修改color为rgb(255,0,0),font-weight为700,看效果;
  3. 给body加font-family,统一整个页面的字体。

易错点提醒

  • ❌ 漏写字体单位:比如font-size: 16,样式不生效,必须加px;
  • ❌ 字体名有空格没加引号:比如font-family: Microsoft Yahei,要加引号("Microsoft Yahei");
  • ❌ 用数字写font-weight:比如font-weight: boldfont-weight: 700效果一样,新手记700加粗即可。

课后小练习

统一个人主页的字体为“Microsoft Yahei”,设置所有p标签的font-size为14px,line-height为1.8。

课时25:CSS文字样式——行高、对齐、装饰

学习目标

掌握line-height、text-align、text-decoration的用法。

通俗讲解

  • line-height:行高,文字行之间的距离,设为数值(比如2)是字体大小的倍数,设为和容器高度相同可垂直居中;
  • text-align:文字水平对齐(left/center/right);
  • text-decoration:文字装饰(none 去掉下划线,underline 加下划线,line-through 删除线)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文字样式进阶</title>
    <style>
        .box {
            width: 300px;
            height: 100px;
            background-color: #eee;
            text-align: center; /* 水平居中 */
            line-height: 100px; /* 垂直居中(和容器高度相同) */
        }
        a {
            text-decoration: none; /* 去掉下划线 */
        }
        .del-text {
            text-decoration: line-through; /* 删除线 */
        }
    </style>
</head>
<body>
    <div class="box">文字水平+垂直居中</div>
    <a href="#">无下划线的链接</a>
    <p class="del-text">带删除线的文字</p>
</body>
</html>

image-20251230192435320

实操步骤

  1. 新建25-文字样式进阶.html,粘贴代码;
  2. 修改line-height为1.8,看垂直居中是否失效;
  3. 给个人主页的导航链接加hover样式(a:hover { text-decoration: underline; })。

易错点提醒

  • ❌ 行高设为数值加px导致垂直居中失效:比如容器高度100px,line-height设为100px才会垂直居中,设为100则是字体大小的100倍;
  • ❌ 给块级元素加text-decoration:只会影响元素内的文字,不会影响子元素(比如div加underline,里面的a标签不会有下划线)。

课后小练习

给个人主页的h1标签设置text-align: center,给“已完成的经历”加删除线样式。

课时26:CSS背景样式——背景颜色、背景图片

学习目标

掌握background-color、background-image的用法。

通俗讲解

  • background-color:背景颜色,可设为颜色值或transparent(透明);
  • background-image:背景图片,用url()指定图片地址,默认重复平铺。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>背景样式</title>
    <style>
        .bg-color {
            width: 200px;
            height: 100px;
            background-color: #007bff; /* 蓝色背景 */
            color: white; /* 文字白色 */
        }
        .bg-img {
            width: 300px;
            height: 200px;
            background-image: url("https://picsum.photos/300/200");
            background-repeat: no-repeat; /* 不平铺 */
            background-size: cover; /* 覆盖容器 */
        }
    </style>
</head>
<body>
    <div class="bg-color">纯色背景</div>
    <div class="bg-img">背景图片</div>
</body>
</html>

image-20251230192455360

实操步骤

  1. 新建26-背景样式.html,粘贴代码;
  2. 修改background-repeat为repeat,看图片是否平铺;
  3. 修改background-size为contain,看图片是否完整显示。

易错点提醒

  • ❌ 背景图片容器没设宽高:背景图片不会显示,必须给容器设置宽高;
  • ❌ url里的图片地址漏加引号:比如url(https://picsum.photos/300/200),虽然生效,但建议加引号;
  • ❌ 背景图片和文字重叠:给文字加背景色或调整文字颜色,保证可读性。

课后小练习

给个人主页的header加背景颜色#f8f8f8,给main加轻微的背景图片(平铺)。

课时27:CSS边框样式——border

学习目标

掌握border的用法,给元素加边框。

通俗讲解

border是「元素的边框」,格式:border: 粗细 样式 颜色;,比如border: 1px solid #ccc;(1像素实线灰色边框);也可以单独设置某一边(border-top、border-left)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>边框样式</title>
    <style>
        .box1 {
            width: 200px;
            height: 100px;
            border: 1px solid #ccc; /* 全边框 */
            margin-bottom: 10px;
        }
        .box2 {
            width: 200px;
            height: 100px;
            border-top: 2px solid red; /* 只加顶部边框 */
            border-left: 1px dashed blue; /* 左侧虚线边框 */
        }
    </style>
</head>
<body>
    <div class="box1">全边框</div>
    <div class="box2">部分边框</div>
</body>
</html>

image-20251230192530934

实操步骤

  1. 新建27-边框样式.html,粘贴代码;
  2. 修改border的样式为dashed(虚线)、dotted(点线),看效果;
  3. 给个人主页的表单输入框加border: 1px solid #ccc;,聚焦时加border: 1px solid #007bff;(:focus)。

易错点提醒

  • ❌ 边框样式漏写:比如border: 1px #ccc;,没有样式(solid/dashed),边框不显示;
  • ❌ 边框加在行内元素上:行内元素加边框后会影响文字排版,建议转块级(display: block);
  • ❌ 边框宽度设为0:边框不显示,新手容易误写。

课后小练习

给个人主页的“留言板”区域加1px solid #eee的边框,设置border-radius: 8px(圆角)。

课时28:CSS盒子模型——width/height

学习目标

理解盒子模型的宽高,区分content-box和border-box。

通俗讲解

CSS盒子模型的宽高默认是「内容区的宽高」(content-box),如果加了边框/内边距,元素实际宽度=width+padding+border;设置box-sizing: border-box后,宽高包含padding和border,更符合直觉。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>盒子模型-宽高</title>
    <style>
        .box1 {
            width: 200px;
            height: 100px;
            padding: 10px;
            border: 1px solid #ccc;
            /* 默认content-box:实际宽度=200+20+2=222px */
            background-color: #eee;
            margin-bottom: 10px;
        }
        .box2 {
            width: 200px;
            height: 100px;
            padding: 10px;
            border: 1px solid #ccc;
            box-sizing: border-box; /* 宽高包含padding和border,实际宽度200px */
            background-color: #ddd;
        }
    </style>
</head>
<body>
    <div class="box1">content-box(实际更宽)</div>
    <div class="box2">border-box(宽200px)</div>
</body>
</html>

image-20251230192623849

实操步骤

  1. 新建28-盒子模型-宽高.html,粘贴代码;
  2. 打开浏览器控制台(F12),查看两个盒子的实际宽度;
  3. 给body加* { box-sizing: border-box; },统一页面所有元素的盒模型。

易错点提醒

  • ❌ 没设box-sizing导致布局错位:新手建议一开始就加* { box-sizing: border-box; },避免计算宽高出错;
  • ❌ 给行内元素设宽高:行内元素(span/a)默认不能设宽高,需加display: blockdisplay: inline-block

课后小练习

给个人主页的所有元素加border-box盒模型,调整各区域的宽高,避免布局错位。

课时29:CSS盒子模型——padding(内边距)

学习目标

掌握padding的用法,控制元素内部的间距。

通俗讲解

padding是「元素内容和边框之间的距离」,可以设单个值(padding: 10px; 上下左右都10px)、两个值(padding: 10px 20px; 上下10px,左右20px)、四个值(padding: 上 右 下 左)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>盒子模型-padding</title>
    <style>
        .box {
            width: 200px;
            height: 100px;
            border: 1px solid #ccc;
            box-sizing: border-box;
            /* 四个值:上10px 右20px 下10px 左20px */
            padding: 10px 20px;
            background-color: #eee;
        }
    </style>
</head>
<body>
    <div class="box">内容和边框之间有间距,文字不会贴边</div>
</body>
</html>

image-20251230192646944

实操步骤

  1. 新建29-盒子模型-padding.html,粘贴代码;
  2. 修改padding为10px(单值)、10px 20px 30px 40px(四值),看间距变化;
  3. 给个人主页的表单输入框加padding: 8px 10px;,提升输入体验。

易错点提醒

  • ❌ padding设为百分比:百分比是相对于父元素宽度的,新手建议用px;
  • ❌ 内边距过大导致内容溢出:比如容器高度100px,padding-top设为110px,内容会溢出容器。

课后小练习

给个人主页的header、nav、main、footer分别加合适的padding,避免内容贴边。

课时30:CSS盒子模型——margin(外边距)

学习目标

掌握margin的用法,控制元素之间的间距。

通俗讲解

margin是「元素和其他元素之间的距离」,用法和padding类似,可设单值/双值/四值;margin: 0 auto;可让固定宽度的块级元素水平居中。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>盒子模型-margin</title>
    <style>
        .box {
            width: 200px;
            height: 100px;
            border: 1px solid #ccc;
            margin: 10px 20px; /* 上下10px,左右20px */
            background-color: #eee;
        }
        .center-box {
            width: 300px;
            height: 100px;
            border: 1px solid #ccc;
            margin: 0 auto; /* 水平居中 */
            background-color: #ddd;
        }
    </style>
</head>
<body>
    <div class="box">有外边距的盒子</div>
    <div class="center-box">水平居中的盒子</div>
</body>
</html>

image-20251230192744752

实操步骤

  1. 新建30-盒子模型-margin.html,粘贴代码;
  2. 观察两个盒子的间距和居中效果;
  3. 给个人主页的main区域设置width: 1200px; margin: 0 auto;,让页面主体居中。

易错点提醒

  • ❌ 行内元素的margin上下无效:行内元素(span/a)的margin-top/margin-bottom不生效,需转块级;
  • ❌ 外边距合并:两个相邻块级元素的margin-top和margin-bottom会合并(取最大值),新手暂时不用处理,知道即可;
  • ❌ margin: 0 auto不生效:必须给元素设置固定宽度,否则无效。

课后小练习

调整个人主页各区域的margin,避免元素之间间距过大/过小。

课时31:CSS显示模式——display(block/inline/inline-block)

学习目标

掌握display的核心值,转换元素的显示模式。

通俗讲解

display控制元素的「显示方式」:

  • block:块级元素,占一行,可设宽高(div/h1/p/ul/ol/table/form);
  • inline:行内元素,和文字同行,不可设宽高(span/a/em/strong);
  • inline-block:行内块元素,和文字同行,可设宽高(img/input)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>显示模式</title>
    <style>
        .inline-box {
            display: inline; /* 转成行内元素 */
            width: 200px; /* 无效 */
            height: 100px; /* 无效 */
            background-color: #eee;
        }
        .inline-block-box {
            display: inline-block; /* 转成行内块 */
            width: 200px; /* 有效 */
            height: 50px; /* 有效 */
            background-color: #ddd;
        }
    </style>
</head>
<body>
    <div class="inline-box">行内元素(宽高无效)</div>
    <div class="inline-box">和我同行</div>
    <br>
    <div class="inline-block-box">行内块(宽高有效)</div>
    <div class="inline-block-box">和我同行</div>
</body>
</html>

image-20251230192804743

实操步骤

  1. 新建31-显示模式.html,粘贴代码;
  2. 观察行内元素和行内块元素的差异;
  3. 给个人主页的导航li标签加display: inline-block;,让导航横向排列。

易错点提醒

  • ❌ 给行内元素设宽高:必须先转block/inline-block,否则无效;
  • ❌ inline-block元素之间有默认间距:多个inline-block元素并排时,标签之间的空格会产生间距,新手可把标签写在同一行(比如<li>首页</li><li>关于我</li>)。

课后小练习

把个人主页的导航从垂直排列改成水平排列(用inline-block)。

课时32:CSS浮动布局——float(基础)

学习目标

掌握float的基础用法,实现简单的左右排列。

通俗讲解

float是「让元素浮动」,脱离普通文档流,实现“左图右文”“多列布局”,常用值:left(左浮动)、right(右浮动)、none(不浮动)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>浮动布局</title>
    <style>
        .img-box {
            float: left; /* 左浮动 */
            width: 100px;
            height: 100px;
            margin-right: 10px;
        }
        .text-box {
            overflow: hidden; /* 清除浮动影响(简单版) */
        }
    </style>
</head>
<body>
    <img src="https://picsum.photos/100/100" alt="图片" class="img-box">
    <div class="text-box">
        这是右边的文字,图片浮动到左边啦<br>
        多行文字也能和图片并排显示<br>
        是不是很简单!
    </div>
</body>
</html>

image-20251230192824638

实操步骤

  1. 新建32-浮动布局.html,粘贴代码;
  2. 把float改成right,看图片是否浮动到右边;
  3. 去掉overflow: hidden,看文字是否环绕图片。

易错点提醒

  • ❌ 浮动元素没设宽高:布局会混乱,必须给浮动元素设置宽高;
  • ❌ 忘记清除浮动:父元素高度会塌陷,新手用overflow: hidden简单清除;
  • ❌ 浮动元素太多导致换行:调整宽高和margin,确保总宽度不超过父元素。

课后小练习

给个人主页的header加浮动,让头像左浮动,姓名右浮动。

课时33-37:CSS Flex布局(核心布局,5课时)

课时33:Flex布局——开启Flex容器

学习目标

掌握display: flex,开启Flex布局,理解容器和项目的概念。

通俗讲解

Flex布局是「弹性布局」,给父元素加display: flex,父元素变成Flex容器,子元素变成Flex项目,可轻松控制项目的排列方式,是新手最容易上手的布局方式。

极简代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Flex容器</title>
    <style>
        .container {
            display: flex; /* 开启Flex布局 */
            width: 400px;
            height: 100px;
            border: 1px solid #ccc;
        }
        .item {
            width: 100px;
            height: 50px;
            background-color: #007bff;
            color: white;
            text-align: center;
            line-height: 50px;
            margin: 0 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="item">项目1</div>
        <div class="item">项目2</div>
        <div class="item">项目3</div>
    </div>
</body>
</html>

image-20251230192848158

实操任务
  1. 新建33-Flex容器.html,粘贴代码;
  2. 观察项目是否横向排列(默认);
  3. 去掉display: flex,看项目是否变回块级元素(垂直排列)。

课时34:Flex布局——主轴排列(justify-content)

学习目标

掌握justify-content,控制项目在主轴(默认水平)的排列方式。

通俗讲解

justify-content控制项目「水平排列方式」,常用值:

  • flex-start:左对齐(默认);
  • flex-end:右对齐;
  • center:水平居中;
  • space-between:两端对齐,项目之间间距相等;
  • space-around:项目两侧间距相等。
极简代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Flex-主轴排列</title>
    <style>
        .container {
            display: flex;
            justify-content: center; /* 水平居中 */
            width: 400px;
            height: 100px;
            border: 1px solid #ccc;
        }
        .item {
            width: 80px;
            height: 50px;
            background-color: #007bff;
            color: white;
            line-height: 50px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="item">1</div>
        <div class="item">2</div>
        <div class="item">3</div>
    </div>
</body>
</html>

image-20251230192908451

实操任务
  1. 依次修改justify-content为flex-end、space-between、space-around,观察排列变化;
  2. 给个人主页的nav容器加justify-content: space-around,让导航项均匀分布。

课时35:Flex布局——侧轴对齐(align-items)

学习目标

掌握align-items,控制项目在侧轴(默认垂直)的对齐方式。

通俗讲解

align-items控制项目「垂直对齐方式」,常用值:

  • flex-start:顶部对齐;
  • flex-end:底部对齐;
  • center:垂直居中;
  • stretch:拉伸(项目没设高度时,占满容器高度)。
极简代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Flex-侧轴对齐</title>
    <style>
        .container {
            display: flex;
            justify-content: center; /* 水平居中 */
            align-items: center; /* 垂直居中 */
            width: 400px;
            height: 150px;
            border: 1px solid #ccc;
        }
        .item {
            width: 80px;
            height: 50px;
            background-color: #007bff;
            color: white;
            line-height: 50px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="item">1</div>
        <div class="item">2</div>
    </div>
</body>
</html>

image-20251230192932093

实操任务
  1. 依次修改align-items为flex-end、stretch,观察变化;
  2. 给个人主页的header容器加align-items: center,让头像和姓名垂直居中。

课时36:Flex布局——换行(flex-wrap)

学习目标

掌握flex-wrap,控制项目超出容器时是否换行。

通俗讲解

flex-wrap默认值是nowrap(不换行),项目总宽度超过容器时会被压缩;设为wrap则换行,避免项目压缩。

极简代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Flex-换行</title>
    <style>
        .container {
            display: flex;
            flex-wrap: wrap; /* 换行 */
            width: 300px;
            height: 150px;
            border: 1px solid #ccc;
        }
        .item {
            width: 100px;
            height: 50px;
            background-color: #007bff;
            color: white;
            line-height: 50px;
            text-align: center;
            margin: 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="item">1</div>
        <div class="item">2</div>
        <div class="item">3</div>
        <div class="item">4</div>
    </div>
</body>
</html>

image-20251230193008828

实操任务
  1. 把flex-wrap改回nowrap,看项目是否被压缩;
  2. 给个人主页的“我的爱好”列表加Flex布局,设置换行,适配小屏幕。

课时37:Flex布局——综合练习(网页头部布局)

学习目标

用Flex布局重构个人主页的头部,实现“头像左、导航右、姓名居中”。

实操任务
  1. 给header加display: flex; justify-content: space-between; align-items: center;;
  2. 头像、姓名、导航分别作为Flex项目;
  3. 调整各项目的宽高和间距,实现美观的头部布局。

参考案例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的个人主页</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            font-family: "微软雅黑", sans-serif;
        }
        /* 核心修改:头部Flex布局 */
        header {
            display: flex; /* 开启Flex布局 */
            justify-content: space-between; /* 项目两端对齐,中间自动填充 */
            align-items: center; /* 项目垂直居中 */
            padding: 20px 30px;
            border-bottom: 1px solid #ccc;
            margin-bottom: 20px;
            height: 180px; /* 固定头部高度,保证布局稳定 */
        }
        /* 头像样式(左) */
        .avatar-box {
            width: 120px;
            flex-shrink: 0; /* 防止头像被压缩 */
        }
        .avatar-box img {
            width: 100%;
            height: 120px;
            border-radius: 50%;
            object-fit: cover; /* 保证图片比例不变形 */
        }
        /* 姓名样式(中) */
        .name-box {
            flex: 1; /* 占满中间剩余空间,实现居中 */
            text-align: center;
        }
        .name-box h1 {
            font-size: 28px;
            color: #333;
            letter-spacing: 2px;
        }
        /* 导航样式(右) */
        .nav-box {
            width: 400px;
            flex-shrink: 0; /* 防止导航被压缩 */
            text-align: right;
        }
        .nav-box a {
            margin: 0 10px;
            text-decoration: none;
            color: #333;
            font-size: 16px;
        }
        .nav-box a:hover {
            color: #0088ff;
            text-decoration: underline;
        }
        /* 主体及底部样式(无修改,保留原有效果) */
        main {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-bottom: 20px;
        }
        .section {
            padding: 20px;
            border: 1px solid #eee;
            border-radius: 8px;
        }
        .section h2 {
            margin-bottom: 15px;
            color: #333;
        }
        ul, ol {
            padding-left: 20px;
            line-height: 1.8;
        }
        form {
            grid-column: 1 / 3;
        }
        .form-item {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="text"], textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        textarea {
            height: 150px;
            resize: vertical;
        }
        button {
            padding: 8px 20px;
            background-color: #0088ff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #0066cc;
        }
        footer {
            text-align: center;
            padding: 10px 0;
            border-top: 1px solid #ccc;
            color: #666;
        }
    </style>
</head>
<body>
    <!-- 重构后的头部:整合头像、姓名、导航到header中 -->
    <header>
        <!-- 头像(左) -->
        <div class="avatar-box">
            <img src="5.jpg" alt="我的头像">
        </div>
        <!-- 姓名(中) -->
        <div class="name-box">
            <h1>张三</h1>
        </div>
        <!-- 导航(右) -->
        <div class="nav-box">
            <a href="#home">首页</a>
            <a href="#hobby">我的爱好</a>
            <a href="#experience">我的经历</a>
            <a href="#message">留言板</a>
        </div>
    </header>

    <!-- 主体内容(无结构修改) -->
    <main>
        <section id="hobby" class="section">
            <h2>我的爱好</h2>
            <ul>
                <li>阅读(偏爱技术类书籍)</li>
                <li>编程(HTML/CSS/JavaScript)</li>
                <li>运动(跑步、羽毛球)</li>
                <li>摄影(记录生活点滴)</li>
            </ul>
        </section>

        <section id="experience" class="section">
            <h2>我的经历</h2>
            <ol>
                <li>2018-2022:XX大学 计算机专业 本科</li>
                <li>2022-2023:XX科技公司 前端实习生</li>
                <li>2023-至今:XX互联网公司 前端开发工程师</li>
            </ol>
        </section>

        <section id="message" class="section">
            <h2>留言板</h2>
            <form action="#" method="post">
                <div class="form-item">
                    <label for="username">姓名:</label>
                    <input type="text" id="username" name="username" placeholder="请输入你的姓名">
                </div>
                <div class="form-item">
                    <label for="content">留言内容:</label>
                    <textarea id="content" name="content" placeholder="请输入你的留言..."></textarea>
                </div>
                <button type="submit">提交留言</button>
            </form>
        </section>
    </main>

    <footer>
        <p>© 2025 张三的个人主页 | 版权所有</p>
    </footer>
</body>
</html>

image-20251230193218998

课时38:CSS定位——相对定位(relative)

学习目标

掌握相对定位,让元素相对于自身位置偏移。

通俗讲解

相对定位是「元素搬家,但原位置还留着」,加position: relative后,用top/right/bottom/left控制偏移,不会脱离文档流,适合微调元素位置。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>相对定位</title>
    <style>
        .box {
            width: 200px;
            height: 100px;
            background-color: #eee;
            position: relative; /* 相对定位 */
            top: 20px; /* 向下偏移20px */
            left: 30px; /* 向右偏移30px */
        }
    </style>
</head>
<body>
    <div>原位置会留空</div>
    <div class="box">相对定位的盒子</div>
</body>
</html>

image-20251230193254206

实操步骤

  1. 新建38-相对定位.html,粘贴代码;
  2. 修改top为-20px(向上偏移),left为-30px(向左偏移);
  3. 给个人主页的“新品”标签加相对定位,微调位置。

易错点提醒

  • ❌ 相对定位脱离文档流:相对定位不会脱离文档流,原位置会保留;
  • ❌ 没加top/left:只加position: relative,元素位置不变;
  • ❌ 偏移值用百分比:百分比是相对于父元素的,新手建议用px。

课后小练习

用相对定位调整个人主页中某个文字的位置,微调1-2px。

课时39:CSS定位——绝对定位(absolute)

学习目标

掌握绝对定位,让元素相对于最近的已定位父元素偏移。

通俗讲解

绝对定位是「元素搬家,原位置不留空」,加position: absolute后,元素脱离文档流,相对于最近的加了position(relative/absolute/fixed)的父元素偏移;如果没有,相对于浏览器窗口偏移。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>绝对定位</title>
    <style>
        .parent {
            position: relative; /* 父元素相对定位,作为参考 */
            width: 300px;
            height: 200px;
            border: 1px solid #ccc;
            margin: 50px;
        }
        .child {
            position: absolute; /* 绝对定位 */
            top: 20px;
            right: 20px;
            width: 100px;
            height: 50px;
            background-color: #007bff;
            color: white;
        }
    </style>
</head>
<body>
    <div class="parent">
        父元素(相对定位)
        <div class="child">绝对定位的子元素</div>
    </div>
</body>
</html>

image-20251231104942402

实操步骤

  1. 新建39-绝对定位.html,粘贴代码;
  2. 去掉父元素的position: relative,看子元素是否相对于浏览器窗口偏移;
  3. 给个人主页的“关闭”按钮加绝对定位,放在右上角。

易错点提醒

  • ❌ 绝对定位没设参考父元素:元素会相对于浏览器窗口偏移,新手一定要给父元素加position: relative;
  • ❌ 绝对定位元素没设宽高:宽高默认由内容决定,可能导致布局混乱;
  • ❌ 绝对定位和浮动同时用:绝对定位会覆盖浮动,只需要选一种即可。

课后小练习

用绝对定位给个人主页加一个“回到顶部”按钮,放在页面右下角。

课时40:CSS定位——固定定位(fixed)

学习目标

掌握固定定位,让元素固定在浏览器窗口的某个位置。

通俗讲解

固定定位是「元素固定在窗口,滚动页面也不动」,加position: fixed后,元素脱离文档流,相对于浏览器窗口偏移,适合做导航栏、回到顶部按钮。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>固定定位</title>
    <style>
        .back-top {
            position: fixed; /* 固定定位 */
            bottom: 50px; /* 距离窗口底部50px */
            right: 50px; /* 距离窗口右侧50px */
            width: 50px;
            height: 50px;
            background-color: #007bff;
            color: white;
            text-align: center;
            line-height: 50px;
            text-decoration: none;
            border-radius: 50%; /* 圆形按钮 */
        }
        /* 占位文字,让页面能滚动 */
        .content {
            height: 2000px;
        }
    </style>
</head>
<body>
    <div class="content">滚动页面看看,按钮不会动</div>
    <a href="#" class="back-top"></a>
</body>
</html>

image-20251231105024360

实操步骤

  1. 新建40-固定定位.html,粘贴代码;
  2. 滚动页面,观察“回到顶部”按钮是否固定在右下角;
  3. 修改bottom/right的值,调整按钮位置。

易错点提醒

  • ❌ 固定定位相对于父元素:固定定位只相对于浏览器窗口,和父元素无关;
  • ❌ 固定定位元素被遮挡:可加z-index: 999;提高层级(z-index只对定位元素生效);
  • ❌ 固定导航栏遮挡内容:给页面主体加padding-top,值等于导航栏高度。

课后小练习

给个人主页加固定导航栏,滚动页面时导航栏始终在顶部。

CSS综合练习

课时41:综合练习1——美化个人主页样式

学习目标

整合CSS样式,给个人主页添加颜色、字体、间距、边框等样式,提升美观度。

实操任务
  1. 统一页面字体为“Microsoft Yahei”,设置body文字颜色#333,背景色#f5f5f5;
  2. 给header设置背景色白色,加阴影(box-shadow: 0 2px 4px rgba(0,0,0,0.1));
  3. 给导航链接设置hover样式(颜色变红、加下划线);
  4. 给表单输入框加聚焦样式(边框变色、加阴影)。

参考案例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的个人主页</title>
    <style>
        /* 全局样式重置 - 基础优化 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        /* ========== 任务1:统一页面字体、body文字色和背景色 ========== */
        body {
            font-family: "Microsoft Yahei", sans-serif; /* 统一字体为微软雅黑 */
            color: #333; /* 文字主色 */
            background-color: #f5f5f5; /* 页面背景色 */
            line-height: 1.6; /* 行高优化,提升阅读体验 */
            padding: 0;
            margin: 0;
        }

        /* ========== 任务2:美化header - 白色背景+阴影 ========== */
        header {
            background-color: #fff; /* 白色背景 */
            box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* 阴影效果 */
            padding: 20px 30px; /* 内边距,增加间距 */
            margin-bottom: 30px; /* 与下方内容间距 */
        }

        /* 导航样式基础 */
        .nav {
            list-style: none;
            display: flex;
            gap: 30px; /* 导航项间距 */
        }

        .nav li a {
            text-decoration: none;
            color: #333; /* 导航默认颜色 */
            font-size: 16px;
            padding: 5px 0; /* 增加点击区域 */
            transition: all 0.3s; /* 过渡动画,hover更丝滑 */
        }

        /* ========== 任务3:导航链接hover样式 - 变红+下划线 ========== */
        .nav li a:hover {
            color: red; /* hover文字变红 */
            text-decoration: underline; /* hover添加下划线 */
            text-underline-offset: 4px; /* 下划线偏移,更美观 */
        }

        /* 内容容器 - 优化整体布局 */
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 0 20px;
        }

        /* 表单基础样式 */
        .form-section {
            background-color: #fff;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            margin-bottom: 30px;
        }

        .form-section h2 {
            margin-bottom: 20px;
            color: #333;
            font-size: 20px;
        }

        .form-group {
            margin-bottom: 20px;
        }

        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
        }

        /* 表单输入框基础样式 */
        .form-control {
            width: 100%;
            max-width: 400px;
            padding: 10px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            font-family: "Microsoft Yahei", sans-serif; /* 继承统一字体 */
        }

        /* ========== 任务4:表单输入框聚焦样式 - 边框变色+阴影 ========== */
        .form-control:focus {
            outline: none; /* 清除默认聚焦轮廓 */
            border-color: #007bff; /* 聚焦边框变色(蓝色更醒目) */
            box-shadow: 0 0 5px rgba(0,123,255,0.3); /* 聚焦阴影效果 */
        }

        /* 按钮样式优化(配套提升美观度) */
        .btn {
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-family: "Microsoft Yahei", sans-serif;
            transition: background-color 0.3s;
        }

        .btn:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <!-- 头部导航 -->
    <header>
        <ul class="nav">
            <li><a href="#home">首页</a></li>
            <li><a href="#about">关于我</a></li>
            <li><a href="#hobby">我的爱好</a></li>
            <li><a href="#message">留言板</a></li>
        </ul>
    </header>

    <!-- 内容容器 -->
    <div class="container">
        <!-- 留言表单区域 -->
        <section class="form-section" id="message">
            <h2>留言板</h2>
            <form>
                <div class="form-group">
                    <label for="username">姓名</label>
                    <input type="text" id="username" class="form-control" placeholder="请输入您的姓名">
                </div>
                <div class="form-group">
                    <label for="content">留言内容</label>
                    <textarea id="content" class="form-control" rows="5" placeholder="请输入留言内容"></textarea>
                </div>
                <button type="submit" class="btn">提交留言</button>
            </form>
        </section>
    </div>
</body>
</html>

image-20251231104852939

课时42:综合练习2——用Flex布局优化个人主页布局

学习目标

用Flex布局重构个人主页的所有区域,实现响应式基础布局。

实操任务
  1. 给header加Flex布局,实现头像、姓名、导航的合理排列;
  2. 给main加Flex布局,让“我的爱好”“我的经历”“留言板”横向排列(大屏)/纵向排列(小屏);
  3. 给footer加Flex布局,让版权信息水平垂直居中。

参考代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的个人主页</title>
    <style>
        /* 全局重置 & 基础样式 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: "Microsoft Yahei", sans-serif;
            color: #333;
            background-color: #f5f5f5;
            line-height: 1.6;
            min-height: 100vh; /* 让body占满视口高度,方便footer置底 */
            display: flex;
            flex-direction: column; /* 让header-main-footer垂直排列 */
        }

        /* ========== 1. Header Flex布局:头像+姓名+导航 ========== */
        header {
            background-color: #fff;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            padding: 15px 30px;
            /* 核心:开启Flex布局 */
            display: flex;
            align-items: center; /* 垂直居中 */
            justify-content: space-between; /* 左右分布(头像+姓名左,导航右) */
            gap: 20px; /* 元素间间距 */
            flex-wrap: wrap; /* 小屏时自动换行,避免挤压 */
        }

        /* 头像样式 */
        .avatar {
            width: 60px;
            height: 60px;
            border-radius: 50%; /* 圆形头像 */
            overflow: hidden;
            flex-shrink: 0; /* 头像不收缩 */
        }

        .avatar img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        /* 姓名区域 */
        .name-card {
            flex-shrink: 0; /* 姓名不收缩 */
        }

        .name-card h1 {
            font-size: 20px;
            margin-bottom: 5px;
        }

        .name-card p {
            font-size: 12px;
            color: #999;
        }

        /* 导航样式 */
        .nav {
            list-style: none;
            /* 导航内部也用Flex */
            display: flex;
            gap: 25px;
        }

        .nav li a {
            text-decoration: none;
            color: #333;
            font-size: 16px;
            padding: 5px 0;
            transition: all 0.3s;
        }

        .nav li a:hover {
            color: red;
            text-decoration: underline;
            text-underline-offset: 4px;
        }

        /* ========== 2. Main Flex布局:大屏横向/小屏纵向 ========== */
        main {
            flex: 1; /* 占据剩余空间,让footer置底 */
            max-width: 1200px;
            margin: 0 auto;
            padding: 30px 20px;
            /* 核心:开启Flex布局 */
            display: flex;
            gap: 25px; /* 模块间间距 */
            flex-wrap: wrap; /* 小屏自动换行 */
            justify-content: flex-start; /* 大屏时靠左排列 */
        }

        /* 每个模块的基础样式 */
        .module {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.08);
            /* 大屏时:每个模块均分宽度(减去gap后) */
            flex: 1;
            min-width: 280px; /* 小屏时最小宽度,避免太窄 */
        }

        .module h2 {
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
            font-size: 18px;
        }

        /* 表单输入框样式 */
        .form-control {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-bottom: 15px;
            font-family: "Microsoft Yahei", sans-serif;
        }

        .form-control:focus {
            outline: none;
            border-color: #007bff;
            box-shadow: 0 0 5px rgba(0,123,255,0.3);
        }

        .btn {
            padding: 10px 20px;
            background-color: #007bff;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        .btn:hover {
            background-color: #0056b3;
        }

        /* ========== 3. Footer Flex布局:版权信息居中 ========== */
        footer {
            background-color: #333;
            color: #fff;
            padding: 20px 0;
            /* 核心:开启Flex布局 */
            display: flex;
            justify-content: center; /* 水平居中 */
            align-items: center; /* 垂直居中 */
        }

        .copyright {
            font-size: 14px;
            text-align: center;
        }

        /* 响应式适配:小屏(768px以下)优化 */
        @media (max-width: 768px) {
            header {
                justify-content: center; /* 小屏时header元素居中 */
                text-align: center;
            }

            .nav {
                margin-top: 15px; /* 换行后增加间距 */
                justify-content: center; /* 导航居中 */
                width: 100%; /* 导航占满宽度 */
            }

            main {
                flex-direction: column; /* 小屏强制纵向排列 */
            }

            .module {
                min-width: 100%; /* 小屏模块占满宽度 */
            }
        }
    </style>
</head>
<body>
    <!-- Header:头像+姓名+导航 -->
    <header>
        <div class="avatar">
            <img src="https://picsum.photos/60/60?random=1" alt="我的头像">
        </div>
        <div class="name-card">
            <h1>张三</h1>
            <p>前端学习中 | 热爱技术</p>
        </div>
        <ul class="nav">
            <li><a href="#hobby">我的爱好</a></li>
            <li><a href="#experience">我的经历</a></li>
            <li><a href="#message">留言板</a></li>
        </ul>
    </header>

    <!-- Main:三个模块横向/纵向排列 -->
    <main>
        <section class="module" id="hobby">
            <h2>我的爱好</h2>
            <ul>
                <li>学习HTML/CSS/JS</li>
                <li>写前端小项目</li>
                <li>阅读技术文档</li>
                <li>和同行交流学习</li>
            </ul>
        </section>

        <section class="module" id="experience">
            <h2>我的经历</h2>
            <p>2025年1月:开始学习前端基础</p>
            <p>2025年2月:完成个人主页项目</p>
            <p>2025年3月:学习Flex布局和响应式</p>
        </section>

        <section class="module" id="message">
            <h2>留言板</h2>
            <form>
                <input type="text" class="form-control" placeholder="请输入您的姓名">
                <textarea class="form-control" rows="3" placeholder="请输入留言内容"></textarea>
                <button type="submit" class="btn">提交留言</button>
            </form>
        </section>
    </main>

    <!-- Footer:版权信息居中 -->
    <footer>
        <div class="copyright">
            <p>© 2025 张三的个人主页 | 前端基础学习笔记</p>
        </div>
    </footer>
</body>
</html>

image-20251231105311496

小屏显示效果:

image-20251231105609286

课时43:CSS模块总结与查漏补缺

学习目标

回顾CSS核心知识点,解决样式不生效、布局错位等问题。

核心回顾
  1. CSS引入方式:行内(临时)、内嵌(单页面)、外部(多页面,后续拓展);
  2. 选择器优先级:ID选择器 > 类选择器 > 标签选择器;
  3. 布局核心:盒子模型(宽高/padding/margin)、Flex布局、定位(辅助);
  4. 常见问题:样式不生效(选择器写错/属性值错误)、布局错位(盒模型没设border-box/浮动没清除)。
查漏补缺

针对前24课时的易错点,逐一检查个人主页的CSS代码:

  • 选择器是否写错(类选择器漏点、ID选择器漏#);
  • 盒模型是否统一设为border-box;
  • Flex布局的容器和项目是否设置正确;
  • 定位元素是否有正确的参考父元素。

第四部分:JS(交互)

课时44:JS引入方式——行内/内嵌

学习目标

掌握JS的两种基础引入方式,执行第一个JS代码。

通俗讲解

JS引入方式和CSS类似:

  • 行内JS:直接写在标签事件里(比如onclick="alert('你好')"),适合简单交互;
  • 内嵌JS:写在<script>标签里(通常放body底部),适合单页面脚本。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS引入方式</title>
</head>
<body>
    <!-- 行内JS:点击按钮弹窗 -->
    <button onclick="alert('行内JS生效了!')">点击我(行内JS)</button>

    <!-- 内嵌JS:放body底部,避免DOM未加载完成 -->
    <script>
        // 控制台输出(F12打开控制台查看)
        console.log('内嵌JS生效了!');
        // 页面弹出提示
        alert('这是内嵌JS的提示!');
    </script>
</body>
</html>

image-20251231105659406

实操步骤

  1. 新建44-JS引入方式.html,粘贴代码;
  2. 点击按钮,看行内JS的弹窗效果;
  3. 打开浏览器控制台(F12→Console),查看console.log的输出。

易错点提醒

  • ❌ script标签放head里:DOM未加载完成时,获取元素会失败,新手先放body底部;
  • ❌ 代码拼写错误:比如alert写成alet,控制台会报错,要学会看报错信息;
  • ❌ 字符串漏加引号:比如alert(你好),会报错,字符串必须加单/双引号。

课后小练习

给个人主页的“留言提交”按钮加行内JS,点击弹窗“留言提交成功!”。

课时45:JS变量——声明和赋值

学习目标

掌握变量的声明(let/const/var)和赋值,理解变量的作用。

通俗讲解

变量是「存放数据的容器」,比如用let name = '张三'把“张三”存到name容器里,后续可直接用name调用这个值:

  • let:可修改的变量(常用);
  • const:不可修改的常量(比如PI=3.14);
  • var:旧版声明方式,新手优先用let/const。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS变量</title>
</head>
<body>
    <script>
        // 声明变量并赋值
        let username = '前端新手'; // 字符串
        let age = 18; // 数字
        const PI = 3.14; // 常量(不可修改)

        // 输出变量
        console.log('用户名:', username);
        console.log('年龄:', age);
        console.log('圆周率:', PI);

        // 修改变量(let可改,const不可改)
        age = 19;
        console.log('修改后的年龄:', age);
        // PI = 3.1415; // 报错:常量不能修改
    </script>
</body>
</html>

image-20251231105756676

实操步骤

  1. 新建45-JS变量.html,粘贴代码;
  2. 尝试修改PI的值,看控制台报错信息;
  3. 声明自己的变量(比如hobby='学习JS'),并输出到控制台。

易错点提醒

  • ❌ 变量名有空格/特殊字符:比如let user name = '张三',报错,变量名只能是字母/数字/下划线/$,且不能以数字开头;
  • ❌ 重复声明let变量:比如let age=18; let age=19;,报错,const同理;
  • ❌ 常量赋值后修改:const声明的变量必须初始化,且不能修改。

课后小练习

声明3个变量:name(你的名字)、gender(性别)、score(学习进度80),并输出到控制台。

课时46:JS数据类型——基本类型(字符串/数字/布尔)

学习目标

掌握JS的3种核心基本数据类型,能区分和转换类型。

通俗讲解

JS的基础数据类型就像不同类型的“容器”:

  • 字符串(String):放文字,必须加引号('张三'、"123");
  • 数字(Number):放数字,不用加引号(18、3.14、-5);
  • 布尔(Boolean):只有两个值,true(真)/false(假),用于判断。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS数据类型</title>
</head>
<body>
    <script>
        // 1. 字符串
        let str1 = 'Hello JS';
        let str2 = "123456"; // 数字加引号也是字符串
        // 2. 数字
        let num1 = 100;
        let num2 = 3.14;
        // 3. 布尔
        let isStudy = true;
        let isSleep = false;

        // 查看数据类型(typeof)
        console.log(typeof str1); // string
        console.log(typeof num1); // number
        console.log(typeof isStudy); // boolean

        // 类型转换:字符串转数字
        let strNum = '18';
        let num = Number(strNum);
        console.log(num + 2); // 20(数字运算)
        console.log(strNum + 2); // 182(字符串拼接)
    </script>
</body>
</html>

image-20251231105827582

实操步骤

  1. 新建46-JS数据类型.html,粘贴代码;
  2. 用typeof查看不同变量的类型,理解字符串和数字的区别;
  3. 尝试把布尔值转数字(Number(true)=1,Number(false)=0)。

易错点提醒

  • ❌ 字符串和数字相加:比如'18'+2=182(拼接),不是20,要先转数字;
  • ❌ 布尔值直接用数字判断:true等价于1,false等价于0,但不要混用;
  • ❌ 空字符串转数字:Number('')=0,新手要注意这个特性。

课后小练习

声明一个字符串变量('99'),转成数字后加1,输出结果(100)。

课时47:JS数据类型——特殊类型(null/undefined)

学习目标

掌握null和undefined的区别,理解“空值”的含义。

通俗讲解

这两种是“空容器”,但含义不同:

  • undefined:变量声明了但没赋值(比如let a;),或者访问不存在的属性,是“默认空”;
  • null:主动赋值为“空”(比如let b = null;),表示“故意为空”。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS特殊数据类型</title>
</head>
<body>
    <script>
        // 1. undefined:声明未赋值
        let a;
        console.log(a); // undefined
        console.log(typeof a); // undefined

        // 2. null:主动赋值为空
        let b = null;
        console.log(b); // null
        console.log(typeof b); // object(历史遗留问题,记住即可)

        // 3. 区别:转数字
        console.log(Number(undefined)); // NaN(非数字)
        console.log(Number(null)); // 0
    </script>
</body>
</html>

image-20251231105904731

实操步骤

  1. 新建47-JS特殊类型.html,粘贴代码;
  2. 观察undefined和null的输出差异;
  3. 尝试访问对象不存在的属性(比如let obj = {}; console.log(obj.name);),结果是undefined。

易错点提醒

  • ❌ 混淆null和undefined:记住“undefined是默认空,null是主动空”;
  • ❌ 认为typeof null是null:实际是object,这是JS的历史bug,不用纠结,记住即可;
  • ❌ 用undefined赋值:比如let a = undefined;,不推荐,主动空用null。

课后小练习

声明变量c,先不赋值(输出undefined),再赋值为null,观察控制台输出变化。

课时48:JS运算符——算术运算符

学习目标

掌握+、-、*、/、%等算术运算符,实现基本的数学运算。

通俗讲解

算术运算符和数学里的运算规则一致:

  • +:加法(数字相加/字符串拼接);
  • -:减法;
  • *:乘法;
  • /:除法;
  • %:取余(求余数,比如7%3=1)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS算术运算符</title>
</head>
<body>
    <script>
        let num1 = 10;
        let num2 = 3;

        // 加法
        console.log(num1 + num2); // 13
        // 减法
        console.log(num1 - num2); // 7
        // 乘法
        console.log(num1 * num2); // 30
        // 除法
        console.log(num1 / num2); // 3.333...
        // 取余
        console.log(num1 % num2); // 1

        // 注意:字符串拼接
        console.log('10' + 3); // 103
        console.log(10 + '3'); // 103
    </script>
</body>
</html>

image-20251231105930166

实操步骤

  1. 新建48-JS算术运算符.html,粘贴代码;
  2. 尝试修改num1和num2的值,观察运算结果;
  3. 用取余判断数字是否为偶数(num%2===0 是偶数)。

易错点提醒

  • ❌ 除法结果是小数:JS的除法不会自动取整,比如10/3=3.333,取整用Math.floor()/Math.ceil();
  • ❌ 取余符号写错:是%不是&,新手容易混淆;
  • ❌ 字符串和数字相加:优先拼接,不是运算,要先转数字。

课后小练习

计算“80课时,每天学2课时,需要多少天”,用除法运算并输出结果(40)。

课时49:JS运算符——赋值/比较运算符

学习目标

掌握赋值运算符(=、+=、-=)和比较运算符(==、===、>、<)。

通俗讲解

  • 赋值运算符:给变量赋值,+=是“累加”(比如a+=2等价于a=a+2);
  • 比较运算符:判断两个值的关系,返回布尔值(true/false),===是严格相等(值+类型都相等),优先用===。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS赋值/比较运算符</title>
</head>
<body>
    <script>
        // 1. 赋值运算符
        let a = 5;
        a += 3; // 等价于a = a + 3
        console.log(a); // 8
        a -= 2; // 等价于a = a - 2
        console.log(a); // 6

        // 2. 比较运算符
        let num1 = 18;
        let num2 = '18';
        console.log(num1 == num2); // true(只比数值)
        console.log(num1 === num2); // false(类型不同)
        console.log(num1 > 10); // true
        console.log(num1 < 5); // false
    </script>
</body>
</html>

image-20251231110004769

实操步骤

  1. 新建49-JS赋值比较运算符.html,粘贴代码;
  2. 理解==和===的区别,记住优先用===;
  3. 用比较运算符判断“年龄是否大于等于18”,输出布尔值。

易错点提醒

  • ❌ 把=当==:if(a=5)是赋值,不是判断,判断要用==/===;
  • ❌ 混用==和===:比如'18'===18是false,新手优先用===避免类型问题;
  • ❌ 比较运算符顺序错:比如a > b < c 不是数学里的连续比较,要写成a > b && b < c。

课后小练习

声明变量score=85,判断是否大于等于80且小于90,输出布尔值(true)。

课时50:JS运算符——逻辑运算符

学习目标

掌握&&(与)、||(或)、!(非)逻辑运算符,实现多条件判断。

通俗讲解

逻辑运算符用于组合多个条件,返回布尔值:

  • &&(与):所有条件都为true,结果才是true(比如“年龄≥18 且 有身份证”);
  • ||(或):只要有一个条件为true,结果就是true(比如“有手机 或 有电脑”);
  • !(非):取反,true变false,false变true(比如!true=false)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS逻辑运算符</title>
</head>
<body>
    <script>
        let age = 20;
        let hasId = true;
        let hasPhone = false;
        let hasComputer = true;

        // &&(与):都真才真
        console.log(age >= 18 && hasId); // true

        // ||(或):一真就真
        console.log(hasPhone || hasComputer); // true

        // !(非):取反
        console.log(!hasPhone); // true
        console.log(!(age > 30)); // true(age>30是false,取反为true)
    </script>
</body>
</html>

image-20251231110033971

实操步骤

  1. 新建50-JS逻辑运算符.html,粘贴代码;
  2. 修改age=17,观察&&的结果是否变成false;
  3. 用逻辑运算符判断“分数≥60 且 <100”,输出是否及格。

易错点提醒

  • ❌ 逻辑运算符顺序错:&&优先级高于||,复杂判断加括号(比如(a||b)&&c);
  • ❌ 混淆&&和||:记住“&&是且,||是或”;
  • ❌ 非运算符用错:!只能加在布尔值前,比如!5是false(非0数字转布尔是true,取反为false)。

课后小练习

声明变量hour=19,判断是否是“晚上”(hour≥18 且 hour<24),输出布尔值(true)。

课时51:JS条件语句——if语句

学习目标

掌握if基本语句,实现“满足条件就执行代码”的逻辑。

通俗讲解

if语句是「条件判断」,就像“如果…就…”:

if (条件) {
  // 条件为true时执行的代码
}

条件必须返回布尔值,非布尔值会自动转换(比如0=false,非0=true)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS if语句</title>
</head>
<body>
    <script>
        let score = 85;

        // 基本if语句
        if (score >= 60) {
            console.log('及格了!');
            alert('恭喜,你的分数是' + score + ',及格啦!');
        }

        // if-else语句:二选一
        if (score >= 90) {
            console.log('优秀');
        } else {
            console.log('良好');
        }
    </script>
</body>
</html>

image-20251231110103890

实操步骤

  1. 新建51-JS if语句.html,粘贴代码;
  2. 修改score=59,观察是否输出“及格了!”(不输出),else部分输出“良好”;
  3. 用if语句判断年龄是否≥18,弹窗“成年”或“未成年”。

易错点提醒

  • ❌ 条件括号漏写:比如if score>=60,报错,条件必须加();
  • ❌ 代码块漏写{}:单条语句可以省略,但新手建议都加,避免逻辑混乱;
  • ❌ 条件是赋值语句:比如if(score=60),永远为true,要写if(score===60)

课后小练习

声明变量time=22,用if-else判断是否超过22点,弹窗“该睡觉了”或“还可以学习一会儿”。

课时52:JS条件语句——if-else if-else

学习目标

掌握多条件判断语句,实现“多选一”的逻辑。

通俗讲解

if-else if-else是「多条件分支」,就像“如果…就…,否则如果…就…,否则…”,按顺序判断,满足第一个条件后不再执行后续判断。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS if-else if-else</title>
</head>
<body>
    <script>
        let score = 85;

        // 多条件判断
        if (score >= 90) {
            console.log('优秀');
            alert('成绩优秀!');
        } else if (score >= 80) {
            console.log('良好');
            alert('成绩良好!');
        } else if (score >= 60) {
            console.log('及格');
            alert('成绩及格!');
        } else {
            console.log('不及格');
            alert('成绩不及格,继续努力!');
        }
    </script>
</body>
</html>

image-20251231110143874

实操步骤

  1. 新建52-JS if-else if-else.html,粘贴代码;
  2. 依次修改score为95、75、55,观察不同的弹窗结果;
  3. 用多条件判断“成绩等级”:100-90优秀,89-80良好,79-70中等,69-60及格,<60不及格。

易错点提醒

  • ❌ 条件顺序错:比如先判断≥60,再判断≥90,会导致90分也被判定为及格;
  • ❌ 漏写else:多条件判断可以没有else,但建议加,处理所有情况;
  • ❌ 条件重叠:比如≥80和≥70重叠,按顺序执行第一个满足的条件。

课后小练习

声明变量month=7,判断季节(3-5春,6-8夏,9-11秋,12-2冬),弹窗对应的季节。

课时53:JS条件语句——switch语句

学习目标

掌握switch语句,实现“固定值匹配”的多条件判断。

通俗讲解

switch语句适合「固定值的多分支判断」(比如匹配月份、星期),比if-else if更简洁:

switch (变量) {
  case 值1:
    // 变量===值1时执行
    break; // 跳出switch,避免执行后续case
  case 值2:
    // 变量===值2时执行
    break;
  default:
    // 都不匹配时执行
}

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS switch语句</title>
</head>
<body>
    <script>
        let week = 3; // 1-周一,2-周二...7-周日

        switch (week) {
            case 1:
                console.log('周一:学习HTML');
                break;
            case 2:
                console.log('周二:学习CSS');
                break;
            case 3:
                console.log('周三:学习JS');
                break;
            default:
                console.log('周末:休息+复习');
        }
    </script>
</body>
</html>

image-20251231110230589

实操步骤

  1. 新建53-JS switch语句.html,粘贴代码;
  2. 修改week为7,观察是否执行default;
  3. 去掉break,看是否会执行后续case(穿透效果)。

易错点提醒

  • ❌ 漏写break:会导致case穿透(执行完当前case继续执行下一个);
  • ❌ switch判断类型:用===严格匹配,比如case '3'和week=3不匹配;
  • ❌ 复杂条件用switch:switch适合固定值匹配,范围判断(比如≥80)用if-else if。

课后小练习

用switch语句判断月份(1-12)对应的季节,弹窗季节名称。

课时54:JS循环——for循环(基础)

学习目标

掌握for循环的基本用法,实现重复执行代码。

通俗讲解

for循环是「重复执行固定次数的代码」,就像“循环做10次题”,语法:

for (初始化变量; 循环条件; 变量更新) {
  // 循环体:满足条件时执行
}

比如for(let i=0; i<5; i++)会执行5次循环体。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS for循环</title>
</head>
<body>
    <script>
        // 循环输出1-5
        for (let i = 1; i <= 5; i++) {
            console.log('第' + i + '次循环');
        }

        // 计算1-100的和
        let sum = 0;
        for (let i = 1; i <= 100; i++) {
            sum += i; // 累加
        }
        console.log('1-100的和:', sum); // 5050
    </script>
</body>
</html>

image-20251231110255287

实操步骤

  1. 新建54-JS for循环.html,粘贴代码;
  2. 修改循环条件为i<10,观察循环执行次数(9次);
  3. 用for循环计算1-50的偶数和。

易错点提醒

  • ❌ 循环条件写死:比如i=10,循环只执行一次;
  • ❌ 变量更新漏写:比如没有i++,循环变成死循环(浏览器会卡死);
  • ❌ 初始化变量重复声明:比如for(let i=1; i<5; let i++),报错。

课后小练习

用for循环输出10次“我要学好前端!”,每次输出的数字从1到10。

课时55:JS循环——for循环(进阶)

学习目标

掌握for循环遍历数组、跳过/终止循环,提升循环灵活性。

通俗讲解

for循环常用来遍历数组(依次访问数组的每个元素),还可以用continue跳过当前循环、break终止循环:

  • continue:跳过本次循环,执行下一次;
  • break:直接终止整个循环。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS for循环进阶</title>
</head>
<body>
    <script>
        // 1. 遍历数组
        let hobbies = ['HTML', 'CSS', 'JS', 'Vue'];
        for (let i = 0; i < hobbies.length; i++) {
            console.log('我的爱好:', hobbies[i]);
        }

        // 2. continue:跳过偶数
        for (let i = 1; i <= 5; i++) {
            if (i % 2 === 0) {
                continue; // 跳过偶数
            }
            console.log('奇数:', i); // 1,3,5
        }

        // 3. break:找到3就终止
        for (let i = 1; i <= 5; i++) {
            if (i === 3) {
                break; // 终止循环
            }
            console.log('循环次数:', i); // 1,2
        }
    </script>
</body>
</html>

image-20251231110330845

实操步骤

  1. 新建55-JS for循环进阶.html,粘贴代码;
  2. 修改数组hobbies,新增元素,观察遍历结果;
  3. 用break实现“找到第一个大于80的分数就终止循环”。

易错点提醒

  • ❌ 数组索引越界:比如i<=hobbies.length,会访问到undefined;
  • ❌ continue/break位置错:要放在条件判断里,否则会直接跳过/终止;
  • ❌ 遍历数组用错变量:比如hobbies[i]写成hobbies,输出整个数组。

课后小练习

声明数组scores=[75,88,92,60,85],用for循环遍历,输出所有≥80的分数。

课时56:JS循环——while循环

学习目标

掌握while循环,实现“满足条件就循环”的逻辑。

通俗讲解

while循环是「先判断条件,再执行循环体」,适合循环次数不确定的场景,语法:

while (循环条件) {
  // 循环体:条件为true时执行
  // 必须有变量更新,避免死循环
}

比如while(score < 60)会一直循环直到分数≥60。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS while循环</title>
</head>
<body>
    <script>
        // 输出1-5
        let i = 1;
        while (i <= 5) {
            console.log('while循环:', i);
            i++; // 变量更新,避免死循环
        }

        // 计算直到和≥100
        let sum = 0;
        let num = 1;
        while (sum < 100) {
            sum += num;
            num++;
        }
        console.log('和≥100时的num:', num); // 14
        console.log('最终和:', sum); // 105
    </script>
</body>
</html>

image-20251231110414635

实操步骤

  1. 新建56-JS while循环.html,粘贴代码;
  2. 去掉i++,看是否变成死循环(浏览器卡死,需关闭标签);
  3. 用while循环实现“输入数字直到输入0为止”(结合prompt)。

易错点提醒

  • ❌ 忘记变量更新:导致死循环,浏览器会卡死;
  • ❌ 条件永远为true:比如while(true),必须加break终止;
  • ❌ 初始化变量在循环内:比如while(i<=5){let i=1; i++;},i永远是1,死循环。

课后小练习

用while循环计算1-10的乘积(1*2*3*...*10=3628800)。

课时57:JS数组——声明/访问/修改

学习目标

掌握数组的基础操作,存储和管理多个数据。

通俗讲解

数组是「存放多个数据的有序列表」,用[]声明,每个元素有索引(从0开始),可以通过索引访问/修改元素:

  • 声明:let arr = [1,2,3]
  • 访问:arr[0](第一个元素);
  • 修改:arr[0] = 10

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS数组基础</title>
</head>
<body>
    <script>
        // 1. 声明数组
        let fruits = ['苹果', '香蕉', '橙子'];
        let nums = [1, 2, 3, 4, 5];
        let mix = ['前端', 80, true]; // 混合类型

        // 2. 访问元素(索引从0开始)
        console.log('第一个水果:', fruits[0]); // 苹果
        console.log('最后一个数字:', nums[nums.length - 1]); // 5

        // 3. 修改元素
        fruits[1] = '葡萄';
        console.log('修改后的数组:', fruits); // ['苹果','葡萄','橙子']

        // 4. 数组长度
        console.log('水果数组长度:', fruits.length); // 3
    </script>
</body>
</html>

image-20251231110545651

实操步骤

  1. 新建57-JS数组基础.html,粘贴代码;
  2. 访问mix数组的第二个元素(80),修改为90;
  3. 声明自己的数组(比如学习计划),访问并修改元素。

易错点提醒

  • ❌ 索引从1开始:数组索引从0开始,arr[0]是第一个元素;
  • ❌ 访问不存在的索引:比如arr[10],返回undefined;
  • ❌ 修改长度导致数据丢失:比如arr.length=2,会删除索引≥2的元素。

课后小练习

声明数组lessons=['HTML','CSS','JS'],添加第四个元素'Vue'(先修改length为4,再赋值lessons[3]='Vue')。

课时58:JS数组——常用方法(增删改查)

学习目标

掌握数组的push/pop/unshift/shift/splice等核心方法,高效操作数组。

通俗讲解

数组提供了便捷方法,不用手动操作索引:

  • push():末尾添加元素;
  • pop():末尾删除元素;
  • unshift():开头添加元素;
  • shift():开头删除元素;
  • splice(起始索引, 删除个数, 新增元素):任意位置增删改。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS数组方法</title>
</head>
<body>
    <script>
        let arr = [1, 2, 3];

        // 1. 末尾添加
        arr.push(4);
        console.log('push后:', arr); // [1,2,3,4]

        // 2. 末尾删除
        arr.pop();
        console.log('pop后:', arr); // [1,2,3]

        // 3. 开头添加
        arr.unshift(0);
        console.log('unshift后:', arr); // [0,1,2,3]

        // 4. 开头删除
        arr.shift();
        console.log('shift后:', arr); // [1,2,3]

        // 5. 任意位置修改(索引1,删除1个,添加10)
        arr.splice(1, 1, 10);
        console.log('splice后:', arr); // [1,10,3]
    </script>
</body>
</html>

image-20251231110629371

实操步骤

  1. 新建58-JS数组方法.html,粘贴代码;
  2. 用splice在索引2的位置添加元素20,观察数组变化;
  3. 用pop删除最后一个元素,再用push添加回来。

易错点提醒

  • ❌ splice参数错:第一个参数是起始索引,第二个是删除个数,不是结束索引;
  • ❌ push传多个参数:push(4,5)会添加两个元素,不是数组;
  • ❌ 混淆unshift和shift:unshift是加,shift是删,新手记“shift=删除开头”。

课后小练习

声明数组hobbies=['跑步','看书'],用unshift添加'学习前端',用splice在索引2的位置添加'看电影',最终数组为['学习前端','跑步','看电影','看书']。

课时59:JS函数——声明和调用

学习目标

掌握函数的声明和调用,实现代码复用。

通俗讲解

函数是「封装可重复执行的代码块」,就像“定义一个公式,多次使用”:

  • 声明:function 函数名(参数1, 参数2) { 代码块; return 返回值; }
  • 调用:函数名(参数值)。 参数是“输入”,返回值是“输出”。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS函数基础</title>
</head>
<body>
    <script>
        // 1. 声明函数:计算两数之和
        function add(a, b) {
            let sum = a + b;
            return sum; // 返回结果
        }

        // 2. 调用函数
        let result1 = add(10, 20);
        let result2 = add(5, 8);
        console.log('10+20=', result1); // 30
        console.log('5+8=', result2); // 13

        // 3. 无返回值函数
        function sayHello(name) {
            alert('你好,' + name + '!');
        }
        sayHello('前端新手'); // 弹窗
    </script>
</body>
</html>

image-20251231110714579

image-20251231110700913

实操步骤

  1. 新建59-JS函数基础.html,粘贴代码;
  2. 修改add函数的参数,计算3个数之和(添加参数c,sum=a+b+c);
  3. 声明函数judgeScore(score),判断分数是否及格,返回true/false。

易错点提醒

  • ❌ 函数名重复:不能声明两个同名函数,后声明的会覆盖前一个;
  • ❌ 参数个数不匹配:调用时参数少了,未传的参数是undefined;
  • ❌ 忘记return:函数默认返回undefined,需要返回值必须加return。

课后小练习

声明函数calculateAge(birthYear),根据出生年份计算年龄(2025 - birthYear),调用函数计算自己的年龄。

课时60:JS函数——参数和返回值

学习目标

深入理解函数的参数(默认值/不定参数)和返回值,提升函数灵活性。

通俗讲解

  • 参数默认值:给参数设置默认值,调用时不传该参数就用默认值;
  • 不定参数:用...args接收任意个数的参数,变成数组;
  • 返回值:一个函数只能有一个return,执行return后函数立即结束。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS函数参数和返回值</title>
</head>
<body>
    <script>
        // 1. 参数默认值
        function sayHello(name = '陌生人') {
            return '你好,' + name + '!';
        }
        console.log(sayHello()); // 你好,陌生人!
        console.log(sayHello('张三')); // 你好,张三!

        // 2. 不定参数:计算任意个数的和
        function sum(...args) {
            let total = 0;
            for (let i = 0; i < args.length; i++) {
                total += args[i];
            }
            return total;
        }
        console.log(sum(1,2,3)); // 6
        console.log(sum(10,20,30,40)); // 100

        // 3. 多个return(但只执行第一个)
        function judgeNum(num) {
            if (num > 0) {
                return '正数';
            } else if (num < 0) {
                return '负数';
            } else {
                return '零';
            }
        }
        console.log(judgeNum(-5)); // 负数
    </script>
</body>
</html>

image-20251231110751130

实操步骤

  1. 新建60-JS函数参数返回值.html,粘贴代码;
  2. 给sum函数加默认值,不传参数时返回0;
  3. 声明函数getMax(...args),返回参数中的最大值。

易错点提醒

  • ❌ 默认参数位置错:默认参数要放在参数列表的最后;
  • ❌ 不定参数多个:一个函数只能有一个不定参数,且放在最后;
  • ❌ return后写代码:return后的代码不会执行,比如return 1; console.log(2);不会输出2。

课后小练习

声明函数getAverage(...args),计算任意个数的平均值,调用函数计算[80,90,85,95]的平均值(87.5)。

课时61:JS DOM——获取元素(基础)

学习目标

掌握DOM基础,获取页面元素(id/标签/类名)。

通俗讲解

DOM是「文档对象模型」,把HTML标签变成JS可以操作的对象,获取元素是第一步:

  • getElementById(id):通过ID获取唯一元素;
  • getElementsByTagName(标签名):通过标签名获取元素集合;
  • getElementsByClassName(类名):通过类名获取元素集合。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS DOM获取元素</title>
</head>
<body>
    <h1 id="title">DOM操作</h1>
    <p class="content">第一个段落</p>
    <p class="content">第二个段落</p>

    <script>
        // 1. 通过ID获取(返回单个元素)
        let title = document.getElementById('title');
        console.log('ID获取:', title); // h1元素
        console.log('标题文字:', title.innerText); // DOM操作

        // 2. 通过标签名获取(返回集合)
        let ps = document.getElementsByTagName('p');
        console.log('标签获取数量:', ps.length); // 2
        console.log('第一个p:', ps[0].innerText); // 第一个段落

        // 3. 通过类名获取(返回集合)
        let contents = document.getElementsByClassName('content');
        console.log('类名获取第二个:', contents[1].innerText); // 第二个段落
    </script>
</body>
</html>

image-20251231111325519

实操步骤

  1. 新建61-JS DOM获取元素.html,粘贴代码;
  2. 尝试获取不存在的ID,看返回值(null);
  3. 遍历ps集合,输出所有p标签的文字。

易错点提醒

  • ❌ 获取集合后直接操作:比如ps.innerText,集合没有innerText,要取索引(ps[0].innerText);
  • ❌ 脚本放head里:DOM未加载完成,获取元素返回null;
  • ❌ 类名/ID拼写错误:返回null或空集合,要检查拼写。

课后小练习

给个人主页的“留言板”div加ID,用getElementById获取该元素,输出其innerHTML。

课时62:JS DOM——获取元素(进阶)

学习目标

掌握querySelector/querySelectorAll,更灵活地获取元素。

通俗讲解

querySelector是「CSS选择器方式获取元素」,比传统方法更灵活:

  • querySelector(选择器):返回第一个匹配的元素(支持ID/#、类/.、标签选择器);
  • querySelectorAll(选择器):返回所有匹配的元素集合。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS DOM获取元素进阶</title>
</head>
<body>
    <div class="box" id="box1">第一个盒子</div>
    <div class="box" id="box2">第二个盒子</div>
    <ul>
        <li>列表项1</li>
        <li>列表项2</li>
    </ul>

    <script>
        // 1. querySelector:返回第一个匹配元素
        let firstBox = document.querySelector('.box');
        console.log('第一个.box:', firstBox.innerText); // 第一个盒子

        let box2 = document.querySelector('#box2');
        console.log('#box2:', box2.innerText); // 第二个盒子

        let li1 = document.querySelector('ul li');
        console.log('第一个li:', li1.innerText); // 列表项1

        // 2. querySelectorAll:返回所有匹配元素
        let boxes = document.querySelectorAll('.box');
        console.log('.box数量:', boxes.length); // 2

        let lis = document.querySelectorAll('ul li');
        lis.forEach(li => { // 遍历集合
            console.log('li文字:', li.innerText);
        });
    </script>
</body>
</html>

image-20251231111354370

实操步骤

  1. 新建62-JS DOM获取元素进阶.html,粘贴代码;
  2. 用querySelectorAll获取所有p标签,遍历输出文字;
  3. 用querySelector获取“ul li:last-child”(最后一个li)。

易错点提醒

  • ❌ querySelector返回集合:querySelector只返回第一个元素,要获取所有用querySelectorAll;
  • ❌ forEach遍历getElementsByTagName集合:传统集合不能用forEach,querySelectorAll的集合可以;
  • ❌ 选择器语法错:比如.box1 #box2是后代选择器,要检查CSS选择器语法。

课后小练习

用querySelectorAll获取个人主页的所有a标签,遍历修改其颜色为#007bff。

课时63:JS DOM——修改元素内容和样式

学习目标

掌握修改元素的innerText/innerHTML和style样式,实现动态效果。

通俗讲解

  • 修改内容:
  • innerText:修改纯文字(不会解析HTML标签);
  • innerHTML:修改HTML内容(会解析标签,比如<b>加粗</b>);
  • 修改样式:元素.style.样式名 = 值(样式名驼峰命名,比如backgroundColor)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS DOM修改内容和样式</title>
    <style>
        .box {
            width: 200px;
            height: 100px;
            border: 1px solid #ccc;
        }
    </style>
</head>
<body>
    <div id="textBox">原始文字</div>
    <div id="styleBox" class="box">修改样式</div>

    <script>
        // 1. 修改内容
        let textBox = document.getElementById('textBox');
        textBox.innerText = '修改后的纯文字';
        // textBox.innerHTML = '<b>加粗的文字</b>'; // 解析HTML标签

        // 2. 修改样式(驼峰命名)
        let styleBox = document.getElementById('styleBox');
        styleBox.style.backgroundColor = '#007bff';
        styleBox.style.color = 'white';
        styleBox.style.fontSize = '18px';
    </script>
</body>
</html>

image-20251231111420879

实操步骤

  1. 新建63-JS DOM修改内容样式.html,粘贴代码;
  2. 切换innerText和innerHTML,观察差异;
  3. 给styleBox加style.borderRadius = '8px',实现圆角。

易错点提醒

  • ❌ 样式名用横线:比如style.background-color,报错,要写成backgroundColor;
  • ❌ innerHTML插入不可信内容:比如用户输入的内容,可能导致XSS攻击,新手暂时用innerText;
  • ❌ 样式值漏加单位:比如style.fontSize = 18,不生效,要加px。

课后小练习

给个人主页的“学习进度”div加ID,用JS修改其innerText为“已完成80%”,并修改样式为color: green; font-weight: bold;。

课时64:JS DOM——修改元素类名

学习目标

掌握className/classList,批量修改元素样式,实现样式复用。

通俗讲解

直接修改style不够灵活,推荐通过修改类名来切换样式:

  • className:替换元素的所有类名(会覆盖原有类);
  • classList:更灵活(add添加/remove删除/toggle切换类)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS DOM修改类名</title>
    <style>
        .box {
            width: 200px;
            height: 100px;
            border: 1px solid #ccc;
        }
        .active {
            background-color: #007bff;
            color: white;
            border-radius: 8px;
        }
        .big {
            font-size: 20px;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <div id="box" class="box">修改类名</div>

    <script>
        let box = document.getElementById('box');

        // 1. className:覆盖原有类
        // box.className = 'box active'; // 保留box,添加active

        // 2. classList:灵活操作
        box.classList.add('active'); // 添加类
        box.classList.add('big'); // 再添加一个类
        // box.classList.remove('big'); // 删除类
        // box.classList.toggle('active'); // 切换类(有则删,无则加)

        console.log('当前类名:', box.className); // box active big
    </script>
</body>
</html>

image-20251231111454969

实操步骤

  1. 新建64-JS DOM修改类名.html,粘贴代码;
  2. 用toggle切换active类,观察样式变化;
  3. 给个人主页的导航项加active类(选中状态)。

易错点提醒

  • ❌ className覆盖原有类:比如box.className='active'会删除box类,要保留需写全;
  • ❌ classList.add传多个类:add('active','big')会添加两个类,正确;
  • ❌ 类名拼写错误:添加不存在的类,样式不生效,要检查CSS类名。

课后小练习

给个人主页的“留言提交”按钮加点击事件,点击时切换active类(背景色变红)。

课时65:JS事件——点击事件(基础)

学习目标

掌握点击事件(onclick/addEventListener),实现交互的核心逻辑。

通俗讲解

事件是「用户操作触发的动作」,点击事件最常用:

  • 行内事件:<button onclick="函数()">(简单但耦合高);
  • 动态绑定:元素.onclick = 函数(中等灵活);
  • 监听事件:元素.addEventListener('click', 函数)(推荐,可绑定多个)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS点击事件</title>
</head>
<body>
    <!-- 1. 行内点击事件 -->
    <button onclick="alert('行内点击!')">按钮1</button>

    <!-- 2. 动态绑定 -->
    <button id="btn2">按钮2</button>

    <!-- 3. 监听事件 -->
    <button id="btn3">按钮3</button>

    <script>
        // 2. 动态绑定点击事件
        let btn2 = document.getElementById('btn2');
        btn2.onclick = function() {
            alert('动态绑定点击!');
        };

        // 3. 监听点击事件(推荐)
        let btn3 = document.getElementById('btn3');
        btn3.addEventListener('click', function() {
            alert('监听点击!');
        });
        // 可绑定多个
        btn3.addEventListener('click', function() {
            console.log('按钮3被点击了');
        });
    </script>
</body>
</html>

image-20251231111547190

image-20251231111604721

image-20251231111627534

实操步骤

  1. 新建65-JS点击事件.html,粘贴代码;
  2. 点击不同按钮,观察效果;
  3. 给btn3添加第三个点击事件,修改页面文字。

易错点提醒

  • ❌ 监听事件重复绑定:多次addEventListener会执行多次,要注意;
  • ❌ 动态绑定覆盖:btn.onclick = fn1; btn.onclick = fn2只会执行fn2;
  • ❌ 函数加括号:onclick = fn()会立即执行,不是点击执行,要写onclick = fn

课后小练习

给个人主页的“回到顶部”按钮加点击事件,点击时页面滚动到顶部(window.scrollTo(0,0))。

课时66:JS事件——鼠标/键盘事件

学习目标

掌握鼠标事件(鼠标移入/移出)和键盘事件(按键按下),丰富交互效果。

通俗讲解

  • 鼠标事件:
  • mouseover/mouseout:鼠标移入/移出(会冒泡,新手先用);
  • mouseenter/mouseleave:鼠标移入/移出(不冒泡,推荐);
  • 键盘事件:
  • keydown:按键按下(任意键);
  • keyup:按键松开;
  • 通常绑定在input/文档上。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS鼠标/键盘事件</title>
    <style>
        .box {
            width: 200px;
            height: 100px;
            border: 1px solid #ccc;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <!-- 鼠标事件 -->
    <div id="mouseBox" class="box">鼠标移入/移出</div>

    <!-- 键盘事件 -->
    <input type="text" id="keyInput" placeholder="输入文字...">

    <script>
        // 1. 鼠标事件
        let mouseBox = document.getElementById('mouseBox');
        // 移入
        mouseBox.addEventListener('mouseenter', function() {
            this.style.backgroundColor = '#007bff';
            this.style.color = 'white';
        });
        // 移出
        mouseBox.addEventListener('mouseleave', function() {
            this.style.backgroundColor = '';
            this.style.color = '';
        });

        // 2. 键盘事件
        let keyInput = document.getElementById('keyInput');
        keyInput.addEventListener('keydown', function(e) {
            // e.key是按下的键,e.keyCode是键码(已废弃,用e.key)
            console.log('按下的键:', e.key);
            if (e.key === 'Enter') {
                alert('你输入了:' + this.value);
                this.value = ''; // 清空输入框
            }
        });
    </script>
</body>
</html>

image-20251231111716357

image-20251231111730450

image-20251231111753539

实操步骤

  1. 新建66-JS鼠标键盘事件.html,粘贴代码;
  2. 鼠标移入/移出div,观察样式变化;
  3. 在输入框输入文字,按回车,观察弹窗和清空效果。

易错点提醒

  • ❌ 键盘事件绑定在div上:div默认不聚焦,键盘事件不生效,要绑定在input/textarea/文档上;
  • ❌ this指向错:事件函数内的this指向触发事件的元素,箭头函数中this不指向元素;
  • ❌ 混淆mouseover和mouseenter:mouseover会冒泡(子元素触发),mouseenter不会,新手优先用mouseenter。

课后小练习

给个人主页的导航项加mouseenter/mouseleave事件,移入时变色,移出时恢复。

课时67:JS事件——表单事件

学习目标

掌握表单事件(聚焦/失焦/内容改变/提交),处理表单交互。

通俗讲解

表单事件是处理用户输入的核心:

  • focus:输入框聚焦;
  • blur:输入框失焦;
  • input:输入框内容实时变化;
  • submit:表单提交(绑定在form上,可阻止默认提交)。

极简代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS表单事件</title>
</head>
<body>
    <form id="myForm">
        <input type="text" id="username" placeholder="请输入用户名">
        <input type="password" id="password" placeholder="请输入密码">
        <button type="submit">提交</button>
    </form>

    <script>
        let username = document.getElementById('username');
        let password = document.getElementById('password');
        let myForm = document.getElementById('myForm');

        // 1. 聚焦事件
        username.addEventListener('focus', function() {
            this.style.border = '1px solid #007bff';
        });

        // 2. 失焦事件
        username.addEventListener('blur', function() {
            this.style.border = '';
            // 失焦验证
            if (this.value.length < 3) {
                alert('用户名至少3位!');
            }
        });

        // 3. 实时输入事件
        password.addEventListener('input', function() {
            console.log('密码实时输入:', this.value);
        });

        // 4. 表单提交事件
        myForm.addEventListener('submit', function(e) {
            e.preventDefault(); // 阻止默认提交(关键)
            let user = username.value;
            let pwd = password.value;
            if (user && pwd) {
                alert('提交成功!用户名:' + user);
                this.reset(); // 重置表单
            } else {
                alert('用户名和密码不能为空!');
            }
        });
    </script>
</body>
</html>

image-20251231111833031

实操步骤

  1. 新建67-JS表单事件.html,粘贴代码;
  2. 聚焦/失焦用户名输入框,观察边框和验证;
  3. 输入密码,看控制台实时输出;
  4. 提交表单,观察验证和重置效果。

易错点提醒

  • ❌ 忘记阻止默认提交:表单会刷新页面,必须加e.preventDefault();
  • ❌ 提交事件绑定在按钮上:按钮点击不等同于表单提交,要绑定在form上;
  • ❌ 空值判断错:if(user)会把空字符串判断为false,正确;if(user==='')更明确。

课后小练习

给个人主页的留言表单加提交事件,验证姓名和留言内容不能为空,为空则弹窗提示,不为空则弹窗“留言成功”并重置表单。

课时68:JS综合练习1——个人主页交互优化(点击事件)

学习目标

给个人主页添加核心点击交互:回到顶部、导航选中切换、区域显隐控制,掌握「事件绑定+类名切换+DOM样式修改」的组合用法。

前置准备

确保你的个人主页已有基础HTML/CSS结构(包含header导航、main内容区、回到顶部按钮、“我的经历”区域),核心结构参考:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的个人主页</title>
    <style>
        /* 基础样式 */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { height: 2000px; /* 让页面可滚动 */ }
        header { background: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 20px; }
        .nav { list-style: none; display: flex; gap: 20px; }
        .nav li a { text-decoration: none; color: #333; padding: 8px 12px; }
        .nav li a.active { background: #007bff; color: #fff; border-radius: 4px; }
        .experience { margin: 20px; padding: 20px; border: 1px solid #ccc; }
        .back-top { position: fixed; bottom: 50px; right: 50px; width: 50px; height: 50px; background: #007bff; color: #fff; text-align: center; line-height: 50px; border-radius: 50%; text-decoration: none; display: none; }
        .toggle-btn { margin: 20px; padding: 8px 16px; cursor: pointer; }
    </style>
</head>
<body>
    <header>
        <ul class="nav">
            <li><a href="#home" class="active">首页</a></li>
            <li><a href="#hobby">我的爱好</a></li>
            <li><a href="#experience">我的经历</a></li>
            <li><a href="#message">留言板</a></li>
        </ul>
    </header>

    <button class="toggle-btn">隐藏/显示我的经历</button>
    <div class="experience" id="experience">
        <h2>我的经历</h2>
        <p>2025年:学习前端HTML/CSS/JS</p>
        <p>目标:掌握前端基础,完成个人博客项目</p>
    </div>

    <a href="#" class="back-top" id="backTop"></a>

    <script>
        // 后续写交互代码
    </script>
</body>
</html>

image-20251231111925973

实操任务拆解

任务1:回到顶部按钮(显示/隐藏+点击滚动)

核心逻辑:页面滚动时,判断滚动距离>300px显示按钮,否则隐藏;点击按钮滚动到顶部。

// 1. 回到顶部功能
const backTop = document.getElementById('backTop');
// 监听页面滚动
window.addEventListener('scroll', function() {
    // 获取滚动距离(兼容写法)
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    // 滚动距离>300px显示按钮,否则隐藏
    backTop.style.display = scrollTop > 300 ? 'block' : 'none';
});
// 点击按钮回到顶部
backTop.addEventListener('click', function(e) {
    e.preventDefault(); // 阻止a标签默认跳转
    // 平滑滚动到顶部
    window.scrollTo({
        top: 0,
        behavior: 'smooth'
    });
});

image-20251231112017501

任务2:导航选中状态切换

核心逻辑:点击导航项时,给当前项加active类,移除其他项的active类。

// 2. 导航选中切换
const navLinks = document.querySelectorAll('.nav a');
navLinks.forEach(link => {
    link.addEventListener('click', function() {
        // 先移除所有导航项的active类
        navLinks.forEach(item => item.classList.remove('active'));
        // 给当前点击的项加active类
        this.classList.add('active');
    });
});

image-20251231112055053

任务3:隐藏/显示“我的经历”区域

核心逻辑:点击按钮切换目标区域的display样式(block/ none),同时修改按钮文字。

// 3. 隐藏/显示我的经历
const toggleBtn = document.querySelector('.toggle-btn');
const experience = document.getElementById('experience');
let isShow = true; // 标记是否显示

toggleBtn.addEventListener('click', function() {
    if (isShow) {
        experience.style.display = 'none';
        this.innerText = '显示我的经历';
        isShow = false;
    } else {
        experience.style.display = 'block';
        this.innerText = '隐藏我的经历';
        isShow = true;
    }
});

image-20251231112126878

image-20251231112155424

完整交互代码(替换script标签内内容)

// 1. 回到顶部功能
const backTop = document.getElementById('backTop');
window.addEventListener('scroll', function() {
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    backTop.style.display = scrollTop > 300 ? 'block' : 'none';
});
backTop.addEventListener('click', function(e) {
    e.preventDefault();
    window.scrollTo({ top: 0, behavior: 'smooth' });
});

// 2. 导航选中切换
const navLinks = document.querySelectorAll('.nav a');
navLinks.forEach(link => {
    link.addEventListener('click', function() {
        navLinks.forEach(item => item.classList.remove('active'));
        this.classList.add('active');
    });
});

// 3. 隐藏/显示我的经历
const toggleBtn = document.querySelector('.toggle-btn');
const experience = document.getElementById('experience');
let isShow = true;
toggleBtn.addEventListener('click', function() {
    if (isShow) {
        experience.style.display = 'none';
        this.innerText = '显示我的经历';
    } else {
        experience.style.display = 'block';
        this.innerText = '隐藏我的经历';
    }
    isShow = !isShow; // 简化:直接取反
});

易错点提醒

  1. ❌ 回到顶部按钮点击没效果:忘记加e.preventDefault()阻止a标签默认跳转,或滚动API写错(behavior: 'smooth'是平滑滚动,低版本浏览器可能不支持,可替换为document.documentElement.scrollTop = 0);
  2. ❌ 导航切换只加不减:只给当前项加active,没移除其他项的,导致多个项高亮;
  3. ❌ 显隐状态错乱:未用isShow标记状态,直接判断display可能因样式优先级出错(建议用标记变量)。

验证效果

  1. 滚动页面,观察回到顶部按钮是否在滚动>300px时显示;
  2. 点击不同导航项,是否只有当前项高亮;
  3. 点击“隐藏/显示”按钮,是否切换区域显隐且按钮文字变化。

课时69:JS综合练习2——个人主页交互优化(表单验证)

学习目标

给个人主页的留言表单添加「实时验证+失焦验证+提交验证」,掌握表单事件(focus/blur/input/submit)和数据校验逻辑。

前置准备

先给个人主页添加留言表单结构(放在backTop按钮前):

<!-- 留言板 -->
<div class="message" id="message" style="margin: 20px;">
    <h2>留言板</h2>
    <form id="messageForm">
        <div style="margin: 10px 0;">
            <label>姓名:</label>
            <input type="text" id="username" placeholder="请输入姓名" style="padding: 8px; width: 300px;">
            <span class="tips" id="usernameTips" style="color: red; font-size: 12px; margin-left: 10px;"></span>
        </div>
        <div style="margin: 10px 0;">
            <label>留言:</label>
            <textarea id="content" rows="5" cols="50" placeholder="请输入留言内容" style="padding: 8px;"></textarea>
            <div style="font-size: 12px; margin-top: 5px;">
                已输入:<span id="wordCount">0</span></div>
        </div>
        <button type="submit" style="padding: 8px 16px; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer;">提交留言</button>
    </form>
</div>

image-20251231112239926

实操任务拆解

任务1:姓名输入框失焦验证

核心逻辑:失焦时检查姓名是否为空、长度是否≥2,通过提示文字反馈结果。

// 1. 姓名失焦验证
const username = document.getElementById('username');
const usernameTips = document.getElementById('usernameTips');

username.addEventListener('blur', function() {
    const value = this.value.trim(); // 去除首尾空格
    if (value === '') {
        usernameTips.innerText = '姓名不能为空!';
    } else if (value.length < 2) {
        usernameTips.innerText = '姓名至少2个字!';
    } else {
        usernameTips.innerText = ''; // 验证通过,清空提示
    }
});

// 聚焦时清空提示(优化体验)
username.addEventListener('focus', function() {
    usernameTips.innerText = '';
});

image-20251231112414270

任务2:留言内容实时统计字数

核心逻辑:监听input事件,实时计算输入框字符长度并显示。

// 2. 留言内容实时统计字数
const content = document.getElementById('content');
const wordCount = document.getElementById('wordCount');

content.addEventListener('input', function() {
    const len = this.value.length;
    wordCount.innerText = len;
    // 可选:限制最大字数(比如200字)
    if (len > 200) {
        this.value = this.value.substring(0, 200); // 截断内容
        wordCount.innerText = 200;
        alert('留言内容最多200字!');
    }
});

image-20251231112532341

任务3:表单提交完整验证

核心逻辑:提交时阻止默认行为,重新校验所有字段,全部通过则提示成功并重置表单,否则提示错误。

// 3. 表单提交验证
const messageForm = document.getElementById('messageForm');

messageForm.addEventListener('submit', function(e) {
    e.preventDefault(); // 阻止表单默认提交(关键)

    // 1. 重新校验姓名
    const userNameVal = username.value.trim();
    let isPass = true; // 标记是否通过验证
    if (userNameVal === '' || userNameVal.length < 2) {
        usernameTips.innerText = '请输入有效的姓名(至少2字)!';
        isPass = false;
        username.focus(); // 聚焦到错误输入框(优化体验)
    }

    // 2. 校验留言内容
    const contentVal = content.value.trim();
    if (contentVal === '') {
        alert('留言内容不能为空!');
        isPass = false;
        content.focus();
        return; // 提前终止,避免后续执行
    }

    // 3. 所有验证通过
    if (isPass) {
        alert(`感谢${userNameVal}的留言!留言内容:${contentVal}`);
        this.reset(); // 重置表单
        wordCount.innerText = 0; // 重置字数统计
    }
});

image-20251231112802663

完整表单验证代码(添加到script标签内)

// 1. 姓名失焦验证
const username = document.getElementById('username');
const usernameTips = document.getElementById('usernameTips');
username.addEventListener('blur', function() {
    const value = this.value.trim();
    if (value === '') {
        usernameTips.innerText = '姓名不能为空!';
    } else if (value.length < 2) {
        usernameTips.innerText = '姓名至少2个字!';
    } else {
        usernameTips.innerText = '';
    }
});
username.addEventListener('focus', function() {
    usernameTips.innerText = '';
});

// 2. 留言内容实时统计字数
const content = document.getElementById('content');
const wordCount = document.getElementById('wordCount');
content.addEventListener('input', function() {
    let len = this.value.length;
    if (len > 200) {
        this.value = this.value.substring(0, 200);
        len = 200;
        alert('留言内容最多200字!');
    }
    wordCount.innerText = len;
});

// 3. 表单提交验证
const messageForm = document.getElementById('messageForm');
messageForm.addEventListener('submit', function(e) {
    e.preventDefault();
    const userNameVal = username.value.trim();
    const contentVal = content.value.trim();
    let isPass = true;

    // 校验姓名
    if (userNameVal === '' || userNameVal.length < 2) {
        usernameTips.innerText = '请输入有效的姓名(至少2字)!';
        isPass = false;
        username.focus();
    }

    // 校验留言
    if (contentVal === '') {
        if (isPass) { // 只有姓名验证通过时才聚焦留言框
            content.focus();
        }
        alert('留言内容不能为空!');
        isPass = false;
    }

    // 提交成功
    if (isPass) {
        alert(`感谢${userNameVal}的留言!\n留言内容:${contentVal}`);
        this.reset();
        wordCount.innerText = 0;
    }
});

易错点提醒

  1. ❌ 表单提交后页面刷新:忘记加e.preventDefault(),这是表单交互的核心;
  2. ❌ 字数统计包含空格:trim()只去除首尾空格,实时统计不需要trim(用户输入的空格也算字数);
  3. ❌ 验证提示重复:提交时未重新校验,仅依赖失焦验证(用户可能跳过失焦直接提交);
  4. ❌ 截断内容后字数错误:截断后未更新len,导致字数显示还是超过200。

验证效果

  1. 姓名输入框失焦时,空值/短于2字会显示红色提示,聚焦时清空;
  2. 留言框输入内容,字数实时更新,超过200字会截断并提示;
  3. 提交表单时,空姓名/空留言会提示错误,验证通过则弹窗并重置表单。

课时70:JS综合练习3——个人主页交互优化(动态数据)

学习目标

用「数组+循环+动态创建DOM」渲染“我的爱好”列表,掌握数据驱动页面的核心思想,同时添加鼠标交互效果。

前置准备

先给个人主页添加“我的爱好”基础结构(放在header后、toggle-btn前):

<!-- 我的爱好 -->
<div class="hobby" id="hobby" style="margin: 20px;">
    <h2>我的爱好</h2>
    <ul id="hobbyList" style="list-style: none; margin-top: 10px; display: flex; gap: 15px; flex-wrap: wrap;"></ul>
</div>

image-20251231112915040

实操任务拆解

任务1:声明爱好数组,动态创建li标签渲染列表

核心逻辑:定义数组存储爱好数据 → 遍历数组 → 逐个创建li元素 → 设置内容和样式 → 添加到ul中。

// 1. 动态渲染我的爱好列表
const hobbies = ['学习HTML', '学习CSS', '学习JS', '写前端项目', '看技术文档', '和同行交流'];
const hobbyList = document.getElementById('hobbyList');

// 清空原有内容(避免重复渲染)
hobbyList.innerHTML = '';

// 遍历数组创建li
hobbies.forEach((item, index) => {
    // 创建li元素
    const li = document.createElement('li');
    // 设置li的内容和样式
    li.innerText = `${index + 1}. ${item}`;
    li.style.padding = '10px 15px';
    li.style.background = '#f5f5f5';
    li.style.borderRadius = '4px';
    li.style.cursor = 'pointer';
    // 添加到ul中
    hobbyList.appendChild(li);
});

image-20251231112950066

任务2:给每个爱好li添加鼠标移入/移出效果

核心逻辑:遍历所有动态创建的li → 绑定mouseenter/mouseleave事件 → 切换背景色和文字色。

// 2. 给爱好li添加鼠标交互
function addHobbyHover() {
    const hobbyItems = document.querySelectorAll('#hobbyList li');
    hobbyItems.forEach(li => {
        // 鼠标移入
        li.addEventListener('mouseenter', function() {
            this.style.background = '#007bff';
            this.style.color = '#fff';
            this.style.transition = 'all 0.3s'; // 过渡动画(优化体验)
        });
        // 鼠标移出
        li.addEventListener('mouseleave', function() {
            this.style.background = '#f5f5f5';
            this.style.color = '#333';
        });
    });
}

// 渲染列表后调用交互函数
addHobbyHover();

image-20251231113025167

任务3:可选拓展——添加“新增爱好”功能

核心逻辑:添加输入框和按钮 → 点击按钮获取输入值 → 验证非空 → 添加到数组 → 重新渲染列表。

<!-- 新增爱好的输入框和按钮(添加到hobby div内,h2下方) -->
<div style="margin-bottom: 10px;">
    <input type="text" id="newHobby" placeholder="请输入新爱好" style="padding: 8px; width: 300px;">
    <button id="addHobbyBtn" style="padding: 8px 16px; margin-left: 10px; background: #28a745; color: #fff; border: none; border-radius: 4px; cursor: pointer;">新增爱好</button>
</div>
// 3. 新增爱好功能
const newHobby = document.getElementById('newHobby');
const addHobbyBtn = document.getElementById('addHobbyBtn');

addHobbyBtn.addEventListener('click', function() {
    const value = newHobby.value.trim();
    if (value === '') {
        alert('请输入爱好内容!');
        newHobby.focus();
        return;
    }
    // 添加到数组
    hobbies.push(value);
    // 重新渲染列表
    hobbyList.innerHTML = '';
    hobbies.forEach((item, index) => {
        const li = document.createElement('li');
        li.innerText = `${index + 1}. ${item}`;
        li.style.padding = '10px 15px';
        li.style.background = '#f5f5f5';
        li.style.borderRadius = '4px';
        li.style.cursor = 'pointer';
        hobbyList.appendChild(li);
    });
    // 重新绑定交互事件
    addHobbyHover();
    // 清空输入框
    newHobby.value = '';
});

image-20251231113130995

image-20251231113150711

完整动态数据代码(添加到script标签内)

// 1. 动态渲染我的爱好列表
const hobbies = ['学习HTML', '学习CSS', '学习JS', '写前端项目', '看技术文档', '和同行交流'];
const hobbyList = document.getElementById('hobbyList');

// 渲染爱好列表的函数(封装复用)
function renderHobbyList() {
    hobbyList.innerHTML = '';
    hobbies.forEach((item, index) => {
        const li = document.createElement('li');
        li.innerText = `${index + 1}. ${item}`;
        li.style.padding = '10px 15px';
        li.style.background = '#f5f5f5';
        li.style.borderRadius = '4px';
        li.style.cursor = 'pointer';
        hobbyList.appendChild(li);
    });
    // 渲染后绑定交互
    addHobbyHover();
}

// 2. 爱好li鼠标交互函数
function addHobbyHover() {
    const hobbyItems = document.querySelectorAll('#hobbyList li');
    hobbyItems.forEach(li => {
        li.addEventListener('mouseenter', function() {
            this.style.background = '#007bff';
            this.style.color = '#fff';
            this.style.transition = 'all 0.3s';
        });
        li.addEventListener('mouseleave', function() {
            this.style.background = '#f5f5f5';
            this.style.color = '#333';
        });
    });
}

// 3. 新增爱好功能
const newHobby = document.getElementById('newHobby');
const addHobbyBtn = document.getElementById('addHobbyBtn');
addHobbyBtn.addEventListener('click', function() {
    const value = newHobby.value.trim();
    if (!value) {
        alert('请输入爱好内容!');
        newHobby.focus();
        return;
    }
    hobbies.push(value);
    renderHobbyList(); // 复用渲染函数
    newHobby.value = '';
});

// 初始化渲染列表
renderHobbyList();

易错点提醒

  1. ❌ 动态元素绑定事件无效:直接在渲染前绑定事件(元素还没创建),必须在渲染后调用绑定函数;
  2. ❌ 重复渲染列表:新增爱好时未清空hobbyList.innerHTML,导致列表重复;
  3. ❌ 索引错误:index + 1写成index,导致爱好编号从0开始;
  4. ❌ 过渡动画不生效:只在mouseenter时加transition,应放在初始样式中(或mouseenter/mouseleave都加)。

验证效果

  1. 页面加载后,自动渲染爱好列表,每个项有默认样式;
  2. 鼠标移入爱好项,背景色变蓝、文字变白(有过渡动画),移出恢复;
  3. 在新增输入框输入内容,点击按钮可添加新爱好到列表末尾,且新项也有鼠标交互效果。

课时71:JS综合练习4——个人主页交互优化(简易轮播图)

学习目标

用JS实现「图片切换+边界控制+按钮禁用」的简易轮播图,掌握“索引控制+DOM属性修改”的核心逻辑。

前置准备

先给个人主页添加轮播图基础结构(放在hobby区域后):

<!-- 简易轮播图 -->
<div class="carousel" id="carousel" style="margin: 20px; width: 500px; height: 300px; border: 1px solid #ccc; position: relative; overflow: hidden;">
    <!-- 轮播图片 -->
    <img id="carouselImg" src="https://picsum.photos/500/300?random=1" alt="轮播图" style="width: 100%; height: 100%; object-fit: cover;">
    <!-- 切换按钮 -->
    <button id="prevBtn" style="position: absolute; left: 10px; top: 50%; transform: translateY(-50%); padding: 8px 12px; background: rgba(0,0,0,0.5); color: #fff; border: none; border-radius: 4px; cursor: pointer;">上一张</button>
    <button id="nextBtn" style="position: absolute; right: 10px; top: 50%; transform: translateY(-50%); padding: 8px 12px; background: rgba(0,0,0,0.5); color: #fff; border: none; border-radius: 4px; cursor: pointer;">下一张</button>
</div>

image-20251231114045978

核心逻辑说明

轮播图的核心是「图片数组+当前索引」:

  1. 定义图片地址数组,存储所有轮播图片;
  2. 用变量记录当前显示的图片索引;
  3. 点击“上一张/下一张”时,修改索引并切换图片;
  4. 索引到边界时(0或数组长度-1),禁用对应按钮。

实操任务拆解

任务1:定义轮播数据,初始化索引和按钮状态

// 1. 轮播图核心数据
const carouselImgs = [
    'https://picsum.photos/500/300?random=1',
    'https://picsum.photos/500/300?random=2',
    'https://picsum.photos/500/300?random=3'
];
const carouselImg = document.getElementById('carouselImg');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
let currentIndex = 0; // 当前图片索引

// 初始化按钮状态(默认第一张,禁用上一张)
function updateBtnStatus() {
    // 索引为0时,禁用上一张按钮
    prevBtn.disabled = currentIndex === 0;
    // 索引为最后一张时,禁用下一张按钮
    nextBtn.disabled = currentIndex === carouselImgs.length - 1;
    // 禁用时修改样式(优化体验)
    prevBtn.style.opacity = prevBtn.disabled ? 0.5 : 1;
    nextBtn.style.opacity = nextBtn.disabled ? 0.5 : 1;
}

// 初始化调用
updateBtnStatus();

任务2:实现图片切换逻辑

// 2. 切换图片函数(复用)
function changeImg(index) {
    carouselImg.src = carouselImgs[index];
    currentIndex = index;
    updateBtnStatus(); // 切换后更新按钮状态
}

// 3. 上一张按钮点击事件
prevBtn.addEventListener('click', function() {
    if (currentIndex > 0) {
        changeImg(currentIndex - 1);
    }
});

// 4. 下一张按钮点击事件
nextBtn.addEventListener('click', function() {
    if (currentIndex < carouselImgs.length - 1) {
        changeImg(currentIndex + 1);
    }
});

任务3:可选拓展——自动轮播

// 5. 自动轮播(可选)
let autoPlayTimer = setInterval(function() {
    // 计算下一个索引:到最后一张则回到0
    const nextIndex = currentIndex === carouselImgs.length - 1 ? 0 : currentIndex + 1;
    changeImg(nextIndex);
}, 3000); // 3秒切换一次

// 鼠标移入轮播图暂停自动播放,移出恢复
const carousel = document.getElementById('carousel');
carousel.addEventListener('mouseenter', function() {
    clearInterval(autoPlayTimer);
});
carousel.addEventListener('mouseleave', function() {
    autoPlayTimer = setInterval(function() {
        const nextIndex = currentIndex === carouselImgs.length - 1 ? 0 : currentIndex + 1;
        changeImg(nextIndex);
    }, 3000);
});

image-20251231114211592

image-20251231114232895

image-20251231114252952

完整轮播图代码(添加到script标签内)

// 简易轮播图核心代码
const carouselImgs = [
    'https://picsum.photos/500/300?random=1',
    'https://picsum.photos/500/300?random=2',
    'https://picsum.photos/500/300?random=3'
];
const carouselImg = document.getElementById('carouselImg');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const carousel = document.getElementById('carousel');
let currentIndex = 0;
let autoPlayTimer;

// 更新按钮状态
function updateBtnStatus() {
    prevBtn.disabled = currentIndex === 0;
    nextBtn.disabled = currentIndex === carouselImgs.length - 1;
    prevBtn.style.opacity = prevBtn.disabled ? 0.5 : 1;
    nextBtn.style.opacity = nextBtn.disabled ? 0.5 : 1;
}

// 切换图片
function changeImg(index) {
    carouselImg.src = carouselImgs[index];
    currentIndex = index;
    updateBtnStatus();
}

// 绑定按钮事件
prevBtn.addEventListener('click', function() {
    if (currentIndex > 0) {
        changeImg(currentIndex - 1);
    }
});
nextBtn.addEventListener('click', function() {
    if (currentIndex < carouselImgs.length - 1) {
        changeImg(currentIndex + 1);
    }
});

// 自动轮播
function startAutoPlay() {
    autoPlayTimer = setInterval(function() {
        const nextIndex = currentIndex === carouselImgs.length - 1 ? 0 : currentIndex + 1;
        changeImg(nextIndex);
    }, 3000);
}

// 暂停自动轮播
function stopAutoPlay() {
    clearInterval(autoPlayTimer);
}

// 初始化
updateBtnStatus();
startAutoPlay();

// 鼠标移入/移出控制自动轮播
carousel.addEventListener('mouseenter', stopAutoPlay);
carousel.addEventListener('mouseleave', startAutoPlay);

易错点提醒

  1. ❌ 索引越界:点击下一张时未判断currentIndex < 数组长度-1,导致访问不存在的图片地址;
  2. ❌ 按钮禁用后仍可点击:仅修改样式不设置disabled,按钮仍能触发事件(必须设置disabled = true);
  3. ❌ 自动轮播内存泄漏:鼠标移入暂停后未清除定时器,多次移入移出会创建多个定时器;
  4. ❌ 图片变形:未设置object-fit: cover,导致图片拉伸(轮播图容器固定宽高时必加)。

验证效果

  1. 页面加载后显示第一张图片,上一张按钮禁用(半透明),下一张按钮可用;
  2. 点击下一张,切换到第二张/第三张,到第三张时下一张按钮禁用;
  3. 点击上一张,可回到前一张,到第一张时上一张按钮禁用;
  4. (可选)鼠标不移入时,每3秒自动切换图片,移入轮播图暂停,移出恢复。

课时72:JS模块总结与查漏补缺

学习目标

系统回顾JS核心知识点,梳理常见bug的排查方法,解决个人主页交互中的高频问题。

一、JS核心知识点回顾(按优先级)

模块 核心内容 应用场景
基础语法 变量(let/const)、数据类型、运算符、条件语句(if/switch)、循环(for/while) 所有逻辑判断、数据处理
数组 声明、访问、push/pop/splice、遍历(for/forEach) 动态列表、数据存储
函数 声明、调用、参数(默认值/不定参数)、返回值 代码复用、逻辑封装
DOM操作 获取元素(querySelector/ById)、修改内容/样式/类名 页面动态更新
事件处理 点击/鼠标/表单事件、阻止默认行为、事件绑定(addEventListener) 页面交互(按钮、表单、滚动)
数据验证 空值检查、长度校验、类型判断 表单提交、用户输入处理

二、JS高频bug排查清单

1. DOM相关问题

问题现象 常见原因 解决方法
获取元素返回null 1. 脚本执行时DOM未加载(脚本放head);2. ID/类名拼写错误;3. 元素不存在 1. 脚本放body末尾;2. 检查选择器拼写;3. console.log打印元素验证
动态元素事件绑定无效 元素创建前绑定事件 1. 渲染后绑定事件;2. 事件委托(进阶)
修改样式不生效 1. 样式名驼峰写错(比如background-color→backgroundColor);2. 样式优先级低 1. 检查样式名;2. 用classList替代直接修改style;3. 加!important(临时)

2. 逻辑相关问题

问题现象 常见原因 解决方法
循环死循环 循环条件永远为true(比如未写i++) 检查循环条件和变量更新,加break/continue控制
条件判断失效 1. 用=代替===;2. 字符串和数字比较('18'===18);3. 逻辑运算符错误 1. 优先用===;2. 统一数据类型;3. 复杂条件加括号
函数返回值undefined 忘记写return,或return在条件里未执行 检查return位置,确保所有分支都有返回值

3. 事件相关问题

问题现象 常见原因 解决方法
表单提交后页面刷新 未阻止默认行为(e.preventDefault()) 在submit事件中第一行加e.preventDefault()
按钮点击无反应 1. 事件未绑定;2. 按钮被禁用(disabled=true);3. 代码报错中断执行 1. 检查事件绑定代码;2. 取消disabled;3. F12控制台看报错信息
滚动事件触发太频繁 未做节流(进阶) 简易解决:加标记变量,100ms内只执行一次

三、个人主页交互优化建议

  1. 代码复用:将重复逻辑封装成函数(比如渲染列表、验证输入),避免冗余;
  2. 体验优化
  3. 所有按钮/输入框添加hover/聚焦样式;
  4. 验证提示用友好的文字(避免“错误!”等生硬表述);
  5. 轮播图/回到顶部添加过渡动画,避免生硬切换;
  6. 容错处理
  7. 输入框验证时去除首尾空格(trim());
  8. 动态渲染列表前先清空,避免重复;
  9. 图片加载失败时显示默认图(carouselImg.onerror = () => { this.src = '默认图地址' });
  10. 代码可读性
  11. 变量/函数名用语义化命名(比如renderHobbyList而非fn1);
  12. 加注释说明核心逻辑(比如轮播图的索引控制、表单的验证规则)。

四、查漏补缺练习

  1. 给个人主页的轮播图添加“图片加载失败”容错:
// 轮播图图片加载失败处理
carouselImg.onerror = function() {
    this.src = 'https://picsum.photos/500/300?random=0'; // 默认图
    alert('图片加载失败,显示默认图!');
};
  1. 给表单验证添加“姓名只能包含中文/字母”的规则:
// 姓名格式验证(新增)
username.addEventListener('blur', function() {
    const value = this.value.trim();
    const reg = /^[a-zA-Z\u4e00-\u9fa5]{2,10}$/; // 2-10位中文/字母
    if (value === '') {
        usernameTips.innerText = '姓名不能为空!';
    } else if (!reg.test(value)) {
        usernameTips.innerText = '姓名只能是2-10位中文/字母!';
    } else {
        usernameTips.innerText = '';
    }
});

课时73:实战1——需求分析与页面规划(简易个人博客)

学习目标

明确“简易个人博客”的核心需求,拆分页面结构和功能模块,制定开发计划。

一、项目需求(极简版,适合新手)

1. 核心功能

  • 页面布局:头部(Logo+导航)、主体(文章列表+侧边栏)、底部(版权);
  • 文章列表:展示文章标题、简介、发布时间、点赞数;
  • 侧边栏:搜索框(筛选文章)、热门标签;
  • 交互功能:文章点赞、留言提交、搜索筛选。

2. 非功能要求

  • 响应式:适配电脑/平板/手机;
  • 样式美观:统一配色、合理间距、hover效果;
  • 交互友好:表单验证、操作反馈(弹窗/提示)。

二、页面结构拆分

区域 子元素 核心功能
头部(header) Logo(h1)、导航栏(nav+ul+li) 页面导航
主体(main) 文章列表(article-list) 展示文章、点赞交互
侧边栏(aside) 搜索文章、显示热门标签
底部(footer) 版权信息(p) 页面底部说明

三、数据规划(模拟数据)

1. 文章数据(数组)

const articles = [
    {
        id: 1,
        title: 'HTML语义化的重要性',
        desc: '语义化标签让页面结构更清晰,利于SEO和维护...',
        time: '2025-01-01',
        like: 12,
        tag: ['HTML', '基础']
    },
    {
        id: 2,
        title: 'Flex布局快速上手',
        desc: 'Flex布局是前端必备的布局方式,轻松实现水平垂直居中...',
        time: '2025-01-05',
        like: 28,
        tag: ['CSS', '布局']
    },
    {
        id: 3,
        title: 'JS DOM操作核心技巧',
        desc: '获取元素、修改样式、绑定事件,DOM操作的核心知识点...',
        time: '2025-01-10',
        like: 45,
        tag: ['JS', 'DOM']
    }
];

2. 热门标签(数组)

const hotTags = ['HTML', 'CSS', 'JS', 'Flex', 'DOM', '表单验证'];

四、开发计划(分阶段)

  1. 阶段1:搭建HTML结构(1课时)→ 课时74;
  2. 阶段2:编写CSS样式(响应式)→ 课时75;
  3. 阶段3:编写JS交互(搜索、点赞、留言)→ 课时76;
  4. 阶段4:测试、修复bug、优化体验 → 课时77。

课时74:实战2——搭建HTML结构(简易个人博客)

学习目标

用语义化标签搭建个人博客的完整HTML结构,为后续样式和交互打下基础。

完整HTML代码(可直接复制使用)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的个人博客</title>
    <!-- 后续引入CSS -->
    <style>
        /* 临时基础样式(课时75替换为完整CSS) */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: "Microsoft Yahei", sans-serif; color: #333; background: #f8f9fa; }
    </style>
</head>
<body>
    <!-- 头部 -->
    <header class="header" id="header">
        <div class="container" style="max-width: 1200px; margin: 0 auto; padding: 20px; display: flex; justify-content: space-between; align-items: center;">
            <h1 class="logo" style="font-size: 24px; color: #007bff;">我的前端博客</h1>
            <nav class="nav">
                <ul style="list-style: none; display: flex; gap: 20px;">
                    <li><a href="#home" style="text-decoration: none; color: #333;">首页</a></li>
                    <li><a href="#articles" style="text-decoration: none; color: #333;">文章列表</a></li>
                    <li><a href="#message" style="text-decoration: none; color: #333;">留言板</a></li>
                </ul>
            </nav>
        </div>
    </header>

    <!-- 主体 -->
    <main class="main" id="main" style="max-width: 1200px; margin: 0 auto; padding: 20px; display: flex; gap: 20px;">
        <!-- 文章列表 -->
        <section class="article-list" id="articleList" style="flex: 7;">
            <h2 style="margin-bottom: 20px; color: #007bff;">文章列表</h2>
            <!-- 文章项(JS动态渲染) -->
            <div class="article-items" id="articleItems"></div>
        </section>

        <!-- 侧边栏 -->
        <aside class="sidebar" id="sidebar" style="flex: 3;">
            <!-- 搜索框 -->
            <div class="search-box" style="margin-bottom: 20px; padding: 15px; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
                <h3 style="margin-bottom: 10px; font-size: 16px;">搜索文章</h3>
                <div style="display: flex; gap: 10px;">
                    <input type="text" id="searchInput" placeholder="输入标题关键词" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                    <button id="searchBtn" style="padding: 8px 16px; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer;">搜索</button>
                </div>
            </div>

            <!-- 热门标签 -->
            <div class="hot-tags" style="padding: 15px; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
                <h3 style="margin-bottom: 10px; font-size: 16px;">热门标签</h3>
                <div id="hotTagsList" style="display: flex; flex-wrap: wrap; gap: 8px;"></div>
            </div>
        </aside>
    </main>

    <!-- 留言板 -->
    <section class="message" id="message" style="max-width: 1200px; margin: 0 auto; padding: 20px;">
        <h2 style="margin-bottom: 20px; color: #007bff;">留言板</h2>
        <form id="messageForm" style="background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
            <div style="margin-bottom: 15px;">
                <label style="display: block; margin-bottom: 5px;">姓名:</label>
                <input type="text" id="msgName" placeholder="请输入姓名" style="width: 100%; max-width: 400px; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                <span class="tips" id="msgNameTips" style="color: red; font-size: 12px; display: block; margin-top: 5px;"></span>
            </div>
            <div style="margin-bottom: 15px;">
                <label style="display: block; margin-bottom: 5px;">留言内容:</label>
                <textarea id="msgContent" rows="5" placeholder="请输入留言内容" style="width: 100%; max-width: 600px; padding: 8px; border: 1px solid #ddd; border-radius: 4px;"></textarea>
            </div>
            <button type="submit" style="padding: 8px 16px; background: #28a745; color: #fff; border: none; border-radius: 4px; cursor: pointer;">提交留言</button>
        </form>
        <!-- 留言列表(JS动态渲染) -->
        <div id="msgList" style="margin-top: 20px;"></div>
    </section>

    <!-- 底部 -->
    <footer class="footer" id="footer" style="background: #333; color: #fff; text-align: center; padding: 20px; margin-top: 40px;">
        <p>© 2025 我的前端博客 | 零基础前端学习笔记</p>
    </footer>

    <!-- JS脚本 -->
    <script>
        // 后续添加JS交互代码(课时76)
    </script>
</body>
</html>

结构说明

  1. 语义化标签:用header/main/aside/section/footer替代div,提升可读性和SEO;
  2. 容器类:所有内容放在max-width: 1200px的容器中,避免页面过宽;
  3. 动态区域:文章列表(articleItems)、热门标签(hotTagsList)、留言列表(msgList)留空,后续用JS动态渲染;
  4. 响应式基础:添加meta viewport标签,主体用flex布局(后续CSS优化响应式)。

验证效果

  1. 打开页面,能看到完整的页面框架:头部Logo+导航、主体文章列表+侧边栏、留言板、底部版权;
  2. 所有输入框、按钮、标题显示正常,无布局错乱;
  3. 检查标签嵌套:无未闭合标签,语义化标签使用正确。

课时75:实战3——编写CSS样式(简易个人博客)

学习目标

编写完整的CSS样式,实现响应式布局,美化博客的视觉效果,统一交互样式。

完整CSS代码(替换head中的style标签内容)

/* 全局样式重置 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* 基础样式 */
body {
    font-family: "Microsoft Yahei", "Helvetica Neue", sans-serif;
    color: #333;
    background-color: #f8f9fa;
    line-height: 1.6;
}

a {
    text-decoration: none;
    color: inherit;
    transition: color 0.3s;
}

a:hover {
    color: #007bff;
}

button {
    cursor: pointer;
    transition: background-color 0.3s;
}

button:hover {
    opacity: 0.9;
}

/* 容器样式(居中+限制宽度) */
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
}

/* 头部样式 */
.header {
    background-color: #fff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    padding: 15px 0;
    position: sticky;
    top: 0;
    z-index: 999;
}

.header .container {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    font-size: 24px;
    color: #007bff;
    font-weight: bold;
}

.nav ul {
    list-style: none;
    display: flex;
    gap: 30px;
}

.nav a {
    font-size: 16px;
    font-weight: 500;
}

.nav a:hover {
    color: #007bff;
    border-bottom: 2px solid #007bff;
}

/* 主体样式 */
.main {
    padding: 30px 0;
    display: flex;
    gap: 20px;
}

/* 文章列表样式 */
.article-list {
    flex: 7;
}

.article-list h2 {
    font-size: 22px;
    color: #007bff;
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid #eee;
}

/* 文章项样式 */
.article-item {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    margin-bottom: 20px;
}

.article-item h3 {
    font-size: 18px;
    margin-bottom: 10px;
}

.article-item h3 a:hover {
    color: #007bff;
}

.article-item .desc {
    color: #666;
    margin-bottom: 15px;
}

.article-item .meta {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 14px;
    color: #999;
}

.article-item .like-btn {
    display: flex;
    align-items: center;
    gap: 5px;
    color: #ff4444;
    background: transparent;
    border: none;
    padding: 5px 0;
}

/* 侧边栏样式 */
.sidebar {
    flex: 3;
}

.search-box, .hot-tags {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    margin-bottom: 20px;
}

.search-box h3, .hot-tags h3 {
    font-size: 16px;
    margin-bottom: 15px;
    padding-bottom: 10px;
    border-bottom: 1px solid #eee;
}

.search-box .search-input-wrap {
    display: flex;
    gap: 10px;
}

#searchInput {
    flex: 1;
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
}

#searchBtn {
    padding: 8px 16px;
    background-color: #007bff;
    color: #fff;
    border: none;
    border-radius: 4px;
}

/* 热门标签样式 */
#hotTagsList {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.tag-item {
    padding: 5px 12px;
    background-color: #e9ecef;
    border-radius: 20px;
    font-size: 14px;
    color: #666;
    cursor: pointer;
    transition: all 0.3s;
}

.tag-item:hover {
    background-color: #007bff;
    color: #fff;
}

/* 留言板样式 */
.message {
    padding: 20px 0;
}

.message h2 {
    font-size: 22px;
    color: #007bff;
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid #eee;
}

#messageForm {
    background-color: #fff;
    padding: 25px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    margin-bottom: 30px;
}

#messageForm label {
    display: block;
    margin-bottom: 8px;
    font-weight: 500;
}

#msgName, #msgContent {
    width: 100%;
    max-width: 600px;
    padding: 10px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
    margin-bottom: 5px;
}

#msgContent {
    resize: vertical;
    min-height: 120px;
}

.tips {
    color: #dc3545;
    font-size: 12px;
    display: block;
    margin-top: 5px;
}

#messageForm button {
    padding: 10px 20px;
    background-color: #28a745;
    color: #fff;
    border: none;
    border-radius: 4px;
    font-size: 16px;
}

/* 留言列表样式 */
.msg-item {
    background-color: #fff;
    padding: 15px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    margin-bottom: 15px;
}

.msg-item .msg-name {
    font-weight: bold;
    color: #007bff;
    margin-bottom: 5px;
}

.msg-item .msg-time {
    font-size: 12px;
    color: #999;
    margin-bottom: 10px;
}

/* 底部样式 */
.footer {
    background-color: #333;
    color: #fff;
    text-align: center;
    padding: 25px 0;
    margin-top: 40px;
    font-size: 14px;
}

/* 响应式样式(平板/手机) */
@media (max-width: 768px) {
    /* 主体改为纵向排列 */
    .main {
        flex-direction: column;
    }

    /* 导航项间距减小 */
    .nav ul {
        gap: 15px;
    }

    /* 文章列表/侧边栏宽度100% */
    .article-list, .sidebar {
        flex: 100%;
    }

    /* Logo字体缩小 */
    .logo {
        font-size: 20px;
    }
}

样式核心亮点

  1. 响应式布局:用@media适配768px以下屏幕,主体从横向变为纵向;
  2. 交互体验:所有链接/按钮添加hover效果、过渡动画,提升流畅度;
  3. 视觉层次:用阴影、边框、间距区分不同模块,重点内容(标题)用蓝色突出;
  4. 粘性导航:头部设置position: sticky,滚动时导航固定在顶部,提升体验。

验证效果

  1. 电脑端:主体分为文章列表(70%)和侧边栏(30%),布局清晰,hover效果正常;
  2. 平板/手机端(缩小浏览器窗口):主体变为纵向排列,导航间距减小,适配小屏幕;
  3. 滚动页面:头部导航固定在顶部,不会被遮挡;
  4. 所有按钮/链接hover时,颜色/透明度变化,反馈明显。

课时76:实战4——编写JS交互功能(简易个人博客)

学习目标

整合JS核心知识,实现博客的核心交互:动态渲染文章/标签、搜索筛选、文章点赞、留言提交。

完整JS代码(替换script标签内内容)

// 1. 模拟数据
const articles = [
    {
        id: 1,
        title: 'HTML语义化的重要性',
        desc: '语义化标签让页面结构更清晰,利于SEO和维护。常用的语义化标签有header、main、aside、footer、article等,替代传统的div布局。',
        time: '2025-01-01',
        like: 12,
        tag: ['HTML', '基础']
    },
    {
        id: 2,
        title: 'Flex布局快速上手',
        desc: 'Flex布局是前端必备的布局方式,轻松实现水平垂直居中、等分布局、响应式布局。核心是容器的display: flex,以及justify-content、align-items等属性。',
        time: '2025-01-05',
        like: 28,
        tag: ['CSS', '布局']
    },
    {
        id: 3,
        title: 'JS DOM操作核心技巧',
        desc: '获取元素、修改样式、绑定事件,是DOM操作的核心知识点。推荐使用querySelector/querySelectorAll,比传统的getElementById更灵活。',
        time: '2025-01-10',
        like: 45,
        tag: ['JS', 'DOM']
    }
];

const hotTags = ['HTML', 'CSS', 'JS', 'Flex', 'DOM', '表单验证'];

// 2. 初始化页面(渲染文章、标签)
window.addEventListener('DOMContentLoaded', function() {
    renderArticles(articles); // 渲染所有文章
    renderHotTags(); // 渲染热门标签
});

// 3. 渲染文章列表
function renderArticles(articleList) {
    const articleItems = document.getElementById('articleItems');
    articleItems.innerHTML = ''; // 清空原有内容

    if (articleList.length === 0) {
        articleItems.innerHTML = '<p style="text-align: center; color: #999; padding: 20px;">未找到相关文章</p>';
        return;
    }

    articleList.forEach(article => {
        // 创建文章项
        const articleItem = document.createElement('div');
        articleItem.className = 'article-item';
        articleItem.innerHTML = `
            <h3><a href="#">${article.title}</a></h3>
            <p class="desc">${article.desc}</p>
            <div class="meta">
                <span>${article.time}</span>
                <button class="like-btn" data-id="${article.id}">
                    <span>👍</span>
                    <span class="like-count">${article.like}</span>
                </button>
            </div>
        `;
        articleItems.appendChild(articleItem);
    });

    // 绑定点赞事件
    bindLikeEvent();
}

// 4. 渲染热门标签
function renderHotTags() {
    const hotTagsList = document.getElementById('hotTagsList');
    hotTagsList.innerHTML = '';

    hotTags.forEach(tag => {
        const tagItem = document.createElement('span');
        tagItem.className = 'tag-item';
        tagItem.innerText = tag;
        // 标签点击筛选(可选)
        tagItem.addEventListener('click', function() {
            const filtered = articles.filter(article => article.tag.includes(tag));
            renderArticles(filtered);
        });
        hotTagsList.appendChild(tagItem);
    });
}

// 5. 文章点赞功能
function bindLikeEvent() {
    const likeBtns = document.querySelectorAll('.like-btn');
    likeBtns.forEach(btn => {
        btn.addEventListener('click', function() {
            const articleId = parseInt(this.dataset.id);
            const likeCount = this.querySelector('.like-count');
            // 找到对应文章,更新点赞数
            const article = articles.find(item => item.id === articleId);
            if (article) {
                article.like += 1;
                likeCount.innerText = article.like;
                // 点赞动画(优化体验)
                this.style.transform = 'scale(1.2)';
                setTimeout(() => {
                    this.style.transform = 'scale(1)';
                }, 300);
            }
        });
    });
}

// 6. 搜索功能
const searchInput = document.getElementById('searchInput');
const searchBtn = document.getElementById('searchBtn');

// 搜索按钮点击
searchBtn.addEventListener('click', function() {
    searchArticles();
});

// 回车搜索
searchInput.addEventListener('keydown', function(e) {
    if (e.key === 'Enter') {
        searchArticles();
    }
});

// 搜索逻辑
function searchArticles() {
    const keyword = searchInput.value.trim().toLowerCase();
    if (!keyword) {
        // 空关键词显示所有文章
        renderArticles(articles);
        return;
    }
    // 筛选标题包含关键词的文章
    const filtered = articles.filter(article => {
        return article.title.toLowerCase().includes(keyword);
    });
    renderArticles(filtered);
}

// 7. 留言功能
const messageForm = document.getElementById('messageForm');
const msgName = document.getElementById('msgName');
const msgContent = document.getElementById('msgContent');
const msgNameTips = document.getElementById('msgNameTips');
const msgList = document.getElementById('msgList');

// 存储留言数据(临时,刷新后丢失)
let messages = [];

messageForm.addEventListener('submit', function(e) {
    e.preventDefault(); // 阻止默认提交
    const name = msgName.value.trim();
    const content = msgContent.value.trim();
    let isPass = true;

    // 验证姓名
    if (name === '') {
        msgNameTips.innerText = '姓名不能为空!';
        isPass = false;
        msgName.focus();
    } else if (name.length < 2) {
        msgNameTips.innerText = '姓名至少2个字!';
        isPass = false;
        msgName.focus();
    } else {
        msgNameTips.innerText = '';
    }

    // 验证留言内容
    if (content === '') {
        if (isPass) {
            alert('留言内容不能为空!');
            msgContent.focus();
        }
        isPass = false;
    }

    // 提交成功
    if (isPass) {
        const now = new Date();
        const time = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
        // 添加留言数据
        const newMsg = {
            id: Date.now(), // 用时间戳做唯一ID
            name: name,
            content: content,
            time: time
        };
        messages.unshift(newMsg); // 新增留言放最前面
        // 渲染留言列表
        renderMessages();
        // 重置表单
        this.reset();
        alert('留言提交成功!');
    }
});

// 渲染留言列表
function renderMessages() {
    msgList.innerHTML = '';
    if (messages.length === 0) {
        msgList.innerHTML = '<p style="text-align: center; color: #999; padding: 20px;">暂无留言,快来第一条留言吧!</p>';
        return;
    }
    messages.forEach(msg => {
        const msgItem = document.createElement('div');
        msgItem.className = 'msg-item';
        msgItem.innerHTML = `
            <div class="msg-name">${msg.name}</div>
            <div class="msg-time">${msg.time}</div>
            <div class="msg-content">${msg.content}</div>
        `;
        msgList.appendChild(msgItem);
    });
}

// 初始化留言列表
renderMessages();

交互功能说明

功能 核心逻辑
动态渲染文章 遍历文章数组,创建DOM元素,添加到页面;支持空结果提示
热门标签 渲染标签列表,点击标签筛选对应文章(按tag匹配)
文章点赞 点击点赞按钮,更新对应文章的点赞数,添加缩放动画反馈
搜索功能 按标题关键词筛选文章(不区分大小写),支持按钮点击/回车搜索,空关键词显示全部
留言功能 验证姓名/留言内容,提交成功后添加到留言数组,渲染留言列表(新增留言置顶)

易错点提醒

  1. ❌ 搜索功能区分大小写:未将标题和关键词转为小写,导致“HTML”和“html”不匹配;
  2. ❌ 点赞数刷新后重置:用临时数组存储数据,未做本地存储(进阶可加localStorage);
  3. ❌ 留言时间格式错乱:月份/日期/小时未补0(比如1月显示为1,应显示为01),用padStart(2, '0')解决;
  4. ❌ 动态元素事件失效:渲染文章后未重新绑定点赞事件(renderArticles中调用bindLikeEvent)。

验证效果

  1. 页面加载后,自动渲染3篇文章和6个热门标签;
  2. 点击标签(如“JS”),筛选出包含该标签的文章;
  3. 点击文章点赞按钮,点赞数+1,有缩放动画;
  4. 在搜索框输入关键词(如“布局”),筛选出标题包含该词的文章,空关键词显示全部;
  5. 留言板输入姓名(≥2字)和内容,提交后显示在留言列表顶部,空值会提示错误。

课时77:实战5——测试与优化(简易个人博客)

学习目标

系统测试博客功能,修复常见bug,优化用户体验和基础性能,让项目更稳定、易用。

一、完整测试清单(逐项验证)

测试类型 测试项 预期结果 测试方法
功能测试 文章列表渲染 页面加载后显示3篇文章,格式正确 打开页面直接查看
热门标签筛选 点击标签(如CSS),只显示对应标签的文章 点击不同标签,检查文章列表变化
文章点赞 点击点赞按钮,数字+1,有缩放动画 点击点赞按钮,观察数字和动画
搜索功能(关键词/回车) 输入关键词筛选文章,回车/点击按钮都生效 输入“HTML”“布局”等关键词测试
留言提交(空值/短姓名/正常) 空值提示错误,正常输入提交成功并显示 分别测试空姓名、1字姓名、正常输入场景
兼容性测试 电脑端布局 文章列表+侧边栏横向排列,无错乱 浏览器窗口宽度≥768px时查看
手机端布局 主体纵向排列,导航适配,无内容溢出 浏览器窗口宽度≤768px或手机模拟器查看
体验测试 导航粘性 滚动页面时导航固定在顶部 向下滚动页面,观察导航位置
按钮/链接hover效果 颜色/透明度变化,反馈明显 鼠标移到按钮/链接上查看
空状态提示(搜索无结果) 显示“未找到相关文章”,而非空白 输入不存在的关键词(如“Python”)测试

二、常见bug修复(实战高频问题)

1. 问题:搜索关键词为空时,仍显示“未找到相关文章”
  • 原因:搜索逻辑中未正确判断空关键词
  • 修复代码(修改searchArticles函数):
function searchArticles() {
    const keyword = searchInput.value.trim().toLowerCase();
    // 修复:空关键词直接渲染所有文章
    if (!keyword) {
        renderArticles(articles);
        searchInput.value = ''; // 清空输入框(优化)
        return;
    }
    const filtered = articles.filter(article => {
        return article.title.toLowerCase().includes(keyword);
    });
    renderArticles(filtered);
}
2. 问题:留言时间显示为“2025-1-5”(月份/日期未补0)
  • 原因:日期格式化时未处理个位数
  • 修复代码(修改留言提交的时间生成逻辑):
// 封装日期格式化函数(复用)
function formatDate(date) {
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');
    const hour = date.getHours().toString().padStart(2, '0');
    const minute = date.getMinutes().toString().padStart(2, '0');
    return `${year}-${month}-${day} ${hour}:${minute}`;
}

// 留言提交时调用
const time = formatDate(now);
3. 问题:手机端输入框宽度溢出
  • 原因:输入框最大宽度限制未适配手机
  • 修复CSS(添加到响应式样式中):
@media (max-width: 768px) {
    #msgName, #msgContent {
        max-width: 100%; // 手机端输入框占满宽度
    }
}
4. 问题:点赞动画重复触发导致样式异常
  • 原因:未清除之前的动画定时器
  • 修复代码(修改bindLikeEvent函数):
btn.addEventListener('click', function() {
    const articleId = parseInt(this.dataset.id);
    const likeCount = this.querySelector('.like-count');
    const article = articles.find(item => item.id === articleId);
    if (article) {
        // 修复:先清除之前的动画,避免叠加
        clearTimeout(this.animateTimer);
        article.like += 1;
        likeCount.innerText = article.like;
        this.style.transform = 'scale(1.2)';
        this.animateTimer = setTimeout(() => {
            this.style.transform = 'scale(1)';
        }, 300);
    }
});

三、用户体验优化(低成本高收益)

1. 本地存储留言(刷新后不丢失)
  • 核心逻辑:用localStorage存储留言数据,初始化时读取
  • 修复代码:
// 初始化留言数据(从本地存储读取)
let messages = JSON.parse(localStorage.getItem('blogMessages')) || [];

// 渲染留言列表函数末尾添加:
localStorage.setItem('blogMessages', JSON.stringify(messages));

// 留言提交成功后,更新本地存储:
function renderMessages() {
    // ... 原有渲染逻辑 ...
    // 新增:存储到localStorage
    localStorage.setItem('blogMessages', JSON.stringify(messages));
}
2. 搜索框输入提示(placeholder优化)
  • 修改HTML中搜索框的placeholder:
<input type="text" id="searchInput" placeholder="输入标题关键词(如HTML、布局)" style="flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
3. 按钮禁用状态(表单提交时防止重复点击)
  • 修改留言提交逻辑:
messageForm.addEventListener('submit', function(e) {
    e.preventDefault();
    const submitBtn = this.querySelector('button[type="submit"]');
    // 禁用按钮,防止重复提交
    submitBtn.disabled = true;
    submitBtn.innerText = '提交中...';

    // 原有验证逻辑...

    if (isPass) {
        // ... 原有提交逻辑 ...
        // 恢复按钮状态
        submitBtn.disabled = false;
        submitBtn.innerText = '提交留言';
    } else {
        // 验证失败也恢复按钮
        submitBtn.disabled = false;
        submitBtn.innerText = '提交留言';
    }
});
4. 平滑滚动(页面内跳转更流畅)
  • 添加全局CSS:
html {
    scroll-behavior: smooth;
}

四、基础性能优化(新手版)

  1. 减少DOM操作次数:渲染列表时先拼接HTML字符串,再一次性插入(替代多次appendChild),示例:
function renderArticles(articleList) {
    const articleItems = document.getElementById('articleItems');
    if (articleList.length === 0) {
        articleItems.innerHTML = '<p style="text-align: center; color: #999; padding: 20px;">未找到相关文章</p>';
        return;
    }
    // 优化:拼接HTML字符串,减少DOM操作
    let html = '';
    articleList.forEach(article => {
        html += `
            <div class="article-item">
                <h3><a href="#">${article.title}</a></h3>
                <p class="desc">${article.desc}</p>
                <div class="meta">
                    <span>${article.time}</span>
                    <button class="like-btn" data-id="${article.id}">
                        <span>👍</span>
                        <span class="like-count">${article.like}</span>
                    </button>
                </div>
            </div>
        `;
    });
    articleItems.innerHTML = html;
    bindLikeEvent();
}
  1. 图片优化(若后续添加文章封面):添加loading="lazy"实现懒加载;
  2. 事件委托(进阶,优化标签点击事件):将标签的点击事件绑定到父元素,减少事件绑定数量。

验证优化效果

  1. 刷新页面后,之前提交的留言仍存在;
  2. 快速点击点赞按钮,动画不会错乱;
  3. 手机端输入框占满宽度,无溢出;
  4. 提交留言时按钮禁用,防止重复提交;
  5. 页面内跳转(如导航点击)有平滑滚动效果。

课时78:项目复盘——从0到1开发个人博客的核心思路

学习目标

总结个人博客项目的开发流程,提炼通用的前端开发思路,让你能复用这套逻辑开发其他项目。

一、前端项目通用开发流程(新手版)

需求分析 → 结构规划 → 样式开发 → 交互开发 → 测试优化 → 上线(可选)
1. 需求分析:明确“做什么”
  • 核心:把模糊需求拆成“可落地的功能点”,比如“博客”拆成: ✅ 展示文章列表 ✅ 标签筛选 ✅ 搜索 ✅ 点赞 ✅ 留言
  • 新手技巧:只做“核心功能”,先跑通再拓展(比如先实现文章渲染,再做点赞)。
2. 结构规划:明确“页面有什么”
  • 核心:用语义化HTML搭建骨架,区分“静态区域”和“动态区域”:
  • 静态区域:导航、底部、标题(固定不变);
  • 动态区域:文章列表、留言列表(JS渲染);
  • 新手技巧:先写死静态内容,确认布局没问题后,再用JS替换为动态数据。
3. 样式开发:明确“页面长什么样”
  • 核心:先写全局样式(重置、基础字体、颜色),再写模块样式(文章、侧边栏),最后写响应式;
  • 新手技巧: ✅ 用flex布局替代浮动,减少兼容性问题; ✅ 先做电脑端,再用@media适配手机端; ✅ 统一配色(比如主色#007bff,辅助色#28a745),避免杂乱。
4. 交互开发:明确“页面能做什么”
  • 核心:数据驱动页面,逻辑封装复用:
  • 定义模拟数据(数组/对象);
  • 封装渲染函数(如renderArticles),数据变了就重新渲染;
  • 事件绑定:优先封装成函数(如bindLikeEvent),动态元素渲染后重新绑定。
  • 新手技巧: ❌ 不要把所有代码写在一起,按功能拆分(比如渲染、事件、工具函数分开); ✅ 用console.log调试(比如渲染前打印数据,确认数据正确)。
5. 测试优化:明确“页面好不好用”
  • 核心:按“测试清单”逐项验证,修复bug+优化体验;
  • 新手技巧: ✅ 先自测(比如自己点一遍所有按钮); ✅ 用浏览器“手机模拟器”测试响应式; ✅ 优先修复“影响使用的bug”(比如搜索失效),再优化体验(比如动画)。

二、项目中踩过的坑 & 避坑技巧

常见坑 避坑技巧
动态元素事件绑定无效 渲染后再绑定事件,或用事件委托
响应式布局错乱 flex+max-width,少用固定宽度
表单重复提交 提交时禁用按钮,成功/失败后恢复
数据刷新后丢失 localStorage存储关键数据(留言、点赞)
样式优先级冲突 少用!important,用类名精准定位元素

三、可拓展的功能(学完后可以尝试)

  1. 文章详情页:点击文章标题跳转到详情页(用location.href传参);
  2. 点赞数持久化:把文章点赞数也存到localStorage
  3. 留言删除功能:给每条留言加删除按钮,点击后删除并更新列表;
  4. 夜间模式:添加切换按钮,修改body的背景色/文字色。

四、新手开发建议

  1. 不要“抄代码”:理解每一行代码的作用,比如e.preventDefault()是阻止表单默认提交;
  2. 遇到问题先自查:
  3. 看浏览器控制台(F12)有没有报错;
  4. 打印关键变量(比如console.log(articles)),确认数据正确;
  5. 复用代码:比如博客的“渲染列表”逻辑,可直接复用在“商品列表”“新闻列表”项目中。

课时79:前端学习路径规划(新手友好版)

学习目标

基于个人博客项目的学习经验,规划后续的前端学习路线,让你知道“接下来该学什么”。

一、新手前端学习路径(6-12个月)

阶段 学习内容 核心目标 实战项目
基础阶段(1-2月) HTML(语义化)、CSS(flex/grid)、JS(DOM/事件) 能做静态页面+简单交互 个人主页、博客、登录页
进阶阶段(2-3月) ES6+(箭头函数/解构/模块化)、Ajax(接口请求)、本地存储 能对接后端接口,动态获取数据 带接口的博客、天气查询、待办清单
框架阶段(3-4月) Vue3(或React)、Vite、组件化开发 能开发复杂交互的单页应用 电商首页、管理后台、个人博客2.0
工程化阶段(2-3月) Git、打包优化、移动端适配、小程序(可选) 能规范开发、上线项目 上线个人博客、小程序版博客

二、当前阶段(基础阶段)的重点任务

  1. 巩固JS核心:
  2. 重点学:数组方法(forEach/filter/map)、函数封装、DOM操作;
  3. 练习:把博客的留言功能改成“可删除/编辑”,加深对数组操作的理解。
  4. 补充CSS知识:
  5. 学习grid布局(适合复杂网格,比如相册);
  6. 学习CSS变量(统一管理配色,比如:root { --main-color: #007bff; })。
  7. 做“多案例练习”:
  8. 仿写:登录页、新闻列表、商品卡片;
  9. 核心:复用“数据渲染+事件绑定”的逻辑,熟练后会形成肌肉记忆。

三、新手学习技巧(避坑)

  1. 不要贪多:先学Vue3或React其中一个,学透再学另一个;
  2. 多动手少看视频:每学一个知识点,立刻写代码验证(比如学了filter,就用它做标签筛选);
  3. 善用工具:
  4. 查API:MDN(https://developer.mozilla.org/zh-CN/);
  5. 找样式:Bootstrap(https://getbootstrap.com/)(可直接用现成样式,减少重复造轮子);
  6. 调试:浏览器F12(Elements看结构、Console看报错、Network看接口)。

四、下一步学习建议(基础阶段→进阶阶段)

  1. 学习ES6+:重点掌握箭头函数、解构赋值、模板字符串、模块化(import/export);
  2. 学习Ajax:用fetchaxios请求后端接口(比如用免费的测试接口https://jsonplaceholder.typicode.com/);
  3. 把博客改造为“接口版”:用Ajax获取文章数据,替代本地模拟数据。

课时80:最终总结与常见问题答疑

学习目标

总结整个前端基础学习的核心知识点,解答新手高频问题,给你后续学习的信心和方向。

一、前端基础核心知识点总结

技术 核心掌握点 一句话概括
HTML 语义化标签、表单、链接、图片 搭建页面骨架,让页面“有结构”
CSS flex/grid布局、响应式、选择器、样式优先级 美化页面,让页面“好看”
JavaScript 变量/数据类型、数组/对象、函数、DOM/事件、本地存储 实现交互,让页面“能动”

二、新手高频问题答疑

1. 学了记不住怎么办?
  • 核心:“用进废退”,不是记不住,是用得少。
  • 解决: ✅ 每天花30分钟写代码(比如给博客加一个小功能); ✅ 把常用代码(比如渲染列表、表单验证)整理成“代码模板”,下次直接复用。
2. 遇到bug解决不了怎么办?
  • 核心:把“大问题拆成小问题”,逐步排查。
  • 解决步骤:
  • 看控制台报错(F12→Console),报错信息会告诉你“哪一行错了”“错在哪”;
  • 打印关键变量(console.log),确认数据是否正确;
  • 简化代码:把无关代码注释掉,只留出问题的部分,比如先注释点赞动画,看点赞数是否正常;
  • 查资料:把报错信息复制到百度/MDN,找解决方案。
3. 什么时候学框架(Vue/React)?
  • 核心:基础打牢再学框架,框架是“简化JS交互的工具”,不是“替代JS”。
  • 判断标准: ✅ 能独立开发像“个人博客”这样的项目,不用抄代码; ✅ 理解“数据驱动页面”的逻辑(比如改数据→重新渲染); ✅ 知道DOM操作、事件绑定、本地存储的核心用法。
4. 新手需要学后端吗?
  • 核心:前期不用深学,够用就行。
  • 建议: ✅ 先学“前端能接触到的后端知识”:比如用免费的测试接口(jsonplaceholder)模拟数据; ✅ 后期想做完整项目,可学Node.js(和JS语法一致,新手友好),或用PHP/Python写简单接口。
5. 项目怎么上线?
  • 核心:新手用“免费平台”快速上线,体验完整流程。
  • 步骤(简易版):
  • 把项目文件(HTML/CSS/JS)整理好;
  • 注册免费托管平台(比如GitHub Pages、Netlify、Vercel);
  • 上传文件,获取在线访问地址。

三、最后寄语

  1. 前端学习是“循序渐进”的,不要着急:从静态页面→简单交互→复杂项目,每一步都有收获;
  2. 不要怕写bug:每个前端工程师都是在改bug中成长的,bug是最好的老师;
  3. 多做项目:最好的学习方式是“做项目”,哪怕是模仿别人的项目,做一遍也比看十遍视频有用;
  4. 保持好奇心:遇到好看的网页,右键→“检查”,看看别人是怎么写的,学习优秀的代码思路。