summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-03-20 18:55:32 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2025-03-20 18:55:32 +0900
commitabddd40c09c7c48d9c741db9cc322517085d8f67 (patch)
tree7e4a595408fb44b1a01da3a60ed4a00d8da01045 /packages/frontend/src
parentfix(frontend): MkRoleSelectDialogでのpopupの使い方が誤っているの... (diff)
downloadmisskey-abddd40c09c7c48d9c741db9cc322517085d8f67.tar.gz
misskey-abddd40c09c7c48d9c741db9cc322517085d8f67.tar.bz2
misskey-abddd40c09c7c48d9c741db9cc322517085d8f67.zip
enhance(frontend): 通常のRouterViewにTransitionを追加
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue3
-rw-r--r--packages/frontend/src/components/global/RouterView.vue70
-rw-r--r--packages/frontend/src/di.ts1
-rw-r--r--packages/frontend/src/utility/random-id.ts15
4 files changed, 84 insertions, 5 deletions
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 4321d69253..59bf80cfca 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -69,6 +69,8 @@ const emit = defineEmits<{
(ev: 'update:tab', key: string);
}>();
+const viewId = inject(DI.viewId);
+const viewTransitionName = computed(() => `${viewId}---pageHeader`);
const injectedPageMetadata = inject(DI.pageMetadata);
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
@@ -140,6 +142,7 @@ onUnmounted(() => {
backdrop-filter: var(--MI-blur, blur(15px));
border-bottom: solid 0.5px var(--MI_THEME-divider);
width: 100%;
+ view-transition-name: v-bind(viewTransitionName);
}
.upper,
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index fbdb7d261e..45cb1e3bd5 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
-<div class="_pageContainer" style="height: 100%;">
+<div ref="rootEl" class="_pageContainer" :class="$style.root">
<KeepAlive :max="prefer.s.numberOfPageCache">
<Suspense :timeout="0">
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
@@ -18,11 +18,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { inject, provide, ref, shallowRef } from 'vue';
+import { inject, nextTick, onMounted, provide, ref, shallowRef, useTemplateRef } from 'vue';
import type { Router } from '@/router.js';
import { prefer } from '@/preferences.js';
import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js';
+import { randomId } from '@/utility/random-id.js';
+import { deepEqual } from '@/utility/deep-equal.js';
const props = defineProps<{
router?: Router;
@@ -34,18 +36,76 @@ if (router == null) {
throw new Error('no router provided');
}
+const viewId = randomId();
+provide(DI.viewId, viewId);
+
const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1);
+const rootEl = useTemplateRef('rootEl');
+onMounted(() => {
+ rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
+});
+
+// view-transition-newなどの<pt-name-selector>にはcss varが使えず、v-bindできないため直接スタイルを生成
+const viewTransitionStylesTag = document.createElement('style');
+viewTransitionStylesTag.textContent = `
+@keyframes ${viewId}-old {
+ to { transform: scale(0.95); opacity: 0; }
+}
+
+@keyframes ${viewId}-new {
+ from { transform: scale(0.95); opacity: 0; }
+}
+
+::view-transition-old(${viewId}) {
+ animation-duration: 0.2s;
+ animation-name: ${viewId}-old;
+}
+
+::view-transition-new(${viewId}) {
+ animation-duration: 0.2s;
+ animation-name: ${viewId}-new;
+}
+`;
+
+window.document.head.appendChild(viewTransitionStylesTag);
+
const current = router.current!;
const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
const currentPageProps = ref(current.props);
+let currentRoutePath = current.route.path;
const key = ref(router.getCurrentFullPath());
router.useListener('change', ({ resolved }) => {
if (resolved == null || 'redirect' in resolved.route) return;
- currentPageComponent.value = resolved.route.component;
- currentPageProps.value = resolved.props;
- key.value = router.getCurrentFullPath();
+ if (resolved.route.path === currentRoutePath && deepEqual(resolved.props, currentPageProps.value)) return;
+
+ function _() {
+ currentPageComponent.value = resolved.route.component;
+ currentPageProps.value = resolved.props;
+ key.value = router.getCurrentFullPath();
+ currentRoutePath = resolved.route.path;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (prefer.s.animation && document.startViewTransition) {
+ document.startViewTransition(() => new Promise((res) => {
+ _();
+ nextTick(() => {
+ res();
+ //setTimeout(res, 100);
+ });
+ }));
+ } else {
+ _();
+ }
});
</script>
+
+<style lang="scss" module>
+.root {
+ height: 100%;
+ background-color: var(--MI_THEME-bg);
+}
+</style>
diff --git a/packages/frontend/src/di.ts b/packages/frontend/src/di.ts
index e9b2c2b650..4977cdbd62 100644
--- a/packages/frontend/src/di.ts
+++ b/packages/frontend/src/di.ts
@@ -11,4 +11,5 @@ export const DI = {
router: Symbol() as InjectionKey<Router>,
mock: Symbol() as InjectionKey<boolean>,
pageMetadata: Symbol() as InjectionKey<Ref<Record<string, any>>>,
+ viewId: Symbol() as InjectionKey<string>,
};
diff --git a/packages/frontend/src/utility/random-id.ts b/packages/frontend/src/utility/random-id.ts
new file mode 100644
index 0000000000..4e5943a97f
--- /dev/null
+++ b/packages/frontend/src/utility/random-id.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+const CHARS = 'abcdefghijklmnopqrstuvwxyz'; // CSSの<custom-ident>などで使われることもあるのでa-z以外使うな
+
+export function randomId(length = 32, characters = CHARS) {
+ let result = '';
+ const charactersLength = characters.length;
+ for ( let i = 0; i < length; i++ ) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}