diff --git a/packages/app-expo/assets/reader/reader.html b/packages/app-expo/assets/reader/reader.html
index 6967e9048..03d2d186a 100644
--- a/packages/app-expo/assets/reader/reader.html
+++ b/packages/app-expo/assets/reader/reader.html
@@ -255,6 +255,7 @@
let currentCustomFontFaceCSS = '';
let currentCustomFontFamily = '';
let currentUseBookFonts = true;
+ let currentSmoothReading = false;
let tapNavigationInFlight = false;
let tapNavigationResetTimer = null;
let readerElement = null;
@@ -380,6 +381,21 @@
}
}
+ // Smooth reading ("平滑阅读"): one toggle drives both the foliate
+ // `animated` page-turn transition and the `scroll-inertia` finger-flick
+ // physics. The attributes are inert until the renderer handles them, so
+ // this is safe to toggle at any time (load, viewMode change, or live).
+ function syncSmoothReadingAttribute(renderer) {
+ if (!renderer) return;
+ if (currentSmoothReading) {
+ renderer.setAttribute('animated', '');
+ renderer.setAttribute('scroll-inertia', '');
+ } else {
+ renderer.removeAttribute('animated');
+ renderer.removeAttribute('scroll-inertia');
+ }
+ }
+
function getTransformViewportSize() {
const root = document.documentElement;
const viewport = window.visualViewport;
@@ -1859,6 +1875,10 @@
if (settings.justifyBodyText !== undefined) {
currentJustifyBodyText = settings.justifyBodyText !== false;
}
+ if (settings.smoothReading !== undefined) {
+ currentSmoothReading = settings.smoothReading === true;
+ syncSmoothReadingAttribute(renderer);
+ }
// PDF pages are fixed-layout canvases. Reader font, line-height, custom
// font and paragraph-spacing rules would move the transparent text layer
// away from the canvas and render a second, misaligned copy of the text.
@@ -5025,8 +5045,8 @@
diff --git a/packages/app-expo/assets/reader/reader.template.html b/packages/app-expo/assets/reader/reader.template.html
index d3658a0b3..32decaccf 100644
--- a/packages/app-expo/assets/reader/reader.template.html
+++ b/packages/app-expo/assets/reader/reader.template.html
@@ -208,6 +208,7 @@
let currentCustomFontFaceCSS = '';
let currentCustomFontFamily = '';
let currentUseBookFonts = true;
+ let currentSmoothReading = false;
let tapNavigationInFlight = false;
let tapNavigationResetTimer = null;
let readerElement = null;
@@ -333,6 +334,21 @@
}
}
+ // Smooth reading ("平滑阅读"): one toggle drives both the foliate
+ // `animated` page-turn transition and the `scroll-inertia` finger-flick
+ // physics. The attributes are inert until the renderer handles them, so
+ // this is safe to toggle at any time (load, viewMode change, or live).
+ function syncSmoothReadingAttribute(renderer) {
+ if (!renderer) return;
+ if (currentSmoothReading) {
+ renderer.setAttribute('animated', '');
+ renderer.setAttribute('scroll-inertia', '');
+ } else {
+ renderer.removeAttribute('animated');
+ renderer.removeAttribute('scroll-inertia');
+ }
+ }
+
function getTransformViewportSize() {
const root = document.documentElement;
const viewport = window.visualViewport;
@@ -1812,6 +1828,10 @@
if (settings.justifyBodyText !== undefined) {
currentJustifyBodyText = settings.justifyBodyText !== false;
}
+ if (settings.smoothReading !== undefined) {
+ currentSmoothReading = settings.smoothReading === true;
+ syncSmoothReadingAttribute(renderer);
+ }
// PDF pages are fixed-layout canvases. Reader font, line-height, custom
// font and paragraph-spacing rules would move the transparent text layer
// away from the canvas and render a second, misaligned copy of the text.
diff --git a/packages/app-expo/src/hooks/use-reader-bridge.ts b/packages/app-expo/src/hooks/use-reader-bridge.ts
index ba9e1f8c0..07bed2b88 100644
--- a/packages/app-expo/src/hooks/use-reader-bridge.ts
+++ b/packages/app-expo/src/hooks/use-reader-bridge.ts
@@ -55,6 +55,8 @@ export interface ReaderInitialSettings {
useBookFonts?: boolean;
viewMode?: "paginated" | "scroll";
paginatedLayout?: "single" | "double";
+ /** Smooth reading: foliate `animated` page-turn + `scroll-inertia` physics */
+ smoothReading?: boolean;
}
function withJustifiedTextSetting(settings: ReaderInitialSettings = {}): ReaderInitialSettings {
@@ -286,6 +288,7 @@ export function useReaderBridge(callbacks: ReaderBridgeCallbacks) {
paginatedLayout?: "single" | "double";
customFontFaceCSS?: string;
customFontFamily?: string;
+ smoothReading?: boolean;
}) => {
const msg = JSON.stringify({
type: "applySettings",
diff --git a/packages/app-expo/src/screens/ReaderScreen.tsx b/packages/app-expo/src/screens/ReaderScreen.tsx
index 3eb75832a..a87fe5edc 100644
--- a/packages/app-expo/src/screens/ReaderScreen.tsx
+++ b/packages/app-expo/src/screens/ReaderScreen.tsx
@@ -595,6 +595,7 @@ export function ReaderScreen({ route, navigation }: Props) {
useBookFonts: settings.useBookFonts,
viewMode: settings.viewMode,
paginatedLayout: settings.paginatedLayout,
+ smoothReading: settings.smoothReading === true,
customFontFaceCSS: fontCSS,
customFontFamily: fontFamily ?? "",
});
@@ -1144,6 +1145,7 @@ export function ReaderScreen({ route, navigation }: Props) {
useBookFonts: readSettings.useBookFonts,
viewMode: readSettings.viewMode,
paginatedLayout: readSettings.paginatedLayout,
+ smoothReading: readSettings.smoothReading === true,
},
});
diff --git a/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx b/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx
index 110484add..f3ae11e95 100644
--- a/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx
+++ b/packages/app-expo/src/screens/reader/ReaderSettingsPanel.tsx
@@ -40,6 +40,7 @@ export function ReaderSettingsPanel({ visible, readSettings, bookId, onClose, on
pageMargin: settingPageMargin,
viewMode: settingViewMode,
volumeButtonsPageTurn,
+ smoothReading,
showTopTitleProgress,
showBottomTimeBattery,
followSystemFontScale,
@@ -261,6 +262,23 @@ export function ReaderSettingsPanel({ visible, readSettings, bookId, onClose, on
+ {/* Smooth reading: animated page turns + scroll inertia */}
+
+
+ {t("settings.smoothReading")}
+
+ {t("settings.smoothReadingDesc", "开启翻页过渡动画与滚动惯性,关闭可提升低性能设备流畅度")}
+
+
+ onUpdateSetting("smoothReading", !smoothReading)}
+ >
+
+ {smoothReading ? t("settings.enabled") : t("settings.disabled")}
+
+
+
{Platform.OS === "android" && (
diff --git a/packages/app-expo/src/stores/settings-store.ts b/packages/app-expo/src/stores/settings-store.ts
index 6c0876618..c72e37371 100644
--- a/packages/app-expo/src/stores/settings-store.ts
+++ b/packages/app-expo/src/stores/settings-store.ts
@@ -55,6 +55,7 @@ const defaultReadSettings: ReadSettings = {
showTopTitleProgress: true,
showBottomTimeBattery: true,
volumeButtonsPageTurn: false,
+ smoothReading: false,
defaultHighlightColor: "yellow",
};
diff --git a/packages/core/src/i18n/locales/en/settings.json b/packages/core/src/i18n/locales/en/settings.json
index 41f7038bc..7c62a68bc 100644
--- a/packages/core/src/i18n/locales/en/settings.json
+++ b/packages/core/src/i18n/locales/en/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "Bottom Time & Battery",
"volumeButtonsPageTurn": "Volume Buttons Turn Pages",
"volumeButtonsPageTurnDesc": "While reading, volume keys turn pages",
+ "smoothReading": "Smooth Reading",
+ "smoothReadingDesc": "Enable page-turn transition animation and scroll inertia. Disable for better performance on low-end devices",
"followSystemFontScale": "Follow System Font Scale",
"followSystemFontScaleDesc": "Auto-scale by the OS accessibility font size",
"fixedLayoutNotice": "PDF is a fixed layout format. Font and layout settings are not available.",
diff --git a/packages/core/src/i18n/locales/es/settings.json b/packages/core/src/i18n/locales/es/settings.json
index 54ec854e6..1169f171d 100644
--- a/packages/core/src/i18n/locales/es/settings.json
+++ b/packages/core/src/i18n/locales/es/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "Hora y batería inferior",
"volumeButtonsPageTurn": "Botones de volumen cambian página",
"volumeButtonsPageTurnDesc": "Durante la lectura, los botones de volumen pasan página",
+ "smoothReading": "Lectura fluida",
+ "smoothReadingDesc": "Activa la animación de transición de página y la inercia de desplazamiento. Desactívelo para mejor rendimiento en dispositivos básicos",
"followSystemFontScale": "Seguir escala de fuente del sistema",
"followSystemFontScaleDesc": "Escalar automáticamente según el tamaño de fuente de accesibilidad del SO",
"fixedLayoutNotice": "PDF es un formato de diseño fijo. Los ajustes de fuente y diseño no están disponibles.",
diff --git a/packages/core/src/i18n/locales/fr/settings.json b/packages/core/src/i18n/locales/fr/settings.json
index b3628bc5e..64bcde715 100644
--- a/packages/core/src/i18n/locales/fr/settings.json
+++ b/packages/core/src/i18n/locales/fr/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "Heure et batterie en bas",
"volumeButtonsPageTurn": "Tourner les pages avec les boutons de volume",
"volumeButtonsPageTurnDesc": "En lecture, les boutons de volume tournent les pages",
+ "smoothReading": "Lecture fluide",
+ "smoothReadingDesc": "Active l'animation de transition de page et l'inertie de défilement. Désactivez pour de meilleures performances sur les appareils modestes",
"followSystemFontScale": "Suivre l'échelle de police système",
"followSystemFontScaleDesc": "Mise à l'échelle auto selon la taille de police d'accessibilité du système",
"fixedLayoutNotice": "Le PDF est un format à mise en page fixe. Les paramètres de police et mise en page ne sont pas disponibles.",
diff --git a/packages/core/src/i18n/locales/ja/settings.json b/packages/core/src/i18n/locales/ja/settings.json
index 7e9b7cdbc..c89c5832a 100644
--- a/packages/core/src/i18n/locales/ja/settings.json
+++ b/packages/core/src/i18n/locales/ja/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "下部時刻&バッテリー表示",
"volumeButtonsPageTurn": "音量ボタンでページ送り",
"volumeButtonsPageTurnDesc": "読書中は音量ボタンでページ送りします",
+ "smoothReading": "スムーズリーディング",
+ "smoothReadingDesc": "ページめくりアニメーションとスクロール慣性を有効化。低スペック端末ではオフにすると快適です",
"followSystemFontScale": "システムフォントサイズに従う",
"followSystemFontScaleDesc": "OSのアクセシビリティフォントサイズで自動調整",
"fixedLayoutNotice": "PDFは固定レイアウト形式です。フォントとレイアウト設定は利用できません。",
diff --git a/packages/core/src/i18n/locales/ko/settings.json b/packages/core/src/i18n/locales/ko/settings.json
index 6870a78e3..18b715456 100644
--- a/packages/core/src/i18n/locales/ko/settings.json
+++ b/packages/core/src/i18n/locales/ko/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "하단 시간 및 배터리",
"volumeButtonsPageTurn": "볼륨 버튼으로 페이지 넘기기",
"volumeButtonsPageTurnDesc": "독서 중 볼륨 버튼으로 페이지를 넘깁니다",
+ "smoothReading": "부드러운 읽기",
+ "smoothReadingDesc": "페이지 넘김 전환 애니메이션과 스크롤 관성을 켭니다. 저사양 기기에서는 끄면 더 부드럽습니다",
"followSystemFontScale": "시스템 글꼴 크기 따르기",
"followSystemFontScaleDesc": "OS 접근성 글꼴 크기에 따라 자동 조정",
"fixedLayoutNotice": "PDF는 고정 레이아웃 형식이에요. 글꼴 및 레이아웃 설정을 사용할 수 없어요.",
diff --git a/packages/core/src/i18n/locales/zh-TW/settings.json b/packages/core/src/i18n/locales/zh-TW/settings.json
index 3b1128ead..1cb61a0ad 100644
--- a/packages/core/src/i18n/locales/zh-TW/settings.json
+++ b/packages/core/src/i18n/locales/zh-TW/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "底部時間與電量",
"volumeButtonsPageTurn": "音量鍵翻頁",
"volumeButtonsPageTurnDesc": "開啟後閱讀時音量鍵用於上下翻頁",
+ "smoothReading": "平滑閱讀",
+ "smoothReadingDesc": "開啟翻頁過渡動畫與滾動慣性,關閉可提升低效能裝置流暢度",
"followSystemFontScale": "跟隨系統字型大小",
"followSystemFontScaleDesc": "按系統輔助功能字型大小自動放大",
"fixedLayoutNotice": "PDF 為固定排版格式,字型和排版設定不可用。",
diff --git a/packages/core/src/i18n/locales/zh/settings.json b/packages/core/src/i18n/locales/zh/settings.json
index d0ce559c7..508744bc6 100644
--- a/packages/core/src/i18n/locales/zh/settings.json
+++ b/packages/core/src/i18n/locales/zh/settings.json
@@ -72,6 +72,8 @@
"showBottomTimeBattery": "底部时间与电量",
"volumeButtonsPageTurn": "音量键翻页",
"volumeButtonsPageTurnDesc": "开启后阅读时音量键用于上下翻页",
+ "smoothReading": "平滑阅读",
+ "smoothReadingDesc": "开启翻页过渡动画与滚动惯性,关闭可提升低性能设备流畅度",
"followSystemFontScale": "跟随系统字号",
"followSystemFontScaleDesc": "按系统辅助功能字号自动放大",
"fixedLayoutNotice": "PDF 为固定布局格式,字体和排版设置不可用。",
diff --git a/packages/core/src/stores/settings-store.ts b/packages/core/src/stores/settings-store.ts
index 444d4ebfe..c9e0bc00b 100644
--- a/packages/core/src/stores/settings-store.ts
+++ b/packages/core/src/stores/settings-store.ts
@@ -57,6 +57,7 @@ const defaultReadSettings: ReadSettings = {
showTopTitleProgress: true,
showBottomTimeBattery: true,
volumeButtonsPageTurn: false,
+ smoothReading: false,
defaultHighlightColor: "yellow",
followSystemFontScale: false,
};
diff --git a/packages/core/src/types/book.ts b/packages/core/src/types/book.ts
index 2415de8f3..f20703d71 100644
--- a/packages/core/src/types/book.ts
+++ b/packages/core/src/types/book.ts
@@ -97,6 +97,14 @@ export interface ReadSettings extends ViewSettings {
showBottomTimeBattery: boolean;
volumeButtonsPageTurn: boolean;
defaultHighlightColor?: HighlightColor;
+ /**
+ * Mobile-only opt-in: smooth reading — enables the foliate `animated`
+ * page-turn transition and the `scroll-inertia` finger-flick physics.
+ * Default false so existing users keep the current (performance-first)
+ * rendering path. Optional so existing persisted settings deserialize
+ * cleanly.
+ */
+ smoothReading?: boolean;
/**
* Mobile-only opt-in: when true, the reader scales fontSize by the OS
* accessibility font scale (PixelRatio.getFontScale()) before rendering.
diff --git a/packages/foliate-js/paginator.js b/packages/foliate-js/paginator.js
index ffe4d7235..d8b48eb38 100644
--- a/packages/foliate-js/paginator.js
+++ b/packages/foliate-js/paginator.js
@@ -950,7 +950,7 @@ export class Paginator extends HTMLElement {
static observedAttributes = [
'flow', 'gap', 'margin', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right',
'max-inline-size', 'max-block-size', 'max-column-count',
- 'no-preload', 'no-background', 'no-continuous-scroll',
+ 'no-preload', 'no-background', 'no-continuous-scroll', 'scroll-inertia',
]
#root = this.attachShadow({ mode: 'closed' })
#observer = new ResizeObserver(() => this.render())
@@ -976,6 +976,11 @@ export class Paginator extends HTMLElement {
#scrollBounds
#touchState
#touchScrolled
+ #scrollInertiaFrame = null
+ #scrollInertiaToken = 0
+ #visibilityHandler = () => {
+ if (document.hidden) this.#cancelScrollInertia()
+ }
#lastVisibleRange
#scrollLocked = false
#isAnimating = false
@@ -1155,6 +1160,7 @@ export class Paginator extends HTMLElement {
this.#footer = this.#root.getElementById('footer')
this.#observer.observe(this.#container)
+ document.addEventListener('visibilitychange', this.#visibilityHandler)
const debouncedScroll = debounce(() => {
if (this.scrolled && !this.#isAnimating) {
// Skip entirely while stabilizing — preserve #justAnchored
@@ -1231,10 +1237,12 @@ export class Paginator extends HTMLElement {
this.addEventListener('touchstart', this.#onTouchStart.bind(this), opts)
this.addEventListener('touchmove', this.#onTouchMove.bind(this), opts)
this.addEventListener('touchend', this.#onTouchEnd.bind(this))
+ this.addEventListener('touchcancel', this.#onTouchCancel.bind(this))
this.addEventListener('load', ({ detail: { doc } }) => {
doc.addEventListener('touchstart', this.#onTouchStart.bind(this), opts)
doc.addEventListener('touchmove', this.#onTouchMove.bind(this), opts)
doc.addEventListener('touchend', this.#onTouchEnd.bind(this))
+ doc.addEventListener('touchcancel', this.#onTouchCancel.bind(this))
})
this.addEventListener('relocate', ({ detail }) => {
@@ -1622,6 +1630,7 @@ export class Paginator extends HTMLElement {
return this.#primaryIndex
}
setAttribute(name, value) {
+ if (name === 'flow') this.#cancelScrollInertia()
// The scrolled-mode scroll handler is debounced, so #anchor and
// #primaryIndex can lag behind the user's actual viewport by up to
// ~250ms. Toggling out of scrolled mode within that window made
@@ -1676,6 +1685,9 @@ export class Paginator extends HTMLElement {
}
}
break
+ case 'scroll-inertia':
+ if (value === null) this.#cancelScrollInertia()
+ break
}
}
open(book) {
@@ -2004,6 +2016,7 @@ export class Paginator extends HTMLElement {
}
set navigationLocked(value) {
this.#navigationLocked = !!value
+ if (this.#navigationLocked) this.#cancelScrollInertia()
}
get noPreload() {
return this.hasAttribute('no-preload')
@@ -2129,6 +2142,7 @@ export class Paginator extends HTMLElement {
this.#scrollToPage(page, 'snap')
}
#onTouchStart(e) {
+ this.#cancelScrollInertia()
if (this.#navigationLocked) return
const contents = this.getContents?.() ?? []
for (const { doc } of contents) {
@@ -2142,6 +2156,8 @@ export class Paginator extends HTMLElement {
vx: 0, xy: 0,
dx: 0, dy: 0,
dt: 0,
+ scrollVelocity: 0,
+ scrollSamples: [],
startX: touch?.screenX,
startY: touch?.screenY,
didPreventDefault: false,
@@ -2173,6 +2189,7 @@ export class Paginator extends HTMLElement {
const dx = state.x - x
const dy = state.y - y
+ const dt = Math.max(1, e.timeStamp - state.t)
state.x = x
state.y = y
state.t = e.timeStamp
@@ -2180,6 +2197,10 @@ export class Paginator extends HTMLElement {
const delta = this.#vertical ? -dx : dy
if (Math.abs(delta) < 0.5) return
+ state.scrollVelocity = delta / dt
+ state.scrollSamples.push({ velocity: state.scrollVelocity, time: e.timeStamp })
+ if (state.scrollSamples.length > 5) state.scrollSamples.shift()
+
e.preventDefault()
this.#touchScrolled = true
@@ -2261,7 +2282,11 @@ export class Paginator extends HTMLElement {
if (!this.#touchScrolled) return
this.#touchScrolled = false
- if (this.scrolled || this.#navigationLocked) return
+ if (this.scrolled) {
+ if (!this.#navigationLocked) this.#startScrollInertia(this.#touchState)
+ return
+ }
+ if (this.#navigationLocked) return
if (this.hasAttribute('no-swipe')) return
// XXX: Firefox seems to report scale as 1... sometimes...?
@@ -2274,6 +2299,66 @@ export class Paginator extends HTMLElement {
}
})
}
+ #onTouchCancel() {
+ this.#cancelScrollInertia()
+ this.#touchState = null
+ this.#touchScrolled = false
+ }
+ #cancelScrollInertia() {
+ this.#scrollInertiaToken += 1
+ if (this.#scrollInertiaFrame !== null) {
+ cancelAnimationFrame(this.#scrollInertiaFrame)
+ this.#scrollInertiaFrame = null
+ }
+ }
+ #startScrollInertia(state) {
+ if (!this.scrolled || !this.hasAttribute('scroll-inertia') || document.hidden) return
+ const samples = state?.scrollSamples || []
+ if (!samples.length) return
+ const recent = samples.slice(-3)
+ const velocity = recent.reduce((sum, sample) => sum + sample.velocity, 0) / recent.length
+ if (!Number.isFinite(velocity) || Math.abs(velocity) < 0.02) return
+
+ this.#cancelScrollInertia()
+ const token = this.#scrollInertiaToken
+ const startedAt = performance.now()
+ let previousTime = startedAt
+ let currentVelocity = velocity
+ const frictionTau = 325
+ const minVelocity = 0.015
+ const maxDuration = 900
+
+ const finish = () => {
+ if (token !== this.#scrollInertiaToken) return
+ this.#scrollInertiaFrame = null
+ this.#afterScroll('scroll')
+ }
+ const step = now => {
+ if (token !== this.#scrollInertiaToken) return
+ if (document.hidden || !this.scrolled) return finish()
+ const elapsed = now - startedAt
+ const dt = Math.min(32, Math.max(1, now - previousTime))
+ previousTime = now
+ currentVelocity *= Math.exp(-dt / frictionTau)
+ if (elapsed >= maxDuration || Math.abs(currentVelocity) < minVelocity) return finish()
+
+ const delta = currentVelocity * dt
+ const before = this.containerPosition
+ if (this.#vertical) this.scrollBy(0, delta)
+ else this.scrollBy(delta, 0)
+ const moved = Math.abs(this.containerPosition - before)
+ if (moved < 0.01) {
+ const forward = this.#vertical ? delta < 0 : delta > 0
+ if (Math.abs(delta) > 2) {
+ if (forward && !this.atEnd) void this.next()
+ else if (!forward && !this.atStart) void this.prev()
+ }
+ return finish()
+ }
+ this.#scrollInertiaFrame = requestAnimationFrame(step)
+ }
+ this.#scrollInertiaFrame = requestAnimationFrame(step)
+ }
// allows one to process rects as if they were LTR and horizontal
#getRectMapper(view) {
if (this.scrolled) {
@@ -2320,6 +2405,7 @@ export class Paginator extends HTMLElement {
return this.#scrollToPage(page, reason)
}
async #scrollTo(offset, reason, smooth) {
+ this.#cancelScrollInertia()
const { size } = this
if (this.containerPosition === offset) {
this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size]
@@ -2904,6 +2990,7 @@ export class Paginator extends HTMLElement {
}
}
async goTo(target) {
+ this.#cancelScrollInertia()
if (this.#locked) return
const resolved = await target
if (this.#canGoToIndex(resolved.index)) return this.#goTo(resolved)
@@ -2953,6 +3040,7 @@ export class Paginator extends HTMLElement {
if (this.sections[index]?.linear !== 'no') return index
}
async #turnPage(dir, distance) {
+ this.#cancelScrollInertia()
if (this.#locked) return
this.#locked = true
const prev = dir === -1
@@ -3046,7 +3134,9 @@ export class Paginator extends HTMLElement {
this.#primaryView?.destroyLoupe()
}
destroy() {
+ this.#cancelScrollInertia()
this.#observer.unobserve(this)
+ document.removeEventListener('visibilitychange', this.#visibilityHandler)
this.#destroyAllViews()
this.#mediaQuery.removeEventListener('change', this.#mediaQueryListener)
}