travel-steps/summer2024-frontend/src/views/PostsView.vue
Nicolas Froger 87e3b40a54
frontend: remove fullpage.js, rewrite equivalent
Signed-off-by: Nicolas Froger <nicolas@kektus.xyz>
2024-07-27 16:43:01 +02:00

254 lines
6.8 KiB
Vue

<script setup>
import PostComponent from '@/components/PostComponent.vue'
import { usePostsStore } from '@/stores/posts.js'
import { nextTick, onMounted, ref } from 'vue'
import WelcomeComponent from '@/components/WelcomeComponent.vue'
import { useRoute, useRouter } from 'vue-router'
const postsStore = usePostsStore()
const route = useRoute()
const router = useRouter()
const menu = ref(null)
const menuItems = ref([])
const fullpage = ref(null)
let fpMouseInScrollableElement = false
let fpIsScrolling = false
let fpCurrentlyInView = 0
let fpTouchScroll = null
let fpTouchScrollX = 0
let fpTouchScrollY = 0
let fpResizeTimeout = false
onMounted(async () => {
await postsStore.fetchPosts()
await nextTick()
fullpageScrollToAnchor(window.location.hash.substring(1))
})
window.addEventListener('popstate', (event) => {
fullpageScrollToAnchor(window.location.hash.substring(1))
})
window.addEventListener('resize', (event) => {
clearTimeout(fpResizeTimeout)
fpResizeTimeout = setTimeout(fullpageScrollToAnchor(window.location.hash.substring(1)), 500)
})
function onFullpageWheel(event) {
if (fpMouseInScrollableElement) return
if (fpIsScrolling) return
// prevent scroll
event.preventDefault()
event.stopPropagation()
fullpageScrollDir(event.deltaY > 0 ? 1 : -1)
}
document.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'PageUp') fullpageScrollDir(-1)
else if (event.key === 'ArrowDown' || event.key === ' ' || event.key === 'PageDown')
fullpageScrollDir(1)
else return
event.preventDefault()
event.stopPropagation()
})
// touch screen support
function onFullpageTouchStart(event) {
// more than one finger, not a scroll
if (fpTouchScroll) {
fpTouchScroll = null
return
}
const touch = event.changedTouches.item(0)
fpTouchScrollX = touch.clientX
fpTouchScrollY = touch.clientY
fpTouchScroll = touch
}
function onFullpageTouchMove(event) {
if (fpMouseInScrollableElement) {
return
}
event.preventDefault()
event.stopPropagation()
}
function onFullpageTouchEnd(event) {
if (fpMouseInScrollableElement) {
fpMouseInScrollableElement = false
return
}
if (fpIsScrolling) return
const touch = event.changedTouches.item(0)
const endTouchX = touch.clientX
const endTouchY = touch.clientY
const diffX = endTouchX - fpTouchScrollX
const diffY = endTouchY - fpTouchScrollY
if (Math.abs(diffY) > Math.abs(diffX)) {
// small diff, not worth scroll
if (Math.abs(diffY) < 10) {
fpTouchScroll = null
return
}
if (diffY > 0) fullpageScrollDir(-1)
else fullpageScrollDir(1)
}
fpTouchScroll = null
}
// actual scrolling
function fullpageScrollDir(direction) {
if (direction < 0 && fpCurrentlyInView <= 0) return
if (direction > 0 && fpCurrentlyInView >= fullpage.value.children.length - 1) return
fullpageScrollToIndex(fpCurrentlyInView + direction)
}
function fullpageScrollToAnchor(target) {
for (let i = 0; i < fullpage.value.children.length; i++) {
const slide = fullpage.value.children[i]
if (slide.dataset.anchor === target) {
fullpageScrollToIndex(i)
break
}
}
}
const postComponents = ref([])
function fullpageScrollToIndex(target) {
fpIsScrolling = true
if (fpCurrentlyInView !== 0) {
const from = postComponents.value[fpCurrentlyInView - 1]
if (Object.hasOwn(from, 'onInvisible')) from.onInvisible()
}
if (target !== 0) {
const to = postComponents.value[target - 1]
if (Object.hasOwn(to, 'onVisible')) to.onVisible()
}
const to = fullpage.value.children[target]
onLeave(null, to.dataset.anchor)
fullpage.value.scroll({
top: window.innerHeight * target,
left: 0,
behavior: 'smooth'
})
router.push({ name: route.name, hash: '#' + to.dataset.anchor })
fpCurrentlyInView = target
for (let i = 0; i < menu.value.children.length; i++) {
const menuItem = menu.value.children[i]
if (menuItem.dataset.menuanchor === to.dataset.anchor) {
menuItem.classList.add('active')
} else {
menuItem.classList.remove('active')
}
}
setTimeout(() => {
fpIsScrolling = false
}, 500)
}
function onFullpageScrollEnd() {
fpIsScrolling = false
}
function fullpageOnScrollableElementEnter() {
fpMouseInScrollableElement = true
}
function fullpageOnScrollableElementLeave() {
fpMouseInScrollableElement = false
}
// eslint-disable-next-line no-unused-vars
function onLeave(origin, destination) {
if (destination === 'welcome-section') {
menu.value.classList.add('opacity-0')
menu.value.classList.remove('scrollable-element')
} else {
menu.value.classList.remove('opacity-0')
menu.value.classList.add('scrollable-element')
// Disgusting way to make the menu automatically scroll when changing slide.
// For some reason this doesn't work on Chrome, I love web dev
for (const menuItem of menuItems.value) {
if (menuItem.dataset.menuanchor === destination) {
menuItem.scrollIntoView({
behavior: 'smooth',
inline: 'center'
})
break
}
}
}
}
</script>
<template>
<div
id="menu-grid"
class="fixed z-50 h-dvh w-full grid grid-cols-5 grid-rows-3 place-items-stretch pointer-events-none"
>
<ul
id="menu"
class="col-start-1 col-span-full row-start-1 max-h-32 lg:max-h-52 h-fit mt-28 sm:mt-16 lg:m-0 lg:col-start-5 lg:col-span-1 lg:row-start-3 lg:row-span-1 pointer-events-auto overflow-y-scroll overscroll-y-contain transition-opacity duration-1000 opacity-0"
ref="menu"
@mouseenter="fullpageOnScrollableElementEnter"
@touchstart="fullpageOnScrollableElementEnter"
@mouseleave="fullpageOnScrollableElementLeave"
>
<li
:data-menuanchor="'post-' + post.id"
v-for="post in postsStore.posts"
:key="post.id"
class="m-2 backdrop-blur-sm rounded-lg bg-black/10 hover:bg-gray-500/10 transition-colors duration-200"
ref="menuItems"
>
<a
:href="'#post-' + post.id"
class="block text-right px-4 py-3 transition-all duration-300"
>{{ post.formatedDate }}</a
>
</li>
</ul>
</div>
<div
class="fixed w-screen h-dvh overflow-y-scroll overscroll-y-contain"
ref="fullpage"
@wheel="onFullpageWheel"
@touchstart="onFullpageTouchStart"
@touchend="onFullpageTouchEnd"
@scrollend="onFullpageScrollEnd"
@touchmove="onFullpageTouchMove"
>
<WelcomeComponent class="w-screen h-dvh" />
<PostComponent
v-for="post in postsStore.posts"
:key="post.id"
:post="post"
@scrollableelemententer="fullpageOnScrollableElementEnter"
@scrollableelementleave="fullpageOnScrollableElementLeave"
ref="postComponents"
/>
</div>
</template>
<style scoped>
#menu li.active,
#menu li.active:hover {
background-color: rgba(255, 255, 255, 0.1);
}
#menu li.active a,
#menu li.active:hover a:hover {
font-weight: bold;
}
</style>