ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

网页制作实战:原生三剑客从页面到部署完整路径

网页制作实战:原生三剑客从页面到部署完整路径 前两天帮一位硬件工程师朋友把ESP32设备的状态显示页做成一个内嵌网页又顺手帮他加了网页转PDF打印的功能。他忙完说了句“原来web网页制作做到这个程度就够用了。”这句话让我挺有感触。很多刚入门的朋友一提web网页制作第一反应就是学Vue、学React、上工程化工具链但实际业务里大部分需求其实是要把页面本身做对、做稳、做得好维护。这篇文章我想站在自己多年接项目、改项目、部署项目的真实经验上完整梳理一条从零开始做网页的路径需求定位、技术栈选择、页面骨架、交互联调、打印PDF、内嵌到ESP32这类设备以及上线前的安全检查和部署。适合两类人一是刚学完HTML和CSS基础、还不知道整条链路怎么串起来的新手二是会用框架但总觉得某些环节没做透、想回归基本功的前端开发。1. 开工前两件事需求定位和技术栈选择比写代码更重要很多人拿起编辑器就开始写div结果写到一半发现要么功能超出预期要么技术选型把自己绕进去了。做web网页制作第一步不是打字而是把“你到底要做什么”说清楚。1.1 先分清楚你要做的网页是“页面”还是“应用”我通常会把网页分成两类一类是展示型页面比如企业官网、产品介绍页、活动报名页、设备状态页。这类页面核心是信息呈现可能只需要定期刷新数据不需要用户登录也没有复杂的操作流程。另一类是工具型应用比如后台管理面板、数据看板、在线编辑工具。这类页面核心是数据流转需要登录、存储、增删改查前端只是一层皮真正的工作发生在服务端。这个区分直接决定你后面要不要引入后端框架。举个实际例子我那位硬件朋友一开始想给ESP32做一个“能配置Wi-Fi的设备配网页”本质上就是一个表单页可以把Wi-Fi账号密码提交给设备保存。这种情况下后端逻辑就落在设备固件里网页本身只需要一个干净的HTML页面加几行JavaScript够用就好。但如果他想要一个能记录多台设备历史数据的平台那就必须上数据库、接口、鉴权网页制作的工作量就完全不一样了。所以动手之前我强烈建议你先拿一张纸画出用户流程打开网页后用户能看到什么、能点什么、数据从哪里来、到哪里去。这个流程可能只有三五步但画完之后你会很清楚地知道哪些技术是必需的哪些只是锦上添花。1.2 技术栈到底怎么选原生三剑客还是框架我接触过不少刚入行的人一上来就问“现在是不是不用Vue就找不到工作”。但放在web网页制作这个场景里答案很实在小项目、内嵌项目、快速交付的项目原生HTMLCSSJavaScript往往比框架更合适。下面这个表格是我自己常用的选型参考项目类型推荐方案理由简单展示页 / 内嵌设备页原生 HTMLCSSJS无需构建工具单文件可运行方便烧录到设备中等复杂度单页应用原生模块化 轻量框架如Petite-Vue、Alpine.js保留响应式开发体验又不像大型框架那么重大型多页项目 / 团队协作Vue/React 构建工具组件复用、状态管理、类型检查是刚需内容型站点 / 需要SEO服务端渲染PHP/Node模板或静态站点生成器首屏内容和搜索引擎友好我并不是否认框架我自己很多项目也用Vue。但你要清楚框架解决的是“复杂状态管理”和“组件复用”的问题而不是“让网页跑起来”的问题。原生写法反而能让你更清楚地理解浏览器到底怎么工作。尤其是做内嵌到ESP32这类设备里的网页资源极其有限一个几百字节的CSS文件可能比引入整个Vue运行时靠谱得多。1.3 一个实用的极简启动模板这里分享一个我每次做快速原型都会用的启动模板它对中文、移动端都做了基本处理也是避免很多人一开始就乱掉的锚点!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title网页标题/title style /* 基础重置避免不同浏览器默认样式差异 */ * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif; line-height: 1.6; color: #222; } main { max-width: 960px; margin: 0 auto; padding: 1rem; } /style /head body header/header main/main footer/footer script // 这里写js后续可拆成独立模块 /script /body /html里面几个很容易被忽略但很关键的点第一langzh-CN很多新手不写结果屏幕阅读器朗读时会把中文念成英文音。第二charsetUTF-8不写的话中文可能直接乱码。第三viewport不写的话手机上看页面会像被缩小成一张缩略图。这三个meta标签是我每次检查别人页面时首先要看的。2. 页面骨架和样式把HTMLCSS做成别人愿意看的样子页面结构是整个网页的骨架。很多新手喜欢一把梭全用div看起来很灵活但后期维护和SEO都会很痛苦。2.1 语义化标签不是装样子它直接影响SEO和可访问性HTML5给了一堆语义化标签header、nav、main、article、section、footer。它们起到的作用不是“换一个类名”而是让浏览器、搜索引擎、屏幕阅读器能理解页面哪块是主要内容、哪块是导航、哪块是页脚。举个我实际改过的例子。有个朋友做产品展示页整页全是div classbox1div classbox2。搜索引擎抓取后很难判断正文在哪里页面排名一直上不去。我帮他改成语义化结构后同样的关键词权重明显有变化。更重要的是他自己后来改样式时也轻松多了——因为article天然表示一篇文章的主体CSS里直接写article img {}就行不用再去理那个div套div的噩梦。语义化还有一个更实际的好处可访问性。视觉正常的用户看不出区别但用屏幕阅读器的用户可以像听一篇文章一样按标题跳转。这个用div是做不到的。所以我的建议是写页面时优先使用语义化标签只有在语义上确实没有对应标签时才用div做无语义容器。2.2 布局方案的取舍Flexbox和Grid怎么选CSS布局现在是幸福时代Flexbox和Grid几乎能覆盖所有常见布局不需要再去记各种hack。我的经验是一维排列用Flex二维栅格用Grid。比如导航栏就是一组项目水平排列用display: flex最自然nav { display: flex; align-items: center; justify-content: space-between; }但如果是产品卡片列表需要按行和列排成网格同时希望每个卡片宽度自动填充就用grid.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 1.5rem; }这里我踩过不少坑先说两个最常见的第一个flex的align-items默认值是stretch不是flex-start。如果你在容器里放了一个高度不确定的图标图标会被拉伸得奇形怪状。解决方法是显式加align-items: center或flex-start。第二个grid里的1fr并不等于“自动剩余空间均匀分配”当内容超出时它可能撑破网格。我通常会用minmax(240px, 1fr)来给最小值兜底这样卡片至少240px宽窄屏时自动换行不会出现20px宽的卡片。响应式方面我尽量不使用固定像素去控制布局。可以用max-width加百分比也可以用很新的clamp()函数做动态字号h1 { font-size: clamp(1.8rem, 4vw, 2.8rem); }这行代码的意思是字号在1.8rem到2.8rem之间随着视口宽度按4%比例动态变化。这样在手机上不用额外写媒体查询标题也能保持一个合理的比例。2.3 字体、图片、色彩那些影响用户停留的细节网页做得“粗糙”和“精致”往往只差几个细节。字体方面别急着引入一堆在线字体。中文字体文件很大一个字体动辄几MB会严重影响加载速度。我一般直接用系统字体栈就是前面模板里写的system-ui那一串。这样在绝大多数设备上都能获得符合用户习惯的字体展示加载速度也快。如果要在标题上做品牌感我才会考虑额外引入一两个字重的webfont并且用font-display: swap防止文字不可见。图片是更常见的性能杀手。我给朋友查过一个页面首屏放了一张8MB的实拍图在手机上要转好几秒。后来我用工具把图压缩到150KB视觉几乎无差别。除了压缩还应该给img标签加srcset和sizes让浏览器按屏幕宽度选择合适尺寸的图片。如果只是装饰背景完全可以考虑纯CSS的背景渐变加载成本为零。还有一种很多人忽略的细节懒加载。对于首屏以下的图片我会统一加上loadinglazy属性。这是个浏览器原生属性一行代码就能让页面滚动到那附近时才加载对应图片对长页面体验提升非常明显。色彩方面建议克制一个页面主色调控制在两到三种。别忘了检查文本和背景的对比度尤其是浅色文字配白色背景在户外亮度下根本看不清。这个不用凭感觉Lighthouse会帮你算。3. 让页面真正“活”起来交互和后端联调的原生玩法静态页面只能看动态交互才能叫“web网页制作”。很多初学者一说到JavaScript就头疼其实现代原生JavaScript已经非常好写了完全不需要jQuery。3.1 现代原生JavaScript也能写得很舒服我的原生JS工具箱大概就这几样querySelector选元素、addEventListener绑定事件、async/await处理异步、模板字符串拼HTML。这四样组合起来已经能覆盖80%的交互。举一个实际场景一个设备列表点击按钮刷新列表。最直接的做法是先写一个渲染函数const listEl document.querySelector(#device-list); async function loadDevices() { const response await fetch(/api/devices); if (!response.ok) { throw new Error(请求失败: ${response.status}); } const devices await response.json(); listEl.innerHTML devices.map(d li classdevice-card strong${d.name}/strong span状态: ${d.status 1 ? 在线 : 离线}/span /li ).join(); } document.querySelector(#refresh-btn).addEventListener(click, loadDevices);这个代码看起来简单但有个很大的坑如果${d.name}是用户输入的内容直接拼进innerHTML会触发XSS攻击。安全做法是把用户内容塞进文本节点或者用textContent。我一般会写一个escapeHTML函数先转义再做模板拼接function escapeHTML(str) { return String(str).replace(/[]/g, s ({ : amp;, : lt;, : gt;, : quot;, : #39; }[s])); }模板一变安全风险就降下来一大截。这个习惯越早养成越好。事件委托也是一个能简化代码的技巧。如果列表里有很多个按钮你不需要给每个按钮绑监听器只需要给它们的父容器绑一次然后判断点击目标listEl.addEventListener(click, (event) { const btn event.target.closest(.delete-btn); if (!btn) return; const id btn.dataset.id; // 执行删除操作 });closest会沿着父级找匹配元素所以新增的动态节点也能生效不需要重新绑事件。3.2 fetch与后端接口交互从GET到POST现代网页和后端通信基本都走fetch。GET请求很简单POST请求需要多写几个参数async function saveConfig(data) { const response await fetch(/api/config, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(保存失败: ${response.status}); } return response.json(); }常见的坑集中在两个地方第一跨域问题CORS。当你的网页在http://localhost:8080但接口在http://localhost:3000浏览器会因为同源策略直接把请求拦住控制台会报“Access-Control-Allow-Origin”错误。解决方法是后端在响应头里放行你的域名比如加一个Access-Control-Allow-Origin: *开发可用生产环境要精确到域名。前端什么都做不了这是浏览器安全策略不是bug。第二错误处理。fetch在HTTP状态为非2xx时不会自动抛异常你必须自己检查response.ok。很多人忘了这个结果接口返回500时页面还跟没事人一样用户看到的是永远不填充的数据。3.3 表单校验和“一键获取手机号”这类需求的正规做法网页里另一大类交互是表单。HTML5自带的校验已经够用没必要一上来就引校验库。比如input typeemail required input typetel pattern[0-9]{11} title请输入11位手机号浏览器会在提交时自动拦截非法输入不需要JavaScript参与。但如果要做更友好的实时反馈比如输入框失焦时提示“手机号格式错误”就需要监听blur事件用input.checkValidity()来判断。关于最近很常见的“web一键获取手机号”需求我要特地多说一句。正规做法是网页在用户主动授权的前提下通过微信开放能力或运营商的认证接口拿到脱敏后的手机号。这个过程中必须明确提示用户“网页将获取你的手机号”并且给用户拒绝的选项。绝不能靠读取浏览器缓存、嗅探数据来做。在开发时如果遇到这种需求第一件事是把合规边界划清楚再谈技术实现。4. 两个高频需求实战网页打印成PDF和把网页内嵌到设备端网页制作做到一定阶段总会遇到两个看起来有点偏、实际上很常见的需求一个是把网页内容生成PDF一个是把网页塞进智能硬件里。这两个需求我都实打实做过踩过不少坑展开说说。4.1 网页PDF打印为什么直接调window.print()不够很多人做“网页导出PDF”的第一反应是直接调window.print()然后用户在打印对话框里选择“另存为PDF”。这个方案表面能跑实际效果惨不忍睹页面上那些按钮、导航栏、图表动画全被打印进去页边距页眉页脚也不受控制明明只想打印一页内容结果打出了五页空白。正确做法是专门写一套打印样式。在CSS里用media print和page规则来接管打印场景media print { .no-print { display: none !important; } main { width: 100%; margin: 0; padding: 0; } body { font-size: 12pt; } a[href]::after { content: attr(href); } } page { size: A4; margin: 20mm; }这个样式的作用是打印时隐藏掉带no-print类的按钮和导航正文铺满页面链接把它背后的URL显示出来方便纸质版阅读。我在做设备说明页时还会强制分页.section { page-break-inside: avoid; }这能避免某个章节被劈成两半一页上只留下半截内容。把样式加好后仍然用window.print()弹系统打印对话框选择“另存为PDF”就能得到一份很干净的文件。如果你需要一键生成PDF文件也可以用一些JS库但我的经验是除非是复杂的图文混排报告否则浏览器原生打印永远是质量最高、兼容性最好的方式而且不依赖任何第三方服务。4.2 把网页内嵌到ESP32等嵌入式设备的思路我那个ESP32项目的核心需求很典型设备端开一个小服务用户用手机浏览器访问设备的局域网IP就能看到设备状态并且能提交配置。说白了就是一个运行在单片机上的web网页制作场景。在ESP32上做内嵌网页跟普通PC网页有很大区别。最大的限制是资源设备内存可能只有几百KB你不可能给它塞一个几MB的框架。所以我的做法是把HTML、CSS、JS全部内联到一个字符串里由设备固件直接返回。同时所有依赖都本地化绝对不引用CDN——因为设备所在的局域网经常是断外网的哪怕只有一个小小的字体文件引用了外部地址页面都可能因为无法解析而白屏。一个简化后的ESP32网页服务端代码长这样功能是返回网页并暴露一个API// 使用ESP32异步WebServer示意代码 server.on(/, HTTP_GET, [](AsyncWebServerRequest *request){ request-send(200, text/html, PAGE_HTML); }); server.on(/api/status, HTTP_GET, [](AsyncWebServerRequest *request){ String json {\cpu_temp\:48.5,\rssi\:-45}; request-send(200, application/json, json); }); server.begin();网页这边用原生JavaScript隔三秒请求一次/api/status更新页面上的温度值和信号强度。整个过程没有后端模板没有框架甚至没有构建步骤但运行得极其稳定。做这类内嵌网页时我总结了三件事第一网页文件越小越好能用CSS实现的装饰就不要用图片第二代码要写得保守兼容设备端极简TCP栈——一些在Chrome上跑得飞起的API在旧内核的浏览器上可能不存在第三调试时一定要多看控制台ESP32网页最常见的毛病是资源路径写绝对了本来应该是/style.css结果写成了http://your-domain/style.css局域网里直接超时。5. 上线前不能跳过的安全检查别等被人扫到再后悔网页做完了本地打开十分完美不代表就可以直接上线。我见过太多Web项目是因为“小”而栽在安全这关。5.1 最常见的三个Web安全坑先说第一个坑XSS跨站脚本攻击。前面提到过把用户输入直接拼进HTML是最常见的漏洞。比如一个留言板别人提交一段scriptalert(1)/script如果你的页面原样渲染就相当于是帮别人在你的站点上执行了任意代码。现在的浏览器和框架会拦掉一部分但如果你用原生JS拼接DOM这个坑全靠自己防。第二个坑是CSRF跨站请求伪造。用户在你网站上登录后另一个恶意网站偷偷发一个表单请求到你的接口浏览器会带上你的Cookie后端如果只靠Cookie判断身份就会被骗。网页制作人在前端能做的是在关键表单里加一个一次性token后端校验token是否合法。这才是真正有效的缓解手段。第三个坑是信息泄露。很多小项目习惯把后台地址放在网页源码注释里或者用默认口令比如admin/admin。我在给一些内嵌设备做安全测试时十个七八个都把配置页挂在/config.html谁都能直接访问。网页上线前至少要把这些显而易见的入口藏起来或者加上访问认证。5.2 基本安全响应头和HTTPS现代浏览器支持一些HTTP响应头能在浏览器层面帮你拦掉不少攻击。我常用的有三个# Nginx配置示例 add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Content-Security-Policy default-src self; script-src self; style-src self unsafe-inline;解读一下X-Content-Type-Options防止浏览器猜测文件类型比如把一个HTML文件当图片解析X-Frame-Options防止自己的网页被嵌入到别人的iframe里做点击劫持Content-Security-Policy是更细粒度的安全策略告诉浏览器只允许加载哪些来源的脚本和样式。应用这些响应头后很多攻击面直接被削掉了。另外现在做web网页制作HTTPS已经不是可选项而是默认项。好处不只是加密传输还关乎权限浏览器很多新特性比如摄像头、地理定位只允许在HTTPS页面上使用。对于公网站点用Lets Encrypt发免费证书一次配置后自动续期成本几乎为零。5.3 免费体检工具与自查清单我每次上线前都会跑一遍Lighthouse它藏在Chrome开发者工具的“Lighthouse”面板里。它会给性能、可访问性、安全、SEO分别打分并且直接告诉你哪里该改。此外我还习惯用在线工具检查响应头有没有加全。送上一份自查清单按顺序过一遍基本能避开九成常规问题检查项标准后台页面不允许匿名访问默认口令全部修改过敏感文件目录列表关闭备份文件不放在站点目录用户输入输出时做HTML转义Cookie加上HttpOnly和Secure属性错误页面不暴露堆栈和版本信息响应头X-Frame-Options、CSP等已配置这套清单看起来琐碎但每一条我都见过对应的真实事故比如某台设备因为留了初始密码被人登录进去改了配置还把自己锁在门外。安全不是上线那一天才做的事而是从写第一行HTML开始就要有的习惯。6. 部署之路从本地文件到一台真服务器网页制作的最后一步是上线。很多人做网页只在本地双击打开一旦要给真人访问就会碰到一堆环境问题。6.1 静态网页最简单的部署方式如果你的网页是纯静态页面没有后端接口最省事的方式不是买一台云服务器而是用云服务商提供的对象存储静态网站托管。你只管把做好的页面扔上去它自动帮你处理CDN、HTTPS、回源这些事成本很低对访问量不大的站点完全够用。如果是要跑PHP或Node后端那就需要一台Linux服务器。我常用的部署方案是Nginx一个干净利落的静态站点配置如下server { listen 80; server_name example.com; root /var/www/myproject; index index.html; location / { try_files $uri $uri/ 404; } }把文件上传到服务器后改一下Nginx配置nginx -t确认无误再systemctl reload nginx就能生效。很多人第一次部署容易犯的错是把文件上传到/root/目录下Nginx因为权限问题读不进去页面一直显示403把目录改到/var/www/并设对属主属组就解决了。6.2 域名和HTTPS证书域名就是用户访问你的门牌号。买好域名后要做DNS解析把A记录指向服务器IP如果用了云托管服务通常用CNAME指向服务商分配的域名。要注意A记录和CNAME不能同时存在如果解析不生效先看看是不是这两者冲突了。HTTPS证书我用的是Certbot一条命令就能签发并自动配置Nginxsudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d example.com -d www.example.com它会在证书快过期时自动续期基本不需要你再操心。前提是你必须在服务器防火墙放行80和443端口并且域名已经解析到这个IP否则Certbot验证会一直失败。6.3 部署后的基础监控和日志上线只是开始维护才是常态。最基础的一件事是看日志。Nginx日志默认在/var/log/nginx/access.log和error.log当用户反馈打不开时先看这两个文件tail -f /var/log/nginx/error.log如果出现大量500错误大概率是后端服务挂了如果是404可能是路由没配对。我因为日志切割吃过一次亏忘记配置logrotate日志文件涨到好几个GB把磁盘塞满后整站白屏。后来我统一加上日志切割策略每天一个日志文件保留30天再也没出过事。监控方面我的做法是用外部的监控服务每5分钟访问一次页面失败就发通知。这个相当于给网页请了一个24小时值班的哨兵比你自己发现用户跑过来投诉时再排查要舒服得多。我在实际项目中最大的体会是web网页制作的核心不是堆技术名词而是能在每一环做出明确决定——这个页面要不要框架、这个交互怎么处理、这个场景怎么打印、这个设备怎么内嵌、上线前怎么加固。把这些主干问题想清楚框架和工具只是顺手的事。最后分享一个小技巧每次做完网页我都会用浏览器自带的设备工具栏把所有常见尺寸点一遍再调出打印预览看一遍排版。这个习惯帮我发现了无数个丑陋的断页和错位的按钮也避免了把这些问题带到用户面前。
返回列表