-
${chapter.title}
-
${content}
-
-
-
+
+ if (currentReadingMode === 'scroll') {
+ // 上下滚动模式
+ $content.html(`
+
+
+
${chapter.title}
+
${content}
+
+
+
+
-
- `);
+ `);
+ } else {
+ // 左右翻页模式
+ $content.html(`
+
+
+
+
${chapter.title}
+
${content}
+
+
+
+ 1 / 1
+
+
+
+ `);
+
+ // 等待DOM渲染后进行分页计算
+ setTimeout(() => {
+ calculatePages();
+ showPage(currentPage);
+ }, 100);
+ }
// 应用设置
applySettings();
}
+ // ========== 翻页模式相关函数 ==========
+
+ function calculatePages() {
+ if (currentReadingMode !== 'page') return;
+
+ const $container = $('.page-container');
+ const $content = $('.page-content');
+
+ if (!$container.length || !$content.length) return;
+
+ const containerHeight = $container.height();
+ const contentHeight = $content[0].scrollHeight;
+
+ // 计算总页数
+ totalPages = Math.max(1, Math.ceil(contentHeight / containerHeight));
+ pageContents = [];
+
+ // 更新页码显示
+ $('.page-total').text(totalPages);
+
+ // 恢复之前的阅读位置
+ const position = Store.getReadingPosition(currentNovelId);
+ if (position && position.chapterIndex === currentChapterIndex && position.pageIndex !== undefined) {
+ currentPage = Math.min(position.pageIndex, totalPages - 1);
+ } else {
+ currentPage = 0;
+ }
+ }
+
+ function showPage(pageIndex) {
+ if (currentReadingMode !== 'page') return;
+
+ const $container = $('.page-container');
+ const $content = $('.page-content');
+
+ if (!$container.length || !$content.length) return;
+
+ // 确保页码在有效范围内
+ pageIndex = Math.max(0, Math.min(pageIndex, totalPages - 1));
+ currentPage = pageIndex;
+
+ const containerHeight = $container.height();
+ const scrollTop = pageIndex * containerHeight;
+
+ // 使用transform进行平滑翻页
+ $content.css('transform', `translateY(-${scrollTop}px)`);
+
+ // 更新页码显示
+ $('.page-current').text(currentPage + 1);
+
+ // 保存阅读位置
+ saveReadingProgress();
+
+ // 更新进度条
+ updateProgressIndicator();
+ }
+
+ function nextPage() {
+ if (currentReadingMode !== 'page') return;
+
+ if (currentPage < totalPages - 1) {
+ showPage(currentPage + 1);
+ } else {
+ // 已是最后一页,跳转下一章
+ nextChapter();
+ }
+ }
+
+ function prevPage() {
+ if (currentReadingMode !== 'page') return;
+
+ if (currentPage > 0) {
+ showPage(currentPage - 1);
+ } else {
+ // 已是第一页,跳转上一章
+ if (currentChapterIndex > 0) {
+ // 跳转上一章并显示最后一页
+ goToPrevChapterLastPage();
+ } else {
+ Toast.show('已是第一章');
+ }
+ }
+ }
+
+ async function goToPrevChapterLastPage() {
+ hideToolbar();
+ try {
+ const chapter = await API.getPrevChapter(currentNovelId, currentChapterIndex);
+ if (chapter) {
+ currentChapter = chapter;
+ currentChapterIndex = chapter.index;
+ renderChapter(chapter);
+ saveReadingProgress();
+ updateHistory(currentNovelId, chapter.id);
+ $('#reader-title').text(chapter.title);
+
+ // 等待分页计算完成后跳到最后一页
+ setTimeout(() => {
+ if (totalPages > 0) {
+ showPage(totalPages - 1);
+ }
+ }, 150);
+ } else {
+ Toast.show('已是第一章');
+ }
+ } catch (error) {
+ Toast.show('加载失败');
+ }
+ }
+
// ========== 章节导航 ==========
async function nextChapter() {
@@ -130,6 +283,7 @@ const Reader = (function() {
if (chapter) {
currentChapter = chapter;
currentChapterIndex = chapter.index;
+ currentPage = 0; // 重置页码
renderChapter(chapter);
scrollToTop();
saveReadingProgress();
@@ -154,6 +308,7 @@ const Reader = (function() {
if (chapter) {
currentChapter = chapter;
currentChapterIndex = chapter.index;
+ currentPage = 0; // 重置页码
renderChapter(chapter);
scrollToTop();
saveReadingProgress();
@@ -170,11 +325,13 @@ const Reader = (function() {
async function goToChapter(index) {
hideToolbar();
hideCatalog();
+ currentPage = 0; // 重置页码
await loadChapter(currentNovelId, index);
scrollToTop();
}
function reload() {
+ currentPage = 0;
loadChapter(currentNovelId, currentChapterIndex);
}
@@ -221,6 +378,7 @@ const Reader = (function() {
function applySettings() {
const fontSize = Store.getFontSize();
const lineHeight = Store.getLineHeight();
+ const readingMode = Store.getReadingMode();
$('.reader-text').css({
'--reader-font-size': fontSize + 'px',
@@ -239,6 +397,26 @@ const Reader = (function() {
const theme = Store.getTheme();
$('.theme-btn').removeClass('active');
$(`.theme-btn[data-theme="${theme}"]`).addClass('active');
+
+ // 更新阅读模式按钮状态
+ $('.reading-mode-btn').removeClass('active');
+ $(`.reading-mode-btn[data-mode="${readingMode}"]`).addClass('active');
+ }
+
+ function changeReadingMode(mode) {
+ if (mode === currentReadingMode) return;
+
+ Store.setReadingMode(mode);
+ currentReadingMode = mode;
+ currentPage = 0;
+
+ // 重新渲染当前章节
+ if (currentChapter) {
+ renderChapter(currentChapter);
+ }
+
+ applySettings();
+ Toast.show(mode === 'page' ? '已切换为翻页模式' : '已切换为滚动模式');
}
function changeFontSize(delta) {
@@ -365,19 +543,31 @@ const Reader = (function() {
function saveReadingProgress() {
if (!currentNovelId) return;
- Store.setReadingPosition(currentNovelId, {
- chapterIndex: currentChapterIndex,
- scrollTop: window.scrollY
- });
+ if (currentReadingMode === 'page') {
+ Store.setReadingPosition(currentNovelId, {
+ chapterIndex: currentChapterIndex,
+ pageIndex: currentPage,
+ readingMode: 'page'
+ });
+ } else {
+ Store.setReadingPosition(currentNovelId, {
+ chapterIndex: currentChapterIndex,
+ scrollTop: window.scrollY,
+ readingMode: 'scroll'
+ });
+ }
}
function restoreScrollPosition() {
const position = Store.getReadingPosition(currentNovelId);
- if (position && position.chapterIndex === currentChapterIndex && position.scrollTop) {
+ if (!position || position.chapterIndex !== currentChapterIndex) return;
+
+ if (currentReadingMode === 'scroll' && position.scrollTop) {
setTimeout(() => {
window.scrollTo(0, position.scrollTop);
}, 100);
}
+ // 翻页模式的位置恢复在 calculatePages 中处理
}
async function updateHistory(novelId, chapterId) {
@@ -400,25 +590,99 @@ const Reader = (function() {
}
function updateProgressIndicator() {
- const scrollTop = window.scrollY;
- const docHeight = document.documentElement.scrollHeight - window.innerHeight;
- const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
+ let progress = 0;
+
+ if (currentReadingMode === 'page') {
+ // 翻页模式:基于页码计算进度
+ progress = totalPages > 1 ? (currentPage / (totalPages - 1)) * 100 : 100;
+ } else {
+ // 滚动模式:基于滚动位置计算进度
+ const scrollTop = window.scrollY;
+ const docHeight = document.documentElement.scrollHeight - window.innerHeight;
+ progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
+ }
+
$('.reading-progress-indicator .progress-bar').css('width', progress + '%');
}
// ========== 事件绑定 ==========
function bindEvents() {
- // 点击内容区域切换工具栏
- $(document).off('click.reader').on('click.reader', '.reader-page', function(e) {
+ // 滚动模式:点击内容区域切换工具栏
+ $(document).off('click.reader').on('click.reader', '.reader-page.scroll-mode', function(e) {
// 排除按钮点击
if ($(e.target).closest('button, a').length) return;
toggleToolbar();
});
- // 滚动事件
+ // 翻页模式:点击区域处理
+ $(document).off('click.tapprev').on('click.tapprev', '.tap-prev', function(e) {
+ e.stopPropagation();
+ prevPage();
+ });
+
+ $(document).off('click.tapcenter').on('click.tapcenter', '.tap-center', function(e) {
+ e.stopPropagation();
+ toggleToolbar();
+ });
+
+ $(document).off('click.tapnext').on('click.tapnext', '.tap-next', function(e) {
+ e.stopPropagation();
+ nextPage();
+ });
+
+ // 翻页模式:触摸滑动事件
+ $(document).off('touchstart.reader').on('touchstart.reader', '.reader-page.page-mode', function(e) {
+ if ($(e.target).closest('button, a, .tap-zone').length) return;
+ touchStartX = e.originalEvent.touches[0].clientX;
+ touchStartY = e.originalEvent.touches[0].clientY;
+ isSwiping = false;
+ });
+
+ $(document).off('touchmove.reader').on('touchmove.reader', '.reader-page.page-mode', function(e) {
+ if (!touchStartX) return;
+ touchEndX = e.originalEvent.touches[0].clientX;
+ touchEndY = e.originalEvent.touches[0].clientY;
+
+ const diffX = touchEndX - touchStartX;
+ const diffY = touchEndY - touchStartY;
+
+ // 判断是否为水平滑动
+ if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 30) {
+ isSwiping = true;
+ }
+ });
+
+ $(document).off('touchend.reader').on('touchend.reader', '.reader-page.page-mode', function(e) {
+ if (!isSwiping) {
+ touchStartX = 0;
+ touchStartY = 0;
+ return;
+ }
+
+ const diffX = touchEndX - touchStartX;
+ const threshold = 50; // 滑动阈值
+
+ if (diffX > threshold) {
+ // 向右滑动 - 上一页
+ prevPage();
+ } else if (diffX < -threshold) {
+ // 向左滑动 - 下一页
+ nextPage();
+ }
+
+ touchStartX = 0;
+ touchStartY = 0;
+ touchEndX = 0;
+ touchEndY = 0;
+ isSwiping = false;
+ });
+
+ // 滚动模式:滚动事件
let scrollTimer;
$(window).off('scroll.reader').on('scroll.reader', function() {
+ if (currentReadingMode !== 'scroll') return;
+
// 更新进度条
updateProgressIndicator();
@@ -437,7 +701,7 @@ const Reader = (function() {
$('#reader-settings').off('click').on('click', showSettings);
$('#reader-bookmark').off('click').on('click', showBookmarkModal);
- // 章节内导航按钮
+ // 章节内导航按钮(滚动模式)
$(document).off('click.navprev').on('click.navprev', '#nav-prev', prevChapter);
$(document).off('click.navnext').on('click.navnext', '#nav-next', nextChapter);
@@ -445,17 +709,48 @@ const Reader = (function() {
$('#close-settings').off('click').on('click', hideSettings);
$('.settings-overlay').off('click').on('click', hideSettings);
- $('#font-decrease').off('click').on('click', () => changeFontSize(-2));
- $('#font-increase').off('click').on('click', () => changeFontSize(2));
+ $('#font-decrease').off('click').on('click', () => {
+ changeFontSize(-2);
+ // 翻页模式下重新计算分页
+ if (currentReadingMode === 'page') {
+ setTimeout(() => {
+ calculatePages();
+ showPage(0);
+ }, 100);
+ }
+ });
+ $('#font-increase').off('click').on('click', () => {
+ changeFontSize(2);
+ // 翻页模式下重新计算分页
+ if (currentReadingMode === 'page') {
+ setTimeout(() => {
+ calculatePages();
+ showPage(0);
+ }, 100);
+ }
+ });
$('.line-height-btn').off('click').on('click', function() {
changeLineHeight($(this).data('value'));
+ // 翻页模式下重新计算分页
+ if (currentReadingMode === 'page') {
+ setTimeout(() => {
+ calculatePages();
+ showPage(0);
+ }, 100);
+ }
});
$('.theme-btn').off('click').on('click', function() {
changeTheme($(this).data('theme'));
});
+ // 阅读模式切换按钮
+ $(document).off('click.readingmode').on('click.readingmode', '.reading-mode-btn', function() {
+ const mode = $(this).data('mode');
+ changeReadingMode(mode);
+ });
+
// 目录面板
$('#close-catalog').off('click').on('click', hideCatalog);
$('.catalog-overlay').off('click').on('click', hideCatalog);
@@ -477,14 +772,32 @@ const Reader = (function() {
const note = $('#bookmark-note').val();
addBookmark(note);
});
+
+ // 窗口大小变化时重新计算分页
+ $(window).off('resize.reader').on('resize.reader', function() {
+ if (currentReadingMode === 'page') {
+ setTimeout(() => {
+ calculatePages();
+ showPage(currentPage);
+ }, 100);
+ }
+ });
}
function unbindEvents() {
$(document).off('click.reader');
+ $(document).off('click.tapprev');
+ $(document).off('click.tapcenter');
+ $(document).off('click.tapnext');
+ $(document).off('touchstart.reader');
+ $(document).off('touchmove.reader');
+ $(document).off('touchend.reader');
$(window).off('scroll.reader');
+ $(window).off('resize.reader');
$(document).off('click.navprev');
$(document).off('click.navnext');
$(document).off('click.catalogitem');
+ $(document).off('click.readingmode');
}
// ========== 退出阅读器 ==========
@@ -534,7 +847,10 @@ const Reader = (function() {
goToChapter,
showCatalog,
showSettings,
- showBookmarkModal
+ showBookmarkModal,
+ changeReadingMode,
+ nextPage,
+ prevPage
};
})();
diff --git a/src/main/resources/static/js/store.js b/src/main/resources/static/js/store.js
index 3d00ed5..c2c4641 100644
--- a/src/main/resources/static/js/store.js
+++ b/src/main/resources/static/js/store.js
@@ -8,7 +8,7 @@ const Store = (function() {
THEME: 'novel_reader_theme',
FONT_SIZE: 'novel_reader_font_size',
LINE_HEIGHT: 'novel_reader_line_height',
- READ_MODE: 'novel_reader_read_mode', // 阅读模式: 'page'(翻页) | 'scroll'(滚动)
+ READING_MODE: 'novel_reader_reading_mode', // 阅读模式:scroll(上下滚动) / page(左右翻页)
READING_POSITION: 'novel_reader_position_',
SHELF_CACHE: 'novel_reader_shelf_cache',
HISTORY_CACHE: 'novel_reader_history_cache',
@@ -23,7 +23,7 @@ const Store = (function() {
theme: 'dark',
fontSize: 18,
lineHeight: 1.8,
- readMode: 'page' // 默认左右翻页模式
+ readingMode: 'page' // 默认左右翻页模式
};
// ========== 基础存储方法 ==========
@@ -102,16 +102,15 @@ const Store = (function() {
// ========== 阅读模式设置 ==========
- function getReadMode() {
- return get(KEYS.READ_MODE, DEFAULTS.readMode);
+ function getReadingMode() {
+ return get(KEYS.READING_MODE, DEFAULTS.readingMode);
}
- function setReadMode(mode) {
- // mode: 'page' (左右翻页) | 'scroll' (上下滚动)
- if (mode === 'page' || mode === 'scroll') {
- set(KEYS.READ_MODE, mode);
+ function setReadingMode(mode) {
+ // mode: 'scroll' (上下滚动) 或 'page' (左右翻页)
+ if (mode === 'scroll' || mode === 'page') {
+ set(KEYS.READING_MODE, mode);
}
- return getReadMode();
}
// ========== 阅读位置缓存 ==========
@@ -227,6 +226,8 @@ const Store = (function() {
setFontSize,
getLineHeight,
setLineHeight,
+ getReadingMode,
+ setReadingMode,
// 阅读位置
getReadingPosition,
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 9ab6b17..175dd87 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -198,6 +198,19 @@