// ==UserScript==
// @name Reading Ruler 阅读标尺
// @namespace http://tampermonkey.net/
// @version 0.1
// @description A reading ruler tool to help focus while reading
// @author lumos momo
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @license GPL-3.0-or-later
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 默认设置
const defaultSettings = {
height: 30,
color: '#ffeb3b',
opacity: 0.3,
isEnabled: false,
isInverted: false,
position: { x: 20, y: '50%' }
};
// 获取保存的设置
let settings = {
...defaultSettings,
...GM_getValue('rulerSettings', {})
};
// 创建样式
const style = document.createElement('style');
style.textContent = `
.reading-ruler {
position: fixed;
left: 0;
width: 100%;
height: ${settings.height}px;
pointer-events: none;
z-index: 9999;
transition: top 0.1s ease;
display: none;
}
.reading-ruler.normal {
background-color: ${settings.color};
opacity: ${settings.opacity};
}
.reading-ruler.inverted {
background-color: transparent;
box-shadow: 0 0 0 100vh ${settings.color};
position: fixed;
left: 0;
right: 0;
width: 100%;
}
.ruler-control {
position: fixed;
left: ${settings.position.x}px;
top: ${settings.position.y};
transform: translateY(-50%);
z-index: 10000;
cursor: move;
user-select: none;
}
.ruler-toggle {
width: 48px;
height: 48px;
border-radius: 50%;
background: white;
border: none;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
font-size: 20px;
font-weight: bold;
color: #666;
}
.ruler-toggle:hover {
background-color: #f5f5f5;
}
.ruler-toggle.active {
background-color: #e3f2fd;
color: #2196f3;
}
.ruler-settings {
position: absolute;
top: 0;
left: 100%;
margin-left: 10px;
background: white;
border-radius: 4px;
padding: 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
display: none;
width: 200px;
}
.ruler-settings.visible {
display: block;
}
.ruler-settings label {
display: block;
margin: 10px 0;
font-size: 14px;
}
.ruler-settings input {
width: 100%;
margin-top: 5px;
}
.ruler-settings .mode-switch {
display: flex;
align-items: center;
margin: 10px 0;
padding: 8px 0;
border-top: 1px solid #eee;
}
.ruler-settings .mode-switch span {
flex-grow: 1;
font-size: 14px;
}
.mode-switch-toggle {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.mode-switch-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.mode-switch-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 20px;
}
.mode-switch-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
.mode-switch-toggle input:checked + .mode-switch-slider {
background-color: #2196F3;
}
.mode-switch-toggle input:checked + .mode-switch-slider:before {
transform: translateX(20px);
}
`;
document.head.appendChild(style);
// 创建标尺元素
const ruler = document.createElement('div');
ruler.className = 'reading-ruler';
document.body.appendChild(ruler);
// 创建控制面板
const control = document.createElement('div');
control.className = 'ruler-control';
control.innerHTML = `
`;
document.body.appendChild(control);
// 获取所有需要的元素
const toggleButton = document.getElementById('toggleRuler');
const modeSwitch = document.getElementById('toggleMode');
const settingsPanel = control.querySelector('.ruler-settings');
// 拖动功能实现
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = settings.position.x;
let yOffset = parseInt(settings.position.y) || window.innerHeight / 2;
function dragStart(e) {
if (e.type === "mousedown") {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
}
if (e.target.closest('.ruler-toggle')) {
isDragging = true;
}
}
function dragEnd(e) {
initialX = currentX;
initialY = currentY;
isDragging = false;
settings.position = {
x: xOffset,
y: yOffset + 'px'
};
saveSettings();
}
function drag(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
control.style.left = currentX + 'px';
control.style.top = currentY + 'px';
control.style.transform = 'none';
}
}
// 更新设置显示
function updateSettingsDisplay() {
document.getElementById('heightValue').textContent = settings.height;
document.getElementById('opacityValue').textContent = Math.round(settings.opacity * 100);
ruler.style.height = `${settings.height}px`;
updateRulerMode();
}
// 更新标尺模式
function updateRulerMode() {
ruler.className = 'reading-ruler ' + (settings.isInverted ? 'inverted' : 'normal');
if (!settings.isInverted) {
ruler.style.backgroundColor = settings.color;
ruler.style.opacity = settings.opacity;
ruler.style.boxShadow = '';
} else {
ruler.style.backgroundColor = 'transparent';
ruler.style.boxShadow = `0 0 0 100vh ${settings.color}`;
ruler.style.opacity = settings.opacity;
}
}
// 保存设置
function saveSettings() {
GM_setValue('rulerSettings', settings);
}
// 更新显示模式
function updateDisplayMode() {
ruler.style.display = settings.isEnabled ? 'block' : 'none';
updateRulerMode();
}
// 事件监听器
toggleButton.addEventListener('click', () => {
settings.isEnabled = !settings.isEnabled;
toggleButton.classList.toggle('active', settings.isEnabled);
updateDisplayMode();
saveSettings();
});
// 模式切换
modeSwitch.addEventListener('change', (e) => {
settings.isInverted = e.target.checked;
updateDisplayMode();
saveSettings();
});
// 右键菜单
toggleButton.addEventListener('contextmenu', (e) => {
e.preventDefault();
settingsPanel.classList.toggle('visible');
});
// 点击其他地方关闭设置面板
document.addEventListener('click', (e) => {
if (!e.target.closest('.ruler-settings') && !e.target.closest('.ruler-toggle')) {
settingsPanel.classList.remove('visible');
}
});
// 拖动事件监听
control.addEventListener("mousedown", dragStart);
document.addEventListener("mousemove", drag);
document.addEventListener("mouseup", dragEnd);
// 设置面板事件监听
document.getElementById('rulerHeight').addEventListener('input', (e) => {
settings.height = parseInt(e.target.value);
updateSettingsDisplay();
saveSettings();
});
document.getElementById('rulerColor').addEventListener('input', (e) => {
settings.color = e.target.value;
updateSettingsDisplay();
saveSettings();
});
document.getElementById('rulerOpacity').addEventListener('input', (e) => {
settings.opacity = parseInt(e.target.value) / 100;
updateSettingsDisplay();
saveSettings();
});
// 鼠标移动时更新位置
document.addEventListener('mousemove', (e) => {
if (settings.isEnabled) {
const y = e.clientY - (settings.height / 2);
ruler.style.top = `${y}px`;
}
});
// 初始化显示状态
if (settings.isEnabled) {
toggleButton.classList.add('active');
updateDisplayMode();
}
})();