用网址导航源码制作一个简洁实用的导航页

AI教程1小时前发布 jdkjadmin
0 00
用网址导航源码制作一个简洁实用的导航页

用网址导航源码制作一个简洁实用的导航页

网址导航页看起来只是把常用链接排列在一起,真正好用却不只取决于链接数量。分类是否清楚、搜索是否顺手、手机上是否易读,以及链接和数据如何维护,都会影响日常使用体验。本文从一个不依赖框架的静态页面开始,说明怎样制作、运行和部署一份简洁的导航页源码,并讨论它适合做什么、不适合做什么。

一、适用场景与边界

静态网址导航适合个人或小团队整理常用网站,例如开发文档、设计工具、内部系统入口、学习资料和运营平台。页面由 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) 协议校验减少不必要的风险。最重要的边界是:静态导航只负责组织入口,不负责保护目标服务,也不能替代链接审核、身份认证或后台管理。先从少量高频链接开始,保持规则清晰,再根据真实使用需求扩展功能,会比一开始堆满复杂交互更容易长期维护。

© 版权声明

相关文章