feat: amélioration de la navigation du Reader + correction affichage des chapitres non visibles
This commit is contained in:
parent
72d7c233f7
commit
05dd7262eb
@@ -1,21 +1,53 @@
|
||||
<template>
|
||||
<div class="infinite-reader" ref="containerRef">
|
||||
<!-- Navigation en haut -->
|
||||
<div class="navigation-wrapper top">
|
||||
<ChapterNavigation position="top" />
|
||||
</div>
|
||||
|
||||
<div v-for="(page, index) in pages" :key="index" class="page-wrapper">
|
||||
<div v-if="page?.loading" class="loading">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
<div v-else-if="page?.error" class="error">
|
||||
{{ page.error }}
|
||||
</div>
|
||||
<ReaderPage v-else-if="page?.base64Content" :page-data="page" :page-number="index + 1" :zoom="zoom" />
|
||||
</div>
|
||||
|
||||
<!-- Navigation en bas -->
|
||||
<div class="navigation-wrapper bottom">
|
||||
<ChapterNavigation position="bottom" />
|
||||
</div>
|
||||
|
||||
<!-- Bouton flottant pour revenir en haut -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
leave-active-class="transition-all duration-300 ease-in"
|
||||
enter-from-class="opacity-0 translate-y-5 scale-75"
|
||||
enter-to-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-from-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-to-class="opacity-0 translate-y-5 scale-75"
|
||||
>
|
||||
<button
|
||||
v-show="showScrollToTop"
|
||||
@click="scrollToTop"
|
||||
class="fixed bottom-6 right-6 z-[9999] bg-blue-600 hover:bg-blue-700 text-white w-12 h-12 rounded-full shadow-lg hover:shadow-xl flex items-center justify-center transition-all duration-200 hover:scale-110 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
title="Revenir en haut"
|
||||
type="button"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
|
||||
</svg>
|
||||
</button>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { nextTick } from 'vue';
|
||||
import ReaderPage from './ReaderPage.vue';
|
||||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import ChapterNavigation from './ChapterNavigation.vue';
|
||||
import ReaderPage from './ReaderPage.vue';
|
||||
|
||||
const props = defineProps({
|
||||
pages: {
|
||||
@@ -33,6 +65,13 @@
|
||||
const containerRef = ref(null);
|
||||
const observer = ref(null);
|
||||
|
||||
// État pour le bouton scroll to top
|
||||
const showScrollToTop = ref(false);
|
||||
|
||||
// Variables pour détecter la direction du scroll
|
||||
let lastScrollTop = 0;
|
||||
let scrollDirection = 'down';
|
||||
|
||||
const observeIntersection = entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
@@ -63,6 +102,91 @@
|
||||
});
|
||||
};
|
||||
|
||||
// Gestion du scroll pour le bouton "revenir en haut"
|
||||
const handleScroll = () => {
|
||||
let scrollTop = 0;
|
||||
|
||||
// Vérifier le scroll sur le conteneur direct
|
||||
if (containerRef.value && containerRef.value.scrollTop > 0) {
|
||||
scrollTop = containerRef.value.scrollTop;
|
||||
} else {
|
||||
// Vérifier le scroll sur les conteneurs parents
|
||||
let currentElement = containerRef.value?.parentElement;
|
||||
while (currentElement && scrollTop === 0) {
|
||||
if (currentElement.scrollTop > 0) {
|
||||
scrollTop = currentElement.scrollTop;
|
||||
break;
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
|
||||
// Vérifier le scroll sur la fenêtre
|
||||
if (scrollTop === 0) {
|
||||
scrollTop = window.scrollY;
|
||||
}
|
||||
}
|
||||
|
||||
// Détecter la direction du scroll
|
||||
if (scrollTop > lastScrollTop) {
|
||||
scrollDirection = 'down';
|
||||
} else if (scrollTop < lastScrollTop) {
|
||||
scrollDirection = 'up';
|
||||
}
|
||||
|
||||
// Mise à jour de la visibilité du bouton
|
||||
// Afficher si on scroll vers le bas et qu'on est à plus de 300px
|
||||
// Masquer si on scroll vers le haut ou qu'on est en haut de page
|
||||
if (scrollDirection === 'down' && scrollTop > 300) {
|
||||
showScrollToTop.value = true;
|
||||
} else if (scrollDirection === 'up' || scrollTop <= 100) {
|
||||
showScrollToTop.value = false;
|
||||
}
|
||||
|
||||
// Sauvegarder la position actuelle pour la prochaine comparaison
|
||||
lastScrollTop = scrollTop;
|
||||
};
|
||||
|
||||
// Fonction pour revenir en haut de la page
|
||||
const scrollToTop = () => {
|
||||
console.log('scrollToTop appelée'); // Debug
|
||||
|
||||
// Stratégie 1: Scroll sur le conteneur direct
|
||||
if (containerRef.value) {
|
||||
console.log('containerRef trouvé, scrollTop actuel:', containerRef.value.scrollTop); // Debug
|
||||
|
||||
if (containerRef.value.scrollTop > 0) {
|
||||
containerRef.value.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
console.log('Scroll sur containerRef effectué'); // Debug
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Stratégie 2: Chercher le conteneur parent avec scroll
|
||||
let currentElement = containerRef.value?.parentElement;
|
||||
while (currentElement) {
|
||||
const styles = window.getComputedStyle(currentElement);
|
||||
if (styles.overflowY === 'auto' || styles.overflowY === 'scroll' || currentElement.scrollTop > 0) {
|
||||
console.log('Conteneur avec scroll trouvé:', currentElement.className, 'scrollTop:', currentElement.scrollTop); // Debug
|
||||
currentElement.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
return;
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
|
||||
// Stratégie 3: Scroll sur la fenêtre entière
|
||||
console.log('Scroll sur window, scrollY actuel:', window.scrollY); // Debug
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.pages,
|
||||
() => {
|
||||
@@ -73,18 +197,34 @@
|
||||
|
||||
onMounted(() => {
|
||||
setupIntersectionObserver();
|
||||
|
||||
// Ajouter l'écouteur de scroll sur le conteneur
|
||||
if (containerRef.value) {
|
||||
containerRef.value.addEventListener('scroll', handleScroll, { passive: true });
|
||||
}
|
||||
|
||||
// Ajouter l'écouteur de scroll sur la fenêtre
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer.value) {
|
||||
observer.value.disconnect();
|
||||
}
|
||||
|
||||
// Nettoyer l'écouteur de scroll du conteneur
|
||||
if (containerRef.value) {
|
||||
containerRef.value.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
|
||||
// Nettoyer l'écouteur de scroll de la fenêtre
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.infinite-reader {
|
||||
@apply flex-1 flex flex-col items-center overflow-y-auto py-8;
|
||||
@apply flex-1 flex flex-col items-center overflow-y-auto py-8 relative;
|
||||
height: calc(100vh - 8rem);
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
@@ -95,12 +235,22 @@
|
||||
|
||||
.loading,
|
||||
.error {
|
||||
@apply flex items-center justify-center;
|
||||
width: 70vw;
|
||||
min-height: 400px;
|
||||
@apply flex items-center justify-center w-[70vw] min-h-[400px];
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply text-red-500 text-xl bg-red-500/10 rounded-lg;
|
||||
}
|
||||
|
||||
.navigation-wrapper {
|
||||
@apply w-full max-w-4xl mx-auto px-4 mb-6;
|
||||
}
|
||||
|
||||
.navigation-wrapper.top {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.navigation-wrapper.bottom {
|
||||
@apply mt-8 mb-4;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user