From 860f68a2a2c06360eb2cfccb63c0b9692a409eba Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 6 Jul 2020 16:08:30 +0900 Subject: [PATCH] Resolve #6500 --- src/client/app.vue | 53 ++++++++++++++++++++------ src/client/pages/preferences/index.vue | 4 ++ src/client/scripts/sticky-sidebar.ts | 43 +++++++++++++++++++++ src/client/store.ts | 2 +- 4 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 src/client/scripts/sticky-sidebar.ts diff --git a/src/client/app.vue b/src/client/app.vue index 673949ce1..34c7543ac 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -87,8 +87,9 @@ @@ -134,6 +137,7 @@ import { ResizeObserver } from '@juggle/resize-observer'; import { v4 as uuid } from 'uuid'; import { host, instanceName } from './config'; import { search } from './scripts/search'; +import { StickySidebar } from './scripts/sticky-sidebar'; const DESKTOP_THRESHOLD = 1100; @@ -232,6 +236,12 @@ export default Vue.extend({ this.showNav = false; this.canBack = (window.history.length > 0 && !['index'].includes(to.name)); }, + + isDesktop() { + this.$nextTick(() => { + this.attachSticky(); + }); + } }, created() { @@ -277,9 +287,24 @@ export default Vue.extend({ if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true; }, { passive: true }); } + + // widget follow + this.attachSticky(); }, methods: { + attachSticky() { + if (!this.isDesktop) return; + if (this.$store.state.device.fixedWidgetsPosition) return; + + const stickyWidgetColumns = this.$refs.widgets.map(w => new StickySidebar(w.children[1], w.children[0], w.offsetTop)); + window.addEventListener('scroll', () => { + for (const stickyWidgetColumn of stickyWidgetColumns) { + stickyWidgetColumn.calc(window.scrollY); + } + }, { passive: true }); + }, + top() { window.scroll({ top: 0, behavior: 'smooth' }); }, @@ -988,15 +1013,14 @@ export default Vue.extend({ } > .widgets { - top: $header-height; - min-height: calc(100vh - #{$header-height}); padding: 0 var(--margin); box-shadow: 1px 0 0 0 var(--divider), -1px 0 0 0 var(--divider); &.fixed { position: sticky; - height: calc(100vh - #{$header-height}); overflow: auto; + height: calc(100vh - #{$header-height}); + top: $header-height; } &:first-of-type { @@ -1007,7 +1031,7 @@ export default Vue.extend({ } } - &:empty { + &.empty { display: none; } @@ -1015,9 +1039,16 @@ export default Vue.extend({ display: none; } - > * { - margin: var(--margin) 0; - width: 300px; + > .container { + position: sticky; + height: min-content; + min-height: calc(100vh - #{$header-height}); + overflow: hidden; + + > * { + margin: var(--margin) 0; + width: 300px; + } } > .add { diff --git a/src/client/pages/preferences/index.vue b/src/client/pages/preferences/index.vue index f3a6f1db2..3dba0a743 100644 --- a/src/client/pages/preferences/index.vue +++ b/src/client/pages/preferences/index.vue @@ -265,6 +265,10 @@ export default Vue.extend({ } location.reload(); }, + + fixedWidgetsPosition() { + location.reload() + }, }, methods: { diff --git a/src/client/scripts/sticky-sidebar.ts b/src/client/scripts/sticky-sidebar.ts new file mode 100644 index 000000000..872e162d2 --- /dev/null +++ b/src/client/scripts/sticky-sidebar.ts @@ -0,0 +1,43 @@ +export class StickySidebar { + private lastScrollTop = 0; + private el: HTMLElement; + private spacer: HTMLElement; + private marginTop: number; + private isTop = false; + private isBottom = false; + + constructor(el: StickySidebar['el'], spacer: StickySidebar['spacer'], marginTop = 0) { + this.el = el; + this.spacer = spacer; + this.marginTop = marginTop; + } + + public calc(scrollTop: number) { + if (scrollTop > this.lastScrollTop) { // downscroll + const overflow = this.el.clientHeight - window.innerHeight; + this.el.style.bottom = null; + this.el.style.top = `${-overflow}px`; + + this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight); + + if (this.isTop) { + this.isTop = false; + this.spacer.style.marginTop = `${scrollTop}px`; + } + } else { // upscroll + const overflow = this.el.clientHeight - window.innerHeight; + this.el.style.top = null; + this.el.style.bottom = `${-overflow - this.marginTop}px`; + + this.isTop = scrollTop <= this.el.offsetTop; + + if (this.isBottom) { + this.isBottom = false; + const overflow = this.el.clientHeight - window.innerHeight; + this.spacer.style.marginTop = `${scrollTop - (overflow + this.marginTop)}px`; + } + } + + this.lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; + } +} diff --git a/src/client/store.ts b/src/client/store.ts index bf1bdf1a8..eee3f5961 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -57,7 +57,7 @@ export const defaultDeviceSettings = { showFixedPostForm: false, disablePagesScript: true, enableInfiniteScroll: true, - fixedWidgetsPosition: true, + fixedWidgetsPosition: false, roomGraphicsQuality: 'medium', roomUseOrthographicCamera: true, sfxVolume: 0.3,