☰
零依赖单文件静态页:5分钟搭可搜索的个人网页版入口
2026/11/4 7:49:52 网站建设 项目流程

1. 先把"入口页"这件事想明白

1.1 这个页面到底解决什么问题

很多人一听"网页版入口",第一反应是"做个官网"。这两个东西差别很大。官网是对外的门面,讲究品牌、讲究 SEO、讲究转化路径;而入口页是给自己或小圈子用的效率工具,它只干一件事——把你每天要打开的十几个地址,收拢到一个页面上,点一下就到位,不用再翻浏览器书签、不用再回忆那一长串路径。我给自己的工具集起了个代号叫 MC·JC,MC 是主体那套东西的缩写,JC 是旁边那堆零散脚本的缩写,两个东西平时分散在不同目录、不同端口上,来回切换非常烦,于是就动了做一个网页版入口的念头。

它的核心需求其实就三条:能记住我要去的地方、能快速找到并且点开、在手机和电脑上都能正常看。没有账号体系,没有后台管理,没有数据库,甚至不需要一个正经的服务器。你完全可以理解成"一个长得好看的、可以搜索的、支持分组的超级书签页"。这种定位决定了后面所有的技术选型——越轻越好,依赖越少越好,因为这东西的维护频率极低,三个月不看一眼是常态,任何需要"先装环境再跑起来"的方案,三个月后你自己都懒得碰。

适合看这篇内容的人:有点 HTML/CSS 基础、想给自己做个统一入口的;手上零散项目多、地址记不住的;以及想练手一个"从零到上线"完整流程的新手。如果你连标签都还没写过,也能跟着做,因为整体代码量控制在一百多行,每一段我都会说清楚它在干嘛。

1.2 为什么我放弃了框架,选了单文件静态方案

做这类页面,最容易踩的坑是"技术栈上瘾"。我第一版用的是某前端框架 + 路由 + 组件库,脚手架一跑,node_modules 一千多个包,构建配置看了半天。结果呢?页面本身只有几个卡片,构建产物比源码大十倍,改一个链接要重新 build 一次,某次依赖升级还把构建搞挂了,我花了一个晚上修一个根本不重要的东西。这就是典型的用大炮打蚊子。

第二版我直接砍到单文件:一个index.html,CSS 写在<style>里,逻辑写在<script>里,双击就能在浏览器打开,扔到任意静态托管上就能访问。这个取舍背后的逻辑很朴素——入口页的复杂度上限,应该由它的使用频率决定。一天点二十次的工具,值得你为它写构建脚本;一周点两次的工具,任何额外的构建步骤都是负债。

对比项框架方案单文件静态方案
首次搭建耗时30 分钟以上5 分钟左右
依赖数量几百到上千0
修改成本改完要重新构建改完刷新即可
三个月后再维护要重装环境、回忆配置打开就能改
首屏体积通常几百 KB 起十几 KB
适合场景有交互复杂度、多人协作纯展示 + 跳转 + 轻交互

表格里最值得看的是最后一行。单文件方案不是"简陋版",它是"匹配场景的最优解"。入口页的交互复杂度极低,无非是搜索过滤、分组展示、点击跳转,这些用原生 JS 几十行就能写完,而且写完之后你能完全看懂每一行在干什么——这一点在长期维护里价值极高。

1.3 "5 分钟"的边界在哪里

标题里写 5 分钟,我得把话说清楚,免得有人做完发现跟预期不符。5 分钟指的是:从空文件到本地点开能用。这段时间里你能拿到一个带搜索、带分组、带响应式布局、数据写在数组里的页面。不包含的部分是:买域名、配证书、接统计、写文档。这些是"上线"环节,不是"搭建"环节,混在一起谈时间没有意义。

具体拆一下这 5 分钟怎么分配的:写 HTML 骨架大约 40 秒,把数据数组填进去(这一步是你唯一需要动脑的地方,把自己常用的地址整理成几条记录)大约 2 分钟,CSS 微调配色和间距 1 分钟,本地打开验证 30 秒,剩下的是留给你改错别字的余量。第一次做可能会超,因为你在纠结配色;做第二个同类页面的时候,基本就是复制粘贴改数据,三分钟都用不了。

