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 [preferences, setPreferences] = useState<ReadingPreferences>(defaultPreferences)
|
||||||
const [highlightedVerse, setHighlightedVerse] = useState<number | null>(null)
|
const [highlightedVerse, setHighlightedVerse] = useState<number | null>(null)
|
||||||
const [showScrollTop, setShowScrollTop] = useState(false)
|
const [showScrollTop, setShowScrollTop] = useState(false)
|
||||||
|
const [previousVerses, setPreviousVerses] = useState<BibleVerse[]>([]) // Keep previous content during loading
|
||||||
|
|
||||||
// Bookmark state
|
// Bookmark state
|
||||||
const [isChapterBookmarked, setIsChapterBookmarked] = useState(false)
|
const [isChapterBookmarked, setIsChapterBookmarked] = useState(false)
|
||||||
@@ -387,11 +388,35 @@ export default function BibleReaderNew() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedBook && selectedChapter) {
|
if (selectedBook && selectedChapter) {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
|
// Store scroll position to prevent jumping
|
||||||
|
const scrollPosition = window.pageYOffset
|
||||||
|
|
||||||
fetch(`/api/bible/verses?bookId=${selectedBook}&chapter=${selectedChapter}`)
|
fetch(`/api/bible/verses?bookId=${selectedBook}&chapter=${selectedChapter}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setVerses(data.verses || [])
|
const newVerses = data.verses || []
|
||||||
setLoading(false)
|
|
||||||
|
// 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 => {
|
.catch(err => {
|
||||||
console.error('Error fetching verses:', err)
|
console.error('Error fetching verses:', err)
|
||||||
@@ -808,7 +833,11 @@ export default function BibleReaderNew() {
|
|||||||
p: preferences.readingMode ? 1 : 2,
|
p: preferences.readingMode ? 1 : 2,
|
||||||
...getThemeStyles(),
|
...getThemeStyles(),
|
||||||
border: preferences.readingMode ? 'none' : `1px solid ${getThemeStyles().borderColor}`,
|
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 */}
|
{/* First Row: Navigation Filters */}
|
||||||
@@ -1188,24 +1217,37 @@ export default function BibleReaderNew() {
|
|||||||
sx={{
|
sx={{
|
||||||
maxWidth: preferences.columnLayout ? 'none' : '800px',
|
maxWidth: preferences.columnLayout ? 'none' : '800px',
|
||||||
mx: 'auto',
|
mx: 'auto',
|
||||||
width: '100%'
|
width: '100%',
|
||||||
|
minHeight: '60vh', // Prevent layout shifts
|
||||||
|
position: 'relative'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loading ? (
|
<Paper
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
elevation={preferences.readingMode ? 0 : 1}
|
||||||
<CircularProgress />
|
sx={{
|
||||||
</Box>
|
...getThemeStyles(),
|
||||||
) : (
|
borderRadius: preferences.readingMode ? 0 : 2,
|
||||||
<Paper
|
p: preferences.readingMode ? 4 : 3,
|
||||||
elevation={preferences.readingMode ? 0 : 1}
|
minHeight: preferences.readingMode ? '100vh' : '60vh', // Consistent minimum height
|
||||||
sx={{
|
border: preferences.readingMode ? 'none' : `1px solid ${getThemeStyles().borderColor}`,
|
||||||
...getThemeStyles(),
|
position: 'relative'
|
||||||
borderRadius: preferences.readingMode ? 0 : 2,
|
}}
|
||||||
p: preferences.readingMode ? 4 : 3,
|
>
|
||||||
minHeight: preferences.readingMode ? '100vh' : 'auto',
|
{loading && (
|
||||||
border: preferences.readingMode ? 'none' : `1px solid ${getThemeStyles().borderColor}`
|
<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 */}
|
{/* Chapter Header */}
|
||||||
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
||||||
<Typography
|
<Typography
|
||||||
@@ -1219,13 +1261,13 @@ export default function BibleReaderNew() {
|
|||||||
{currentBook?.name} {selectedChapter}
|
{currentBook?.name} {selectedChapter}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{verses.length} {t('verses')}
|
{(loading && previousVerses.length > 0 ? previousVerses : verses).length} {t('verses')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Verses */}
|
{/* Verses */}
|
||||||
<Box sx={{ mb: 4 }}>
|
<Box sx={{ mb: 4 }}>
|
||||||
{verses.map(renderVerse)}
|
{(loading && previousVerses.length > 0 ? previousVerses : verses).map(renderVerse)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Chapter Navigation */}
|
{/* Chapter Navigation */}
|
||||||
@@ -1248,8 +1290,8 @@ export default function BibleReaderNew() {
|
|||||||
{t('nextChapter')}
|
{t('nextChapter')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Box>
|
||||||
)}
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user