您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
带历史记录管理的位置记忆器(支持拖拽)
// ==UserScript== // @name PageMemory // @namespace scroll-historian.js // @version 2.2 // @description 带历史记录管理的位置记忆器(支持拖拽) // @author QWAS-zx // @match *://*/* // @match about:srcdoc // @match file:///* // @license MIT // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY = 'Global_Position_History'; const HELPER_POSITION_KEY = 'Global_Helper_Position'; let menuVisible = false; let historyVisible = false; let isDragging = false; let dragStartX, dragStartY; let initialX, initialY; // 添加全局样式 GM_addStyle(` /* 主按钮和菜单样式 */ #mdn-position-helper { position: fixed; bottom: 25px; right: 25px; z-index: 10000; cursor: grab; } #mdn-position-helper.dragging { cursor: grabbing; opacity: 0.9; box-shadow: 0 0 15px rgba(38, 139, 210, 0.8); } #mdn-main-btn { width: 55px; height: 55px; border-radius: 50%; background: #002b36; color: #fdf6e3; border: 2px solid #268bd2; display: flex; align-items: center; justify-content: center; font-weight: bold; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.25); transition: all 0.3s; user-select: none; } #mdn-main-btn:hover { transform: scale(1.1); background: #073642; } #mdn-action-menu { position: absolute; bottom: 70px; right: 0; width: 180px; background: #002b36; border: 1px solid #268bd2; border-radius: 8px; padding: 10px 0; box-shadow: 0 5px 15px rgba(0,0,0,0.3); display: none; z-index: 10001; } .mdn-menu-item { padding: 10px 15px; color: #fdf6e3; cursor: pointer; transition: background 0.2s; display: flex; align-items: center; } .mdn-menu-item:hover { background: #073642; } /* 历史记录面板样式 */ #mdn-history-panel { position: fixed; bottom: 100px; right: 30px; width: 320px; max-height: 60vh; background: #002b36; border: 1px solid #268bd2; border-radius: 8px; box-shadow: 0 8px 30px rgba(0,0,0,0.5); z-index: 10002; display: none; overflow: hidden; font-family: Arial, sans-serif; } #mdn-history-header { padding: 15px; background: #073642; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #268bd2; } #mdn-history-title { font-size: 1.2em; font-weight: bold; color: #268bd2; } #mdn-clear-history { background: #dc322f; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer; transition: background 0.3s; } #mdn-clear-history:hover { background: #ff4136; } #mdn-history-list { padding: 10px; overflow-y: auto; max-height: calc(60vh - 100px); } .mdn-history-item { padding: 12px; margin-bottom: 10px; background: rgba(255,255,255,0.05); border-radius: 6px; border-left: 3px solid #268bd2; cursor: pointer; transition: all 0.3s; } .mdn-history-item:hover { background: rgba(38, 139, 210, 0.15); transform: translateX(-3px); } .mdn-history-title { font-weight: bold; margin-bottom: 5px; color: #fdf6e3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .mdn-history-meta { display: flex; justify-content: space-between; font-size: 0.85em; color: #93a1a1; } .mdn-history-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; } .mdn-restore-btn, .mdn-delete-btn { padding: 4px 10px; border-radius: 4px; font-size: 0.85em; cursor: pointer; } .mdn-restore-btn { background: rgba(38, 139, 210, 0.3); color: #268bd2; } .mdn-delete-btn { background: rgba(220, 50, 47, 0.3); color: #dc322f; } /* 标记线 */ #mdn-position-marker { position: absolute; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, transparent, #ff4136, transparent); z-index: 9999; pointer-events: none; display: none; } /* 通知样式 */ #mdn-position-notify { position: fixed; bottom: 100px; right: 30px; background: rgba(0, 43, 54, 0.9); color: #fdf6e3; border: 1px solid #268bd2; padding: 12px 18px; border-radius: 8px; z-index: 10001; max-width: 300px; backdrop-filter: blur(4px); animation: fadeIn 0.3s; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } `); // 创建主容器 const helperContainer = document.createElement('div'); helperContainer.id = 'mdn-position-helper'; document.body.appendChild(helperContainer); // 创建主按钮 const mainBtn = document.createElement('div'); mainBtn.id = 'mdn-main-btn'; mainBtn.textContent = '📌'; helperContainer.appendChild(mainBtn); // 创建菜单 const actionMenu = document.createElement('div'); actionMenu.id = 'mdn-action-menu'; helperContainer.appendChild(actionMenu); // 创建保存按钮 const saveBtn = document.createElement('div'); saveBtn.className = 'mdn-menu-item'; saveBtn.innerHTML = '<span style="margin-right:8px">💾</span> 保存当前位置'; actionMenu.appendChild(saveBtn); // 创建历史记录按钮 const historyBtn = document.createElement('div'); historyBtn.className = 'mdn-menu-item'; historyBtn.innerHTML = '<span style="margin-right:8px">📋</span> 历史记录'; actionMenu.appendChild(historyBtn); // 创建历史记录面板 const historyPanel = document.createElement('div'); historyPanel.id = 'mdn-history-panel'; historyPanel.innerHTML = ` <div id="mdn-history-header"> <div id="mdn-history-title">保存的位置历史</div> <button id="mdn-clear-history">清空记录</button> </div> <div id="mdn-history-list"></div> `; document.body.appendChild(historyPanel); // 创建位置标记线 const positionMarker = document.createElement('div'); positionMarker.id = 'mdn-position-marker'; document.body.appendChild(positionMarker); // 获取历史记录 function getHistory() { return GM_getValue(STORAGE_KEY, []); } // 保存历史记录 function saveHistory(history) { GM_setValue(STORAGE_KEY, history); } // 添加新记录 function addNewRecord() { const history = getHistory(); const newRecord = { id: Date.now(), url: window.location.href, path: window.location.pathname, scrollY: window.scrollY, timestamp: Date.now(), pageTitle: document.title, scrollPercent: getScrollPercentage() }; // 添加到历史记录开头 history.unshift(newRecord); // 只保留最近的20条记录 if (history.length > 20) history.pop(); saveHistory(history); showNotification(`📍 位置已保存!<br>${newRecord.scrollPercent}%`); updateHistoryUI(); } // 删除记录 function deleteRecord(id) { const history = getHistory(); const newHistory = history.filter(record => record.id !== id); saveHistory(newHistory); updateHistoryUI(); showNotification('🗑️ 记录已删除'); } // 清空历史 function clearHistory() { saveHistory([]); updateHistoryUI(); showNotification('🧹 历史记录已清空'); hideHistoryPanel(); } // 恢复记录 function restoreRecord(record) { // 显示位置标记线 positionMarker.style.display = 'block'; positionMarker.style.top = `${record.scrollY}px`; setTimeout(() => positionMarker.style.display = 'none', 3000); if (window.location.href === record.url) { window.scrollTo({ top: record.scrollY, behavior: 'smooth' }); showNotification(`↩️ 已恢复位置!<br>${record.scrollPercent}%`); } else { showNotification(`⏳ 正在跳转到保存的页面...`); setTimeout(() => { window.location.href = record.url; // 存储记录以便新页面加载后滚动 GM_setValue('Global_Pending_Restore', record); }, 500); } hideHistoryPanel(); } // 更新历史记录UI function updateHistoryUI() { const history = getHistory(); const historyList = document.getElementById('mdn-history-list'); if (history.length === 0) { historyList.innerHTML = `<div style="padding:20px; text-align:center; color:#93a1a1;"> 暂无保存的位置记录 </div>`; return; } historyList.innerHTML = ''; history.forEach(record => { const item = document.createElement('div'); item.className = 'mdn-history-item'; item.innerHTML = ` <div class="mdn-history-title">${record.pageTitle}</div> <div class="mdn-history-meta"> <span>${formatTime(record.timestamp)}</span> <span>${record.scrollPercent}%</span> </div> <div class="mdn-history-actions"> <div class="mdn-restore-btn">恢复</div> <div class="mdn-delete-btn">删除</div> </div> `; // 添加事件监听 item.querySelector('.mdn-restore-btn').addEventListener('click', (e) => { e.stopPropagation(); restoreRecord(record); }); item.querySelector('.mdn-delete-btn').addEventListener('click', (e) => { e.stopPropagation(); deleteRecord(record.id); }); item.addEventListener('click', () => { restoreRecord(record); }); historyList.appendChild(item); }); } // 格式化时间 function formatTime(timestamp) { const date = new Date(timestamp); return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; } // 获取滚动百分比 function getScrollPercentage(scrollY = window.scrollY) { const totalHeight = document.documentElement.scrollHeight - window.innerHeight; return totalHeight > 0 ? Math.round((scrollY / totalHeight) * 100) : 0; } // 显示通知 function showNotification(message) { const existingNote = document.getElementById('mdn-position-notify'); if (existingNote) existingNote.remove(); const notification = document.createElement('div'); notification.id = 'mdn-position-notify'; notification.innerHTML = message; document.body.appendChild(notification); setTimeout(() => notification.remove(), 3000); } // 显示菜单 function showMenu() { menuVisible = true; actionMenu.style.display = 'block'; hideHistoryPanel(); } // 隐藏菜单 function hideMenu() { menuVisible = false; actionMenu.style.display = 'none'; } // 显示历史面板 function showHistoryPanel() { historyVisible = true; historyPanel.style.display = 'block'; updateHistoryUI(); hideMenu(); } // 隐藏历史面板 function hideHistoryPanel() { historyVisible = false; historyPanel.style.display = 'none'; } // 切换菜单显示 function toggleMenu() { if (menuVisible) { hideMenu(); } else { showMenu(); } } // 切换历史面板显示 function toggleHistory() { if (historyVisible) { hideHistoryPanel(); } else { showHistoryPanel(); } } // 加载保存的位置 function loadSavedPosition() { const savedPos = GM_getValue(HELPER_POSITION_KEY, null); if (savedPos) { helperContainer.style.left = savedPos.left; helperContainer.style.top = savedPos.top; helperContainer.style.right = 'auto'; helperContainer.style.bottom = 'auto'; } } // 保存当前位置 function saveCurrentPosition() { const rect = helperContainer.getBoundingClientRect(); const pos = { left: `${rect.left}px`, top: `${rect.top}px` }; GM_setValue(HELPER_POSITION_KEY, pos); } // 主按钮点击事件 mainBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleMenu(); }); // 菜单按钮事件 saveBtn.addEventListener('click', (e) => { e.stopPropagation(); addNewRecord(); hideMenu(); }); historyBtn.addEventListener('click', (e) => { e.stopPropagation(); toggleHistory(); }); // 清空历史按钮 document.getElementById('mdn-clear-history').addEventListener('click', (e) => { e.stopPropagation(); clearHistory(); }); // 点击页面其他区域关闭所有面板 document.addEventListener('click', (e) => { if (!helperContainer.contains(e.target) && !historyPanel.contains(e.target)) { hideMenu(); hideHistoryPanel(); } }); // 检查是否有待恢复的记录(跨页面恢复) const pendingRestore = GM_getValue('Global_Pending_Restore', null); if (pendingRestore) { setTimeout(() => { window.scrollTo({ top: pendingRestore.scrollY, behavior: 'smooth' }); showNotification(`✅ 位置已恢复!<br>${pendingRestore.pageTitle}`); GM_setValue('Global_Pending_Restore', null); }, 1000); } // 初始化历史记录 updateHistoryUI(); // 初始化位置 loadSavedPosition(); // ============================== // 拖拽功能实现 // ============================== mainBtn.addEventListener('mousedown', startDrag); function startDrag(e) { if (e.button !== 0) return; // 只处理左键点击 // 防止拖拽时触发其他事件 e.preventDefault(); e.stopPropagation(); // 记录初始位置 isDragging = true; dragStartX = e.clientX; dragStartY = e.clientY; // 获取当前helperContainer的位置 const rect = helperContainer.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; // 添加拖拽样式 helperContainer.classList.add('dragging'); // 添加事件监听 document.addEventListener('mousemove', doDrag); document.addEventListener('mouseup', stopDrag); // 关闭菜单 hideMenu(); hideHistoryPanel(); } function doDrag(e) { if (!isDragging) return; // 计算偏移量 const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; // 更新位置 helperContainer.style.left = `${initialX + dx}px`; helperContainer.style.top = `${initialY + dy}px`; helperContainer.style.right = 'auto'; helperContainer.style.bottom = 'auto'; } function stopDrag() { if (!isDragging) return; isDragging = false; helperContainer.classList.remove('dragging'); // 保存位置 saveCurrentPosition(); // 移除事件监听 document.removeEventListener('mousemove', doDrag); document.removeEventListener('mouseup', stopDrag); } })();