frontend: remove fullpage.js, rewrite equivalent

Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
This commit is contained in:
Nicolas Froger 2024-07-27 16:35:53 +02:00
commit 87e3b40a54
No known key found for this signature in database
7 changed files with 353 additions and 185 deletions

View file

@ -1,8 +1,9 @@
<script setup>
import { ref } from 'vue'
import { EyeOff, Info } from 'lucide-vue-next'
import { ChevronLeft, ChevronRight, EyeOff, Info } from 'lucide-vue-next'
import { cn } from '@/lib/utils.js'
defineProps(['post'])
const props = defineProps(['post', 'class'])
const section = ref(null)
let postDataVisible = ref(true)
@ -11,98 +12,182 @@ const postDataTitle = ref(null)
const postDataDescription = ref(null)
const postActionBtn = ref(null)
defineEmits(['scrollableelemententer', 'scrollableelementleave'])
function onVisible() {
document.addEventListener('keydown', onFullpageKeyDown)
}
function onInvisible() {
document.removeEventListener('keydown', onFullpageKeyDown)
}
defineExpose({ onVisible, onInvisible })
function onFullpageKeyDown(event) {
if (event.key === 'ArrowLeft') scrollSlideDir(-1)
else if (event.key === 'ArrowRight') scrollSlideDir(1)
else return
event.preventDefault()
event.stopPropagation()
}
function postDataToggle() {
if (postDataVisible.value) {
for (const data of postData.value) {
data.classList.remove('backdrop-blur-sm')
data.classList.remove('bg-gray-400/10')
data.classList.add('bg-gray-400/0')
}
for (const title of postDataTitle.value) {
title.classList.add('opacity-0')
}
for (const desc of postDataDescription.value) {
desc.classList.add('opacity-0')
desc.classList.remove('scrollable-element')
}
for (const btn of postActionBtn.value) {
btn.classList.remove('top-0')
btn.classList.remove('right-0')
btn.classList.add('left-0')
btn.classList.add('bottom-0')
}
postData.value.classList.remove('backdrop-blur-sm')
postData.value.classList.remove('bg-gray-400/10')
postData.value.classList.add('bg-gray-400/0')
postDataTitle.value.classList.add('opacity-0')
postDataDescription.value.classList.add('opacity-0')
postDataDescription.value.classList.remove('scrollable-element')
postActionBtn.value.classList.remove('top-0')
postActionBtn.value.classList.remove('right-0')
postActionBtn.value.classList.add('left-0')
postActionBtn.value.classList.add('bottom-0')
postDataVisible.value = false
setTimeout(() => {
postDataTitle.value.classList.add('hidden')
postDataDescription.value.classList.add('hidden')
}, 500)
} else {
for (const data of postData.value) {
data.classList.add('backdrop-blur-sm')
data.classList.add('bg-gray-400/10')
data.classList.remove('bg-gray-400/0')
}
for (const title of postDataTitle.value) {
title.classList.remove('opacity-0')
}
for (const desc of postDataDescription.value) {
desc.classList.remove('opacity-0')
desc.classList.add('scrollable-element')
}
for (const btn of postActionBtn.value) {
btn.classList.add('top-0')
btn.classList.add('right-0')
btn.classList.remove('left-0')
btn.classList.remove('bottom-0')
}
postDataTitle.value.classList.remove('hidden')
postDataDescription.value.classList.remove('hidden')
postData.value.classList.add('backdrop-blur-sm')
postData.value.classList.add('bg-gray-400/10')
postData.value.classList.remove('bg-gray-400/0')
postDataTitle.value.classList.remove('opacity-0')
postDataDescription.value.classList.remove('opacity-0')
postDataDescription.value.classList.add('scrollable-element')
postActionBtn.value.classList.add('top-0')
postActionBtn.value.classList.add('right-0')
postActionBtn.value.classList.remove('left-0')
postActionBtn.value.classList.remove('bottom-0')
postDataVisible.value = true
}
}
let slidesContainer = ref(null)
let fpCurrentSlide = 0
function scrollSlideDir(direction) {
scrollSlideToIndex(
(((fpCurrentSlide + direction) % slidesContainer.value.children.length) +
slidesContainer.value.children.length) %
slidesContainer.value.children.length
)
}
function stopChildrenVideo(element) {
for (const child of element.children) {
if (child.nodeName === 'VIDEO') {
child.pause()
child.currentTime = 0
return
} else {
stopChildrenVideo(child)
}
}
}
function playChildrenVideo(element) {
for (const child of element.children) {
if (child.nodeName === 'VIDEO') {
child.play()
return
} else {
playChildrenVideo(child)
}
}
}
function scrollSlideToIndex(index) {
const fromSlide = slidesContainer.value.children[fpCurrentSlide]
stopChildrenVideo(fromSlide)
slidesContainer.value.scroll({
top: 0,
left: window.innerWidth * index,
behavior: 'smooth'
})
fpCurrentSlide = index
const toSlide = slidesContainer.value.children[index]
playChildrenVideo(toSlide)
}
</script>
<template>
<div class="section relative overflow-hidden" ref="section" :data-anchor="'post-' + post.id">
<div class="slide relative overflow-hidden" v-for="asset in post.assets" :key="asset.id">
<img
class="absolute top-0 left-0 w-full h-full object-cover -z-10"
:data-src="asset.presignedUrl"
v-if="asset.contentType.startsWith('image/')"
/>
<video
class="absolute top-0 left-0 w-full h-full object-cover -z-10"
loop
muted="true"
playsinline
data-autoplay
v-else
>
<source :src="asset.presignedUrl" :type="asset.contentType" />
</video>
<div
:class="cn('w-screen h-dvh relative overscroll-y-contain', props.class)"
ref="section"
:data-anchor="'post-' + post.id"
>
<div
class="absolute top-1/2 left-0 mx-4 cursor-pointer z-30"
v-if="post.assets.length > 1"
@click="scrollSlideDir(-1)"
>
<ChevronLeft size="64" />
</div>
<div
class="absolute bottom-0 left-0 min-w-full max-w-full w-full lg:min-w-[20%] lg:max-w-[40%] lg:w-auto z-40 flex"
>
<div
class="grid grid-cols-3 grid-rows-5 absolute w-full h-full top-0 left-0 pointer-events-none"
ref="postData"
class="m-6 p-2 bg-gray-400/10 h-full w-full backdrop-blur-sm rounded-lg pointer-events-auto transition-colors duration-500"
>
<div
ref="postData"
class="col-start-1 col-span-3 lg:col-span-1 row-start-4 row-span-2 self-end place-self-stretch m-6 p-2 bg-gray-400/10 backdrop-blur-sm rounded-lg pointer-events-auto transition-colors duration-500"
class="absolute top-0 right-0 m-2 p-2 backdrop-blur-sm rounded-sm bg-black/10 hover:bg-black/20 cursor-pointer transition-colors duration-200 opacity-100"
ref="postActionBtn"
@click="postDataToggle"
>
<div
class="absolute top-0 right-0 m-2 p-2 backdrop-blur-sm rounded-sm bg-black/10 hover:bg-black/20 cursor-pointer transition-colors duration-200 opacity-100"
ref="postActionBtn"
@click="postDataToggle"
>
<EyeOff v-if="postDataVisible" :size="20" />
<Info :size="20" v-else />
</div>
<h2
ref="postDataTitle"
class="mx-2 scroll-m-20 pb-2 text-3xl font-semibold tracking-tight transition-opacity duration-500 mt-1"
>
{{ post.city }}, {{ post.country }}
</h2>
<div
ref="postDataDescription"
class="m-2 scrollable-element max-h-48 overflow-y-scroll transition-opacity duration-500"
v-html="post.formatedDescription"
></div>
<EyeOff v-if="postDataVisible" :size="20" />
<Info :size="20" v-else />
</div>
<h2
ref="postDataTitle"
class="mx-2 scroll-m-20 pb-2 text-3xl font-semibold tracking-tight transition-opacity duration-500 mt-1"
>
{{ post.city }}, {{ post.country }}
</h2>
<div
ref="postDataDescription"
class="m-2 max-h-48 overflow-y-scroll transition-opacity duration-500 overscroll-y-contain"
v-html="post.formatedDescription"
@mouseenter="$emit('scrollableelemententer')"
@touchstart="$emit('scrollableelemententer')"
@mouseleave="$emit('scrollableelementleave')"
></div>
</div>
</div>
<div ref="slidesContainer" class="h-dvh flex flex-nowrap overflow-hidden">
<div
class="w-screen h-dvh relative overflow-hidden overscroll-y-contain flex-grow-0 flex-shrink-0"
v-for="asset in post.assets"
:key="asset.id"
>
<img
class="absolute top-0 left-0 w-full h-dvh object-cover -z-10"
:src="asset.presignedUrl"
v-if="asset.contentType.startsWith('image/')"
/>
<video
class="absolute top-0 left-0 w-full h-dvh object-cover -z-10"
loop
muted="true"
playsinline
v-else
>
<source :src="asset.presignedUrl" :type="asset.contentType" />
</video>
</div>
</div>
<div
class="absolute top-1/2 right-0 mx-4 cursor-pointer z-30"
v-if="post.assets.length > 1"
@click="scrollSlideDir(1)"
>
<ChevronRight size="64" />
</div>
</div>
</template>

