这是一款简洁的个人导航页面源码,采用纯 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>
使用方法
- 新建一个名为
index.html的文件,将上方完整代码粘贴进去。 - 在导航卡片的
href、data-title和文字内容中替换为自己的网址。 - 如需增加分类,在筛选按钮中添加对应的
data-filter,并给卡片设置相同的data-category。 - 将文件上传到服务器,或设置为浏览器启动页即可使用。
自定义提示
如果不需要百度搜索,可以删除 searchForm 的提交事件;如果需要接入站内搜索,也可以将该事件替换为自己的搜索接口。页面使用 localStorage 保存主题设置,清理浏览器站点数据后会恢复为浅色模式。
© 版权声明
内容来自互联网,若有侵权请告知
THE END