forked from Gitlink/gitlink_help_center
166 lines
5.1 KiB
JavaScript
166 lines
5.1 KiB
JavaScript
import { useEffect, useCallback } from 'react';
|
||
import { useLocation } from '@docusaurus/router';
|
||
|
||
const ReadingPosition = () => {
|
||
const location = useLocation();
|
||
|
||
// 保存滚动位置
|
||
const saveScrollPosition = useCallback(() => {
|
||
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
|
||
const currentPath = location.pathname;
|
||
|
||
// 使用localStorage保存每个页面的滚动位置
|
||
const savedPositions = JSON.parse(localStorage.getItem('docReadingPositions') || '{}');
|
||
savedPositions[currentPath] = {
|
||
scrollTop: scrollPosition,
|
||
timestamp: Date.now(),
|
||
};
|
||
|
||
// 只保留最近30天的阅读位置
|
||
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
||
Object.keys(savedPositions).forEach(path => {
|
||
if (savedPositions[path].timestamp < thirtyDaysAgo) {
|
||
delete savedPositions[path];
|
||
}
|
||
});
|
||
|
||
localStorage.setItem('docReadingPositions', JSON.stringify(savedPositions));
|
||
}, [location.pathname]);
|
||
|
||
// 恢复滚动位置
|
||
const restoreScrollPosition = useCallback(() => {
|
||
const currentPath = location.pathname;
|
||
const savedPositions = JSON.parse(localStorage.getItem('docReadingPositions') || '{}');
|
||
|
||
if (savedPositions[currentPath]) {
|
||
const { scrollTop, timestamp } = savedPositions[currentPath];
|
||
|
||
// 如果保存的位置是在最近7天内的,才恢复位置
|
||
const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
||
if (timestamp > sevenDaysAgo && scrollTop > 100) { // 只有滚动超过100px才恢复
|
||
// 延迟恢复,确保页面完全加载
|
||
setTimeout(() => {
|
||
window.scrollTo({
|
||
top: scrollTop,
|
||
behavior: 'smooth'
|
||
});
|
||
|
||
// 显示提示信息
|
||
showPositionNotification(scrollTop);
|
||
}, 300);
|
||
}
|
||
}
|
||
}, [location.pathname]);
|
||
|
||
// 显示位置恢复提示
|
||
const showPositionNotification = (scrollTop) => {
|
||
// 创建提示元素
|
||
const notification = document.createElement('div');
|
||
notification.innerHTML = `
|
||
<div style="
|
||
position: fixed;
|
||
top: 80px;
|
||
right: 20px;
|
||
background: #25c2a0;
|
||
color: white;
|
||
padding: 12px 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
z-index: 9999;
|
||
font-size: 14px;
|
||
max-width: 280px;
|
||
animation: slideInRight 0.3s ease-out;
|
||
">
|
||
<div style="display: flex; align-items: center; gap: 8px;">
|
||
<span>📖</span>
|
||
<span>已恢复到上次阅读位置</span>
|
||
<button onclick="this.parentElement.parentElement.parentElement.remove()"
|
||
style="background: none; border: none; color: white; font-size: 16px; cursor: pointer; margin-left: auto;">×</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
// 添加动画样式
|
||
if (!document.querySelector('#reading-position-styles')) {
|
||
const styles = document.createElement('style');
|
||
styles.id = 'reading-position-styles';
|
||
styles.textContent = `
|
||
@keyframes slideInRight {
|
||
from {
|
||
transform: translateX(100%);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
`;
|
||
document.head.appendChild(styles);
|
||
}
|
||
|
||
// 3秒后自动隐藏
|
||
setTimeout(() => {
|
||
if (notification.parentElement) {
|
||
notification.remove();
|
||
}
|
||
}, 3000);
|
||
};
|
||
|
||
// 节流函数
|
||
const throttle = (func, delay) => {
|
||
let timeoutId;
|
||
let lastExecTime = 0;
|
||
return function (...args) {
|
||
const currentTime = Date.now();
|
||
|
||
if (currentTime - lastExecTime > delay) {
|
||
func.apply(this, args);
|
||
lastExecTime = currentTime;
|
||
} else {
|
||
clearTimeout(timeoutId);
|
||
timeoutId = setTimeout(() => {
|
||
func.apply(this, args);
|
||
lastExecTime = Date.now();
|
||
}, delay - (currentTime - lastExecTime));
|
||
}
|
||
};
|
||
};
|
||
|
||
useEffect(() => {
|
||
// 页面加载时恢复滚动位置
|
||
restoreScrollPosition();
|
||
|
||
// 监听滚动事件,节流保存位置
|
||
const throttledSavePosition = throttle(saveScrollPosition, 1000);
|
||
window.addEventListener('scroll', throttledSavePosition);
|
||
|
||
// 页面卸载时保存位置
|
||
const handleBeforeUnload = () => {
|
||
saveScrollPosition();
|
||
};
|
||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||
|
||
return () => {
|
||
window.removeEventListener('scroll', throttledSavePosition);
|
||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||
};
|
||
}, [saveScrollPosition, restoreScrollPosition]);
|
||
|
||
// 路径变化时保存当前位置并尝试恢复新位置
|
||
useEffect(() => {
|
||
saveScrollPosition();
|
||
// 短暂延迟后恢复新页面的位置
|
||
const timer = setTimeout(() => {
|
||
restoreScrollPosition();
|
||
}, 100);
|
||
|
||
return () => clearTimeout(timer);
|
||
}, [location.pathname, saveScrollPosition, restoreScrollPosition]);
|
||
|
||
return null; // 这是一个功能性组件,不渲染任何内容
|
||
};
|
||
|
||
export default ReadingPosition;
|