diff options
| -rw-r--r-- | packages/frontend/src/pages/user/home.vue | 99 |
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> |