阅读位置记忆功能demo
功能说明
1. 自动保存:滚动页面时,系统会自动保存当前阅读位置(防抖处理,每秒保存一次)
2. 自动恢复:重新打开页面时,会自动跳转到上次阅读的位置
3. 手动控制:
-
手动保存当前位置
-
手动跳转到上次保存的位置
-
清除保存的记录
4. 视图反馈
-
状态指示器显示保存状态
-
恢复时高亮显示当前章节
-
显示滚动进度条
-
侧边位置标记显示当前章节和进度
使用方法
-
直接复制上面的代码到HTML文件中
-
用浏览器打开该文件
-
滚动页面阅读内容
-
刷新页面或关闭后重新打开,页面会自动跳转到上次阅读的位置

xml
<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
padding: 30px 0;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto;
}
.control-panel {
background-color: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 30px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background-color: #4a6ee0;
color: white;
}
.btn-primary:hover {
background-color: #3a5ed0;
transform: translateY(-2px);
}
.btn-success {
background-color: #10b981;
color: white;
}
.btn-success:hover {
background-color: #0da271;
transform: translateY(-2px);
}
.btn-warning {
background-color: #f59e0b;
color: white;
}
.btn-warning:hover {
background-color: #e5900a;
transform: translateY(-2px);
}
.status-indicator {
display: flex;
align-items: center;
padding: 12px 20px;
background-color: #f8fafc;
border-radius: 5px;
font-weight: 500;
}
.indicator-dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 10px;
background-color: #6b7280;
}
.indicator-dot.active {
background-color: #10b981;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.content {
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.chapter {
margin-bottom: 40px;
padding-bottom: 30px;
border-bottom: 1px solid #e5e7eb;
}
.chapter:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.chapter-title {
font-size: 1.8rem;
color: #1f2937;
margin-bottom: 20px;
padding-left: 15px;
border-left: 5px solid #4a6ee0;
}
.chapter-content {
font-size: 1.05rem;
}
.chapter-content p {
margin-bottom: 15px;
text-align: justify;
}
.highlight {
background-color: rgba(255, 255, 0, 0.3);
transition: background-color 0.5s ease;
}
footer {
text-align: center;
padding: 20px;
color: #6b7280;
font-size: 0.9rem;
}
.position-marker {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
background-color: rgba(74, 110, 224, 0.9);
color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 100;
width: 180px;
text-align: center;
display: none;
}
.position-marker h3 {
font-size: 1rem;
margin-bottom: 8px;
}
.position-info {
font-size: 1.2rem;
font-weight: bold;
}
.scroll-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 4px;
background-color: #e5e7eb;
z-index: 1000;
}
.scroll-progress-bar {
height: 100%;
background: linear-gradient(90deg, #6a11cb 0%, #2575fc 100%);
width: 0%;
transition: width 0.2s ease;
}
@media (max-width: 768px) {
.container {
padding: 15px;
}
h1 {
font-size: 2rem;
}
.control-panel {
flex-direction: column;
align-items: stretch;
}
.btn {
justify-content: center;
}
.position-marker {
display: none !important;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- 滚动进度条 -->
<div class="scroll-progress">
<div class="scroll-progress-bar"></div>
</div>
<!-- 位置标记 -->
<div class="position-marker">
<h3>上次阅读位置</h3>
<div class="position-info">第 <span id="chapter-num">0</span> 章</div>
<div class="position-info" id="position-percent">0%</div>
</div>
<div class="container">
<header>
<h1><i class="fas fa-book-bookmark"></i> 阅读位置记忆功能</h1>
<p class="subtitle">离开页面后,系统会自动保存您的阅读位置。重新打开时,会自动跳转到上次阅读的位置。</p>
</header>
<div class="control-panel">
<div class="status-indicator">
<span class="indicator-dot" id="status-dot"></span>
<span id="status-text">状态:未检测到历史记录</span>
</div>
<button class="btn btn-primary" id="save-btn">
<i class="fas fa-save"></i> 手动保存当前位置
</button>
<button class="btn btn-success" id="jump-btn">
<i class="fas fa-arrow-right"></i> 跳转到上次位置
</button>
<button class="btn btn-warning" id="clear-btn">
<i class="fas fa-trash-alt"></i> 清除记录
</button>
</div>
<div class="content" id="content">
<!-- 内容将通过JavaScript生成 -->
</div>
<footer>
<p>© 2025 阅读位置记忆演示 | 使用 localStorage 实现位置记忆功能</p>
<p>尝试滚动页面,然后刷新或关闭页面,重新打开时会自动跳转到上次阅读的位置。</p>
</footer>
</div>
<script>
// 生成示例内容
const chapters = [
{
title: "第一章:初识前端开发",
content: `前端开发是创建Web页面或App等前端界面呈现给用户的过程。通过HTML、CSS及JavaScript以及衍生出来的各种技术、框架、解决方案,来实现互联网产品的用户界面交互。
随着互联网技术的发展,HTML5、CSS3、ES6等现代前端技术的应用,使得前端开发能够实现更丰富的交互和更好的用户体验。前端工程师需要与设计师、后端工程师协作,完成产品的前端开发工作。
前端开发领域技术更新迅速,开发者需要不断学习新技术、新框架,以适应快速发展的行业需求。React、Vue、Angular等框架的出现,大大提高了前端开发的效率。`
},
{
title: "第二章:JavaScript的核心概念",
content: `JavaScript是一种具有函数优先的轻量级、解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中。
变量作用域、闭包、原型链、异步编程等是JavaScript的核心概念。理解这些概念对于编写高质量JavaScript代码至关重要。
ES6引入了许多新特性,如let和const声明、箭头函数、模板字符串、解构赋值、Promise等,这些特性使得JavaScript更加强大和易用。现代前端开发几乎都基于ES6及以上版本。`
},
{
title: "第三章:DOM操作与事件处理",
content: `文档对象模型(DOM)是HTML和XML文档的编程接口。它提供了对文档的结构化表述,并定义了一种方式可以使程序对该结构进行访问,从而改变文档的结构、样式和内容。
DOM将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。
事件处理是前端交互的核心。JavaScript通过事件监听器来响应用户的操作,如点击、悬停、滚动等。事件委托是一种常用的优化技术,它利用事件冒泡机制,将事件监听器添加到父元素上,而不是每个子元素上。`
},
{
title: "第四章:现代前端框架",
content: `React、Vue和Angular是目前最流行的三大前端框架。它们都采用了组件化的开发模式,将UI拆分为独立可复用的代码片段,并对每个片段进行独立构思。
React由Facebook开发,以其虚拟DOM和单向数据流而闻名。Vue由尤雨溪创建,以其渐进式框架和易用性受到开发者喜爱。Angular由Google维护,是一个完整的企业级框架。
这些框架都提供了状态管理、路由、构建工具等完整的前端开发解决方案。选择哪个框架取决于项目需求、团队技能和个人偏好。`
},
{
title: "第五章:响应式设计与移动优先",
content: `响应式Web设计是一种网页设计方法,使网站能在各种设备(从桌面电脑到移动电话)上很好地工作。其核心是使用弹性网格布局、弹性图片和媒体查询。
移动优先是一种设计策略,首先为移动设备设计网站,然后逐步增强为平板电脑和桌面电脑的设计。这种策略确保网站在小屏幕上有良好的体验。
随着移动设备使用量的增加,响应式设计和移动优先策略变得越来越重要。CSS框架如Bootstrap、Tailwind CSS等提供了实现响应式设计的工具。`
},
{
title: "第六章:前端性能优化",
content: `前端性能优化是提高网站加载速度和响应速度的过程。性能优化的目标包括减少页面加载时间、减少资源大小、优化渲染路径等。
常见的前端性能优化技术包括:代码压缩、图片优化、懒加载、代码分割、缓存策略、减少重绘和回流等。使用Webpack、Rollup等构建工具可以自动化许多优化任务。
性能直接影响用户体验和SEO排名。Google的Core Web Vitals指标已成为衡量网站用户体验的重要标准,包括LCP(最大内容绘制)、FID(首次输入延迟)和CLS(累积布局偏移)。`
},
{
title: "第七章:前端工程化",
content: `前端工程化是指将软件工程的方法和原则应用到前端开发中,以提高开发效率、代码质量和团队协作。它包括构建工具、代码规范、测试、部署等流程。
现代前端工程化通常包括以下工具:包管理器(npm、yarn)、模块打包器(Webpack、Rollup)、编译器(Babel)、代码检查工具(ESLint)、样式预处理(Sass、Less)等。
持续集成/持续部署(CI/CD)也是前端工程化的重要组成部分,它可以自动化测试和部署流程,确保代码质量。`
},
{
title: "第八章:前端未来发展",
content: `前端领域正在快速发展,新技术不断涌现。WebAssembly允许在浏览器中运行高性能代码;Progressive Web Apps(PWA)提供类似原生应用的体验;Web Components实现真正的组件复用。
随着物联网、人工智能和5G技术的发展,前端开发将面临新的机遇和挑战。前端工程师可能需要掌握更多的跨平台开发技能,如React Native、Flutter等。
前端开发的未来将是多元化、全栈化的。前端工程师不仅需要掌握前端技术,还需要了解后端、DevOps、设计等相关知识,以应对日益复杂的产品需求。`
}
];
// 全局变量
let scrollTimeout;
let lastSavedPosition = 0;
let isRestoring = false;
const STORAGE_KEY = 'reading_position';
// DOM元素
const contentEl = document.getElementById('content');
const statusDot = document.getElementById('status-dot');
const statusText = document.getElementById('status-text');
const saveBtn = document.getElementById('save-btn');
const jumpBtn = document.getElementById('jump-btn');
const clearBtn = document.getElementById('clear-btn');
const positionMarker = document.querySelector('.position-marker');
const chapterNumEl = document.getElementById('chapter-num');
const positionPercentEl = document.getElementById('position-percent');
const scrollProgressBar = document.querySelector('.scroll-progress-bar');
// 初始化:生成内容
function initContent() {
let contentHTML = '';
chapters.forEach((chapter, index) => {
contentHTML += `
<div class="chapter" id="chapter-${index + 1}">
<h2 class="chapter-title">${chapter.title}</h2>
<div class="chapter-content">
${chapter.content.split('\n').map(p => `<p>${p}</p>`).join('')}
</div>
</div>
`;
});
contentEl.innerHTML = contentHTML;
}
// 保存阅读位置
function saveReadingPosition() {
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = totalHeight > 0 ? Math.round((scrollPosition / totalHeight) * 100) : 0;
// 计算当前章节
let currentChapter = 1;
const chaptersElements = document.querySelectorAll('.chapter');
for (let i = 0; i < chaptersElements.length; i++) {
const rect = chaptersElements[i].getBoundingClientRect();
if (rect.top <= window.innerHeight / 2) {
currentChapter = i + 1;
}
}
const positionData = {
scrollTop: scrollPosition,
timestamp: new Date().getTime(),
chapter: currentChapter,
percent: scrollPercent
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(positionData));
lastSavedPosition = scrollPosition;
// 更新状态
updateStatus(true);
// 显示保存提示
showNotification('位置已保存!');
console.log('位置已保存:', positionData);
}
// 恢复阅读位置
function restoreReadingPosition() {
const savedData = localStorage.getItem(STORAGE_KEY);
if (!savedData) {
updateStatus(false);
return false;
}
try {
const positionData = JSON.parse(savedData);
isRestoring = true;
// 滚动到保存的位置
window.scrollTo({
top: positionData.scrollTop,
behavior: 'smooth'
});
// 高亮当前章节
highlightCurrentChapter(positionData.chapter);
// 更新状态
updateStatus(true, positionData);
// 显示恢复提示
showNotification(`已恢复到上次阅读位置:第${positionData.chapter}章`);
console.log('位置已恢复:', positionData);
// 重置标志
setTimeout(() => {
isRestoring = false;
}, 1000);
return true;
} catch (error) {
console.error('恢复位置时出错:', error);
updateStatus(false);
return false;
}
}
// 清除保存的位置
function clearSavedPosition() {
localStorage.removeItem(STORAGE_KEY);
updateStatus(false);
positionMarker.style.display = 'none';
showNotification('位置记录已清除');
}
// 更新状态指示器
function updateStatus(hasData, positionData = null) {
if (hasData) {
statusDot.classList.add('active');
if (positionData) {
const timeAgo = Math.round((new Date().getTime() - positionData.timestamp) / (1000 * 60));
statusText.textContent = `状态:已保存 (${timeAgo}分钟前,第${positionData.chapter}章)`;
// 显示位置标记
positionMarker.style.display = 'block';
chapterNumEl.textContent = positionData.chapter;
positionPercentEl.textContent = `${positionData.percent}%`;
} else {
statusText.textContent = '状态:已启用自动保存';
}
} else {
statusDot.classList.remove('active');
statusText.textContent = '状态:未检测到历史记录';
}
}
// 高亮当前章节
function highlightCurrentChapter(chapterIndex) {
// 移除所有高亮
document.querySelectorAll('.chapter').forEach(chapter => {
chapter.classList.remove('highlight');
});
// 高亮当前章节
const currentChapter = document.getElementById(`chapter-${chapterIndex}`);
if (currentChapter) {
currentChapter.classList.add('highlight');
// 5秒后移除高亮
setTimeout(() => {
currentChapter.classList.remove('highlight');
}, 5000);
}
}
// 显示通知
function showNotification(message) {
// 创建通知元素
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background-color: #10b981;
color: white;
padding: 15px 20px;
border-radius: 5px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10000;
font-weight: 500;
transform: translateX(120%);
transition: transform 0.3s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
// 显示通知
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 10);
// 3秒后隐藏并移除
setTimeout(() => {
notification.style.transform = 'translateX(120%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// 更新滚动进度条
function updateScrollProgress() {
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrolled = (winScroll / height) * 100;
scrollProgressBar.style.width = scrolled + "%";
}
// 初始化
function init() {
// 生成内容
initContent();
// 检查是否有保存的位置并尝试恢复
const hasRestored = restoreReadingPosition();
// 如果没有恢复位置,更新状态
if (!hasRestored) {
updateStatus(false);
}
// 事件监听
saveBtn.addEventListener('click', saveReadingPosition);
jumpBtn.addEventListener('click', restoreReadingPosition);
clearBtn.addEventListener('click', clearSavedPosition);
// 监听滚动事件(防抖处理)
window.addEventListener('scroll', () => {
// 更新滚动进度条
updateScrollProgress();
// 如果不是正在恢复位置,则保存位置
if (!isRestoring) {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(saveReadingPosition, 1000);
}
// 检测当前章节
const chaptersElements = document.querySelectorAll('.chapter');
let currentChapter = 1;
for (let i = 0; i < chaptersElements.length; i++) {
const rect = chaptersElements[i].getBoundingClientRect();
if (rect.top <= window.innerHeight / 2) {
currentChapter = i + 1;
}
}
// 更新位置标记
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
const scrollPercent = totalHeight > 0 ? Math.round((scrollPosition / totalHeight) * 100) : 0;
chapterNumEl.textContent = currentChapter;
positionPercentEl.textContent = `${scrollPercent}%`;
});
// 页面卸载前保存位置
window.addEventListener('beforeunload', () => {
saveReadingPosition();
});
// 初始化滚动进度条
updateScrollProgress();
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
