feat(frontend): スクロール時の要素表示機能をFriendly以外のUIにも対応
This commit is contained in:
parent
3aeb5b52a1
commit
ec5dd7c5bf
|
@ -35,6 +35,7 @@
|
|||
- 이모티콘 피커의 검색 건수를 100개로 증가 (misskey-dev/misskey#11371)
|
||||
- about-misskey 페이지에서 클라이언트 버전을 누르면 변경 사항을 볼 수 있음
|
||||
- 새로운 신고가 있는 경우, 네비게이션 바의 제어판 아이콘과 제어판 페이지의 신고 섹션에 점을 표시
|
||||
- 스크롤 시 요소 표시 기능을 Friendly 이외의 UI에도 대응
|
||||
- Enhance: 「제어판 - 신고」페이지의 버튼 가독성 향상
|
||||
- Enhance: 「모달에 흐림 효과 사용」옵션이 비활성화된 경우, 이미지를 탭하여 표시할 때 표시되는 배경을 어둡게 조정
|
||||
- Fix: (Friendly) 흐림 효과를 사용할 때 하단 내비게이션 바의 가독성이 매우 떨어지는 문제
|
||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div ref="rootEl">
|
||||
<div ref="headerEl" :class="[$style.root, {[$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name !== 'index', [$style.showElTl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name === 'index' }]">
|
||||
<div ref="headerEl" :class="[$style.root, {[$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isAllowHideHeader && (mainRouter.currentRoute.value.name !== 'index' || !isFriendly), [$style.showElTl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isAllowHideHeader && mainRouter.currentRoute.value.name === 'index' && isFriendly }]">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div ref="bodyEl" :data-sticky-container-header-height="headerHeight">
|
||||
|
@ -25,7 +25,9 @@ import { deviceKind } from '@/scripts/device-kind';
|
|||
import { mainRouter } from '@/router';
|
||||
import { defaultStore } from '@/store';
|
||||
import { eventBus } from '@/scripts/cherrypick/eventBus';
|
||||
import { miLocalStorage } from '@/local-storage';
|
||||
|
||||
const isFriendly = ref(miLocalStorage.getItem('ui') === 'friendly');
|
||||
const isAllowHideHeader = ref(['index', 'explore', 'my-notifications', 'my-favorites'].includes(<string>mainRouter.currentRoute.value.name));
|
||||
const MOBILE_THRESHOLD = 500;
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:enterFromClass="defaultStore.state.animation ? $style.transition_new_enterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.transition_new_leaveTo : ''"
|
||||
>
|
||||
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'default'" :class="[$style.new, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i>{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'default'" :class="[$style.new, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && !isFriendly, [$style.showElTab]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isFriendly, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i>{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
</transition>
|
||||
<transition
|
||||
:enterActiveClass="defaultStore.state.animation ? $style.transition_new_enterActive : ''"
|
||||
|
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:enterFromClass="defaultStore.state.animation ? $style.transition_new_enterFrom : ''"
|
||||
:leaveToClass="defaultStore.state.animation ? $style.transition_new_leaveTo : ''"
|
||||
>
|
||||
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'count'" :class="[$style.new, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i><I18n :src="i18n.ts.newNoteRecivedCount" textTag="span"><template #n>{{ queue }}</template></I18n></button></div>
|
||||
<div v-if="queue > 0 && defaultStore.state.newNoteReceivedNotificationBehavior === 'count'" :class="[$style.new, { [$style.showEl]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && !isFriendly, [$style.showElTab]: (showEl && ['hideHeaderOnly', 'hideHeaderFloatBtn', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) && isMobile && isFriendly, [$style.reduceAnimation]: !defaultStore.state.animation }]"><button class="_buttonPrimary" :class="$style.newButton" @click="top()"><i class="ti ti-arrow-up"></i><I18n :src="i18n.ts.newNoteRecivedCount" textTag="span"><template #n>{{ queue }}</template></I18n></button></div>
|
||||
</transition>
|
||||
<div :class="$style.tl">
|
||||
<MkTimeline
|
||||
|
@ -288,9 +288,13 @@ definePageMetadata(computed(() => ({
|
|||
}
|
||||
|
||||
&.showEl {
|
||||
transform: translateY(calc(var(--stickyTop, 0px) - 181px))
|
||||
transform: translateY(calc(var(--stickyTop, 0px) - 101px))
|
||||
}
|
||||
|
||||
&.showElTab {
|
||||
transform: translateY(calc(var(--stickyTop, 0px) - 181px))
|
||||
}
|
||||
|
||||
&.reduceAnimation {
|
||||
transition: opacity 0s, transform 0s;
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<XWidgets/>
|
||||
</div>
|
||||
|
||||
<button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
|
||||
<button v-if="!isDesktop && !isMobile" :class="[$style.widgetButton, { [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideHeaderFloatBtn', 'hideFloatBtnOnly', 'hideFloatBtnNavBar', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) }]" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
|
||||
|
||||
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
|
||||
<div v-if="isMobile" ref="navFooter" :class="[$style.nav, { [$style.reduceAnimation]: !defaultStore.state.animation, [$style.showEl]: (showEl && ['hideFloatBtnNavBar', 'hide'].includes(<string>defaultStore.state.displayHeaderNavBarWhenScroll)) }]">
|
||||
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"><i :class="$style.navButtonIcon" class="ti ti-bell"></i><span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
|
||||
|
@ -85,7 +85,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, shallowRef, Ref } from 'vue';
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
provide,
|
||||
onMounted,
|
||||
computed,
|
||||
ref,
|
||||
ComputedRef,
|
||||
watch,
|
||||
shallowRef,
|
||||
Ref,
|
||||
onBeforeUnmount
|
||||
} from 'vue';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
|
||||
import { instanceName } from '@/config';
|
||||
|
@ -101,6 +112,7 @@ import { deviceKind } from '@/scripts/device-kind';
|
|||
import { miLocalStorage } from '@/local-storage';
|
||||
import { CURRENT_STICKY_BOTTOM } from '@/const';
|
||||
import { useScrollPositionManager } from '@/nirax';
|
||||
import {eventBus} from "@/scripts/cherrypick/eventBus";
|
||||
|
||||
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
|
||||
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
|
||||
|
@ -116,6 +128,9 @@ window.addEventListener('resize', () => {
|
|||
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
|
||||
});
|
||||
|
||||
let showEl = $ref(false);
|
||||
let lastScrollPosition = $ref(0);
|
||||
|
||||
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
|
||||
const widgetsShowing = $ref(false);
|
||||
const navFooter = $shallowRef<HTMLElement>();
|
||||
|
@ -173,8 +188,30 @@ onMounted(() => {
|
|||
if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
contents.value.rootEl.addEventListener('scroll', onScroll);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
contents.value.rootEl.removeEventListener('scroll', onScroll);
|
||||
});
|
||||
|
||||
function onScroll() {
|
||||
const currentScrollPosition = contents.value.rootEl.scrollTop;
|
||||
if (currentScrollPosition < 0) {
|
||||
return;
|
||||
}
|
||||
// Stop executing this function if the difference between
|
||||
// current scroll position and last scroll position is less than some offset
|
||||
if (Math.abs(currentScrollPosition - lastScrollPosition) < 60) {
|
||||
return;
|
||||
}
|
||||
showEl = currentScrollPosition < lastScrollPosition;
|
||||
lastScrollPosition = currentScrollPosition;
|
||||
showEl = !showEl;
|
||||
eventBus.emit('showEl', showEl);
|
||||
}
|
||||
|
||||
const onContextmenu = (ev) => {
|
||||
const isLink = (el: HTMLElement) => {
|
||||
if (el.tagName === 'A') return true;
|
||||
|
@ -343,6 +380,15 @@ $widgets-hide-threshold: 1090px;
|
|||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
font-size: 22px;
|
||||
background: var(--panel);
|
||||
transition: opacity 0.5s, transform 0.5s;
|
||||
|
||||
&.reduceAnimation {
|
||||
transition: opacity 0s, transform 0s;
|
||||
}
|
||||
|
||||
&.showEl {
|
||||
transform: translateX(100px);
|
||||
}
|
||||
}
|
||||
|
||||
.widgetsDrawerBg {
|
||||
|
@ -390,6 +436,15 @@ $widgets-hide-threshold: 1090px;
|
|||
backdrop-filter: var(--blur, blur(24px));
|
||||
background-color: var(--header);
|
||||
border-top: solid 0.5px var(--divider);
|
||||
transition: opacity 0.5s, transform 0.5s;
|
||||
|
||||
&.reduceAnimation {
|
||||
transition: opacity 0s, transform 0s;
|
||||
}
|
||||
|
||||
&.showEl {
|
||||
transform: translateY(84.55px);
|
||||
}
|
||||
}
|
||||
|
||||
.navButton {
|
||||
|
|
Loading…
Reference in a new issue