HTML简易个人导航页面源码(单文件响应式版)

这是一款简洁的个人导航页面源码,采用纯 HTML、CSS 和原生 JavaScript 编写,无需安装依赖,保存为单个 HTML 文件即可打开。页面内置分类筛选、关键词搜索、深浅色切换和响应式布局,适合作为浏览器首页、个人书签页或团队常用网址入口。

页面特点

  • 单文件结构,HTML、CSS 和 JavaScript 全部写在同一个文件中。
  • 支持常用工具、开发资源和设计灵感分类筛选。
  • 输入关键词可以实时过滤导航卡片,按回车可使用百度搜索。
  • 支持深色模式,主题选择会自动保存到浏览器本地存储。
  • 桌面端使用卡片网格布局,手机端自动调整为单列显示。
  • 导航链接使用新窗口打开,并预留了清晰的修改位置。

完整源码

将下面代码保存为 index.html 后直接打开即可预览。源码中的链接、标题、描述和分类都可以根据自己的需求替换。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>我的个人导航</title>
  <style>
    :root {
      --bg: #f5f7fb;
      --surface: rgba(255, 255, 255, .82);
      --surface-solid: #ffffff;
      --text: #172033;
      --muted: #6d7890;
      --line: #e4e9f2;
      --primary: #3c63f3;
      --primary-soft: #eaf0ff;
      --shadow: 0 18px 50px rgba(36, 56, 99, .1);
    }

    [data-theme="dark"] {
      --bg: #121722;
      --surface: rgba(27, 34, 48, .86);
      --surface-solid: #1b2230;
      --text: #f2f5fb;
      --muted: #9aa7bb;
      --line: #313b4f;
      --primary: #8ca7ff;
      --primary-soft: #26365f;
      --shadow: 0 18px 50px rgba(0, 0, 0, .2);
    }

    * {
      box-sizing: border-box;
    }

    html {
      scroll-behavior: smooth;
    }

    body {
      min-height: 100vh;
      margin: 0;
      color: var(--text);
      background:
        radial-gradient(circle at 10% 0%, rgba(92, 133, 255, .2), transparent 28rem),
        radial-gradient(circle at 92% 12%, rgba(255, 191, 105, .16), transparent 24rem),
        var(--bg);
      font: 15px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
        "Microsoft YaHei", sans-serif;
      transition: background .25s ease, color .25s ease;
    }

    button,
    input {
      font: inherit;
    }

    button,
    a {
      -webkit-tap-highlight-color: transparent;
    }

    a {
      color: inherit;
      text-decoration: none;
    }

    .page {
      width: min(1120px, calc(100% - 32px));
      margin: 0 auto;
      padding: 24px 0 42px;
    }

    .topbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      margin-bottom: 70px;
    }

    .brand {
      display: inline-flex;
      align-items: center;
      gap: 11px;
      font-size: 17px;
      font-weight: 800;
      letter-spacing: .02em;
    }

    .brand-mark {
      display: grid;
      width: 38px;
      height: 38px;
      place-items: center;
      color: #fff;
      background: linear-gradient(135deg, #3c63f3, #8c63f5);
      border-radius: 12px;
      box-shadow: 0 8px 20px rgba(60, 99, 243, .24);
    }

    .theme-toggle {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      padding: 9px 13px;
      color: var(--muted);
      background: var(--surface);
      border: 1px solid var(--line);
      border-radius: 12px;
      cursor: pointer;
      transition: border-color .2s ease, color .2s ease, transform .2s ease;
    }

    .theme-toggle:hover {
      color: var(--primary);
      border-color: var(--primary);
      transform: translateY(-2px);
    }

    .hero {
      max-width: 760px;
      margin-bottom: 32px;
    }

    .eyebrow {
      margin: 0 0 12px;
      color: var(--primary);
      font-size: 12px;
      font-weight: 800;
      letter-spacing: .16em;
    }

    h1 {
      max-width: 700px;
      margin: 0;
      font-size: clamp(34px, 6vw, 62px);
      line-height: 1.08;
      letter-spacing: -.055em;
    }

    .hero p {
      max-width: 610px;
      margin: 20px 0 0;
      color: var(--muted);
      font-size: 17px;
    }

    .search-form {
      display: flex;
      align-items: center;
      gap: 12px;
      max-width: 700px;
      padding: 8px 8px 8px 18px;
      background: var(--surface);
      border: 1px solid var(--line);
      border-radius: 18px;
      box-shadow: var(--shadow);
      backdrop-filter: blur(18px);
    }

    .search-form:focus-within {
      border-color: var(--primary);
      box-shadow: 0 0 0 4px rgba(60, 99, 243, .12), var(--shadow);
    }

    .search-icon {
      flex: 0 0 auto;
      color: var(--muted);
      font-size: 20px;
    }

    .search-form input {
      min-width: 0;
      flex: 1;
      color: var(--text);
      background: transparent;
      border: 0;
      outline: 0;
    }

    .search-form input::placeholder {
      color: var(--muted);
    }

    .search-form kbd {
      padding: 3px 8px;
      color: var(--muted);
      background: var(--primary-soft);
      border: 1px solid var(--line);
      border-radius: 7px;
      font-size: 12px;
    }

    .filter-bar {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin: 34px 0 18px;
    }

    .filter-button {
      padding: 8px 15px;
      color: var(--muted);
      background: transparent;
      border: 1px solid var(--line);
      border-radius: 999px;
      cursor: pointer;
      transition: color .2s ease, background .2s ease, border-color .2s ease;
    }

    .filter-button:hover,
    .filter-button.active {
      color: #fff;
      background: var(--primary);
      border-color: var(--primary);
    }

    .link-grid {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 16px;
    }

    .link-card {
      position: relative;
      display: flex;
      min-height: 158px;
      flex-direction: column;
      justify-content: space-between;
      padding: 21px;
      overflow: hidden;
      background: var(--surface);
      border: 1px solid var(--line);
      border-radius: 20px;
      box-shadow: 0 8px 28px rgba(36, 56, 99, .05);
      backdrop-filter: blur(18px);
      transition: transform .22s ease, border-color .22s ease, box-shadow .22s ease;
    }

    .link-card::after {
      position: absolute;
      right: -28px;
      bottom: -42px;
      width: 110px;
      height: 110px;
      content: "";
      background: var(--primary-soft);
      border-radius: 50%;
      opacity: .75;
    }

    .link-card:hover {
      border-color: var(--primary);
      box-shadow: var(--shadow);
      transform: translateY(-5px);
    }

    .link-card[hidden] {
      display: none;
    }

    .card-top,
    .card-bottom {
      position: relative;
      z-index: 1;
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 14px;
    }

    .card-icon {
      display: grid;
      width: 42px;
      height: 42px;
      place-items: center;
      color: var(--primary);
      background: var(--primary-soft);
      border-radius: 13px;
      font-size: 20px;
    }

    .card-arrow {
      color: var(--muted);
      font-size: 22px;
      line-height: 1;
      transition: color .2s ease, transform .2s ease;
    }

    .link-card:hover .card-arrow {
      color: var(--primary);
      transform: translate(3px, -3px);
    }

    .card-title {
      margin: 18px 0 3px;
      font-size: 17px;
      font-weight: 750;
    }

    .card-desc {
      margin: 0;
      color: var(--muted);
      font-size: 13px;
    }

    .card-tag {
      color: var(--muted);
      font-size: 12px;
    }

    .empty-state {
      display: none;
      padding: 55px 20px;
      color: var(--muted);
      text-align: center;
      background: var(--surface);
      border: 1px dashed var(--line);
      border-radius: 20px;
    }

    .empty-state.show {
      display: block;
    }

    footer {
      margin-top: 48px;
      color: var(--muted);
      font-size: 13px;
      text-align: center;
    }

    @media (max-width: 820px) {
      .link-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
      }
    }

    @media (max-width: 560px) {
      .page {
        width: min(100% - 22px, 480px);
        padding-top: 16px;
      }

      .topbar {
        margin-bottom: 54px;
      }

      h1 {
        font-size: 39px;
      }

      .hero p {
        font-size: 15px;
      }

      .search-form kbd {
        display: none;
      }

      .link-grid {
        grid-template-columns: 1fr;
      }

      .link-card {
        min-height: 140px;
      }
    }
  </style>
