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,78 +1,176 @@
<script setup>
import PostComponent from '@/components/PostComponent.vue'
import { usePostsStore } from '@/stores/posts.js'
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
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 fullpage = ref(null)
const fullpageOptions = ref({
licenseKey: 'gplv3-license',
menu: '#menu',
scrollBar: true,
normalScrollElements: '.scrollable-element',
scrollOverflow: false,
onLeave: onLeave,
credits: { enabled: false }
})
const menu = ref(null)
const menuItems = ref([])
let fullPageInit = false
const fullpageEnable = ref(false)
const fullpageKey = ref(Math.floor(Math.random() * 1000))
/*
There is a huge mess in this code because I couldn't find a way to make fullpage.js work well when
changing views.
At this point I've tried so many things that in the end I don't know which attempt worked best, so
I'm leaving this as is because reloading the page is the only way I could make the app work for the
users. How come after every ***** hack I've tried to completely destroy and rebuild the fullpage
instance it still finds a way to be alive and break when changing views????
*/
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
async function initFullpage() {
if (fullPageInit) return
fullpageKey.value += 5
fullpageEnable.value = true
onMounted(async () => {
await postsStore.fetchPosts()
await nextTick()
try {
fullpage.value.init()
} catch (e) {
console.log(
'failed to reload fullpage.js because it sucks with vue, reloading page as last resort'
)
window.location.reload()
return
}
fullPageInit = true
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)
}
onMounted(() => {
console.log('post view mounted')
postsStore.fetchPosts().then(() => {
initFullpage()
})
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()
})
onBeforeUnmount(async () => {
console.log('caught post view unmount')
if (
typeof window.fullpage_api !== 'undefined' &&
typeof window.fullpage_api.destroy !== 'undefined'
) {
window.fullpage_api.destroy('all')
// touch screen support
function onFullpageTouchStart(event) {
// more than one finger, not a scroll
if (fpTouchScroll) {
fpTouchScroll = null
return
}
fullpageKey.value++
fullpageEnable.value = false
await nextTick()
fullPageInit = false
})
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, direction, trigger) {
if (destination.anchor === 'welcome-section') {
function onLeave(origin, destination) {
if (destination === 'welcome-section') {
menu.value.classList.add('opacity-0')
menu.value.classList.remove('scrollable-element')
} else {
@ -82,7 +180,7 @@ function onLeave(origin, destination, direction, trigger) {
// 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.anchor) {
if (menuItem.dataset.menuanchor === destination) {
menuItem.scrollIntoView({
behavior: 'smooth',
inline: 'center'
@ -97,12 +195,15 @@ function onLeave(origin, destination, direction, trigger) {
<template>
<div
id="menu-grid"
class="fixed z-50 h-full w-full grid grid-cols-5 grid-rows-3 place-items-stretch pointer-events-none"
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-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 transition-opacity duration-1000 opacity-0"
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"
@ -119,18 +220,25 @@ function onLeave(origin, destination, direction, trigger) {
</li>
</ul>
</div>
<full-page
id="fullpage"
<div
class="fixed w-screen h-dvh overflow-y-scroll overscroll-y-contain"
ref="fullpage"
:options="fullpageOptions"
@on-leave="onLeave"
:skip-init="true"
:key="fullpageKey"
v-if="fullpageEnable"
@wheel="onFullpageWheel"
@touchstart="onFullpageTouchStart"
@touchend="onFullpageTouchEnd"
@scrollend="onFullpageScrollEnd"
@touchmove="onFullpageTouchMove"
>
<WelcomeComponent />
<PostComponent class="section" v-for="post in postsStore.posts" :key="post.id" :post="post" />
</full-page>
<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>