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:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user