Center and scale the catalog to fill mobile fullscreen.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -255,6 +255,7 @@ watch(
|
||||
:index="index"
|
||||
:single-page="singlePage"
|
||||
:flip-ms="flipMs"
|
||||
:immersive="immersive"
|
||||
@update:index="index = $event"
|
||||
@fallback="fallbackSlide = true"
|
||||
/>
|
||||
@@ -265,6 +266,7 @@ watch(
|
||||
:mode="visualEffect === 'fade' ? 'fade' : visualEffect === 'card' ? 'card' : 'slide'"
|
||||
:zoom="zoom"
|
||||
:flip-ms="flipMs"
|
||||
:immersive="immersive"
|
||||
@next="onUserNav(next)"
|
||||
@prev="onUserNav(prev)"
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ const props = defineProps<{
|
||||
mode: "slide" | "fade" | "card";
|
||||
zoom: boolean;
|
||||
flipMs: number;
|
||||
immersive?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -54,7 +55,7 @@ function onPointerUp(ev: PointerEvent) {
|
||||
<template>
|
||||
<div
|
||||
class="fx-stage"
|
||||
:class="[mode, { 'zoom-in': zoom }]"
|
||||
:class="[mode, { 'zoom-in': zoom, fill: immersive }]"
|
||||
:style="{ '--flip-ms': `${flipMs}ms` }"
|
||||
@pointerdown.prevent="onPointerDown"
|
||||
@pointerup.prevent="onPointerUp"
|
||||
@@ -81,6 +82,17 @@ function onPointerUp(ev: PointerEvent) {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.fx-stage.fill {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.fx-stage.fill .page-img {
|
||||
max-width: min(100vw, 100%);
|
||||
max-height: 100dvh;
|
||||
}
|
||||
|
||||
.fx-stage.card {
|
||||
perspective: 2600px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { PageFlip } from "page-flip";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -7,6 +7,7 @@ const props = defineProps<{
|
||||
index: number;
|
||||
singlePage: boolean;
|
||||
flipMs: number;
|
||||
immersive?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -17,10 +18,43 @@ const emit = defineEmits<{
|
||||
const host = ref<HTMLElement | null>(null);
|
||||
const outer = ref<HTMLElement | null>(null);
|
||||
const mountKey = ref(0);
|
||||
const fitScale = ref(1);
|
||||
let book: PageFlip | null = null;
|
||||
let ignore = false;
|
||||
let prepared: string[] = [];
|
||||
let makeGen = 0;
|
||||
let fitTimer = 0;
|
||||
|
||||
function updateFitScale() {
|
||||
if (!props.immersive || !host.value || !outer.value) {
|
||||
fitScale.value = 1;
|
||||
return;
|
||||
}
|
||||
const w = host.value.offsetWidth;
|
||||
const h = host.value.offsetHeight;
|
||||
const maxW = outer.value.clientWidth;
|
||||
const maxH = outer.value.clientHeight;
|
||||
if (w < 8 || h < 8 || maxW < 8 || maxH < 8) {
|
||||
fitScale.value = 1;
|
||||
return;
|
||||
}
|
||||
const pad = 16;
|
||||
const s = Math.min((maxW - pad) / w, (maxH - pad) / h);
|
||||
if (!Number.isFinite(s) || s <= 0) {
|
||||
fitScale.value = 1;
|
||||
return;
|
||||
}
|
||||
fitScale.value = Math.min(s, 8);
|
||||
}
|
||||
|
||||
function scheduleFit() {
|
||||
window.clearTimeout(fitTimer);
|
||||
fitTimer = window.setTimeout(() => {
|
||||
void nextTick().then(() => {
|
||||
requestAnimationFrame(updateFitScale);
|
||||
});
|
||||
}, 40);
|
||||
}
|
||||
|
||||
function loadImage(src: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -153,6 +187,7 @@ async function makeBook() {
|
||||
});
|
||||
});
|
||||
for (const url of previous) URL.revokeObjectURL(url);
|
||||
scheduleFit();
|
||||
} catch {
|
||||
emit("fallback");
|
||||
}
|
||||
@@ -178,7 +213,20 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.immersive,
|
||||
() => {
|
||||
scheduleFit();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("resize", scheduleFit);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", scheduleFit);
|
||||
window.clearTimeout(fitTimer);
|
||||
try {
|
||||
book?.destroy();
|
||||
} catch {
|
||||
@@ -220,7 +268,7 @@ function onPointerUp(ev: PointerEvent) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="magazine-frame" dir="ltr">
|
||||
<div class="magazine-frame" dir="ltr" :class="{ fill: immersive }">
|
||||
<div class="magazine-glow"></div>
|
||||
<div
|
||||
ref="outer"
|
||||
@@ -228,7 +276,12 @@ function onPointerUp(ev: PointerEvent) {
|
||||
@pointerdown.prevent="onPointerDown"
|
||||
@pointerup.prevent="onPointerUp"
|
||||
>
|
||||
<div :key="mountKey" ref="host" class="magazine-host"></div>
|
||||
<div
|
||||
:key="mountKey"
|
||||
ref="host"
|
||||
class="magazine-host"
|
||||
:style="{ transform: `scaleX(-1) scale(${fitScale})` }"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -256,11 +309,21 @@ function onPointerUp(ev: PointerEvent) {
|
||||
justify-content: center;
|
||||
direction: ltr;
|
||||
}
|
||||
.magazine-frame.fill {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
}
|
||||
.magazine-frame.fill .magazine-outer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.magazine-host {
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
max-width: 100%;
|
||||
max-width: none;
|
||||
transform: scaleX(-1);
|
||||
transform-origin: center center;
|
||||
filter: drop-shadow(0 28px 50px rgba(0, 0, 0, 0.55));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -415,9 +415,25 @@ a {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.stage-page.immersive {
|
||||
height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stage-page.immersive .viewer-wrap {
|
||||
padding: 0 !important;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100dvh;
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.stage-page.immersive .magazine-frame,
|
||||
.stage-page.immersive .fx-stage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tap-exit {
|
||||
|
||||
Reference in New Issue
Block a user