用网址导航源码制作一个简洁实用的导航页
网址导航页看起来只是把常用链接排列在一起,真正好用却不只取决于链接数量。分类是否清楚、搜索是否顺手、手机上是否易读,以及链接和数据如何维护,都会影响日常使用体验。本文从一个不依赖框架的静态页面开始,说明怎样制作、运行和部署一份简洁的导航页源码,并讨论它适合做什么、不适合做什么。
一、适用场景与边界
静态网址导航适合个人或小团队整理常用网站,例如开发文档、设计工具、内部系统入口、学习资料和运营平台。页面由 HTML、CSS 和 JavaScript 组成,可以放在普通静态网站托管服务上,也可以在本地打开。若链接内容不常变,或者可以接受通过修改源码来更新,这种做法结构简单、维护成本低。
它并不自动具备用户账号、后台管理、权限控制、云端同步或多设备数据同步。下面的示例把链接写在 JavaScript 数组中;要增删项目,需要编辑文件并重新部署。若需要多人共同维护、按用户显示不同链接、记录访问统计或对接企业身份认证,应使用具备相应功能的后端或内容管理系统,不能把静态页面误当成完整的门户系统。
导航页展示的是链接,不代表这些网站由导航页运营,也不代表链接一定可用或安全。发布前应确认链接来源、用途和维护责任;如果导航页面向公众,还应避免放入未经授权的内部地址、敏感系统入口或仅供特定人员使用的信息。
二、页面结构怎么规划
一个实用的导航页通常包含四部分:页面标题和简短说明、搜索框、分类标题,以及每个站点的名称与说明。初期建议先整理真正经常访问的链接,再按任务或用途分类。分类不宜过细,否则用户需要先判断“该去哪一类”才能找链接;也不宜把所有内容堆在一个长列表里。
下面示例采用“数据与页面分离”的方式:站点信息放在数组中,页面渲染、搜索和链接校验集中在脚本里。每个条目包含分类、名称、说明和网址。示例允许搜索名称、说明和分类,输入内容会在浏览器本地过滤,不会发送到服务器。
三、可运行的单文件示例
将下面代码保存为 index.html,用现代浏览器打开即可运行。示例使用纯 HTML、CSS 和 JavaScript,不依赖 CDN 或第三方框架。后续修改时,主要编辑 sites 数组即可。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="常用网站导航页">
<title>常用网址导航</title>
<style>
:root {
color-scheme: light;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
color: #202632;
background: #f5f7fa;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.page {
width: min(100% - 32px, 1040px);
margin: 48px auto;
}
header {
margin-bottom: 28px;
}
h1 {
margin: 0 0 8px;
font-size: clamp(1.8rem, 4vw, 2.5rem);
}
.intro {
margin: 0;
color: #5e6878;
line-height: 1.6;
}
.search {
margin: 24px 0 32px;
}
.search label {
display: block;
margin-bottom: 8px;
font-weight: 600;
}
.search input {
width: 100%;
min-height: 46px;
padding: 10px 12px;
border: 1px solid #cbd3df;
border-radius: 8px;
background: #fff;
color: inherit;
font: inherit;
}
.search input:focus {
outline: 3px solid #b9d4ff;
border-color: #3974c6;
}
.group {
margin: 0 0 30px;
}
.group h2 {
margin: 0 0 12px;
font-size: 1.2rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
}
.card {
display: block;
min-height: 104px;
padding: 16px;
border: 1px solid #e1e6ee;
border-radius: 10px;
background: #fff;
color: inherit;
text-decoration: none;
transition: border-color .15s ease, transform .15s ease;
}
.card:hover {
border-color: #8aaddb;
transform: translateY(-2px);
}
.card:focus-visible {
outline: 3px solid #3974c6;
outline-offset: 2px;
}
.card strong {
display: block;
margin-bottom: 7px;
}
.card span {
color: #5e6878;
font-size: .92rem;
line-height: 1.5;
}
.empty {
padding: 18px;
border-radius: 8px;
background: #fff;
color: #5e6878;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
scroll-behavior: auto !important;
transition-duration: .01ms !important;
}
}
</style>
</head>
<body>
<main class="page">
<header>
<h1>常用网址导航</h1>
<p class="intro">把常用工具和资料集中到一个页面,按名称、说明或分类搜索。</p>
</header>
<div class="search">
<label for="searchInput">搜索网址</label>
<input id="searchInput" type="search"
placeholder="例如:文档、设计、代码" autocomplete="off">
</div>
<div id="results" aria-live="polite"></div>
</main>
<script>
"use strict";
const sites = [
{
category: "开发",
name: "MDN Web Docs",
description: "Web 标准与浏览器 API 参考文档",
url: "https://developer.mozilla.org/"
},
{
category: "开发",
name: "GitHub",
description: "代码托管与协作平台",
url: "https://github.com/"
},
{
category: "设计",
name: "Figma",
description: "在线界面设计与协作工具",
url: "https://www.figma.com/"
},
{
category: "效率",
name: "示例搜索",
description: "用于替换成你自己的搜索或工作入口",
url: "https://www.example.com/"
}
];
const input = document.querySelector("#searchInput");
const results = document.querySelector("#results");
function getSafeHttpUrl(value) {
try {
const url = new URL(value);
if (url.protocol === "https:" || url.protocol === "http:") {
return url.href;
}
} catch {
// 地址无法解析时,不显示该链接。
}
return null;
}
function render() {
const query = input.value.trim().toLocaleLowerCase();
const groups = new Map();
for (const site of sites) {
const searchable = [
site.category,
site.name,
site.description
].join(" ").toLocaleLowerCase();
if (!searchable.includes(query)) continue;
const safeUrl = getSafeHttpUrl(site.url);
if (!safeUrl) continue;
if (!groups.has(site.category)) {
groups.set(site.category, []);
}
groups.get(site.category).push({ ...site, safeUrl });
}
results.replaceChildren();
if (groups.size === 0) {
const message = document.createElement("p");
message.className = "empty";
message.textContent = "没有找到匹配的网址。";
results.append(message);
return;
}
for (const [category, items] of groups) {
const section = document.createElement("section");
section.className = "group";
const heading = document.createElement("h2");
heading.textContent = category;
const grid = document.createElement("div");
grid.className = "grid";
for (const site of items) {
const link = document.createElement("a");
link.className = "card";
link.href = site.safeUrl;
link.target = "_blank";
link.rel = "noopener noreferrer";
const name = document.createElement("strong");
name.textContent = site.name;
const description = document.createElement("span");
description.textContent = site.description;
link.append(name, description);
grid.append(link);
}
section.append(heading, grid);
results.append(section);
}
}
input.addEventListener("input", render);
render();
</script>
</body>
</html>
四、代码中的关键处理
1. 用数组维护内容
sites 中每个对象代表一个导航项目。分类和名称都是普通文本,网址单独放在 url 字段,便于检查和维护。添加新条目时可以复制一个对象,但要保证字段齐全、逗号位置正确。网址应包含协议,例如 https://example.com/;不要只写一个容易产生歧义的域名字符串。
2. 搜索只在当前页面筛选
脚本监听输入框的 input 事件,把分类、名称和说明组合起来进行不区分大小写的包含匹配。它不访问搜索引擎,也不向服务器提交查询。此实现适合几十到几百个手工维护的项目;如果数据规模很大,或需要拼音、同义词、标签筛选、排序和分页,应考虑专门的搜索方案,而不是不断把逻辑塞进单个页面。
3. 创建元素时使用 textContent
代码用 document.createElement() 创建元素,并用 textContent 设置站点名称与说明,没有把数据拼成 HTML 字符串后交给 innerHTML。这样即使某条文本中含有尖括号,也会作为文字显示,而不是被浏览器解释成 HTML。维护导航数据时仍应检查内容来源;安全的 DOM 写法不能替代对不可信网址和业务数据的审核。
4. 限制可点击的网址协议
getSafeHttpUrl() 使用浏览器的 URL API 解析地址,仅接受协议为 http: 或 https: 的链接。解析失败或协议不在允许范围时,该项目不会渲染。这样可以避免把 javascript: 等非网页协议作为普通站点链接使用。它不是完整的反钓鱼或恶意网站检测,也不能证明目标网站可信;用户仍需核实域名和链接来源。
协议限制适用于这个示例的导航需求。如果你的业务确实需要邮件、电话或其他链接形式,应明确列出所需协议并分别校验,而不是移除校验或无条件接受任意输入。校验应发生在实际使用网址的地方,不能只依赖输入框上的格式限制。
5. 新标签页与 rel 属性
示例用 target="_blank" 在新标签页打开链接,并设置 rel="noopener noreferrer"。其中 noopener 用于隔离新页面对打开它的页面窗口对象的访问;noreferrer 会阻止浏览器在该次导航中发送来源信息,同时也具有隔离打开页面的效果。是否需要隐藏来源信息,应结合业务和分析需求决定;如果不要求新标签页,也可以去掉 target="_blank" 与对应的 rel 设置。
五、怎样修改并保持易维护
添加网址时,优先填写简短、能说明用途的名称和描述。描述不是宣传语,最好回答“点开后能做什么”。分类名称保持稳定、简洁,例如“开发”“设计”“运维”“资料”;同一站点如果属于多个用途,可以选择一个主分类,避免无明确规则地重复添加。
链接较多时,可以把数据移到单独的 JSON 文件,再通过 fetch() 加载。但浏览器直接以 file:// 打开页面时,读取本地 JSON 可能受浏览器安全策略限制;使用本地开发服务器或部署到 HTTP(S) 网站通常更适合这种方式。不要为解决本地读取问题而关闭浏览器安全设置。若数据需要频繁编辑,独立 JSON 也不等于后台管理,仍需明确谁有权限修改和如何发布。
静态页面通常不需要把搜索记录写入本地存储。只有确实需要保存用户偏好,例如主题或自定义布局时,才考虑 localStorage。它是浏览器本地存储,不是加密保险箱,也不会自动跨设备同步;同一浏览器中的脚本可读取其中内容。不要在其中保存密码、令牌、密钥或其他敏感信息。
六、部署和检查
个人使用时,可以把文件放在本地;多人访问时,可以部署到支持静态文件的 Web 主机或静态托管服务。部署前检查首页文件名、大小写和相对路径,并在手机与桌面浏览器中各测试一次。直接通过 HTTPS 提供页面通常更合适,特别是导航目标本身使用 HTTPS 时。
上线前逐条点击链接,确认没有拼写错误、旧域名、跳转到非预期站点或失效页面。还应测试搜索无结果时的提示、键盘能否聚焦卡片、窄屏下卡片是否挤压,以及浏览器控制台是否出现异常。导航页的链接可能会随目标站点调整而失效,定期复查比一次性“发布完成”更可靠。
七、常见错误
错误一:网址漏写协议
把 example.com 直接赋给链接的 href,浏览器可能把它当成相对地址,而不是目标域名。完整填写 https:// 或实际需要的 http://,并确认最终解析出的域名正确。
错误二:把内容直接拼进 innerHTML
当标题、说明或链接来自可编辑文件、接口或用户输入时,直接拼接到 innerHTML 可能导致脚本注入风险。示例采用 DOM 节点和 textContent;如果确实需要展示富文本,应使用经过审查的消毒方案,并遵循可信内容策略,而不是自行用字符串替换几个字符。
错误三:以为 URL 解析成功就代表安全
new URL() 只能帮助解析地址,协议白名单只能排除不需要的协议类型。它不会判断网站是否钓鱼、是否被入侵、是否属于组织,也不会保证页面没有恶意内容。需要更强的访问控制时,应依靠组织的网络策略、身份认证和服务端安全措施。
错误四:把前端隐藏当成权限控制
在静态页面中删掉某个链接,只是让它不出现在导航上,并不会保护目标系统。知道地址的人仍可能直接访问。敏感服务必须在服务端实施身份验证和授权,不能依靠导航页不显示入口来保护。
错误五:分类太多或信息太少
分类名称相近、每类只有一两个项目,会增加查找成本;只显示站点名称,又可能让用户分不清用途。先按实际任务整理,再用少量分类和一句话说明帮助判断。若项目确实很多,可增加标签、分组折叠或分类筛选,但每增加一种交互,都应评估维护和无障碍成本。
结语
用静态网址导航源码制作导航页,适合内容相对稳定、无需账号和复杂权限的个人或小团队场景。示例通过数据数组集中维护链接,以浏览器端搜索改善查找体验,并用 DOM 文本节点和 HTTP(S) 协议校验减少不必要的风险。最重要的边界是:静态导航只负责组织入口,不负责保护目标服务,也不能替代链接审核、身份认证或后台管理。先从少量高频链接开始,保持规则清晰,再根据真实使用需求扩展功能,会比一开始堆满复杂交互更容易长期维护。




