|
|
// 滚动控制器
|
|
|
export class ScrollController {
|
|
|
constructor() {
|
|
|
this.isAutoScrolling = false;
|
|
|
this.scrollSpeed = 30;
|
|
|
this.scrollType = 'line';
|
|
|
this.scrollInterval = null;
|
|
|
this.totalDuration = 10; // 分钟
|
|
|
this.autoCalculateDuration = false;
|
|
|
this.currentScrollPosition = 0;
|
|
|
this.targetScrollPosition = 0;
|
|
|
this.smoothScrollFrame = null;
|
|
|
|
|
|
this.init();
|
|
|
}
|
|
|
|
|
|
init() {
|
|
|
this.setupScrollSettings();
|
|
|
this.updateReadingLine();
|
|
|
}
|
|
|
|
|
|
setupScrollSettings() {
|
|
|
const autoScrollCheckbox = document.getElementById('autoScroll');
|
|
|
const autoScrollOptions = document.getElementById('autoScrollOptions');
|
|
|
const scrollTypeSelect = document.getElementById('scrollType');
|
|
|
const scrollSpeedRange = document.getElementById('scrollSpeed');
|
|
|
const scrollSpeedValue = document.getElementById('scrollSpeedValue');
|
|
|
const autoDurationCheckbox = document.getElementById('autoDuration');
|
|
|
const durationSettings = document.getElementById('durationSettings');
|
|
|
const totalDurationInput = document.getElementById('totalDuration');
|
|
|
|
|
|
// 自动滚动开关
|
|
|
autoScrollCheckbox.addEventListener('change', () => {
|
|
|
if (autoScrollCheckbox.checked) {
|
|
|
autoScrollOptions.style.display = 'block';
|
|
|
} else {
|
|
|
autoScrollOptions.style.display = 'none';
|
|
|
this.stopAutoScroll();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 滚动类型选择
|
|
|
scrollTypeSelect.addEventListener('change', () => {
|
|
|
this.scrollType = scrollTypeSelect.value;
|
|
|
if (this.autoCalculateDuration) {
|
|
|
this.calculateOptimalSpeed();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 滚动速度调整
|
|
|
scrollSpeedRange.addEventListener('input', () => {
|
|
|
this.scrollSpeed = parseInt(scrollSpeedRange.value);
|
|
|
scrollSpeedValue.textContent = this.scrollSpeed;
|
|
|
});
|
|
|
|
|
|
// 自动计算时长
|
|
|
autoDurationCheckbox.addEventListener('change', () => {
|
|
|
this.autoCalculateDuration = autoDurationCheckbox.checked;
|
|
|
if (autoDurationCheckbox.checked) {
|
|
|
durationSettings.style.display = 'block';
|
|
|
this.calculateOptimalSpeed();
|
|
|
} else {
|
|
|
durationSettings.style.display = 'none';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 总时长设置
|
|
|
totalDurationInput.addEventListener('input', () => {
|
|
|
this.totalDuration = parseInt(totalDurationInput.value);
|
|
|
if (this.autoCalculateDuration) {
|
|
|
this.calculateOptimalSpeed();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
startAutoScroll() {
|
|
|
if (this.isAutoScrolling) return;
|
|
|
|
|
|
const autoScrollEnabled = document.getElementById('autoScroll').checked;
|
|
|
if (!autoScrollEnabled) return;
|
|
|
|
|
|
this.isAutoScrolling = true;
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
this.currentScrollPosition = editor.scrollTop;
|
|
|
|
|
|
this.smoothAutoScroll();
|
|
|
}
|
|
|
|
|
|
smoothAutoScroll() {
|
|
|
if (!this.isAutoScrolling) return;
|
|
|
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const maxScroll = editor.scrollHeight - editor.clientHeight;
|
|
|
|
|
|
if (this.currentScrollPosition >= maxScroll) {
|
|
|
this.stopAutoScroll();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 计算每帧的滚动距离,实现平滑滚动
|
|
|
const scrollStep = this.calculateScrollStep();
|
|
|
this.currentScrollPosition += scrollStep;
|
|
|
|
|
|
// 平滑更新滚动位置
|
|
|
editor.scrollTop = this.currentScrollPosition;
|
|
|
|
|
|
this.updateReadingLine();
|
|
|
if (window.app && window.app.updateScrollbar) {
|
|
|
window.app.updateScrollbar();
|
|
|
}
|
|
|
|
|
|
// 使用 requestAnimationFrame 实现平滑滚动
|
|
|
this.smoothScrollFrame = requestAnimationFrame(() => {
|
|
|
this.smoothAutoScroll();
|
|
|
});
|
|
|
}
|
|
|
|
|
|
calculateScrollStep() {
|
|
|
// 根据滚动速度和类型计算每帧的滚动距离
|
|
|
const baseSpeed = this.scrollSpeed / 100; // 将速度转换为0-1的比例
|
|
|
|
|
|
if (this.scrollType === 'line') {
|
|
|
// 按行滚动:基于行高计算
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const lineHeight = parseInt(window.getComputedStyle(editor).lineHeight) || 24;
|
|
|
return (lineHeight * baseSpeed) / 60; // 60fps
|
|
|
} else {
|
|
|
// 按字滚动:更细粒度的滚动
|
|
|
return baseSpeed * 2; // 每帧2像素的基础速度
|
|
|
}
|
|
|
}
|
|
|
|
|
|
stopAutoScroll() {
|
|
|
this.isAutoScrolling = false;
|
|
|
if (this.smoothScrollFrame) {
|
|
|
cancelAnimationFrame(this.smoothScrollFrame);
|
|
|
this.smoothScrollFrame = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
calculateOptimalSpeed() {
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const text = editor.textContent;
|
|
|
|
|
|
let unitCount;
|
|
|
if (this.scrollType === 'line') {
|
|
|
unitCount = text.split('\n').length;
|
|
|
} else {
|
|
|
unitCount = text.length;
|
|
|
}
|
|
|
|
|
|
// 根据总时长计算最优速度
|
|
|
const totalSeconds = this.totalDuration * 60;
|
|
|
const optimalSpeed = Math.round((unitCount / totalSeconds) * 10);
|
|
|
|
|
|
this.scrollSpeed = Math.max(1, Math.min(100, optimalSpeed));
|
|
|
|
|
|
// 更新UI
|
|
|
const scrollSpeedRange = document.getElementById('scrollSpeed');
|
|
|
const scrollSpeedValue = document.getElementById('scrollSpeedValue');
|
|
|
scrollSpeedRange.value = this.scrollSpeed;
|
|
|
scrollSpeedValue.textContent = this.scrollSpeed;
|
|
|
}
|
|
|
|
|
|
updateReadingLine() {
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const readingLine = document.getElementById('readingLine');
|
|
|
|
|
|
// 计算当前阅读位置
|
|
|
const scrollRatio = editor.scrollTop / (editor.scrollHeight - editor.clientHeight);
|
|
|
const totalLines = editor.textContent.split('\n').length;
|
|
|
const currentLine = Math.floor(scrollRatio * totalLines);
|
|
|
|
|
|
// 高亮当前行
|
|
|
this.highlightCurrentLine(currentLine);
|
|
|
}
|
|
|
|
|
|
highlightCurrentLine(lineNumber) {
|
|
|
// 简化的高亮处理,避免复杂的DOM操作
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const lines = editor.textContent.split('\n');
|
|
|
|
|
|
// 这里可以添加更复杂的高亮逻辑
|
|
|
// 由于contenteditable的复杂性,暂时保持简单处理
|
|
|
}
|
|
|
|
|
|
scrollToPosition(ratio) {
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const targetScrollTop = ratio * (editor.scrollHeight - editor.clientHeight);
|
|
|
|
|
|
// 平滑滚动到目标位置
|
|
|
this.smoothScrollTo(targetScrollTop);
|
|
|
}
|
|
|
|
|
|
smoothScrollTo(targetPosition) {
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
const startPosition = editor.scrollTop;
|
|
|
const distance = targetPosition - startPosition;
|
|
|
const duration = 500; // 500ms的滚动动画
|
|
|
let startTime = null;
|
|
|
|
|
|
const animateScroll = (currentTime) => {
|
|
|
if (startTime === null) startTime = currentTime;
|
|
|
const timeElapsed = currentTime - startTime;
|
|
|
const progress = Math.min(timeElapsed / duration, 1);
|
|
|
|
|
|
// 使用缓动函数实现平滑效果
|
|
|
const easeInOutQuad = progress < 0.5
|
|
|
? 2 * progress * progress
|
|
|
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
|
|
|
|
|
editor.scrollTop = startPosition + distance * easeInOutQuad;
|
|
|
|
|
|
if (progress < 1) {
|
|
|
requestAnimationFrame(animateScroll);
|
|
|
} else {
|
|
|
this.updateReadingLine();
|
|
|
if (window.app && window.app.updateScrollbar) {
|
|
|
window.app.updateScrollbar();
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
requestAnimationFrame(animateScroll);
|
|
|
}
|
|
|
|
|
|
getScrollProgress() {
|
|
|
const editor = document.getElementById('textEditor');
|
|
|
return editor.scrollTop / (editor.scrollHeight - editor.clientHeight);
|
|
|
}
|
|
|
} |