Fix Bible reader page jumping and layout stability issues

Layout Stability Improvements:
- Add consistent minHeight (60vh) to content containers to prevent layout shifts
- Implement sticky navigation bar with smooth transitions
- Position loading spinner absolutely to prevent content jumping
- Add smooth opacity transitions during content loading

Content Loading Enhancements:
- Preserve previous verses during loading to prevent content flashing
- Display previous verse count during transitions for visual continuity
- Use requestAnimationFrame and setTimeout for smoother state transitions
- Implement content fade-in effects with reduced opacity during loading

Scroll Position Management:
- Store scroll position before loading new content
- Restore scroll position after content loads (maintains reading flow)
- Smart scroll restoration that only applies when not navigating to specific verse
- Prevent scroll jumping during chapter transitions

User Experience Improvements:
- Eliminate page "jumping" between chapter transitions
- Remove container resizing issues that disrupted reading flow
- Provide professional, book-like reading experience
- Maintain visual continuity during all navigation actions
- Smooth loading states with professional transitions

Technical Implementation:
- Add previousVerses state for content continuity
- Implement position: sticky for navigation stability
- Use CSS transitions for smooth visual effects
- Optimize loading timing with requestAnimationFrame
- Maintain consistent container dimensions throughout app lifecycle

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-24 20:31:34 +00:00
parent 274f57f95d
commit 218d94107d

View File

@@ -147,6 +147,7 @@ export default function BibleReaderNew() {
const [preferences, setPreferences] = useState<ReadingPreferences>(defaultPreferences)
const [highlightedVerse, setHighlightedVerse] = useState<number | null>(null)
const [showScrollTop, setShowScrollTop] = useState(false)
const [previousVerses, setPreviousVerses] = useState<BibleVerse[]>([]) // Keep previous content during loading
// Bookmark state
const [isChapterBookmarked, setIsChapterBookmarked] = useState(false)
@@ -387,11 +388,35 @@ export default function BibleReaderNew() {
useEffect(() => {
if (selectedBook && selectedChapter) {
setLoading(true)
// Store scroll position to prevent jumping
const scrollPosition = window.pageYOffset
fetch(`/api/bible/verses?bookId=${selectedBook}&chapter=${selectedChapter}`)
.then(res => res.json())
.then(data => {
setVerses(data.verses || [])
setLoading(false)
const newVerses = data.verses || []
// Store previous verses before updating
setPreviousVerses(verses)
// Use requestAnimationFrame to ensure smooth transition
requestAnimationFrame(() => {
setVerses(newVerses)
// Small delay to allow content to render before removing loading state
setTimeout(() => {
setLoading(false)
setPreviousVerses([]) // Clear previous content after transition
// Restore scroll position if we're not navigating to a specific verse
const urlVerse = new URLSearchParams(window.location.search).get('verse')
if (!urlVerse) {
// Maintain scroll position for better UX
window.scrollTo({ top: Math.min(scrollPosition, document.body.scrollHeight), behavior: 'auto' })
}
}, 50) // Small delay for smoother transition
})
})
.catch(err => {
console.error('Error fetching verses:', err)
@@ -808,7 +833,11 @@ export default function BibleReaderNew() {
p: preferences.readingMode ? 1 : 2,
...getThemeStyles(),
border: preferences.readingMode ? 'none' : `1px solid ${getThemeStyles().borderColor}`,
backgroundColor: preferences.readingMode ? 'transparent' : getThemeStyles().backgroundColor
backgroundColor: preferences.readingMode ? 'transparent' : getThemeStyles().backgroundColor,
position: 'sticky',
top: 0,
zIndex: 1,
transition: 'all 0.2s ease'
}}
>
{/* First Row: Navigation Filters */}
@@ -1188,24 +1217,37 @@ export default function BibleReaderNew() {
sx={{
maxWidth: preferences.columnLayout ? 'none' : '800px',
mx: 'auto',
width: '100%'
width: '100%',
minHeight: '60vh', // Prevent layout shifts
position: 'relative'
}}
>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</Box>
) : (
<Paper
elevation={preferences.readingMode ? 0 : 1}
sx={{
...getThemeStyles(),
borderRadius: preferences.readingMode ? 0 : 2,
p: preferences.readingMode ? 4 : 3,
minHeight: preferences.readingMode ? '100vh' : 'auto',
border: preferences.readingMode ? 'none' : `1px solid ${getThemeStyles().borderColor}`
}}
>
<Paper
elevation={preferences.readingMode ? 0 : 1}
sx={{
...getThemeStyles(),
borderRadius: preferences.readingMode ? 0 : 2,
p: preferences.readingMode ? 4 : 3,
minHeight: preferences.readingMode ? '100vh' : '60vh', // Consistent minimum height
border: preferences.readingMode ? 'none' : `1px solid ${getThemeStyles().borderColor}`,
position: 'relative'
}}
>
{loading && (
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 2
}}
>
<CircularProgress />
</Box>
)}
<Box sx={{ opacity: loading ? 0.3 : 1, transition: 'opacity 0.3s ease' }}>
{/* Chapter Header */}
<Box sx={{ mb: 4, textAlign: 'center' }}>
<Typography
@@ -1219,13 +1261,13 @@ export default function BibleReaderNew() {
{currentBook?.name} {selectedChapter}
</Typography>
<Typography variant="body2" color="text.secondary">
{verses.length} {t('verses')}
{(loading && previousVerses.length > 0 ? previousVerses : verses).length} {t('verses')}
</Typography>
</Box>
{/* Verses */}
<Box sx={{ mb: 4 }}>
{verses.map(renderVerse)}
{(loading && previousVerses.length > 0 ? previousVerses : verses).map(renderVerse)}
</Box>
{/* Chapter Navigation */}
@@ -1248,8 +1290,8 @@ export default function BibleReaderNew() {
{t('nextChapter')}
</Button>
</Box>
</Paper>
)}
</Box>
</Paper>
</Box>
</Container>