在浏览器右侧查看本地小说txt,支持gbk/utf8编码、调整字体透明度、阅读进度、鼠标悬停显示,移开隐藏等
// ==UserScript==
// @name 摸鱼偷懒小说阅读器
// @namespace http://tampermonkey.net/
// @version 0.4
// @description 在浏览器右侧查看本地小说txt,支持gbk/utf8编码、调整字体透明度、阅读进度、鼠标悬停显示,移开隐藏等
// @author CYC
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 用于存储文件内容和滚动定时器
let contentData = '';
let scrollTimeout;
const STORAGE_KEY = 'novel_reader_progress'; // 本地存储进度的键名
// 创建主阅读器容器
const reader = document.createElement('div');
reader.style.position = 'fixed';
reader.style.right = '10px';
reader.style.top = '50%';
reader.style.transform = 'translateY(-50%)';
reader.style.width = '250px';
reader.style.height = '400px';
reader.style.background = 'transparent';
reader.style.zIndex = 99999;
reader.style.pointerEvents = 'auto';
reader.style.display = 'flex';
reader.style.flexDirection = 'column';
reader.style.justifyContent = 'space-between';
// 显示文本的区域
const textEl = document.createElement('div');
textEl.style.flex = '1';
textEl.style.flex = '1';
textEl.style.overflowY = 'auto';
textEl.style.fontSize = '14px';
textEl.style.color = 'rgba(0,0,0,0.8)';
textEl.style.lineHeight = '1.5';
// 设置面板容器(初始隐藏)
const controlWrapper = document.createElement('div');
controlWrapper.style.position = 'absolute';
controlWrapper.style.top = '6%';
controlWrapper.style.right = '0';
controlWrapper.style.width = '100%';
controlWrapper.style.background = 'rgba(255,255,255,0.3)';
controlWrapper.style.display = 'none';
controlWrapper.style.flexDirection = 'column';
controlWrapper.style.gap = '1px';
controlWrapper.style.padding = '1px';
// ⚙️按钮:用于显示/隐藏设置面板
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '⚙️';
toggleBtn.style.position = 'absolute';
toggleBtn.style.top = '0';
toggleBtn.style.right = '0';
toggleBtn.style.zIndex = 999999;
toggleBtn.onclick = () => {
controlWrapper.style.display = controlWrapper.style.display === 'none' ? 'flex' : 'none';
};
// 文件选择输入(支持.txt 和 .epub)
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.txt,.epub';
// 字体大小调节
const fontSizeInput = document.createElement('input');
fontSizeInput.type = 'range';
fontSizeInput.min = 10;
fontSizeInput.max = 30;
fontSizeInput.value = 14;
fontSizeInput.title = '字体大小';
fontSizeInput.oninput = () => {
textEl.style.fontSize = fontSizeInput.value + 'px';
};
// 字体透明度调节
const opacityInput = document.createElement('input');
opacityInput.type = 'range';
opacityInput.min = 0;
opacityInput.max = 100;
opacityInput.value = 80;
opacityInput.title = '字体透明度';
opacityInput.oninput = () => {
textEl.style.color = `rgba(0,0,0,${opacityInput.value / 100})`;
};
// 切换深色模式按钮
const darkModeToggle = document.createElement('button');
darkModeToggle.textContent = '切换深色模式';
darkModeToggle.onclick = () => {
if (reader.style.background === 'black') {
reader.style.background = 'transparent';
textEl.style.color = `rgba(0,0,0,${opacityInput.value / 100})`;
} else {
reader.style.background = 'black';
textEl.style.color = 'rgba(255,255,255,0.9)';
}
};
// 章节选择器(通过正则识别章节标题)
const chapterSelect = document.createElement('select');
chapterSelect.title = '跳转章节';
chapterSelect.style.maxWidth = '100%';
chapterSelect.onchange = () => {
const index = parseInt(chapterSelect.value);
const pos = textEl.scrollHeight * index / 100;
textEl.scrollTop = pos;
};
// 阅读进度条
const progressInput = document.createElement('input');
progressInput.type = 'range';
progressInput.min = 0;
progressInput.max = 100000;
progressInput.value = 0;
progressInput.title = '阅读进度';
progressInput.oninput = () => {
const percent = progressInput.value / 1000;
const scrollHeight = textEl.scrollHeight - textEl.clientHeight;
textEl.scrollTop = scrollHeight * percent / 100;
progressLabel.textContent = percent.toFixed(3) + '%';
};
// 显示百分比的标签
const progressLabel = document.createElement('div');
progressLabel.textContent = '0.000%';
progressLabel.style.fontSize = '12px';
progressLabel.style.textAlign = 'center';
// 文件选择逻辑(自动判断是否是gbk编码)
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const ext = file.name.split('.').pop().toLowerCase();
if (ext === 'epub') {
alert('EPUB暂未支持,未来版本支持');
return;
}
const readerUTF8 = new FileReader();
readerUTF8.onload = () => {
let content = readerUTF8.result;
if (/�/.test(content)) { // 如果有乱码,尝试用GBK重新读
const readerGBK = new FileReader();
readerGBK.onload = () => {
const gbkText = new TextDecoder('gbk').decode(readerGBK.result);
contentData = gbkText;
renderContent(contentData);
};
readerGBK.readAsArrayBuffer(file);
} else {
contentData = content;
renderContent(contentData);
}
};
readerUTF8.readAsText(file, 'utf-8');
});
// 渲染小说内容并提取章节信息
function renderContent(text) {
textEl.innerText = text;
chapterSelect.innerHTML = '';
const lines = text.split('\n');
let chapterCount = 0;
lines.forEach((line, i) => {
if (/第.{1,9}[章节卷集]/.test(line)) {
const opt = document.createElement('option');
opt.text = line.trim().slice(0, 20);
opt.value = Math.floor(i / lines.length * 100);
chapterSelect.appendChild(opt);
chapterCount++;
}
});
if (chapterCount === 0) {
const opt = document.createElement('option');
opt.text = '无章节';
opt.value = 0;
chapterSelect.appendChild(opt);
}
}
// 滚动监听,更新阅读进度并存入 localStorage
textEl.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
const scrollTop = textEl.scrollTop;
const scrollHeight = textEl.scrollHeight - textEl.clientHeight;
const percent = (scrollTop / scrollHeight) * 100;
progressInput.value = Math.floor(percent * 1000);
progressLabel.textContent = percent.toFixed(3) + '%';
localStorage.setItem(STORAGE_KEY, percent);
}, 300);
});
// 创建一个透明的感应层
const triggerArea = document.createElement('div');
triggerArea.id = 'reader-hover-trigger';
Object.assign(triggerArea.style, {
position: 'fixed',
right: '20px',
top: '50%',
transform: 'translateY(-50%)',
width: '250px',
height: '400px',
zIndex: '9998',
background: 'transparent',
pointerEvents: 'auto',
});
document.body.appendChild(triggerArea);
// 设置初始 reader 样式为隐藏
reader.style.opacity = '0';
reader.style.pointerEvents = 'none';
reader.style.transition = 'opacity 0.3s';
// 绑定 hover 事件
triggerArea.addEventListener('mouseenter', () => {
reader.style.opacity = '1';
reader.style.pointerEvents = 'auto';
});
reader.addEventListener('mouseleave', () => {
reader.style.opacity = '0';
reader.style.pointerEvents = 'none';
});
// 控件添加到设置面板
controlWrapper.appendChild(fileInput);
controlWrapper.appendChild(fontSizeInput);
controlWrapper.appendChild(opacityInput);
controlWrapper.appendChild(progressInput);
controlWrapper.appendChild(progressLabel);
controlWrapper.appendChild(chapterSelect);
controlWrapper.appendChild(darkModeToggle);
// 鼠标移出阅读器时隐藏控制面板
reader.addEventListener('mouseleave', () => {
controlWrapper.style.display = 'none';
});
// DOM结构添加
reader.appendChild(toggleBtn);
reader.appendChild(controlWrapper);
reader.appendChild(textEl);
document.body.appendChild(reader);
// 页面加载后尝试恢复上次阅读进度
setTimeout(() => {
const saved = parseFloat(localStorage.getItem(STORAGE_KEY));
if (!isNaN(saved)) {
const scrollHeight = textEl.scrollHeight - textEl.clientHeight;
textEl.scrollTop = scrollHeight * saved / 100;
progressInput.value = Math.floor(saved * 1000);
progressLabel.textContent = saved.toFixed(3) + '%';
}
}, 1000);
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址