注意:不要把"5 分钟"理解成"不用理解原理也能做"。代码可以抄,但你要知道哪一行控制分组、哪一行控制跳转,否则后面加一个功能就得重新问人。

2. 页面结构怎么拆:三层骨架

2.1 顶部区:搜索框是核心,不是装饰

很多入口页把搜索框做成了摆设,输入了没反应,或者只能搜标题。这是浪费。一个人收藏了三十个地址之后,靠眼睛在卡片墙里找,效率极低;而搜索框只要接一个input事件,把输入值和每张卡片的名字、描述、标签做一次包含匹配,就能立刻把结果筛出来。这十几行代码带来的效率提升,比任何视觉美化都大。

顶部区的构成我建议就三样:左边一个项目名或代号(让人一眼知道这是哪个入口),中间或下面一个搜索输入框,右边放一个小的状态标识(比如最近更新时间)。不要放轮播图,不要放欢迎语,不要放大段介绍文字——那都是官网的思路。入口页的第一屏应该尽可能多地把卡片露出来,用户打开就是为了点,不是为了读。

搜索框还有一个细节值得做:按斜杠键聚焦。这是从代码编辑器里借来的习惯,用户按/光标直接进搜索框,输入完按Esc清空并失焦。整个交互链路不用碰鼠标,熟练之后速度非常快。实现上就是监听keydown,判断event.key === '/'且当前焦点不在输入框里,然后focus()。

2.2 中间区:按"用途"分组,不要按"时间"分组

分组这件事,很多人按添加时间分,结果就是越加越乱。我的做法是按使用场景分,比如"日常工具"、"开发相关"、"文档资料"、"临时存放"。分组的粒度控制在三到五组,每组三到八个卡片,超过八个就该拆新组了,否则一屏塞不下,要滚动,滚动的瞬间分类的意义就打折了。

每张卡片包含四个要素:名称(要短,两到六个字)、一句话描述(说清楚点进去是干嘛的,别写"点击查看"这种废话)、跳转地址、标签色(用来在视觉上区分组别)。名称和描述是搜索匹配的主要字段,所以描述写得越具体,搜索命中率越高。我一开始描述写得很敷衍,后来发现自己搜"日志"搜不到,因为那条描述写的是"工具"——这就是自找麻烦。

卡片在桌面端用网格布局,三到四列;平板两列;手机一列。这个响应式不用写复杂的媒体查询,grid-template-columns: repeat(auto-fill, minmax(220px, 1fr))一行就能搞定,浏览器自己算每行放几个。这是我最推荐新手用的布局写法,比死记断点省心得多。

2.3 底部区:信息量小,但别省

底部放三样东西就够了:版本号或更新日期、一个返回顶部的按钮(页面长的时候有用)、一句说明这页是给谁用的。版本号看着没用,但你三个月后回来改东西,看到"v1.3 2024-11"就知道上次动到哪儿了,比翻文件修改时间靠谱。

还有个小设计:把常用的三到五个地址做成"最近访问",用localStorage记下点击过的条目,下次打开自动排在前面。这个功能代码不超过二十行,但体感提升明显——因为绝大多数人的实际使用是高度重复的,每天点的就是那四五个,剩下的几十个是"备着"的。让高频项自动浮上来,比任何手动排序都省事。

2.4 关键设计:数据与视图分离

这是整个方案里唯一一处"架构性"的决定,也是我觉得最值回票价的地方。所有卡片的名称、描述、地址、分组,全部写在一个 JS 数组里,页面渲染时用循环生成 DOM。这样做的好处是:加一个新地址,你只需要在数组里加一行对象,不用去 HTML 里复制粘贴一段 div 再改四五个地方。后者是新手最常见的做法,也是最容易出错的——复制的片段里忘了改href,点进去跳到别的页面,排查半天。