</head>
<body>
  <div class="page">
    <header class="topbar">
      

        <a class="link-card" href="https://developer.mozilla.org/zh-CN/" target="_blank" rel="noopener" data-category="develop" data-title="MDN Web Docs">
          <div class="card-top">
            <span class="card-icon"></></span>
            <span class="card-arrow">↗</span>
          </div>
          <div class="card-bottom">
            <div>
              <h2 class="card-title">MDN Web Docs</h2>
              <p class="card-desc">权威的 Web 开发文档</p>
            </div>
            <span class="card-tag">文档</span>
          </div>
        </a>

        <a class="link-card" href="https://codepen.io/" target="_blank" rel="noopener" data-category="develop" data-title="CodePen">
          <div class="card-top">
            <span class="card-icon">✦</span>
            <span class="card-arrow">↗</span>
          </div>
          <div class="card-bottom">
            <div>
              <h2 class="card-title">CodePen</h2>
              <p class="card-desc">在线编写和分享前端效果</p>
            </div>
            <span class="card-tag">实验</span>
          </div>
        </a>

        <a class="link-card" href="https://caniuse.com/" target="_blank" rel="noopener" data-category="develop" data-title="Can I Use">
          <div class="card-top">
            <span class="card-icon">✓</span>
            <span class="card-arrow">↗</span>
          </div>
          <div class="card-bottom">
            <div>
              <h2 class="card-title">Can I Use</h2>
              <p class="card-desc">查询浏览器兼容性</p>
            </div>
            <span class="card-tag">兼容</span>
          </div>
        </a>

        <a class="link-card" href="https://unsplash.com/" target="_blank" rel="noopener" data-category="design" data-title="Unsplash">
          <div class="card-top">
            <span class="card-icon">▧</span>
            <span class="card-arrow">↗</span>
          </div>
          <div class="card-bottom">
            <div>
              <h2 class="card-title">Unsplash</h2>
              <p class="card-desc">发现高质量免费图片</p>
            </div>
            <span class="card-tag">图片</span>
          </div>
        </a>

        <a class="link-card" href="https://iconify.design/" target="_blank" rel="noopener" data-category="design develop" data-title="Iconify">
          <div class="card-top">
            <span class="card-icon">✚</span>
            <span class="card-arrow">↗</span>
          </div>
          <div class="card-bottom">
            <div>
              <h2 class="card-title">Iconify</h2>
              <p class="card-desc">搜索并使用开源图标</p>
            </div>
            <span class="card-tag">图标</span>
          </div>
        </a>
      </section>

      <div class="empty-state" id="emptyState">没有找到匹配的导航内容,试试其他关键词。</div>
    </main>

    <footer>按自己的习惯修改链接,这里就是你的专属起始页。<span id="year"></span></footer>
  </div>

  <script>
    const root = document.documentElement;
    const themeToggle = document.querySelector("#themeToggle");
    const themeIcon = document.querySelector("#themeIcon");
    const themeText = document.querySelector("#themeText");
    const searchForm = document.querySelector("#searchForm");
    const searchInput = document.querySelector("#searchInput");
    const cards = [...document.querySelectorAll(".link-card")];
    const filters = [...document.querySelectorAll(".filter-button")];
    const emptyState = document.querySelector("#emptyState");
    let activeFilter = "all";

    function setTheme(theme) {
      root.dataset.theme = theme;
      localStorage.setItem("nav-theme", theme);
      const isDark = theme === "dark";
      themeIcon.textContent = isDark ? "☀" : "☾";
      themeText.textContent = isDark ? "浅色模式" : "深色模式";
    }

    setTheme(localStorage.getItem("nav-theme") || "light");

    themeToggle.addEventListener("click", () => {
      setTheme(root.dataset.theme === "dark" ? "light" : "dark");
    });

    function updateCards() {
      const keyword = searchInput.value.trim().toLowerCase();
      let visibleCount = 0;

      cards.forEach((card) => {
        const matchesFilter = activeFilter === "all" || card.dataset.category.split(" ").includes(activeFilter);
        const matchesKeyword = !keyword || card.dataset.title.toLowerCase().includes(keyword);
        const visible = matchesFilter && matchesKeyword;
        card.hidden = !visible;
        visibleCount += visible ? 1 : 0;
      });

      emptyState.classList.toggle("show", visibleCount === 0);
    }

    filters.forEach((button) => {
      button.addEventListener("click", () => {
        filters.forEach((item) => item.classList.remove("active"));
        button.classList.add("active");
        activeFilter = button.dataset.filter;
        updateCards();
      });
    });

    searchInput.addEventListener("input", updateCards);

    searchForm.addEventListener("submit", (event) => {
      event.preventDefault();
      const keyword = searchInput.value.trim();
      if (keyword) {
        window.open("https://www.baidu.com/s?wd=" + encodeURIComponent(keyword), "_blank", "noopener");
      }
    });

    document.addEventListener("keydown", (event) => {
      if (event.key === "/" && document.activeElement !== searchInput) {
        event.preventDefault();
        searchInput.focus();
      }
    });

    document.querySelector("#year").textContent = " " + new Date().getFullYear();
  </script>
</body>
</html>

使用方法

  1. 新建一个名为 index.html 的文件,将上方完整代码粘贴进去。
  2. 在导航卡片的 hrefdata-title 和文字内容中替换为自己的网址。
  3. 如需增加分类,在筛选按钮中添加对应的 data-filter,并给卡片设置相同的 data-category
  4. 将文件上传到服务器,或设置为浏览器启动页即可使用。

自定义提示

如果不需要百度搜索,可以删除 searchForm 的提交事件;如果需要接入站内搜索,也可以将该事件替换为自己的搜索接口。页面使用 localStorage 保存主题设置,清理浏览器站点数据后会恢复为浅色模式。

© 版权声明
THE END
喜欢就支持一下吧