fix(tts): scroll more accurately to the highlighted text in TTS when header/footer bars are shown (#2242)

This commit is contained in:
Huang Xin
2025-10-15 23:50:56 +08:00
committed by GitHub
parent 7f5b6959e1
commit 9dda5d4c88
5 changed files with 31 additions and 14 deletions
@@ -68,8 +68,8 @@ const SectionInfo: React.FC<SectionInfoProps> = ({
}
: {
top: `${topInset}px`,
insetInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`,
width: `calc(100% - ${contentInsets.left + contentInsets.right}px)`,
paddingInlineStart: `calc(${horizontalGap / 2}% + ${contentInsets.left}px)`,
width: '100%',
}
}
>
@@ -11,7 +11,7 @@ import { BookSearchConfig, BookSearchResult } from '@/types/book';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
import { debounce } from '@/utils/debounce';
import { isCJKStr } from '@/utils/lang';
import { createRejecttFilter } from '@/utils/node';
import { createRejectFilter } from '@/utils/node';
import Dropdown from '@/components/Dropdown';
import SearchOptions from './SearchOptions';
@@ -127,7 +127,7 @@ const SearchBar: React.FC<SearchBarProps> = ({
...searchConfig,
index,
query: term,
acceptNode: createRejecttFilter({
acceptNode: createRejectFilter({
tags: primaryLang.startsWith('ja') ? ['rt'] : [],
}),
});
@@ -208,6 +208,30 @@ const TTSControl: React.FC<TTSControlProps> = ({ bookKey, gridInsets }) => {
viewSettings.ttsLocation = cfi || '';
setViewSettings(bookKey, viewSettings);
}
if (!view.renderer.scrolled) {
view.renderer.scrollToAnchor(range);
} else {
const rect = range.getBoundingClientRect();
const { start, size, viewSize, sideProp } = view.renderer;
const positionStart = rect[sideProp === 'height' ? 'y' : 'x'] + viewSettings.marginTopPx;
const positionEnd = rect[sideProp === 'height' ? 'height' : 'width'] + positionStart;
const offsetStart = view.book.dir === 'rtl' ? viewSize - positionStart : positionStart;
const offsetEnd = view.book.dir === 'rtl' ? viewSize - positionEnd : positionEnd;
const showHeader = viewSettings.showHeader;
const showFooter = viewSettings.showFooter;
const showBarsOnScroll = viewSettings.showBarsOnScroll;
const headerScrollOverlap = showHeader && showBarsOnScroll ? 44 : 0;
const footerScrollOverlap = showFooter && showBarsOnScroll ? 44 : 0;
const scrollingOverlap = viewSettings.scrollingOverlap;
const endInNextView = offsetEnd > start + size - footerScrollOverlap - scrollingOverlap;
const startInPrevView = offsetStart < start + headerScrollOverlap + scrollingOverlap;
if (endInNextView || startInPrevView) {
const scrollTo = offsetStart - headerScrollOverlap - scrollingOverlap;
view.renderer.scrollToAnchor(scrollTo / viewSize);
}
}
};
ttsController.addEventListener('tts-speak-mark', handleSpeakMark);
@@ -3,7 +3,7 @@ import { AppService } from '@/types/system';
import { parseSSMLMarks } from '@/utils/ssml';
import { Overlayer } from 'foliate-js/overlayer.js';
import { TTSGranularity, TTSHighlightOptions, TTSMark, TTSVoice } from './types';
import { createRejecttFilter } from '@/utils/node';
import { createRejectFilter } from '@/utils/node';
import { WebSpeechClient } from './WebSpeechClient';
import { NativeTTSClient } from './NativeTTSClient';
import { EdgeTTSClient } from './EdgeTTSClient';
@@ -85,13 +85,6 @@ export class TTSController extends EventTarget {
const { style, color } = options;
overlayer?.remove(HIGHLIGHT_KEY);
overlayer?.add(HIGHLIGHT_KEY, range, Overlayer[style], { color });
const rect = range.getBoundingClientRect();
const { start, size, viewSize, sideProp } = this.view.renderer;
const position = rect[sideProp === 'height' ? 'y' : 'x'] + 88;
const offset = this.view.book.dir === 'rtl' ? viewSize - position : position;
if (!this.view.renderer.scrolled || offset < start || offset > start + size) {
this.view.renderer.scrollToAnchor(range);
}
};
}
@@ -109,7 +102,7 @@ export class TTSController extends EventTarget {
const highlightOptions: TTSHighlightOptions = { style: 'highlight', color: 'gray' };
await this.view.initTTS(
granularity,
createRejecttFilter({
createRejectFilter({
tags: ['rt', 'sup'],
contents: [{ tag: 'a', content: /^\d+$/ }],
}),
+1 -1
View File
@@ -1,4 +1,4 @@
export const createRejecttFilter = ({
export const createRejectFilter = ({
tags = [],
classes = [],
attributes = [],