const SITES = [ { group: '日常工具', name: '待办清单', desc: '当天任务记录与勾选', url: 'https://example.com/todo', tag: 'green' }, { group: '日常工具', name: '便签', desc: '临时的想法快速记一笔', url: 'https://example.com/note', tag: 'green' }, { group: '开发相关', name: '本地服务', desc: '主体项目的调试入口', url: 'http://127.0.0.1:8080', tag: 'blue' }, { group: '文档资料', name: '接口备忘', desc: '常用参数和返回结构', url: 'https://example.com/docs', tag: 'orange' } ];

数据结构定好之后,后面所有的功能——分组、搜索、最近访问——都是对这个数组的操作,逻辑非常干净。这也是我一直强调的一点:结构清晰比代码短更重要。短代码看三天就忘,清晰的结构能撑三年。

3. 动手:从空文件到能用的页面

3.1 准备:一个文件就够

新建一个文件夹,比如叫mcjc-portal,里面建一个index.html。就这一个文件。不需要package.json,不需要node_modules,不需要任何构建工具。用系统自带的文本编辑器或者你习惯的编辑器打开它,写完保存,双击文件,浏览器就打开了。

这个"零准备"的体验很重要。我见过太多人卡在"环境装不上"这一步,还没开始写代码就放弃了。静态页面最大的优势就是它没有环境——浏览器就是它的运行时,这玩意儿你电脑上肯定有。

需要额外说明的是本地服务。如果你的入口页里有指向127.0.0.1的地址,双击打开file://协议的页面去点那些链接是没问题的(浏览器会正常跳转)。但如果之后你想加一些需要同源的能力,比如读取本地存的配置,就得用一个本地静态服务跑起来。这一步不是必须的,什么时候需要什么时候再说。

3.2 HTML 骨架:先搭框,后填肉

骨架的阶段不要想样式,先把结构写出来。整个页面就五个部分:文档头、顶部区、搜索区、内容区、底部区。内容区先留空,一会儿用 JS 填。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>MC·JC 入口</title> </head> <body> <header class="topbar"> <h1 class="brand">MC·JC</h1> <span class="meta">v1.0</span> </header> <section class="searchbar"> <input id="q" type="search" placeholder="搜索名称、描述或分组(按 / 聚焦)" autocomplete="off"> <span class="hint" id="hint"></span> </section> <main id="app"></main> <footer class="foot"> <span>个人入口页 · 静态托管</span> <a href="#top">回到顶部</a> </footer> </body> </html>

viewport那一行千万别漏,漏了手机打开会变成缩小版的桌面页,字小到看不清。autocomplete="off"建议加上,否则浏览器会在搜索框下面弹出一堆历史输入,挡住卡片。

3.3 CSS:三个变量定调,其余交给网格

样式部分我建议先定三个变量:背景色、卡片色、主文字色。三个变量定完,整个页面的调性就定了,剩下的间距、圆角都是微调。很多人一上来就调十几种颜色,最后页面花得像调色盘,反而不如两三种颜色耐看。

