summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/frontend/src/pages/user/home.vue99
1 files changed, 68 insertions, 31 deletions
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 42455bd18e..a2c17f6165 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-if="user.host == null && user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo>
<div :key="user.id" class="main _panel">
- <div class="banner-container" :style="style">
- <div ref="bannerEl" class="banner" :style="style"></div>
+ <div ref="bannerEl" class="banner-container">
+ <div class="banner" :style="style"></div>
<div class="fade"></div>
<div class="title">
<MkUserName class="name" :user="user" :nowrap="true"/>
@@ -159,9 +159,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
+import { defineAsyncComponent, computed, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, watch, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { getScrollPosition } from '@@/js/scroll.js';
+import { getScrollContainer } from '@@/js/scroll.js';
import MkNote from '@/components/MkNote.vue';
import MkFollowButton from '@/components/MkFollowButton.vue';
import MkAccountMoved from '@/components/MkAccountMoved.vue';
@@ -221,7 +221,6 @@ const emit = defineEmits<{
const router = useRouter();
const user = ref(props.user);
-const parallaxAnimationId = ref<null | number>(null);
const narrow = ref<null | boolean>(null);
const rootEl = ref<null | HTMLElement>(null);
const bannerEl = ref<null | HTMLElement>(null);
@@ -257,24 +256,6 @@ function menu(ev: MouseEvent) {
os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
}
-function parallaxLoop() {
- parallaxAnimationId.value = window.requestAnimationFrame(parallaxLoop);
- parallax();
-}
-
-function parallax() {
- const banner = bannerEl.value;
- if (banner == null) return;
-
- const top = getScrollPosition(rootEl.value);
-
- if (top < 0) return;
-
- const z = 1.75; // 奥行き(小さいほど奥)
- const pos = -(top / z);
- banner.style.backgroundPosition = `center calc(50% - ${pos}px)`;
-}
-
function showMemoTextarea() {
isEditingMemo.value = true;
nextTick(() => {
@@ -304,8 +285,38 @@ async function reload() {
// TODO
}
+let bannerParallaxResizeObserver: ResizeObserver | null = null;
+
+function calcBannerParallax() {
+ if (!bannerEl.value || !CSS.supports('view-timeline-inset', 'auto 100px')) return;
+ const elRect = bannerEl.value.getBoundingClientRect();
+ const scrollEl = getScrollContainer(bannerEl.value);
+ const scrollPosition = scrollEl?.scrollTop ?? window.scrollY;
+ const scrollContainerHeight = scrollEl?.clientHeight ?? window.innerHeight;
+ const scrollContainerTop = scrollEl?.getBoundingClientRect().top ?? 0;
+ const top = scrollPosition + elRect.top - scrollContainerTop;
+ const bottom = scrollContainerHeight - top;
+ bannerEl.value.style.setProperty('--bannerParallaxInset', `auto ${bottom}px`);
+}
+
+function initCalcBannerParallax() {
+ const scrollEl = bannerEl.value ? getScrollContainer(bannerEl.value) : null;
+ if (scrollEl != null && CSS.supports('view-timeline-inset', 'auto 100px')) {
+ bannerParallaxResizeObserver = new ResizeObserver(() => {
+ calcBannerParallax();
+ });
+ bannerParallaxResizeObserver.observe(scrollEl);
+ }
+}
+
+function disposeBannerParallaxResizeObserver() {
+ if (bannerParallaxResizeObserver) {
+ bannerParallaxResizeObserver.disconnect();
+ bannerParallaxResizeObserver = null;
+ }
+}
+
onMounted(() => {
- window.requestAnimationFrame(parallaxLoop);
narrow.value = rootEl.value!.clientWidth < 1000;
if (props.user.birthday) {
@@ -319,16 +330,24 @@ onMounted(() => {
});
}
}
+
nextTick(() => {
+ calcBannerParallax();
adjustMemoTextarea();
});
+
+ initCalcBannerParallax();
});
-onUnmounted(() => {
- if (parallaxAnimationId.value) {
- window.cancelAnimationFrame(parallaxAnimationId.value);
+onActivated(() => {
+ if (bannerEl.value) {
+ calcBannerParallax();
+ initCalcBannerParallax();
}
});
+
+onUnmounted(disposeBannerParallaxResizeObserver);
+onDeactivated(disposeBannerParallaxResizeObserver);
</script>
<style lang="scss" scoped>
@@ -353,14 +372,23 @@ onUnmounted(() => {
overflow: clip;
background-size: cover;
background-position: center;
+ view-timeline-name: --bannerParallax;
+ view-timeline-inset: var(--bannerParallaxInset, auto);
+ view-timeline-axis: block;
> .banner {
- height: 100%;
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 100%;
+ height: 300%;
background-color: #4c5e6d;
- background-size: cover;
+ background-repeat: repeat-y;
background-position: center;
- box-shadow: 0 0 128px rgba(0, 0, 0, 0.5) inset;
- will-change: background-position;
+ will-change: transform;
+ animation: bannerParallaxKeyframes linear both;
+ animation-timeline: --bannerParallax;
+ animation-range: cover;
}
> .fade {
@@ -716,6 +744,15 @@ onUnmounted(() => {
}
}
}
+
+@keyframes bannerParallaxKeyframes {
+ from {
+ transform: translateY(-50%);
+ }
+ to {
+ transform: translateY(-30%);
+ }
+}
</style>
<style lang="scss" module>