历史记录API(History API)是HTML5提供的接口,允许Web应用与浏览器历史记录进行交互。通过这个API,可以在不刷新页面的情况下修改浏览器地址栏URL,实现单页应用(SPA)的路由功能。
东巴文(db-w.cn) 认为:历史记录API是现代单页应用的核心技术之一,让Web应用具备了类似原生应用的流畅体验。
| 特点 | 说明 |
|---|---|
| 无刷新导航 | 修改URL而不刷新页面 |
| 历史管理 | 添加、替换、遍历历史记录 |
| 状态保存 | 可以保存状态对象 |
| 前进后退 | 支持浏览器前进后退按钮 |
// History对象属性
console.log(window.history.length); // 历史记录数量
// History对象方法
window.history.back(); // 后退
window.history.forward(); // 前进
window.history.go(-1); // 后退一步
window.history.go(1); // 前进一步
window.history.go(0); // 刷新当前页面
// 添加历史记录
history.pushState(state, title, url);
// 参数说明
// state: 状态对象,可以存储任意数据
// title: 标题(目前大多数浏览器忽略此参数)
// url: 新的URL(必须同源)
// 示例
history.pushState(
{ page: 1, name: '首页' },
'首页',
'/home'
);
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>pushState示例</title>
</head>
<body>
<nav>
<a href="/home" data-page="home">首页</a>
<a href="/about" data-page="about">关于</a>
<a href="/contact" data-page="contact">联系</a>
</nav>
<div id="content"></div>
<script>
const content = document.getElementById('content');
// 页面内容
const pages = {
home: '<h1>首页</h1><p>欢迎访问首页</p>',
about: '<h1>关于</h1><p>这是关于页面</p>',
contact: '<h1>联系</h1><p>这是联系页面</p>'
};
// 导航点击事件
document.querySelectorAll('nav a').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = this.dataset.page;
const url = this.getAttribute('href');
// 添加历史记录
history.pushState({ page }, '', url);
// 更新内容
content.innerHTML = pages[page];
});
});
// 处理前进后退
window.addEventListener('popstate', function(e) {
if (e.state) {
content.innerHTML = pages[e.state.page];
}
});
// 初始加载
content.innerHTML = pages.home;
</script>
</body>
</html>
// 替换当前历史记录
history.replaceState(state, title, url);
// 示例:替换当前记录
history.replaceState(
{ page: 'new-page' },
'新页面',
'/new-page'
);
// pushState: 添加新记录
// 历史记录: [page1, page2, page3] -> [page1, page2, page3, page4]
history.pushState({ page: 4 }, '', '/page4');
// replaceState: 替换当前记录
// 历史记录: [page1, page2, page3] -> [page1, page2, page4]
history.replaceState({ page: 4 }, '', '/page4');
东巴文点评:pushState添加新记录,用户可以后退;replaceState替换当前记录,用户不能后退到之前的页面。
// 监听历史记录变化
window.addEventListener('popstate', function(e) {
console.log('状态对象:', e.state);
console.log('当前URL:', location.href);
// 根据状态更新页面
if (e.state) {
updatePage(e.state);
}
});
// 注意:pushState和replaceState不会触发popstate事件
// popstate事件只在浏览器前进后退时触发
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>历史记录API路由示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
nav {
background: #667eea;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
nav a {
color: white;
text-decoration: none;
margin-right: 15px;
padding: 5px 10px;
border-radius: 3px;
transition: background 0.3s;
}
nav a:hover,
nav a.active {
background: rgba(255, 255, 255, 0.2);
}
#content {
padding: 20px;
background: #f5f5f5;
border-radius: 5px;
min-height: 300px;
}
.page {
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.info {
margin-top: 20px;
padding: 10px;
background: #fff3cd;
border-radius: 5px;
font-size: 14px;
}
</style>
</head>
<body>
<h1>历史记录API路由示例</h1>
<nav>
<a href="/" data-route="home">首页</a>
<a href="/about" data-route="about">关于</a>
<a href="/products" data-route="products">产品</a>
<a href="/contact" data-route="contact">联系</a>
</nav>
<div id="content"></div>
<div class="info">
<p>当前URL: <span id="currentUrl"></span></p>
<p>历史记录数: <span id="historyLength"></span></p>
</div>
<script>
// 路由配置
const routes = {
home: {
title: '首页',
content: `
<div class="page">
<h2>欢迎来到首页</h2>
<p>这是一个使用History API实现的单页应用路由示例。</p>
<ul>
<li>点击导航链接查看不同页面</li>
<li>使用浏览器前进后退按钮</li>
<li>观察URL变化</li>
</ul>
</div>
`
},
about: {
title: '关于',
content: `
<div class="page">
<h2>关于我们</h2>
<p>东巴文(db-w.cn)致力于提供高质量的编程教程。</p>
<p>我们的使命是让编程学习更简单、更有趣!</p>
</div>
`
},
products: {
title: '产品',
content: `
<div class="page">
<h2>产品列表</h2>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px;">
<div style="padding: 15px; background: white; border-radius: 5px; text-align: center;">
<div style="font-size: 48px;">📚</div>
<h3>HTML教程</h3>
<p>从基础到进阶</p>
</div>
<div style="padding: 15px; background: white; border-radius: 5px; text-align: center;">
<div style="font-size: 48px;">🎨</div>
<h3>CSS教程</h3>
<p>美化你的网页</p>
</div>
<div style="padding: 15px; background: white; border-radius: 5px; text-align: center;">
<div style="font-size: 48px;">⚡</div>
<h3>JavaScript教程</h3>
<p>让网页动起来</p>
</div>
</div>
</div>
`
},
contact: {
title: '联系',
content: `
<div class="page">
<h2>联系我们</h2>
<form style="max-width: 400px;">
<div style="margin-bottom: 15px;">
<label>姓名:</label><br>
<input type="text" style="width: 100%; padding: 8px; margin-top: 5px;">
</div>
<div style="margin-bottom: 15px;">
<label>邮箱:</label><br>
<input type="email" style="width: 100%; padding: 8px; margin-top: 5px;">
</div>
<div style="margin-bottom: 15px;">
<label>消息:</label><br>
<textarea style="width: 100%; padding: 8px; margin-top: 5px;" rows="4"></textarea>
</div>
<button type="button" style="padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer;">
发送
</button>
</form>
</div>
`
}
};
// 路由类
class Router {
constructor() {
this.routes = routes;
this.contentEl = document.getElementById('content');
this.init();
}
init() {
// 监听导航点击
document.querySelectorAll('nav a[data-route]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const route = e.target.dataset.route;
this.navigate(route);
});
});
// 监听历史记录变化
window.addEventListener('popstate', (e) => {
if (e.state) {
this.render(e.state.route);
} else {
this.render('home');
}
});
// 初始渲染
const initialRoute = this.getRouteFromURL();
this.navigate(initialRoute, true);
}
navigate(route, replace = false) {
const url = route === 'home' ? '/' : `/${route}`;
const state = { route };
if (replace) {
history.replaceState(state, '', url);
} else {
history.pushState(state, '', url);
}
this.render(route);
}
render(route) {
const routeConfig = this.routes[route];
if (routeConfig) {
document.title = routeConfig.title + ' - 东巴文';
this.contentEl.innerHTML = routeConfig.content;
// 更新导航激活状态
this.updateNavActive(route);
// 更新信息显示
this.updateInfo();
}
}
updateNavActive(route) {
document.querySelectorAll('nav a[data-route]').forEach(link => {
if (link.dataset.route === route) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
updateInfo() {
document.getElementById('currentUrl').textContent = location.href;
document.getElementById('historyLength').textContent = history.length;
}
getRouteFromURL() {
const path = location.pathname;
const route = path.substring(1) || 'home';
return this.routes[route] ? route : 'home';
}
}
// 初始化路由
const router = new Router();
</script>
</body>
</html>
// 保存复杂状态
history.pushState({
page: 'products',
category: 'electronics',
filters: {
price: '100-500',
brand: 'apple'
},
scrollPosition: 500
}, '', '/products/electronics');
// 读取状态
window.addEventListener('popstate', function(e) {
if (e.state) {
const { page, category, filters, scrollPosition } = e.state;
// 恢复页面状态
loadPage(page, category);
applyFilters(filters);
window.scrollTo(0, scrollPosition);
}
});
// 状态对象会被序列化,注意限制
const state = {
id: 123,
name: '产品名称',
timestamp: Date.now()
};
// 保存状态
history.pushState(state, '', '/product/123');
// 注意:状态对象有大小限制(通常640KB)
// 避免保存大量数据或循环引用
东巴文点评:状态对象应该只保存必要的信息,大数据应该存储在IndexedDB或服务器。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>历史记录API综合示例 - 东巴文</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
h1 {
text-align: center;
color: #333;
}
.section {
margin: 20px 0;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section h2 {
margin-top: 0;
color: #667eea;
}
/* 导航样式 */
.main-nav {
display: flex;
gap: 10px;
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
margin-bottom: 20px;
}
.main-nav a {
color: white;
text-decoration: none;
padding: 10px 20px;
border-radius: 5px;
transition: background 0.3s;
}
.main-nav a:hover,
.main-nav a.active {
background: rgba(255, 255, 255, 0.2);
}
/* 内容区域 */
.main-content {
background: white;
padding: 30px;
border-radius: 10px;
min-height: 400px;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.pagination button {
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
cursor: pointer;
border-radius: 5px;
transition: all 0.3s;
}
.pagination button:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
.pagination button.active {
background: #667eea;
color: white;
border-color: #667eea;
}
/* 产品列表 */
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
.product-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
transition: all 0.3s;
}
.product-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-5px);
}
.product-icon {
font-size: 64px;
margin-bottom: 10px;
}
.product-name {
font-weight: bold;
margin-bottom: 5px;
}
.product-price {
color: #667eea;
font-weight: bold;
font-size: 18px;
}
/* 历史记录信息 */
.history-info {
display: flex;
gap: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 5px;
margin-top: 20px;
}
.history-info div {
flex: 1;
text-align: center;
}
.history-info strong {
display: block;
margin-bottom: 5px;
color: #667eea;
}
/* 标签页样式 */
.tabs {
display: flex;
border-bottom: 2px solid #ddd;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
transition: all 0.3s;
}
.tab:hover {
color: #667eea;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* 按钮样式 */
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
</style>
</head>
<body>
<h1>历史记录API综合示例</h1>
<!-- 导航 -->
<nav class="main-nav">
<a href="/" data-route="home">首页</a>
<a href="/products" data-route="products">产品</a>
<a href="/tabs" data-route="tabs">标签页</a>
<a href="/about" data-route="about">关于</a>
</nav>
<!-- 主内容 -->
<div id="mainContent" class="main-content"></div>
<!-- 历史记录信息 -->
<div class="history-info">
<div>
<strong>当前URL</strong>
<span id="currentUrl"></span>
</div>
<div>
<strong>历史记录数</strong>
<span id="historyLength"></span>
</div>
<div>
<strong>当前状态</strong>
<span id="currentState"></span>
</div>
</div>
<script>
// 应用状态
const appState = {
products: {
page: 1,
perPage: 4,
total: 12
},
tabs: {
activeTab: 'tab1'
}
};
// 产品数据
const products = [
{ id: 1, name: 'HTML教程', price: 99, icon: '📚' },
{ id: 2, name: 'CSS教程', price: 99, icon: '🎨' },
{ id: 3, name: 'JavaScript教程', price: 149, icon: '⚡' },
{ id: 4, name: 'Vue教程', price: 199, icon: '💚' },
{ id: 5, name: 'React教程', price: 199, icon: '💙' },
{ id: 6, name: 'Node.js教程', price: 179, icon: '💚' },
{ id: 7, name: 'Python教程', price: 149, icon: '🐍' },
{ id: 8, name: '数据库教程', price: 129, icon: '🗄️' },
{ id: 9, name: 'Git教程', price: 79, icon: '📦' },
{ id: 10, name: '算法教程', price: 199, icon: '🧮' },
{ id: 11, name: '网络安全教程', price: 169, icon: '🔒' },
{ id: 12, name: 'AI教程', price: 249, icon: '🤖' }
];
// 路由配置
const routes = {
home: {
title: '首页',
render: renderHome
},
products: {
title: '产品',
render: renderProducts
},
tabs: {
title: '标签页',
render: renderTabs
},
about: {
title: '关于',
render: renderAbout
}
};
// 渲染首页
function renderHome() {
return `
<div style="text-align: center; padding: 50px 0;">
<h2 style="font-size: 32px; margin-bottom: 20px;">欢迎来到东巴文</h2>
<p style="font-size: 18px; color: #666; margin-bottom: 30px;">
这是一个使用History API实现的单页应用示例
</p>
<div style="display: flex; justify-content: center; gap: 20px;">
<button class="btn btn-primary" onclick="router.navigate('products')">
浏览产品
</button>
<button class="btn btn-secondary" onclick="router.navigate('about')">
了解更多
</button>
</div>
<div style="margin-top: 50px; padding: 30px; background: #f9f9f9; border-radius: 10px;">
<h3>History API 功能演示</h3>
<ul style="text-align: left; max-width: 400px; margin: 20px auto;">
<li>✅ 无刷新导航</li>
<li>✅ URL地址更新</li>
<li>✅ 浏览器前进后退支持</li>
<li>✅ 状态保存与恢复</li>
<li>✅ 分页状态保持</li>
</ul>
</div>
</div>
`;
}
// 渲染产品页
function renderProducts(state = {}) {
const page = state.page || appState.products.page;
const perPage = appState.products.perPage;
const totalPages = Math.ceil(products.length / perPage);
const startIndex = (page - 1) * perPage;
const pageProducts = products.slice(startIndex, startIndex + perPage);
return `
<h2>产品列表</h2>
<div class="product-grid">
${pageProducts.map(product => `
<div class="product-card">
<div class="product-icon">${product.icon}</div>
<div class="product-name">${product.name}</div>
<div class="product-price">¥${product.price}</div>
</div>
`).join('')}
</div>
<div class="pagination">
<button onclick="changePage(${page - 1})" ${page === 1 ? 'disabled' : ''}>
上一页
</button>
${Array.from({ length: totalPages }, (_, i) => i + 1).map(p => `
<button class="${p === page ? 'active' : ''}" onclick="changePage(${p})">
${p}
</button>
`).join('')}
<button onclick="changePage(${page + 1})" ${page === totalPages ? 'disabled' : ''}>
下一页
</button>
</div>
<p style="text-align: center; margin-top: 20px; color: #666;">
当前第 ${page} 页,共 ${totalPages} 页
</p>
`;
}
// 渲染标签页
function renderTabs(state = {}) {
const activeTab = state.activeTab || appState.tabs.activeTab;
const tabs = [
{ id: 'tab1', title: '标签一', content: '这是标签一的内容,展示了标签页切换功能。' },
{ id: 'tab2', title: '标签二', content: '这是标签二的内容,标签状态会被保存到历史记录中。' },
{ id: 'tab3', title: '标签三', content: '这是标签三的内容,使用浏览器后退可以回到上一个标签。' }
];
return `
<h2>标签页示例</h2>
<div class="tabs">
${tabs.map(tab => `
<div class="tab ${tab.id === activeTab ? 'active' : ''}"
onclick="changeTab('${tab.id}')">
${tab.title}
</div>
`).join('')}
</div>
${tabs.map(tab => `
<div class="tab-content ${tab.id === activeTab ? 'active' : ''}">
<p>${tab.content}</p>
<p>当前激活的标签: <strong>${tab.title}</strong></p>
</div>
`).join('')}
<div style="margin-top: 20px; padding: 15px; background: #f9f9f9; border-radius: 5px;">
<p><strong>提示:</strong>切换标签会添加历史记录,可以使用浏览器后退按钮返回上一个标签。</p>
</div>
`;
}
// 渲染关于页
function renderAbout() {
return `
<h2>关于历史记录API</h2>
<div style="margin: 20px 0;">
<h3>什么是History API?</h3>
<p>History API是HTML5提供的接口,允许Web应用与浏览器历史记录进行交互。</p>
</div>
<div style="margin: 20px 0;">
<h3>主要方法</h3>
<ul>
<li><strong>pushState()</strong> - 添加新的历史记录</li>
<li><strong>replaceState()</strong> - 替换当前历史记录</li>
<li><strong>back()</strong> - 后退</li>
<li><strong>forward()</strong> - 前进</li>
<li><strong>go()</strong> - 跳转到指定历史记录</li>
</ul>
</div>
<div style="margin: 20px 0;">
<h3>主要事件</h3>
<ul>
<li><strong>popstate</strong> - 历史记录变化时触发</li>
</ul>
</div>
<div style="margin: 20px 0;">
<h3>应用场景</h3>
<ul>
<li>单页应用(SPA)路由</li>
<li>无刷新页面切换</li>
<li>状态保存与恢复</li>
<li>分页导航</li>
</ul>
</div>
`;
}
// 路由类
class Router {
constructor() {
this.routes = routes;
this.contentEl = document.getElementById('mainContent');
this.init();
}
init() {
// 监听导航点击
document.querySelectorAll('.main-nav a[data-route]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const route = e.target.dataset.route;
this.navigate(route);
});
});
// 监听历史记录变化
window.addEventListener('popstate', (e) => {
if (e.state) {
this.render(e.state.route, e.state);
} else {
this.navigate('home', true);
}
});
// 初始渲染
const initialRoute = this.getRouteFromURL();
this.navigate(initialRoute, true);
}
navigate(route, replace = false, state = {}) {
const url = route === 'home' ? '/' : `/${route}`;
const fullState = { route, ...state };
if (replace) {
history.replaceState(fullState, '', url);
} else {
history.pushState(fullState, '', url);
}
this.render(route, fullState);
}
render(route, state = {}) {
const routeConfig = this.routes[route];
if (routeConfig) {
document.title = routeConfig.title + ' - 东巴文';
this.contentEl.innerHTML = routeConfig.render(state);
// 更新导航激活状态
this.updateNavActive(route);
// 更新信息显示
this.updateInfo(state);
}
}
updateNavActive(route) {
document.querySelectorAll('.main-nav a[data-route]').forEach(link => {
if (link.dataset.route === route) {
link.classList.add('active');
} else {
link.classList.remove('active');
}
});
}
updateInfo(state) {
document.getElementById('currentUrl').textContent = location.href;
document.getElementById('historyLength').textContent = history.length;
document.getElementById('currentState').textContent = JSON.stringify(state);
}
getRouteFromURL() {
const path = location.pathname;
const route = path.substring(1) || 'home';
return this.routes[route] ? route : 'home';
}
}
// 初始化路由
const router = new Router();
// 切换页面
function changePage(page) {
const totalPages = Math.ceil(products.length / appState.products.perPage);
if (page >= 1 && page <= totalPages) {
appState.products.page = page;
router.navigate('products', false, { page });
}
}
// 切换标签
function changeTab(tabId) {
appState.tabs.activeTab = tabId;
router.navigate('tabs', false, { activeTab: tabId });
}
</script>
</body>
</html>
// 推荐:使用pushState
history.pushState({ page: 1 }, '', '/page/1');
// URL: https://example.com/page/1
// 不推荐:使用hash
location.hash = '#page/1';
// URL: https://example.com/#page/1
东巴文点评:pushState提供更干净的URL,更符合RESTful风格,对SEO更友好。
// 推荐:处理初始URL
window.addEventListener('DOMContentLoaded', function() {
const route = getRouteFromURL();
renderPage(route);
});
// 不推荐:忽略初始URL
window.addEventListener('DOMContentLoaded', function() {
renderPage('home'); // 总是渲染首页
});
// 推荐:保存必要状态
history.pushState({
page: 'detail',
id: 123,
scrollPosition: window.scrollY
}, '', '/detail/123');
// 不推荐:保存过多数据
history.pushState({
page: 'detail',
data: hugeDataObject, // 大数据对象
dom: element // DOM元素(无法序列化)
}, '', '/detail');
// 推荐:检查API支持
if (window.history && history.pushState) {
// 使用History API
setupSPA();
} else {
// 降级到传统导航
window.location.href = url;
}
问题1:以下哪个方法会触发popstate事件?
A. history.pushState()
B. history.replaceState()
C. history.back()
D. history.go(0)
答案:C
东巴文解释:pushState和replaceState不会触发popstate事件。只有浏览器前进后退(如back()、forward()、go())或用户点击前进后退按钮时才会触发popstate事件。
问题2:pushState和replaceState的主要区别是?
A. pushState会刷新页面,replaceState不会
B. pushState添加新记录,replaceState替换当前记录
C. pushState可以跨域,replaceState不能
D. 没有区别
答案:B
东巴文解释:pushState会在历史记录中添加新记录,用户可以后退;replaceState会替换当前历史记录,用户不能后退到之前的页面。两者都不会刷新页面,都必须同源。
任务:创建一个简单的图片浏览器,支持前进后退导航,使用History API保存当前图片索引。
<details> <summary>点击查看参考答案</summary><!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>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
text-align: center;
}
.image-container {
position: relative;
width: 100%;
height: 400px;
background: #f5f5f5;
border-radius: 10px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.image-container img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
.controls button {
padding: 10px 30px;
font-size: 16px;
border: none;
background: #667eea;
color: white;
border-radius: 5px;
cursor: pointer;
}
.controls button:hover {
background: #5568d3;
}
.controls button:disabled {
background: #ccc;
cursor: not-allowed;
}
.info {
margin-top: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 5px;
}
.thumbnails {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.thumbnail {
width: 60px;
height: 60px;
border-radius: 5px;
overflow: hidden;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s;
}
.thumbnail:hover {
border-color: #667eea;
}
.thumbnail.active {
border-color: #667eea;
}
.thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
</head>
<body>
<h1>图片浏览器</h1>
<div class="image-container">
<img id="currentImage" src="" alt="">
</div>
<div class="controls">
<button id="prevBtn" onclick="prevImage()">上一张</button>
<button id="nextBtn" onclick="nextImage()">下一张</button>
</div>
<div class="info">
<p>当前: <span id="currentIndex">1</span> / <span id="totalCount">0</span></p>
<p>提示: 使用浏览器前进后退按钮或按钮导航</p>
</div>
<div class="thumbnails" id="thumbnails"></div>
<script>
// 图片数据
const images = [
{ id: 1, url: 'https://picsum.photos/800/400?random=1', title: '图片1' },
{ id: 2, url: 'https://picsum.photos/800/400?random=2', title: '图片2' },
{ id: 3, url: 'https://picsum.photos/800/400?random=3', title: '图片3' },
{ id: 4, url: 'https://picsum.photos/800/400?random=4', title: '图片4' },
{ id: 5, url: 'https://picsum.photos/800/400?random=5', title: '图片5' }
];
let currentIndex = 0;
// 初始化
function init() {
// 渲染缩略图
const thumbnailsEl = document.getElementById('thumbnails');
thumbnailsEl.innerHTML = images.map((img, index) => `
<div class="thumbnail ${index === 0 ? 'active' : ''}"
onclick="goToImage(${index})">
<img src="${img.url}" alt="${img.title}">
</div>
`).join('');
// 更新总数
document.getElementById('totalCount').textContent = images.length;
// 监听历史记录变化
window.addEventListener('popstate', function(e) {
if (e.state && typeof e.state.index === 'number') {
showImage(e.state.index, false);
}
});
// 从URL获取初始索引
const initialIndex = getIndexFromURL();
showImage(initialIndex, false);
history.replaceState({ index: initialIndex }, '', `?image=${initialIndex + 1}`);
}
// 显示图片
function showImage(index, addToHistory = true) {
if (index < 0 || index >= images.length) return;
currentIndex = index;
const image = images[index];
// 更新图片
document.getElementById('currentImage').src = image.url;
document.getElementById('currentImage').alt = image.title;
// 更新索引显示
document.getElementById('currentIndex').textContent = index + 1;
// 更新按钮状态
document.getElementById('prevBtn').disabled = index === 0;
document.getElementById('nextBtn').disabled = index === images.length - 1;
// 更新缩略图激活状态
document.querySelectorAll('.thumbnail').forEach((thumb, i) => {
thumb.classList.toggle('active', i === index);
});
// 添加历史记录
if (addToHistory) {
history.pushState({ index }, '', `?image=${index + 1}`);
}
}
// 上一张
function prevImage() {
if (currentIndex > 0) {
showImage(currentIndex - 1);
}
}
// 下一张
function nextImage() {
if (currentIndex < images.length - 1) {
showImage(currentIndex + 1);
}
}
// 跳转到指定图片
function goToImage(index) {
showImage(index);
}
// 从URL获取索引
function getIndexFromURL() {
const params = new URLSearchParams(location.search);
const imageParam = params.get('image');
if (imageParam) {
const index = parseInt(imageParam) - 1;
if (index >= 0 && index < images.length) {
return index;
}
}
return 0;
}
// 键盘导航
document.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
prevImage();
} else if (e.key === 'ArrowRight') {
nextImage();
}
});
// 初始化
init();
</script>
</body>
</html>
</details>
东巴文(db-w.cn) - 让编程学习更有趣、更高效!