RouterViewにScrollPositiionManagerを埋め込む

This commit is contained in:
tamaina 2023-07-17 08:46:32 +00:00
parent 4bef4953b8
commit e7251220d5
5 changed files with 69 additions and 61 deletions

View file

@ -18,7 +18,7 @@
</template> </template>
<div ref="contents" :class="$style.root" style="container-type: inline-size;"> <div ref="contents" :class="$style.root" style="container-type: inline-size;">
<RouterView :key="reloadCount" :router="router"/> <RouterView :key="reloadCount" :router="router" :scrollContainer="contents"/>
</div> </div>
</MkWindow> </MkWindow>
</template> </template>
@ -32,12 +32,11 @@ import copyToClipboard from '@/scripts/copy-to-clipboard';
import { url } from '@/config'; import { url } from '@/config';
import { mainRouter, routes, page } from '@/router'; import { mainRouter, routes, page } from '@/router';
import { $i } from '@/account'; import { $i } from '@/account';
import { Router, useScrollPositionManager } from '@/nirax'; import { Router } from '@/nirax';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
import { openingWindowsCount } from '@/os'; import { openingWindowsCount } from '@/os';
import { claimAchievement } from '@/scripts/achievements'; import { claimAchievement } from '@/scripts/achievements';
import { getScrollContainer } from '@/scripts/scroll';
const props = defineProps<{ const props = defineProps<{
initialPath: string; initialPath: string;
@ -141,8 +140,6 @@ function popout() {
windowEl.close(); windowEl.close();
} }
useScrollPositionManager(() => getScrollContainer(contents.value), router);
onMounted(() => { onMounted(() => {
openingWindowsCount.value++; openingWindowsCount.value++;
if (openingWindowsCount.value >= 3) { if (openingWindowsCount.value >= 3) {

View file

@ -11,12 +11,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onBeforeUnmount, provide } from 'vue'; import { computed, inject, onBeforeUnmount, provide, nextTick } from 'vue';
import { Resolved, Router } from '@/nirax'; import { NiraxChangeEvent, Resolved, Router } from '@/nirax';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { getScrollContainer } from '@/scripts/scroll';
const props = defineProps<{ const props = defineProps<{
router?: Router; router?: Router;
/**
* Set any element if scroll position management needed
*/
scrollContainer?: HTMLElement | null;
}>(); }>();
const router = props.router ?? inject('router'); const router = props.router ?? inject('router');
@ -45,17 +51,49 @@ let currentPageComponent = $shallowRef(current.route.component);
let currentPageProps = $ref(current.props); let currentPageProps = $ref(current.props);
let key = $ref(current.route.path + JSON.stringify(Object.fromEntries(current.props))); let key = $ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
function onChange({ resolved, key: newKey }) { const scrollContainer = computed(() => props.scrollContainer ? (getScrollContainer(props.scrollContainer) ?? document.getElementsByTagName('html')[0]) : undefined);
const current = resolveNested(resolved);
const scrollPosStore = new Map<string, number>();
function onChange(ctx: NiraxChangeEvent) {
// save scroll position
if (scrollContainer.value) scrollPosStore.set(key, scrollContainer.value.scrollTop);
//#region change page
const current = resolveNested(ctx.resolved);
if (current == null) return; if (current == null) return;
currentPageComponent = current.route.component; currentPageComponent = current.route.component;
currentPageProps = current.props; currentPageProps = current.props;
key = current.route.path + JSON.stringify(Object.fromEntries(current.props)); key = current.route.path + JSON.stringify(Object.fromEntries(current.props));
//#endregion
//#region scroll
nextTick(() => {
if (!scrollContainer.value) return;
const scrollPos = scrollPosStore.get(key) ?? 0;
scrollContainer.value.scroll({ top: scrollPos, behavior: 'instant' });
if (scrollPos !== 0) {
window.setTimeout(() => { //
if (!scrollContainer.value) return;
scrollContainer.value.scroll({ top: scrollPos, behavior: 'instant' });
}, 100);
}
});
//#endregion
} }
router.addListener('change', onChange); router.addListener('change', onChange);
function onSame() {
if (!scrollContainer.value) return;
scrollContainer.value.scroll({ top: 0, behavior: 'smooth' });
}
router.addListener('same', onSame);
onBeforeUnmount(() => { onBeforeUnmount(() => {
router.removeListener('change', onChange); router.removeListener('change', onChange);
router.removeListener('same', onSame);
}); });
</script> </script>

View file

@ -49,24 +49,30 @@ function parsePath(path: string): ParsedPath {
return res; return res;
} }
export class Router extends EventEmitter<{ export type NiraxChangeEvent = {
change: (ctx: {
beforePath: string; beforePath: string;
path: string; path: string;
resolved: Resolved; resolved: Resolved;
key: string; key: string;
}) => void; };
replace: (ctx: {
export type NiraxExportEvent = {
path: string; path: string;
key: string; key: string;
}) => void; };
push: (ctx: {
export type NiraxPushEvent = {
beforePath: string; beforePath: string;
path: string; path: string;
route: RouteDef | null; route: RouteDef | null;
props: Map<string, string> | null; props: Map<string, string> | null;
key: string; key: string;
}) => void; };
export class Router extends EventEmitter<{
change: (ctx: NiraxChangeEvent) => void;
replace: (ctx: NiraxExportEvent) => void;
push: (ctx: NiraxExportEvent) => void;
same: () => void; same: () => void;
}> { }> {
private routes: RouteDef[]; private routes: RouteDef[];
@ -271,29 +277,3 @@ export class Router extends EventEmitter<{
this.navigate(path, key); this.navigate(path, key);
} }
} }
export function useScrollPositionManager(getScrollContainer: () => HTMLElement, router: Router) {
const scrollPosStore = new Map<string, number>();
onMounted(() => {
const scrollContainer = getScrollContainer();
scrollContainer.addEventListener('scroll', () => {
scrollPosStore.set(router.getCurrentKey(), scrollContainer.scrollTop);
}, { passive: true });
router.addListener('change', ctx => {
const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
if (scrollPos !== 0) {
window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
}, 100);
}
});
router.addListener('same', () => {
scrollContainer.scroll({ top: 0, behavior: 'smooth' });
});
});
}

View file

@ -8,7 +8,7 @@
</template> </template>
<div ref="contents"> <div ref="contents">
<RouterView @contextmenu.stop="onContextmenu"/> <RouterView :scrollContainer="contents" @contextmenu.stop="onContextmenu"/>
</div> </div>
</XColumn> </XColumn>
</template> </template>
@ -21,8 +21,6 @@ import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { mainRouter } from '@/router'; import { mainRouter } from '@/router';
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata'; import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
import { useScrollPositionManager } from '@/nirax';
import { getScrollContainer } from '@/scripts/scroll';
defineProps<{ defineProps<{
column: Column; column: Column;
@ -66,6 +64,4 @@ function onContextmenu(ev: MouseEvent) {
}, },
}], ev); }], ev);
} }
useScrollPositionManager(() => getScrollContainer(contents.value), mainRouter);
</script> </script>

View file

@ -4,7 +4,7 @@
<MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;" @contextmenu.stop="onContextmenu"> <MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;" @contextmenu.stop="onContextmenu">
<template #header><XStatusBars :class="$style.statusbars"/></template> <template #header><XStatusBars :class="$style.statusbars"/></template>
<RouterView/> <RouterView :scrollContainer="contents?.rootEl"/>
<div :class="$style.spacer"></div> <div :class="$style.spacer"></div>
</MkStickyContainer> </MkStickyContainer>
@ -95,7 +95,6 @@ import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
import { miLocalStorage } from '@/local-storage'; import { miLocalStorage } from '@/local-storage';
import { CURRENT_STICKY_BOTTOM } from '@/const'; import { CURRENT_STICKY_BOTTOM } from '@/const';
import { useScrollPositionManager } from '@/nirax';
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
@ -214,8 +213,6 @@ watch($$(navFooter), () => {
}, { }, {
immediate: true, immediate: true,
}); });
useScrollPositionManager(() => contents.value.rootEl, mainRouter);
</script> </script>
<style> <style>