面向零基础读者的原生JavaScript建站教程:从本地目录准备、HTML语义骨架、CSS布局与状态类到原生JS事件监听与DOM更新,通过完整可运行示例掌握网页制作流程。
零基础用JavaScript做网站:从HTML/CSS到原生JS交互
时间:2026-11-14
浏览:642
文章目录
零基础用 JavaScript 做网站,不需要先搭建复杂的开发环境,也不需要提前学习前端框架。只要一个纯文本编辑器和浏览器,就能从零写出一个可点击、可输入、可实时更新内容的网页。全过程按职责分成三步:先用 HTML 搭出网页骨架,再用 CSS 安排版面与状态样式,最后用浏览器自带的原生 JavaScript 监听用户操作并修改页面节点。下面通过一个本地可运行的“今日待办清单”页面,完整走一遍从空白文件夹到动态交互的过程。
一、准备本地文件与理解三层分工
网页本质上是一组按规范编写的文本文件。浏览器打开入口文件后,会自上而下读取内容,并把结构、样式和行为分别交给对应文件处理。
开始编码前,先在电脑桌面或常用文档目录下新建一个文件夹,命名为 my-first-site,在其中创建三个空白文本文件,注意文件名和扩展名必须完全一致:
index.html:存放网页的文字、输入框、按钮和列表结构。style.css:控制页面居中、字体、边框以及“已完成”“深色模式”的样式规则。app.js:编写原生 JavaScript 代码,处理添加任务、切换完成状态、删除条目和主题切换。
编辑器推荐使用 VS Code,或者系统自带的记事本(保存时编码选择 UTF-8,避免出现 .txt 隐藏后缀)。双击 index.html 即可用 Chrome、Edge 或 Safari 打开;在页面中按 F12(macOS 上为 Command + Option + I)并切换到 Console(控制台) 标签页,就能看到后续 JS 运行时是否报错。
二、第一步:用 HTML 搭出网页骨架
写 HTML 时不要急着考虑颜色或圆角,先把页面里“有什么元素”表达清楚。在这个待办清单中,页面包含四块内容:顶部标题与主题切换按钮、输入新任务的表单、未完成数量提示,以及任务列表容器。
把下面的代码完整写入 my-first-site/index.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>
<link rel="stylesheet" href="style.css">
<script src="app.js" defer></script>
</head>
<body>
<main class="card">
<header class="card-header">
<h1>今日待办清单</h1>
<button id="theme-toggle" type="button" class="btn-secondary">切换深色</button>
</header>
<form id="task-form" class="task-form">
<label for="task-input" class="sr-only">输入新任务</label>
<input id="task-input" type="text" placeholder="输入一项任务,例如:写完首页 HTML" autocomplete="off">
<button type="submit" class="btn-primary">添加</button>
</form>
<p id="task-count" class="task-count">剩余 1 项未完成</p>
<ul id="task-list" class="task-list">
<li class="task-item">
<span class="task-text">认识 HTML、CSS 与原生 JS 的分工</span>
<button type="button" class="btn-delete">删除</button>
</li>
</ul>
</main>
</body>
</html>
这里有三处细节是为后续 CSS 和 JS 提前留好的接口:
id与class属性:id(如task-form、task-input、task-list)用于在 JS 中唯一定位某个元素;class(如task-item、btn-delete)既用来统一设置样式,也方便 JS 识别同一类可重复元素。- 按钮的
type属性:表单外的按钮或列表里的“删除”按钮显式写上type="button",只有提交表单的“添加”按钮用type="submit"。因为<button>默认行为就是提交表单,不标明类型容易在点击时意外触发页面提交。 <script src="app.js" defer>:defer告诉浏览器一边继续解析后面的 HTML,一边下载脚本,等整个文档解析完成后再执行app.js,避免脚本运行时找不到页面元素。
此时用浏览器打开 index.html,应当能看到左上角对齐的标题、输入框、按钮和一条默认列表项。画面还很朴素,点击“添加”会发现地址栏末尾多了一个问号并刷新页面——这正是浏览器默认表单提交行为,后面会用 JS 接管它。
三、第二步:用 CSS 安排版面与状态样式
CSS 的作用不只是让页面居中、加圆角,更重要的是提前定义好状态类。当某个任务被标记为完成,或者页面切到深色模式时,JavaScript 不需要逐行修改几十个颜色属性,只需要给元素加上或去掉一个类名(例如 is-done 或 is-dark),样式就会自动生效。
把下面的代码写入 my-first-site/style.css 并保存:
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 40px 16px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background-color: #f5f5f2;
color: #2c2f2d;
transition: background-color 0.2s, color 0.2s;
}
body.is-dark {
background-color: #1f2220;
color: #e8e8e4;
}
.card {
max-width: 520px;
margin: 0 auto;
padding: 24px;
background-color: #ffffff;
border: 1px solid #dcdcd5;
border-radius: 10px;
}
body.is-dark .card {
background-color: #2a2e2b;
border-color: #404541;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
}
.card-header h1 {
margin: 0;
font-size: 20px;
}
.task-form {
display: flex;
gap: 8px;
}
.task-form input {
flex: 1;
padding: 10px 12px;
font-size: 14px;
border: 1px solid #c9c9c0;
border-radius: 6px;
background-color: #ffffff;
color: inherit;
}
body.is-dark .task-form input {
background-color: #1f2220;
border-color: #4b504c;
}
button {
padding: 9px 14px;
font-size: 14px;
border-radius: 6px;
border: 1px solid transparent;
cursor: pointer;
}
.btn-primary {
background-color: #464b48;
color: #ffffff;
}
.btn-secondary {
background-color: transparent;
border-color: #c9c9c0;
color: inherit;
}
.task-count {
margin: 14px 0 10px;
font-size: 13px;
color: #6a706c;
}
body.is-dark .task-count {
color: #a9afa9;
}
.task-list {
list-style: none;
margin: 0;
padding: 0;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-top: 1px solid #ecece6;
}
body.is-dark .task-item {
border-top-color: #3a3f3b;
}
.task-text {
cursor: pointer;
flex: 1;
margin-right: 12px;
}
.task-item.is-done .task-text {
text-decoration: line-through;
color: #8c918d;
}
.btn-delete {
padding: 4px 10px;
font-size: 12px;
background-color: transparent;
border-color: #d6d6ce;
color: inherit;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
保存后刷新浏览器,应当能看到一个居中对齐的白色圆角卡片,输入框占满剩余宽度,“添加”按钮靠右,列表项呈水平两端对齐。其中 body.is-dark 和 .task-item.is-done 这两条规则现在还没有被触发,接下来就用原生 JavaScript 去控制它们。
四、第三步:用原生 JS 获取元素并实现交互
几乎所有原生 JS 页面交互都遵循同一条主线:选中元素(querySelector) → 监听事件(addEventListener) → 更新 DOM 或类名。下面以最核心的“提交表单添加任务”为例,先看完整的四步执行流程。
在 my-first-site/app.js 中写入以下完整代码。除了添加任务,它还实现了点击文字切换完成状态、点击“删除”移除条目,以及点击右上角按钮切换深浅色主题:
// 1. 获取页面上需要操作的 DOM 元素
const taskForm = document.querySelector('#task-form');
const taskInput = document.querySelector('#task-input');
const taskList = document.querySelector('#task-list');
const taskCount = document.querySelector('#task-count');
const themeToggle = document.querySelector('#theme-toggle');
// 2. 统计并更新“剩余未完成”数量
function updateCount() {
const remaining = taskList.querySelectorAll('.task-item:not(.is-done)').length;
taskCount.textContent = `剩余 ${remaining} 项未完成`;
}
// 3. 构造一条新的任务列表节点
function createTaskItem(text) {
const li = document.createElement('li');
li.className = 'task-item';
const span = document.createElement('span');
span.className = 'task-text';
span.textContent = text;
const deleteBtn = document.createElement('button');
deleteBtn.type = 'button';
deleteBtn.className = 'btn-delete';
deleteBtn.textContent = '删除';
li.append(span, deleteBtn);
return li;
}
// 4. 监听表单提交:添加任务(点击“添加”按钮或按回车都会触发)
taskForm.addEventListener('submit', (event) => {
event.preventDefault();
const text = taskInput.value.trim();
if (text === '') {
taskInput.focus();
return;
}
const newItem = createTaskItem(text);
taskList.append(newItem);
taskInput.value = '';
taskInput.focus();
updateCount();
});
// 5. 事件委托:在父容器 ul 上统一处理“删除”和“标记完成”
taskList.addEventListener('click', (event) => {
const deleteBtn = event.target.closest('.btn-delete');
if (deleteBtn) {
const item = deleteBtn.closest('.task-item');
item.remove();
updateCount();
return;
}
const taskText = event.target.closest('.task-text');
if (taskText) {
const item = taskText.closest('.task-item');
item.classList.toggle('is-done');
updateCount();
}
});
// 6. 切换深浅色主题
themeToggle.addEventListener('click', () => {
const isDark = document.body.classList.toggle('is-dark');
themeToggle.textContent = isDark ? '切换浅色' : '切换深色';
});
这段代码中有两个新手很容易受益的原生 JS 写法:
- 用
textContent而不是innerHTML写入用户输入:如果用字符串拼接配合innerHTML插入用户输入的文本,当输入内容里出现<或>等符号时,浏览器会尝试把它当作 HTML 标签解析;改用document.createElement加textContent,无论输入什么字符都只会原样显示为安全文本。 - 用事件委托监听列表点击:任务项是用户动态添加的,如果只给页面初始那一条绑定点击事件,新添加的任务点击就会没反应。把监听函数挂在始终存在的父元素
#task-list上,利用事件冒泡和event.target.closest(...)判断点击的究竟是“.btn-delete”还是“.task-text”,这样新增的条目不需要重复绑定事件就能直接工作。
验证页面是否正常工作
保存 app.js 后回到浏览器刷新页面,依次做下面四项操作:
- 在输入框中输入“练习事件委托”并按回车或点击“添加”,页面应新增一条任务,输入框自动清空,计数变为“剩余 2 项未完成”。
- 点击任意一条任务的文字部分,文字应当出现删除线并变浅;计数减少 1。再次点击则恢复未完成状态。
- 点击某条任务右侧的“删除”按钮,该行应从页面移除,计数同步更新。
- 点击右上角“切换深色”,整页背景应变为深灰、按钮文字变为“切换浅色”;同时开发者工具 Console 面板中没有任何红色报错。
五、容易混淆的说法辨析
刚开始接触用 JavaScript 做网站时,网上的教程和讨论里常出现几种看似合理、但条件没说清就容易把新手带偏的说法。
说法一:“做网站用 JavaScript,必须先装 Node.js 并学 React 或 Vue”
这个说法在团队开发大型单页应用时成立:当网站有几十个页面、复杂的数据流和多人协作需求时,框架负责组件复用和状态同步,Node.js 负责打包和工程化工具链。但它并不意味着“没有框架和 Node.js 就不能用 JS 做网站”。
浏览器本身就内置了完整的 HTML 解析器、CSS 引擎和 JavaScript 引擎。像上面的待办清单、产品展示页、个人简历站或表单计算器,只要双击本地 index.html 就能运行,部署时也只需把这几个静态文件上传到服务器即可。先掌握原生 DOM 查询、事件机制和 classList,再去学任何框架都会明白它们底层在替你封装什么。
说法二:“把 <script> 放在 <head> 里最规范,或者直接把 JS 写在 onclick 里更省事”
把资源统一放在 <head> 里看起来整齐,在 HTML 标签上直接写 <button onclick="..."> 在三行代码的演示里也显得很短。但如果不加条件照做,就会踩到两个典型问题。
第一,如果在 <head> 里写普通 <script src="app.js"></script> 且不加 defer,浏览器解析到这一行时会暂停解析后面的 <body>,立刻下载并执行 app.js。此时 #task-form 根本还没有被创建出来,document.querySelector('#task-form') 的结果就是 null,紧接着调用 addEventListener 就会报错崩溃。
<!-- 错误写法:位于 head 且没有 defer,执行时 body 尚未解析 -->
<head>
<script src="app.js"></script>
</head>
<!-- 正确写法一:放在 head 中并加上 defer(推荐) -->
<head>
<script src="app.js" defer></script>
</head>
<!-- 正确写法二:放在 </body> 结束标签之前 -->
<script src="app.js"></script>
</body>
第二,虽然 onclick="addTask()" 在极小的演示里能跑通,但它把 JS 函数名硬编码在 HTML 里,随着功能增加,你不得不在多个文件之间来回找某个按钮调用了哪个全局函数,也难以配合事件委托一起使用。坚持用 addEventListener 把交互逻辑留在 app.js,后期改起来清晰得多。
六、新手排查问题的三个检查点
按上面步骤编写代码时,如果页面没有出现预期效果,可以对照 Console 报错按下面顺序检查:
- 点击“添加”后页面闪一下,刚输入的字没了:检查
taskForm.addEventListener('submit', ...)里的第一行是否漏写了event.preventDefault(),或者事件是不是误绑在了按钮的click上而非表单的submit上。 - 控制台提示
Cannot read properties of null:说明document.querySelector(...)没找到对应元素。先核对选择器前面有没有漏掉#(id)或.(class),再核对index.html里的拼写与<script>是否带了defer。 - 样式或 JS 完全没生效:检查三个文件是否真的放在同一个文件夹
my-first-site下,以及index.html中href="style.css"和src="app.js"的文件名大小写是否完全匹配。
七、总结与下一步扩展方向
到这里,你已经完成了用原生前端技术做一个可交互网站的完整闭环:HTML 负责定义表单与列表结构,CSS 负责页面排版和 .is-done、.is-dark 等视觉状态,原生 JavaScript 负责接管事件并动态增删 DOM 节点。由于整个项目只包含三个纯静态文件,只要把文件夹上传到任意静态托管平台(如 GitHub Pages、Cloudflare Pages 或自己的 Web 服务器静态目录),别人就能通过网址访问你的页面。
在这个页面稳定运行之后,如果希望继续完善它,可以沿两个最自然的方向扩展:第一是用浏览器自带的 localStorage 把任务数组保存下来,这样刷新或重新打开页面后任务不会丢失;第二是用原生 fetch() 请求后端接口,把本地列表升级成能联网保存数据的完整 Web 应用。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。