:root { --bg: #0f1216; --card: #1a1f26; --text: #e6e9ee; --muted: #8b95a3; --accent: #4c8bf5; --radius: 10px; } * { box-sizing: border-box; } body { margin: 0; padding: 24px 16px 48px; background: var(--bg); color: var(--text); font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; line-height: 1.6; } .topbar { display: flex; align-items: baseline; gap: 12px; max-width: 1080px; margin: 0 auto 20px; } .brand { font-size: 20px; margin: 0; letter-spacing: 1px; } .meta { color: var(--muted); font-size: 12px; } .searchbar { max-width: 1080px; margin: 0 auto 28px; } #q { width: 100%; padding: 12px 14px; border-radius: var(--radius); border: 1px solid #2a313b; background: var(--card); color: var(--text); font-size: 14px; outline: none; } #q:focus { border-color: var(--accent); } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; } .card { display: block; padding: 14px 16px; background: var(--card); border-radius: var(--radius); border: 1px solid #232a33; text-decoration: none; color: var(--text); transition: transform .12s ease, border-color .12s ease; } .card:hover { transform: translateY(-2px); border-color: var(--accent); } .card .name { font-weight: 600; font-size: 15px; } .card .desc { color: var(--muted); font-size: 13px; margin-top: 4px; } .group-title { max-width: 1080px; margin: 28px auto 12px; font-size: 13px; color: var(--muted); letter-spacing: 2px; } .foot { max-width: 1080px; margin: 48px auto 0; display: flex; justify-content: space-between; color: var(--muted); font-size: 12px; }

这段 CSS 里最关键的是.grid那一行auto-fill+minmax。它的意思是"每列最少 220 像素,能塞几列塞几列",桌面宽屏自动出四列,手机窄屏自动变一列,完全不用写媒体查询。我用了好几年这个写法,各种屏幕下都没翻过车。

box-sizing: border-box那一行也别省。默认的盒模型下,加了 padding 的元素宽度会超出预期,典型的症状是"输入框比容器宽一点点,右边被切掉"。加上这一行,所有元素的宽高包含内边距,排版立刻变可预测。

3.4 JS:一个数组渲染整页

渲染逻辑分三步:按group字段把数组归并、循环生成 DOM、绑定搜索过滤。归并用一次遍历就能完成,不需要任何库。

const SITES = [ /* 上面那段数据 */ ]; const app = document.getElementById('app'); const q = document.getElementById('q'); const hint = document.getElementById('hint'); const RECENT_KEY = 'mcjc-recent'; function getRecent() { try { return JSON.parse(localStorage.getItem(RECENT_KEY)) || []; } catch (e) { return []; } } function pushRecent(url) { const list = getRecent().filter(u => u !== url); list.unshift(url); localStorage.setItem(RECENT_KEY, JSON.stringify(list.slice(0, 5))); } function render(keyword = '') { const kw = keyword.trim().toLowerCase(); const recent = getRecent(); const filtered = SITES.filter(s => { if (!kw) return true; return (s.name + s.desc + s.group).toLowerCase().includes(kw); }); // 按分组归并,保持原始顺序 const groups = []; const map = {}; filtered.forEach(s => { if (!map[s.group]) { map[s.group] = []; groups.push(s.group); } map[s.group].push(s); }); let html = ''; groups.forEach(g => { html += `<h2 class="group-title">${g}</h2><div class="grid">`; map[g].forEach(s => { const star = recent.includes(s.url) ? '· ' : ''; html += `<a class="card" href="${s.url}" target="_blank" rel="noopener"> <div class="name">${star}${s.name}</div> <div class="desc">${s.desc}</div> </a>`; }); html += '</div>'; }); app.innerHTML = html || '<p class="hint">没有匹配的条目</p>'; hint.textContent = kw ? `匹配 ${filtered.length} 条` : ''; } app.addEventListener('click', e => { const card = e.target.closest('.card'); if (card) pushRecent(card.getAttribute('href')); }); q.addEventListener('input', e => render(e.target.value)); document.addEventListener('keydown', e => { if (e.key === '/' && document.activeElement !== q) { e.preventDefault(); q.focus(); } if (e.key === 'Escape' && document.activeElement === q) { q.value = ''; q.blur(); render(''); } }); render();

这段代码有几个地方值得单独说。第一,target="_blank"一定要配rel="noopener",否则新开的页面能通过window.opener反向操作你的入口页,这是老生常谈但至今还有人漏。第二,localStorage的读写包在try/catch里,因为在隐身模式或者某些受限环境下它会直接抛异常,不包起来整个页面就白屏了。第三,最近访问用的是 URL 做标识而不是名字,因为名字可能会改,URL 相对稳定。

搜索过滤只匹配三个字段,实际用下来命中率不错。如果你想更宽松,可以把 URL 也加进去。但别加太多字段,匹配太泛会导致搜什么都能出一堆结果,反而失去筛选意义。

3.5 本地验证与上线

保存文件,双击打开。你应该能看到深色背景、顶部标题、搜索框,以及按分组排列的卡片。挨个点一下,确认跳转地址没写错;在搜索框输入一两个字,确认能过滤;刷新页面,确认最近访问的条目排到了前面(那个小圆点是标记)。

验证没问题就可以上线了。随便找一个静态托管服务,把index.html传到它的静态目录里,或者直接在它的控制台上传文件,它会给你一个访问地址。整个过程不需要配置任何运行环境,因为它本来就是一堆静态文件。这里我不点名具体平台,因为这类服务换得很快,你搜"静态托管"就能找到一堆,选个免费额度够用的就行。

需要提醒的是路径问题。如果你把文件放在子目录里(比如/portal/index.html),那访问地址也要带上子目录,直接访问根路径会 404。这不是托管平台的问题,是路径没对上。另外有些托管服务默认把index.html当作目录的默认页,有些不会,如果访问目录地址打不开,就加上/index.html试试。

4. 打磨:让它看起来像个正经东西

4.1 移动端适配的三个真实痛点

响应式不是"加上 viewport 就完事",实际用手机打开会暴露几个具体问题。第一个是点击区域太小。卡片如果只有一行小字,手指点上去经常点偏,解决办法是给卡片设一个最小高度,大概 64 像素,保证指腹能盖住。第二个是横向溢出。某个元素宽度算错一点,手机上就出现左右滚动条,整页体验立刻垮掉。排查办法是临时给所有元素加outline: 1px solid red,一眼就能看出谁超宽了。第三个是搜索框被键盘顶走。手机点搜索框会弹起软键盘,如果页面高度用了100vh,键盘一弹页面就会跳动,改成min-height或者干脆不用固定高度就能避开。

这三个问题我都踩过,尤其是第二个。当时怎么查都查不出来,最后用红框法十秒钟定位到是一个卡片的padding加固定宽度导致的。这个技巧值得记下来,比一行行看代码快得多。

4.2 键盘流:给重度用户留一条快路

除了斜杠聚焦,还有两个小键盘操作值得加。一个是数字键 1 到 9 快速打开前九个条目,另一个是上下方向键在卡片之间移动焦点。第一个实现简单:监听keydown,如果是数字,取对应序号的卡片click()一下。加了这个之后,我日常打开入口页基本不碰鼠标,敲两下键盘就到地方了。

第二个稍微麻烦点,需要给卡片加tabindex并且自己管理焦点顺序。我做过一版,后来删了——因为浏览器原生的 Tab 键其实已经够用,自己实现反而容易出现焦点跑丢的问题。不是所有能做的功能都值得做,这条经验我是在删掉那几十行代码的时候体会到的。

4.3 图标:能不用图片就别用图片

新手很容易掉进"每个卡片配一个图标"的坑,然后去找图标库、下载几十个 SVG、一个个改颜色。这件事的投入产出比极低。我的做法是用名称首字加色块代替图标:一个圆角方块,里面放名称的第一个字,背景色按分组区分。这样做的好处是零外部资源、零加载时间、风格统一,而且新增条目时不用再去找图标。

如果确实想要图标,用内联 SVG 比引外部图标库好,因为外链资源一旦对方服务挂了或者网络不通,图标就变成空白方块,很尴尬。内联 SVG 写在 HTML 里,跟着页面一起加载,永远不会丢。

4.4 首屏速度:为什么我把字体也换成了系统字体

最后说性能。这个页面的体积理论上可以压到十几 KB,但如果你引入了网络字体和图标库,轻轻松松涨到几百 KB,首屏白屏时间从几十毫秒变成一两秒。对于一个"打开就要点"的工具页,这一两秒的等待是纯粹的浪费。

我的做法是全部用系统字体栈:-apple-system, "Segoe UI", "Microsoft YaHei", sans-serif。这串字体的意思是"优先用系统自带的界面字体,找不到就降级到无衬线字体"。不同系统上显示会略有差异,但都很好看,而且零加载。同理,所有 CSS 和 JS 都内联在 HTML 里,整个页面就一次请求,网络再差也是一下子就出来。

提示:如果你的入口页需要放一些说明性长文本,考虑单独拆一个页面,别把正文塞进入口页。入口页的第一屏必须留给卡片。

5. 踩过的坑与排查实录

5.1 打开是白屏,什么都没有

这是最常见的问题,原因通常有两个。第一个是 JS 报错导致渲染中断。排查办法是打开浏览器开发者工具的控制台,看有没有红色报错,八成是某个变量名拼错,或者数组里少了个逗号。第二个是数据数组本身是空的,或者group字段全部写成了同一个值导致渲染逻辑出问题。

还有一种隐蔽的情况:代码里用了模板字符串(反引号),但你把整段代码复制到了一个会自动替换引号的编辑器里,反引号变成了普通引号,整个字符串就断了。这个坑我踩过,控制台的报错信息还不直观,看了十分钟才反应过来。

5.2 部署后样式全乱,本地却正常

本地双击打开正常,传到托管上就样式错乱,通常是相对路径的问题。如果你在 CSS 里引了图片或者字体文件,用的又是相对路径,那么文件的实际请求地址会基于 HTML 所在的目录计算,一旦目录层级和本地不一致,资源就 404 了。解决办法要么全部改成绝对路径,要么把所有资源内联进 HTML——后者更省心,也是我一直推荐单文件的原因。

还有一个可能:托管服务开了压缩或者缓存,你传上去的是旧版本。清一下浏览器缓存,或者用无痕窗口打开验证。

5.3 手机上点了没反应,或者点错

移动端点不动,常见原因是某个透明元素盖在了卡片上面。比如你给搜索框的容器设了很高的高度,或者给某个装饰元素设了position: absolute却没设pointer-events: none,它就会把点击事件吃掉。排查办法是在开发者工具里用元素选择器点一下那个位置,看选中的是卡片还是别的什么。

点错的情况多半是卡片之间的间距太小。网格的gap建议不要小于 10 像素,低于这个值手指很容易误触到隔壁。另外卡片的可点击区域应该覆盖整张卡片,而不是只有文字部分,这一点用<a>包住整个卡片就能做到。

5.4 明明改了文件,刷新还是老样子

缓存。浏览器缓存、托管服务的边缘缓存,两层都可能。最快的验证方式是无痕窗口打开,如果无痕下是新的,那百分百是缓存问题。开发阶段可以在开发者工具的 Network 面板里勾选"禁用缓存",省得每次手动清。

还有一个容易忽略的点:有些托管服务在你上传同名文件后不会立即刷新缓存,要等几十秒到几分钟。遇到这种情况别急着改代码,先等一会儿再刷新。

5.5 问题速查表

现象最可能的原因处理方式
页面白屏JS 报错中断渲染打开控制台看红色报错
样式全丢外链资源路径错误或缓存无痕窗口验证,资源改内联
点击无反应有透明元素遮挡用元素选择器定位遮挡层
手机上被切掉元素宽度溢出红框法定位超宽元素
搜索没结果匹配字段写错或数据为空打印数组长度确认
最近访问不生效存储被禁用或异常确认 try/catch 已加
部署后 404访问路径少了子目录地址补全/index.html
卡片点开跳错页复制片段忘改地址检查href属性

这张表基本覆盖了新手会遇到的九成问题。遇到没列出来的,先做两件事:看控制台、用无痕窗口,能解决大部分"玄学问题"。

6. 几点实操心得

做了几个版本的入口页之后,我最大的感受是:这类工具的价值不在于功能多,而在于你真的会去用它。我见过太多人花一周时间做了一个带登录、带后台、带数据统计的导航站,用了三天就不用了,因为打开它要加载、要登录、要等,还不如直接在地址栏输入。反过来说,一个丑但秒开的单文件页面,能用好几年。

第二个体会是数据要能随手改。入口页的地址是会变的,项目上线了、下线了、换端口了,你得能在三十秒内改完。这就是我一直坚持把数据抽成数组的原因——改一行对象,比在 HTML 里找那段 div 快十倍。同理,分组名也用数据控制,别写死在 HTML 标题里,否则改个组名要动好几处。

最后一个技巧是给它留一个"临时"分组。人总有那种"先存下来回头再看"的地址,如果硬要塞进正经分类里,会污染分类;单独开一个"临时"组,定期清理,页面就能长期保持干净。这个小设计看似不起眼,但它是让工具能撑过三个月的关键——毕竟一个越用越乱的入口页,最后的下场就是被遗忘在书签栏的最深处。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询