Detachable scrollTop button
This commit is contained in:
parent
706975e41d
commit
47d4cb8f17
7 changed files with 73 additions and 25 deletions
1
changelog.d/better-scroll-button.add
Normal file
1
changelog.d/better-scroll-button.add
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add support for detachable scrollTop button
|
||||||
23
src/App.js
23
src/App.js
|
|
@ -20,6 +20,8 @@ import { defineAsyncComponent } from 'vue'
|
||||||
import { useShoutStore } from './stores/shout'
|
import { useShoutStore } from './stores/shout'
|
||||||
import { useInterfaceStore } from './stores/interface'
|
import { useInterfaceStore } from './stores/interface'
|
||||||
|
|
||||||
|
import { throttle } from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -58,16 +60,23 @@ export default {
|
||||||
// Load the locale from the storage
|
// Load the locale from the storage
|
||||||
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
||||||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||||
window.addEventListener('resize', this.updateMobileState)
|
|
||||||
document.getElementById('modal').classList = ['-' + this.layoutType]
|
document.getElementById('modal').classList = ['-' + this.layoutType]
|
||||||
|
|
||||||
|
// Create bound handlers
|
||||||
|
this.updateScrollState = throttle(this.scrollHandler, 200)
|
||||||
|
this.updateMobileState = throttle(this.resizeHandler, 200)
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
window.addEventListener('resize', this.updateMobileState)
|
||||||
|
this.scrollParent.addEventListener('scroll', this.updateScrollState)
|
||||||
|
|
||||||
if (useInterfaceStore().themeApplied) {
|
if (useInterfaceStore().themeApplied) {
|
||||||
this.removeSplash()
|
this.removeSplash()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unmounted () {
|
unmounted () {
|
||||||
window.removeEventListener('resize', this.updateMobileState)
|
window.removeEventListener('resize', this.updateMobileState)
|
||||||
|
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
themeApplied () {
|
themeApplied () {
|
||||||
|
|
@ -146,13 +155,23 @@ export default {
|
||||||
},
|
},
|
||||||
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
|
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
|
||||||
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
|
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
|
||||||
|
scrollParent () { return window; /* this.$refs.appContentRef */ },
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig'])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMobileState () {
|
resizeHandler () {
|
||||||
useInterfaceStore().setLayoutWidth(windowWidth())
|
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||||
useInterfaceStore().setLayoutHeight(windowHeight())
|
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||||
},
|
},
|
||||||
|
scrollHandler () {
|
||||||
|
const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop
|
||||||
|
|
||||||
|
if (scrollPosition != 0) {
|
||||||
|
this.$refs.appContentRef.classList.add(['-scrolled'])
|
||||||
|
} else {
|
||||||
|
this.$refs.appContentRef.classList.remove(['-scrolled'])
|
||||||
|
}
|
||||||
|
},
|
||||||
removeSplash () {
|
removeSplash () {
|
||||||
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
|
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
|
||||||
const splashscreenRoot = document.querySelector('#splash')
|
const splashscreenRoot = document.querySelector('#splash')
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<Notifications v-if="currentUser" />
|
<Notifications v-if="currentUser" />
|
||||||
<div
|
<div
|
||||||
id="content"
|
id="content"
|
||||||
|
ref="appContentRef"
|
||||||
class="app-layout container"
|
class="app-layout container"
|
||||||
:class="classes"
|
:class="classes"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
18
src/components/scroll_top_button/scroll_top_button.js
Normal file
18
src/components/scroll_top_button/scroll_top_button.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const ScrollTopButton = {
|
||||||
|
props: {
|
||||||
|
fast: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
scrollToTop() {
|
||||||
|
const speed = this.fast ? 'instant' : 'smooth';
|
||||||
|
|
||||||
|
window.scrollTo({ top: 0, behavior: speed })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ScrollTopButton
|
||||||
29
src/components/scroll_top_button/scroll_top_button.vue
Normal file
29
src/components/scroll_top_button/scroll_top_button.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<div class="rightside-button scroll-to-top">
|
||||||
|
<button
|
||||||
|
class="button-unstyled scroll-to-top-button"
|
||||||
|
type="button"
|
||||||
|
:title="$t('general.scroll_to_top')"
|
||||||
|
@click="scrollToTop"
|
||||||
|
>
|
||||||
|
<FALayers class="fa-scale-110 fa-old-padding-layer">
|
||||||
|
<FAIcon icon="arrow-up" />
|
||||||
|
<FAIcon
|
||||||
|
icon="minus"
|
||||||
|
transform="up-7"
|
||||||
|
/>
|
||||||
|
</FALayers>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./scroll_top_button.js"></script>
|
||||||
|
<style lang="scss">
|
||||||
|
.scroll-to-top {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.-scrolled .scroll-to-top {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -5,6 +5,7 @@ import Conversation from '../conversation/conversation.vue'
|
||||||
import TimelineMenu from '../timeline_menu/timeline_menu.vue'
|
import TimelineMenu from '../timeline_menu/timeline_menu.vue'
|
||||||
import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
|
import QuickFilterSettings from '../quick_filter_settings/quick_filter_settings.vue'
|
||||||
import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
|
import QuickViewSettings from '../quick_view_settings/quick_view_settings.vue'
|
||||||
|
import ScrollTopButton from '../scroll_top_button/scroll_top_button.vue'
|
||||||
import { debounce, throttle, keyBy } from 'lodash'
|
import { debounce, throttle, keyBy } from 'lodash'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons'
|
import { faCircleNotch, faCirclePlus, faCog, faMinus, faArrowUp, faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
@ -47,6 +48,7 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Status,
|
Status,
|
||||||
|
ScrollTopButton,
|
||||||
Conversation,
|
Conversation,
|
||||||
TimelineMenu,
|
TimelineMenu,
|
||||||
QuickFilterSettings,
|
QuickFilterSettings,
|
||||||
|
|
@ -144,9 +146,6 @@ const Timeline = {
|
||||||
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
|
this.$store.commit('setLoading', { timeline: this.timelineName, value: false })
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scrollToTop () {
|
|
||||||
window.scrollTo({ top: this.$el.offsetTop })
|
|
||||||
},
|
|
||||||
stopBlockingClicks: debounce(function () {
|
stopBlockingClicks: debounce(function () {
|
||||||
this.blockingClicks = false
|
this.blockingClicks = false
|
||||||
}, 1000),
|
}, 1000),
|
||||||
|
|
@ -251,7 +250,6 @@ const Timeline = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleScroll: throttle(function (e) {
|
handleScroll: throttle(function (e) {
|
||||||
this.showScrollTop = this.$el.offsetTop < window.scrollY
|
|
||||||
this.determineVisibleStatuses()
|
this.determineVisibleStatuses()
|
||||||
this.scrollLoad(e)
|
this.scrollLoad(e)
|
||||||
}, 200),
|
}, 200),
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,7 @@
|
||||||
v-if="!embedded"
|
v-if="!embedded"
|
||||||
:timeline-name="timelineName"
|
:timeline-name="timelineName"
|
||||||
/>
|
/>
|
||||||
<div
|
<ScrollTopButton />
|
||||||
v-if="showScrollTop"
|
|
||||||
class="rightside-button"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="button-unstyled scroll-to-top-button"
|
|
||||||
type="button"
|
|
||||||
:title="$t('general.scroll_to_top')"
|
|
||||||
@click="scrollToTop"
|
|
||||||
>
|
|
||||||
<FALayers class="fa-scale-110 fa-old-padding-layer">
|
|
||||||
<FAIcon icon="arrow-up" />
|
|
||||||
<FAIcon
|
|
||||||
icon="minus"
|
|
||||||
transform="up-7"
|
|
||||||
/>
|
|
||||||
</FALayers>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<template v-if="mobileLayout">
|
<template v-if="mobileLayout">
|
||||||
<div
|
<div
|
||||||
v-if="showLoadButton"
|
v-if="showLoadButton"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue