feat(frontend): スクロール時の要素表示機能をFriendly以外のUIにも対応

This commit is contained in:
NoriDev 2023-08-09 18:02:03 +09:00
parent 3aeb5b52a1
commit ec5dd7c5bf
4 changed files with 69 additions and 7 deletions

View file

@ -35,6 +35,7 @@
- 이모티콘 피커의 검색 건수를 100개로 증가 (misskey-dev/misskey#11371)
- about-misskey 페이지에서 클라이언트 버전을 누르면 변경 사항을 볼 수 있음
- 새로운 신고가 있는 경우, 네비게이션 바의 제어판 아이콘과 제어판 페이지의 신고 섹션에 점을 표시
- 스크롤 시 요소 표시 기능을 Friendly 이외의 UI에도 대응
- Enhance: 「제어판 - 신고」페이지의 버튼 가독성 향상
- Enhance: 「모달에 흐림 효과 사용」옵션이 비활성화된 경우, 이미지를 탭하여 표시할 때 표시되는 배경을 어둡게 조정
- Fix: (Friendly) 흐림 효과를 사용할 때 하단 내비게이션 바의 가독성이 매우 떨어지는 문제

View file

@ -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;

View file

@ -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;
}

View file

@ -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 {