View file

@ -1,7 +1,10 @@
<script setup>
const props = defineProps(['class'])
import { usePostsStore } from '@/stores/posts.js'
import Countdown from '@/components/Countdown.vue'
import { onMounted, ref } from 'vue'
import { ChevronDown } from 'lucide-vue-next'
const enableCountdown = ref(false)
const countdownDate = ref(new Date('2024-07-28 07:00:00'))
@ -18,14 +21,16 @@ const postsStore = usePostsStore()
</script>
<template>
<div class="section" ref="section" data-anchor="welcome-section">
<div :class="props.class" ref="section" data-anchor="welcome-section">
<div class="flex flex-col w-full h-full items-center justify-center text-center">
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight mx-4 my-4">
3 semaines pour traverser 7 pays d'Europe en train
</h3>
<div v-if="postsStore.posts.length > 0">
<p class="mx-4">suivez mon voyage en naviguant vers le bas</p>
<div class="arrow" />
<p v-if="postsStore.posts.length > 0" class="mx-4">
suivez mon voyage en naviguant vers le bas
</p>
<div v-if="postsStore.posts.length > 0" class="arrow mt-6">
<ChevronDown size="96" />
</div>
<div v-else>
<p class="mx-4">le contenu n'est pas encore disponible, revenez plus tard</p>
@ -40,29 +45,20 @@ const postsStore = usePostsStore()
<style scoped>
.arrow {
margin-top: 40px;
margin-left: auto;
margin-right: auto;
display: block;
width: 3vh;
height: 3vh;
border-bottom: 5px solid white;
border-right: 5px solid white;
transform: rotate(45deg);
animation: arrowAnimation 3s infinite;
}
@keyframes arrowAnimation {
0% {
opacity: 0;
transform: rotate(45deg) translate(-20px, -20px);
transform: translate(0, 0);
}
50% {
opacity: 1;
}
100% {
opacity: 0;
transform: rotate(45deg) translate(20px, 20px);
transform: translate(0, 40px);
}
}
</style>