资讯动态

HTML示例源码拆解:表单校验与Flex/Grid、Canvas/SVG

发布时间:2026/10/26 17:47:32 来源:尧图企业网站定制
简介面向HTML初学者与网页开发入门者的实践型示例包围绕“边看边改、边跑边学”的方式组织内容帮助读者把标签语法落到可运行的页面中。压缩包共14个文件包含12个HTML页面及2个图片素材整体约52KBHTML文件可直接在浏览器打开图片用于配合图文、链接与布局示例演示体量轻便便于随取随用。素材覆盖文档基本结构、标题与段落、超链接、图像插入、列表、表格、表单控件以及引用块、框架布局等常见知识点并包含同一元素多种写法的对照练习适合课堂演示、课后练习与自学临摹。当前已有156人学习读者可通过逐页查看源码与渲染效果理解标签嵌套、属性设置和页面组织方式再动手修改文本、图片与表单结构逐步形成可复用的HTML页面编写思路。1. 从一份能独立打开的示例说起这套 HTML 源码解决什么问题拿到的是一份 html 系列编程示例的源码解压之后没有构建配置、没有依赖清单双击就能开页面。它最有用的地方在于把 HTML 从标签、列表、表格、表单一路到 Flex、Grid、多媒体、Canvas、SVG 的常见写法各自落成一个可以直接打开的独立页面再配一层公共的 reset.css 和 base.css 兜底样式。适合两类人一类是把 HTML 当工具在学、想找能照抄能改的整页示例的新手另一类是前端熟手需要一个干净的最小复现环境用来验证某个标签或某个属性的实际表现。后面按「跑起来 → 拆核心页 → 排坑 → 改造」四步走尽量把每一处能改的参数都说到位。2. 目录结构与运行方式把示例集跑起来的三条路径示例集的组织逻辑决定了它好不好改。一份设计得凑合的示例集往往把所有页面平铺在一个目录下文件名从 test1.html 排到 test37.html看名字完全猜不出内容而一个能长期用的示例集会先按知识点分层再用统一的资源目录收口公共样式让每个页面只关心自己那部分结构。这份源码属于后者先把结构看明白后面改起来才不至于找不到北。2.1 按知识点分组的目录长什么样目录大致是这么排的具体文件数可能因版本不同有出入但分层方式是这样html-demo/ ├── index.html # 示例总目录卡片式列出全部页面 ├── assets/ │ ├── css/ │ │ ├── reset.css # 清零默认样式 │ │ └── base.css # 排版、颜色变量、示例页公共壳 │ ├── js/ │ │ └── main.js # 目录页渲染、通用小工具 │ └── img/ # 示例用图统一放这里 ├── 01-basic/ # 文本、列表、链接、图片 ├── 02-layout/ # 浮动、Flex、Grid 三套布局对照 ├── 03-form/ # 表单结构、原生校验、文件上传 ├── 04-media/ # audio/video、picture、SVG ├── 05-canvas/ # 2D 绘制与逐帧动画 └── 06-modern/ # 语义化标签、响应式、dialog这套分层的好处加页面时能立刻感受到。要补一个 Grid 的变体直接丢进02-layout/文件名按05-grid-area.html续号目录页只要加一条链接不用碰其它任何文件。数字前缀是给文件管理器排序用的浏览器不认这个顺序别指望它在服务器目录列表里也按这个排。还有一个细节值得学每个分组目录里都不放.css文件。样式全部集中在assets/css/页面里靠link引进去。如果某个示例需要独占样式就写在页面head里的style中这样打开单个文件就能看到全部样式不用来回跳。做法有点土但对示例集来说是对的——它优先保证的是「一个文件能自解释」而不是工程化。2.2 三条运行路径双击、本地静态服务、编辑器预览绝大多数示例是纯静态的双击index.html就能看协议是file://。这条路最快但有边界任何涉及fetch、XMLHttpRequest读本地 JSON、ES Modulescript typemodule的示例在file://下都会被同源策略拦下来控制台报一大段 CORS 相关的话。如果目录页是用 JS 动态读取示例清单渲染的那就必须走 HTTP。第二条路Python 和 Node 环境各起一个静态服务就行# 在项目根目录执行Python 3 自带不用装任何东西 python3 -m http.server 8080 # 或者用 Node 生态的 serve支持目录浏览和缓存控制 npx serve . -l 8080参数说明8080是端口被占用就换8081、9090python3 -m http.server默认绑定0.0.0.0本机访问用http://localhost:8080npx serve的-l是--listen的缩写加了它会自动打开浏览器。两者都把当前目录当站点根所以访问01-basic/01-text.html的地址就是http://localhost:8080/01-basic/01-text.html路径层级和文件系统完全对得上。第三条路是编辑器内置的预览服务多数主流编辑器装个静态服务插件右键「Open with Live Server」就能起一个带热更新的服务。热更新对改 CSS 特别友好改完保存浏览器里的页面自己刷新不用手动按 F5。代价是它有时会在 URL 里注入一段脚本某些严格的 CSP 示例会因此报错遇到那种情况老老实实回到第二条路。提示判断一个示例能不能直接双击打开看它的head里有没有script typemodule有没有fetch链接里有没有以/开头的绝对路径。三者占一样就走 HTTP 服务省掉一轮玄学排查。2.3 公共资源层reset.css 与 main.js 怎么挂每个示例页的head里都挂着同样两行顺序不能反link relstylesheet href../../assets/css/reset.css link relstylesheet href../../assets/css/base.cssreset.css先执行把margin、padding、box-sizing、标题字号这些浏览器默认值抹平base.css后执行在里面定义:root上的颜色变量、正文字体栈、示例页统一的容器宽度和标题样式。顺序反了reset.css会把base.css里的排版规则再清一遍页面看着「没生效」实际是被覆盖了——这类问题最难查因为规则确实写对了。assets/js/main.js挂在/body前面或者用deferscript src../../assets/js/main.js defer/scriptdefer的作用是等 HTML 解析完再执行脚本里可以直接拿document.querySelector而不必包DOMContentLoaded。不加defer又放在head里脚本执行时 DOM 还没建完取到的元素是null控制台第一行红字就来了。这两种挂法在示例里混着用没关系但同一个页面里别两种都用容易让人不知道到底是哪个在跑。新增一个示例页的操作步骤按这个顺序走不会漏复制一个同组的现成页面 → 改title和页面内标题 → 改href里的相对层级多一层目录就多一个../→ 在index.html的卡片列表里补一条链接 → 打开验证。第三步是血泪经验../少写一层样式全丢页面裸奔但控制台只有两条 404很容易被当成「样式没写对」。3. 表单与表格示例拆解从 label 绑定到数据收集表单和表格是这套示例里最值得细看的两块因为它们是 HTML 里最容易被写错的标签看着简单细节全在可访问性和默认行为上。表单写错用户点标签点不中表格写错读屏软件念出来的顺序是乱的。这两块拆明白剩下的标签基本是平推。3.1 表单骨架label 的 for、name 的分组、fieldset 的语义03-form/里的基础表单页大致是这样搭的form idsignup novalidate fieldset legend账号信息/legend !-- for 和 id 必须对上点文字才能聚焦输入框 -- label foremail邮箱/label input idemail nameemail typeemail autocompleteemail placeholdernameexample.com required label forpwd密码/label input idpwd namepwd typepassword minlength8 autocompletenew-password required !-- 单选组name 相同才算一组只能选一个 -- p套餐/p labelinput typeradio nameplan valuebasic checked 基础/label labelinput typeradio nameplan valuepro 专业/label /fieldset button typesubmit提交/button button typereset重置/button /form逻辑说明几个关键点。label foremail里的for值必须和输入框的id完全一致这是原生提供的点击放大热区的机制没有 JS 参与性价比极高如果懒得写for也可以把input直接嵌在label里面两种写法效果一样但嵌套写法在复杂布局里容易被 flex 撑开变形我更倾向for写法。name是提交时的字段名不写name的控件在表单提交时会被直接忽略这是新手最常见的「数据丢了」原因之一。fieldset和legend不是装饰它给一组相关控件套了语义边界读屏软件会在进入这组控件时播报legend内容。视觉上它默认带一圈边框reset.css里应该把它清掉改成靠间距分组。novalidate加在form上是关掉浏览器自带的校验气泡交给 3.2 的 JS 校验接管。两个都开也行但体验会割裂原生气泡先弹一次JS 再弹一次。示例里用novalidate是为了让校验逻辑完整可见方便改写。autocomplete的值别乱填email、new-password、current-password、one-time-code这些都是有明确含义的填对了浏览器才会正确填充。填错不会报错只会静默不生效——这类就是典型的「不报错但不对」。3.2 原生校验与自定义校验的分工校验拆两层能交给浏览器的绝不自己写浏览器管不了的再补 JS。const form document.getElementById(signup); // 提交时统一校验checkValidity 会跑完所有原生约束 form.addEventListener(submit, (e) { e.preventDefault(); // 挡住默认提交否则页面会跳走 if (!form.checkValidity()) { // reportValidity 会聚焦到第一个不合法的控件并弹出提示 form.reportValidity(); return; } collect(form); // 校验通过才收集 }); // 两次密码一致性原生约束表达不了只能手写 function collect(formEl) { const data new FormData(formEl); const pwd data.get(pwd); const pwd2 data.get(pwd2); if (pwd ! pwd2) { formEl.querySelector(#pwd2).setCustomValidity(两次输入不一致); formEl.reportValidity(); return; } // 清掉上一次的脏错误否则后面永远过不了 formEl.querySelector(#pwd2).setCustomValidity(); console.log(Object.fromEntries(data)); // { email, pwd, plan, ... } }参数与逻辑说明checkValidity()只返回布尔值不弹提示reportValidity()返回布尔值的同时把第一个不合法控件的提示弹出来并聚焦。两个配合用用户才不会「点了提交没反应」。setCustomValidity(msg)是给单个控件挂自定义错误注意它一旦被设成非空字符串这个控件就永远处于不合法状态所以校验通过时必须用空字符串清掉——这条坑踩过的人不少现象是「第一次输错之后再怎么输都提交不了」翻车点就在这。FormData是收集数据的标准姿势data.get(plan)能拿到选中项的值只选一个的组自然只有一个值。Object.fromEntries(data)把FormData转成普通对象方便console.log看结构也方便后续JSON.stringify发给接口。注意文件上传用FormData拿到的值是File对象不能直接JSON.stringify会变成空对象{}这个在 5.4 节展开。3.3 表格示例thead/tbody/caption 与横向滚动容器02-layout/01-table.html里那份表格重点不在样式在于结构标签齐全div classtable-wrap table caption近三个月示例页面访问量/caption thead tr th scopecol页面/th th scopecol二月/th th scopecol三月/th /tr /thead tbody tr th scoperow表单示例/th td1,204/td td1,398/td /tr /tbody /table /divcaption放在table内部第一行它在语义上是这张表的标题不是普通段落读屏软件会先播报它。scopecol和scoperow告诉辅助技术这个表头管的是列还是行表格一宽这个属性就直接决定播报出来的内容能不能理解。结构上thead、tbody、tfoot该分就分浏览器会自动处理它们的排列顺序——哪怕tfoot写在tbody前面渲染时也会排到最下面。分组的另一个好处是tbody里塞多少行都能整体换背景色不用逐行写nth-child。表格宽度失控是另一个高频问题。td里的长文本会撑破容器尤其在窄屏上。外面套一层.table-wrap并给它.table-wrap { overflow-x: auto; } .table-wrap table { min-width: 640px; border-collapse: collapse; }overflow-x: auto让容器自己横向滚动页面整体不会被撑出水平滚动条min-width保证列不会被压得太窄配合border-collapse: collapse去掉单元格之间的双线。窄屏下用户左右滑表格这是可接受的降级比强行让表格换行成卡片要诚实。4. 布局与多媒体示例拆解三种布局写法的对照与取舍02-layout/下把同一个三栏结构用浮动、Flex、Grid 各写了一遍放在一起对照。这种「同一目标、多种实现」的写法在示例集里特别有价值因为单独看一种写法你分不清它是「最佳实践」还是「刚好能跑」。4.1 浮动、Flex、Grid 三份布局示例的差异三份代码的结构基本一致只是容器类名不同方便直接对比!-- 02-layout/02-float.html -- div classfloat-box div classside侧栏/div div classmain正文区/div div classclearfix/div /div.float-box .side { float: left; width: 200px; } .float-box .main { margin-left: 200px; } .clearfix { clear: both; }浮动实现的核心是「侧栏固定宽度 正文用margin-left让位 最后清浮动」。清浮动这步漏了父容器高度会塌成 0背景色和边框都看不见现象就是「布局好像没错但框没了」。现代写法用.float-box::after { content: ; display: block; clear: both; }更干净不用额外加空节点。Flex 版本换成.flex-box { display: flex; gap: 16px; } .flex-box .side { flex: 0 0 200px; } /* 不放大不缩小固定 200px */ .flex-box .main { flex: 1 1 auto; } /* 吃掉剩余空间 */flex: 0 0 200px三个值分别是grow、shrink、basis写全比写width: 200px稳因为它在 flex 上下文里不会被shrink压扁。gap比margin更省心不用处理最后一项的多余边距。Grid 版本最省事.grid-box { display: grid; grid-template-columns: 200px 1fr; gap: 16px; }三者怎么选整页大框架、需要行列同时对齐的用 Grid一维排列、内容宽度不一致的用 Flex浮动只在自己要复现老代码时看。这套示例把三者摆在一起的价值就在这——看完你会发现浮动的复杂度基本都是「善后成本」而 Flex/Grid 把这个成本收进了引擎。4.2 picture 与 source响应式图片的写法04-media/02-picture.html展示了两种按条件换图的写法用途不一样!-- 方案一按格式择优浏览器自动挑支持的最小格式 -- picture source srcset../../assets/img/hero.webp typeimage/webp source srcset../../assets/img/hero.avif typeimage/avif img src../../assets/img/hero.jpg alt示例配图 width800 height450 /picture !-- 方案二按视口宽度换不同裁切的图 -- picture source media(max-width: 640px) srcset../../assets/img/hero-sm.jpg img src../../assets/img/hero-lg.jpg alt示例配图 width800 height450 /picture参数说明type是格式声明浏览器不支持就跳过这个source去试下一个media是媒体查询命中的第一个生效顺序从上到下宽的写后面、窄的写前面反了会一直命中第一个。srcset加w描述符如hero.jpg 800w走的是分辨率协商需要配合sizes示例里为了简单用了不带描述符的写法够用。最后那个img是必需的兜底没有它整块picture什么都不显示——这是最容易翻车的地方很多人以为写了source就够了。width和height两个属性一定要写浏览器据此提前算好占位比例图片加载完不会把下面的内容顶下去这个位移在移动端体感特别明显。alt别留空除非这张图纯装饰。4.3 Canvas 与 SVG两个绘制示例的边界05-canvas/和04-media/03-svg.html是两个不同的绘制范式示例刻意分开写// 05-canvas/01-draw.html 内的脚本 const canvas document.getElementById(board); const ctx canvas.getContext(2d); // 按设备像素比放大画布避免高分屏发虚 const dpr window.devicePixelRatio || 1; canvas.width 600 * dpr; canvas.height 360 * dpr; canvas.style.width 600px; canvas.style.height 360px; ctx.scale(dpr, dpr); ctx.fillStyle #2b6cb0; ctx.fillRect(40, 40, 200, 120); // 实心矩形 ctx.strokeStyle #1a202c; ctx.lineWidth 2; ctx.beginPath(); ctx.moveTo(300, 60); ctx.lineTo(520, 200); ctx.stroke(); // 描边线段逻辑说明getContext(2d)拿到绘图上下文之后所有绘制都作用在它上面DOM 里只留一个canvas画出来的东西不可选中、不可用 CSS 单独改色。dpr那段是关键——设canvas.width和style.width是两套坐标系前者是位图的实际像素数后者是页面上的显示尺寸两个不按dpr对齐高分屏上线条就是模糊的这就是那个「说不清为什么糊」的常见现象。ctx.scale(dpr, dpr)之后后面所有坐标都按 CSS 像素写不用自己换算。SVG 则完全是另一套svg viewBox0 0 200 120 width200 height120 roleimg aria-label示例图形 circle cx60 cy60 r40 fill#2b6cb0/circle path dM110 60 L190 60 stroke#1a202c stroke-width2/path /svgviewBox定义了内部坐标系之后所有坐标都按这个系写外部width/height缩放时图形自动按比例缩放不会糊因为它是矢量。SVG 里每个图形都是真实 DOM 节点能挂 CSS、能绑事件、能被querySelector选中Canvas 里画完就只剩像素要交互得自己算坐标。取舍就是一句话图形数量少、要交互、要无限缩放用 SVG图形数量多成百上千个点、要逐帧动画、要像素级处理用 Canvas。示例里两个都留着就是为了让这个边界看得见——同一个圆两边的写法和后续操作完全不同。5. 常见问题排查五个让示例「打不开」的真实原因示例集跑不起来九成不是代码逻辑问题是环境和路径问题。下面五条按「现象 → 原因 → 解决」写基本覆盖了这套示例被问到的绝大多数情况。5.1 现象页面白屏样式全丢控制台几条 404原因相对路径层级写错。示例页在03-form/xxx.html引assets得写../../assets/写成../assets/就少了一层请求落到01-basic/assets/...上自然 404。这个问题在复制粘贴新页面时出现频率最高因为新页面的位置未必和模板页在同一层。解决先把出问题页面的路径念一遍从文件到根目录数几层../就写几个。更稳的做法是全部走以/开头的绝对路径前提是走 HTTP 服务站点根是项目根这样就彻底不受页面层级影响。改完记得硬刷新CtrlF5普通刷新的缓存可能还留着旧文件。5.2 现象本地双击打开fetch报 CORS模块脚本不执行原因file://协议下浏览器把每个本地文件当成独立的源fetch本地 JSON、加载 ES Module 全部被同源策略拦掉。报错信息里通常带origin null has been blocked看到null就基本能确定是这个原因。解决起静态服务python3 -m http.server 8080一行就够然后从http://localhost:8080/进。这一步没有替代方案也别去改浏览器启动参数关同源策略那是给自己挖坑。判断一个示例需不需要服务看2.2节说的那三条特征就行。5.3 现象中文全变乱码问号、方块或者一堆奇怪字符原因文件实际编码是 GBK 或 UTF-8 带 BOM而meta charset声明的和它不一致或者meta charsetUTF-8写在了title后面浏览器已经把前面那截按默认编码解析完了。解决meta charsetUTF-8必须是head里的第一个标签前面不要有任何内容注释也算。然后确认文件本身存成了 UTF-8 无 BOM多数编辑器右下角能看到并切换。这两个地方对齐乱码问题基本清零。还有一种是服务端返回的Content-Type里没带 charset本地静态服务一般不会有这问题部署后才会遇到。5.4 现象表单看着提交了但数据里字段少了或者文件变成{}原因控件没写name属性或者写了但被disabled了——这两种情况下FormData和原生提交都会跳过这个控件不报错、不提示。文件上传那块File对象不是可序列化的普通值JSON.stringify之后就是{}。解决逐个检查需要提交的控件是否都有namedisabled换成readonlyreadonly仍会提交disabled不会。文件上传要么直接用FormData原样发给接口不要设Content-Type让浏览器自己带 boundary要么单独FileReader读成 base64别指望JSON.stringify能带上文件。5.5 现象窄屏上页面能左右拖动或者100vh的元素被地址栏挡住原因某个子元素宽度超出视口撑出了水平滚动条最常见的是固定宽度的表格、min-width过大的容器、100vw加上滚动条宽度。移动端100vh则是因为地址栏收起展开时可视高度在变但vh不会跟着变。解决先用document.documentElement.scrollWidth window.innerWidth确认是不是真的溢出再给可疑容器加outline: 1px solid red逐个定位。表格类溢出用3.3节那个.table-wrap包一层。全屏高度改成height: 100dvh不支持的老浏览器用100vh兜底写成height: 100vh; height: 100dvh;两行后者能识别就覆盖前者。6. 二次开发改造让散落的示例页变成可检索的示例站示例页攒到三四十个之后index.html里手写的卡片列表会先撑不住——加一个页面要改两处删一个页面会留死链。我一般会把这份清单抽成一个数据文件让索引页自己渲染。// assets/js/examples.js export const examples [ { group: 基础, path: 01-basic/01-text.html, title: 文本与标题, tags: [标签] }, { group: 基础, path: 01-basic/02-list.html, title: 列表嵌套, tags: [标签] }, { group: 布局, path: 02-layout/03-flex.html, title: Flex 三栏, tags: [布局, flex] }, // 新增示例只加这一行 ];渲染端十来行就够按group分组path拼成a hreftags拼成可点的筛选按钮输入框做title.includes(kw)的实时过滤。注意path是相对站点根的所以索引页必须走 HTTP 服务打开file://下 ES Module 会直接不执行这个坑在 5.2 已经说过一次。改造后怎么验证没改坏打开索引页确认卡片数量和数组长度一致随便点三个不同分组的链接能正常跳转且样式正常在筛选框输入一个必定命中的关键词看列表是否只剩对应项最后把某个path故意改错一位确认页面上能看到明确的 404 而不是静默空白。这四步跑完清单机制就是可信的。顺带提一个能省很多事的技巧给index.html加一个「最近修改」区块用构建脚本读文件mtime排序生成的静态 HTML 直接内联进去不引入任何运行时依赖。这样每次打开索引页第一眼看到的就是刚改过的示例不用在几十个卡片里翻。我以前改这类示例集总是改完页面就关掉浏览器第二天再打开就记不清哪个文件对应哪个效果。从那以后我每次新增或修改示例都强制走一遍「改路径 → 刷新验证 → 补索引 → 再点一次链接」这条流程宁可慢三十秒也不留一个点进去是 404 的卡片。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