bugfix: reader-nav is fully fixed; no settle-on-scroll (0.21.3)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mangalord-frontend",
|
||||
"version": "0.21.2",
|
||||
"version": "0.21.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -61,10 +61,12 @@
|
||||
--icon-lg: 22px;
|
||||
|
||||
/* App-frame heights (fixed-position bars at the top and bottom of
|
||||
the viewport). Used by the layout + reader to reserve content
|
||||
space and animate fullscreen mode. Recomputed once if the
|
||||
header padding/font-size ever changes — keep in sync. */
|
||||
the viewport). These are first-paint fallbacks — the real
|
||||
values are written by ResizeObservers on the actual elements
|
||||
in +layout.svelte and the reader, so they reflect rendered
|
||||
size and survive font / zoom / wrap changes. */
|
||||
--app-header-h: 60px;
|
||||
--reader-nav-h: 56px;
|
||||
--reader-bar-h: 56px;
|
||||
|
||||
--z-dropdown: 10;
|
||||
|
||||
@@ -226,9 +226,9 @@
|
||||
main {
|
||||
padding: var(--space-4);
|
||||
/* Reserve room for the fixed header so its presence doesn't
|
||||
overlap content. The min-height is a fallback that matches
|
||||
the header at typical viewport sizes (60–72px); resize
|
||||
observers would be more accurate but the gap is forgiving. */
|
||||
overlap content. The header height comes from a runtime
|
||||
ResizeObserver (see onMount above) so this always tracks
|
||||
the rendered size. */
|
||||
padding-top: calc(var(--app-header-h) + var(--space-4));
|
||||
max-width: 64rem;
|
||||
margin: 0 auto;
|
||||
@@ -236,6 +236,8 @@
|
||||
}
|
||||
|
||||
:global(html[data-reader-fullscreen='true']) main {
|
||||
padding-top: var(--space-4);
|
||||
/* No top reservation in focus mode — the chapter image runs
|
||||
edge-to-edge once the header has slid off. */
|
||||
padding-top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -69,6 +69,31 @@
|
||||
let index = $state(initialIndex);
|
||||
let continuousPageEls: HTMLImageElement[] = $state([]);
|
||||
let chapterBarEl: HTMLElement | undefined = $state();
|
||||
let readerNavEl: HTMLElement | undefined = $state();
|
||||
|
||||
// Publish the reader nav's actual measured height. Sticky
|
||||
// positioning had a "settle on scroll" effect: the bar's natural
|
||||
// position sat 16px below the app header (main's space-4 padding),
|
||||
// and only docked against the header once the user scrolled
|
||||
// enough to consume that gap. The fix is to lift the bar out of
|
||||
// document flow entirely (position: fixed) and reserve space in
|
||||
// the chapter content via `--reader-nav-h`.
|
||||
$effect(() => {
|
||||
if (!readerNavEl) return;
|
||||
const publish = () => {
|
||||
document.documentElement.style.setProperty(
|
||||
'--reader-nav-h',
|
||||
`${readerNavEl!.offsetHeight}px`
|
||||
);
|
||||
};
|
||||
publish();
|
||||
const ro = new ResizeObserver(publish);
|
||||
ro.observe(readerNavEl);
|
||||
return () => {
|
||||
ro.disconnect();
|
||||
document.documentElement.style.removeProperty('--reader-nav-h');
|
||||
};
|
||||
});
|
||||
|
||||
// Publish the bottom chapter-bar's actual measured height so the
|
||||
// continuous container's `padding-bottom` exactly matches it. Same
|
||||
@@ -389,7 +414,7 @@
|
||||
<title>{pageTitle}</title>
|
||||
</svelte:head>
|
||||
|
||||
<nav class="reader-nav" aria-label="reader">
|
||||
<nav class="reader-nav" aria-label="reader" bind:this={readerNavEl}>
|
||||
<a href="/manga/{manga.id}" class="back" data-testid="back-to-manga">
|
||||
<ArrowLeft size={18} aria-hidden="true" />
|
||||
{#if manga.cover_image_path}
|
||||
@@ -605,45 +630,36 @@
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* Sticky directly under the fixed layout header. `top: var(--app-header-h)`
|
||||
pins the bar's top edge to the bottom edge of the layout
|
||||
header so it never slides under it. Focus mode slides it up
|
||||
off-screen via a transform AND clips its height to 0 so the
|
||||
chapter pages get the full top of the viewport. */
|
||||
/* Pinned to the viewport directly below the (also fixed) layout
|
||||
header. `position: fixed` rather than `sticky` because the
|
||||
latter would have a "settle on scroll" period until its natural
|
||||
position consumes main's padding-top. Chapter content reserves
|
||||
room for this bar via `--reader-nav-h` (measured at runtime by
|
||||
a ResizeObserver in onMount). Focus mode slides it up
|
||||
off-screen via transform — the fixed origin keeps the slide
|
||||
distance equal to the bar's height regardless of scroll. */
|
||||
.reader-nav {
|
||||
position: sticky;
|
||||
position: fixed;
|
||||
top: var(--app-header-h);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: var(--space-2);
|
||||
padding-bottom: var(--space-2);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin: 0 calc(-1 * var(--space-4)) var(--space-3);
|
||||
padding-left: var(--space-4);
|
||||
padding-right: var(--space-4);
|
||||
background: var(--bg);
|
||||
gap: var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
transition:
|
||||
transform 220ms ease-out,
|
||||
max-height 220ms ease-out,
|
||||
opacity 220ms ease-out,
|
||||
padding 220ms ease-out,
|
||||
margin 220ms ease-out;
|
||||
max-height: 200px;
|
||||
overflow: hidden;
|
||||
opacity 220ms ease-out;
|
||||
}
|
||||
|
||||
:global(html[data-reader-fullscreen='true']) .reader-nav {
|
||||
transform: translateY(-100%);
|
||||
max-height: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
opacity: 0;
|
||||
border-bottom-color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -763,17 +779,37 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Reserve room for the fixed reader-nav above so the first page
|
||||
image (single mode) and the top of the chapter stack
|
||||
(continuous mode) aren't hidden behind it. The variable is
|
||||
written by the ResizeObserver in onMount so the reservation
|
||||
always matches actual rendered height. Focus mode collapses
|
||||
the reservation in lockstep with the bar's slide-out. */
|
||||
.page-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
padding-top: var(--reader-nav-h);
|
||||
transition: padding-top 220ms ease-out;
|
||||
}
|
||||
|
||||
.continuous {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: var(--reader-nav-h);
|
||||
transition:
|
||||
padding-top 220ms ease-out,
|
||||
padding-bottom 220ms ease-out;
|
||||
}
|
||||
|
||||
:global(html[data-reader-fullscreen='true']) .page-wrap,
|
||||
:global(html[data-reader-fullscreen='true']) .continuous {
|
||||
padding-top: 0;
|
||||
}
|
||||
:global(html[data-reader-fullscreen='true']) .continuous {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.page-image {
|
||||
|
||||
Reference in New Issue
Block a user