remove deprecations, remove header when scrolling
This commit is contained in:
@@ -1,21 +1,86 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { RouterLink, useRouter, useRoute } from 'vue-router'
|
||||
import { useFeeds, logout as logoutSession } from '@/composables/useFeeds'
|
||||
import Modal from './modal/AddUrl.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { sync, showModal, viewMode, toggleViewMode, layout, toggleLayout, markAllRead, feeds } = useFeeds()
|
||||
const { sync, showModal, viewMode, toggleViewMode, layout, toggleLayout, markAllRead, feeds, lastProgrammaticScroll } = useFeeds()
|
||||
|
||||
const headerRef = ref(null)
|
||||
|
||||
// Scroll-driven show/hide: the header slides out of view on scroll-down and
|
||||
// back in on scroll-up. This is show/hide via `transform` (not the old
|
||||
// resize behaviour) — the header is position:fixed, so translating it never
|
||||
// reflows content, and the app's programmatic scrolls resolve to sensible
|
||||
// states: scrollTo(0, 0) lands near the top → shown; the list-view
|
||||
// read-correction scrollBy moves only a few px → stays under the threshold.
|
||||
const hidden = ref(false)
|
||||
const REVEAL_THRESHOLD = 12 // px of accumulated travel before toggling
|
||||
// When the feed list mutates itself (read-correction scrollBy + array-splice
|
||||
// scroll anchoring) the page jumps *upward* without user intent. For this long
|
||||
// after such a jump we gate only the reveal direction, so the jump can't pop
|
||||
// the header back into view mid-read. Hiding stays allowed the whole time
|
||||
// (the jump never scrolls down), so scrolling down still hides normally even
|
||||
// while articles are being marked read. See lastProgrammaticScroll in useFeeds.
|
||||
const PROGRAMMATIC_SUPPRESS_MS = 300
|
||||
let lastY = 0
|
||||
let accumulated = 0
|
||||
|
||||
function onScroll() {
|
||||
const y = Math.max(0, window.scrollY)
|
||||
const headerH = headerRef.value?.offsetHeight ?? 0
|
||||
|
||||
// Always reveal near the very top, and keep it visible while the menu is
|
||||
// open (the dropdown is anchored to the header, so hiding it would slide the
|
||||
// open menu off-screen).
|
||||
if (y <= headerH || menuOpen.value) {
|
||||
hidden.value = false
|
||||
accumulated = 0
|
||||
lastY = y
|
||||
return
|
||||
}
|
||||
|
||||
const delta = y - lastY
|
||||
lastY = y
|
||||
// Reset the accumulator whenever direction flips, so the threshold is
|
||||
// measured from the last turning point (not from page load).
|
||||
if ((delta > 0) !== (accumulated > 0)) accumulated = 0
|
||||
accumulated += delta
|
||||
|
||||
// Hiding (scroll-down) is always allowed. Revealing (scroll-up) is gated for
|
||||
// a short window after a programmatic list update, whose induced jump is
|
||||
// upward and would otherwise pop the header back into view mid-read. A
|
||||
// genuine scroll-up reveals once the window has elapsed.
|
||||
const afterProgrammatic = performance.now() - lastProgrammaticScroll.value < PROGRAMMATIC_SUPPRESS_MS
|
||||
if (accumulated > REVEAL_THRESHOLD) hidden.value = true // scrolling down
|
||||
else if (accumulated < -REVEAL_THRESHOLD && !afterProgrammatic) hidden.value = false // scrolling up
|
||||
}
|
||||
|
||||
let ticking = false
|
||||
function onScrollRaf() {
|
||||
if (ticking) return
|
||||
ticking = true
|
||||
requestAnimationFrame(() => {
|
||||
onScroll()
|
||||
ticking = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Drives #app's padding-top / RssFeeds' scroll-margin-top so content below
|
||||
// the fixed header isn't hidden behind it at scroll position 0. The header is
|
||||
// a fixed size, so this is measured once on mount and never changes.
|
||||
const h = headerRef.value?.getBoundingClientRect().height ?? 0
|
||||
document.documentElement.style.setProperty('--app-nav-height', `${h}px`)
|
||||
|
||||
lastY = Math.max(0, window.scrollY)
|
||||
window.addEventListener('scroll', onScrollRaf, { passive: true })
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', onScrollRaf)
|
||||
})
|
||||
|
||||
const onFeedsPage = computed(() => route.path === '/feeds')
|
||||
@@ -65,7 +130,7 @@ function handleToggleLayout() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header ref="headerRef" class="app-nav">
|
||||
<header ref="headerRef" class="app-nav" :class="{ 'app-nav--hidden': hidden }">
|
||||
<div class="app-nav__wrapper">
|
||||
<span class="app-nav__title">RSS Reader<span v-if="unreadCount" class="app-nav__unread"> ({{ unreadCount }})</span></span>
|
||||
<button
|
||||
@@ -127,6 +192,12 @@ function handleToggleLayout() {
|
||||
z-index: 20;
|
||||
background: var(--color-background);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.25s ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.app-nav--hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.app-nav__wrapper {
|
||||
|
||||
Reference in New Issue
Block a user