fix: avoid many scrollTo calls for n components

Thank you @vbelolapotkov for pointing out the flaws here:
https://github.com/Human-Connection/Human-Connection/pull/1756#discussion_r329361572

So here is my attempt to fix it:
* Install `vue-scrollto` which relies on `requestAnimationFrame`
  - apparently this is better on Safari and IE? 🤔
  - Mocking out entire modules is easier in jest:
    https://jestjs.io/docs/en/bypassing-module-mocks
* Require `checkAnchor` to be implemented on the component
This commit is contained in:
roschaefer 2019-10-01 11:55:18 +02:00
parent 2c705a8680
commit 9da40c4895
6 changed files with 79 additions and 22 deletions

View File

@ -10,7 +10,7 @@
</ds-card> </ds-card>
</div> </div>
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }"> <div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
<ds-card :id="`commentId-${comment.id}`"> <ds-card :id="anchor">
<ds-space margin-bottom="small" margin-top="small"> <ds-space margin-bottom="small" margin-top="small">
<hc-user :user="author" :date-time="comment.createdAt" /> <hc-user :user="author" :date-time="comment.createdAt" />
<!-- Content Menu (can open Modals) --> <!-- Content Menu (can open Modals) -->
@ -111,6 +111,9 @@ export default {
user: 'auth/user', user: 'auth/user',
isModerator: 'auth/isModerator', isModerator: 'auth/isModerator',
}), }),
anchor() {
return `commentId-${this.comment.id}`
},
displaysComment() { displaysComment() {
return !this.unavailable || this.isModerator return !this.unavailable || this.isModerator
}, },
@ -144,6 +147,9 @@ export default {
}, },
}, },
methods: { methods: {
checkAnchor(anchor) {
return `#${this.anchor}` === anchor
},
isAuthor(id) { isAuthor(id) {
return this.user.id === id return this.user.id === id
}, },

View File

@ -41,6 +41,9 @@ export default {
post: { type: Object, default: () => {} }, post: { type: Object, default: () => {} },
}, },
methods: { methods: {
checkAnchor(anchor) {
return anchor === '#comments'
},
updateCommentList(updatedComment) { updateCommentList(updatedComment) {
this.post.comments = this.post.comments.map(comment => { this.post.comments = this.post.comments.map(comment => {
return comment.id === updatedComment.id ? updatedComment : comment return comment.id === updatedComment.id ? updatedComment : comment

View File

@ -1,32 +1,20 @@
export function scrollToAnchor(anchor) { import { scrollTo } from 'vue-scrollto'
if (!anchor) return
if (!window || !document) {
return
}
const container = document.querySelector(anchor)
if (container) {
const { top } = container.getBoundingClientRect()
setTimeout(() => {
// we have to set a small timeout to ensure this part comes after nuxt
// scrollBehaviour: https://nuxtjs.org/api/configuration-router/#scrollbehavior
window.scroll({
top,
left: 0,
behavior: 'smooth',
})
}, 250)
}
}
export default { export default {
watch: { watch: {
$route(to, from) { $route(to, from) {
const anchor = to && to.hash const anchor = to && to.hash
scrollToAnchor(anchor) if (!this.checkAnchor(anchor)) return
setTimeout(() => {
scrollTo(anchor, 1000)
}, 250)
}, },
}, },
mounted() { mounted() {
const anchor = this.$route && this.$route.hash const anchor = this.$route && this.$route.hash
scrollToAnchor(anchor) if (!this.checkAnchor(anchor)) return
setTimeout(() => {
scrollTo(anchor, 1000)
}, 250)
}, },
} }

View File

@ -0,0 +1,47 @@
import { scrollTo } from 'vue-scrollto'
import scrollToAnchor from './scrollToAnchor'
jest.mock('vue-scrollto')
let component
describe('scrollToAnchor', () => {
beforeEach(() => {
jest.useFakeTimers()
scrollTo.mockClear()
})
describe('scrollToAnchor', () => {
const action = hash => {
let {
watch: { $route },
} = scrollToAnchor
$route.bind(component)({ hash })
jest.runAllTimers()
}
describe('given anchor `commentId-4711`', () => {
beforeEach(() => {
component = {
anchor: 'commentId-4711',
checkAnchor(anchor) {
return this.anchor === anchor
},
}
})
describe('$route.hash === anchor', () => {
it('calls window.scroll', () => {
action('commentId-4711')
expect(scrollTo).toHaveBeenCalled()
})
})
describe('$route.hash !== anchor', () => {
it('skips window.scroll', () => {
action('commentId-4712')
expect(scrollTo).not.toHaveBeenCalled()
})
})
})
})
})

View File

@ -83,6 +83,7 @@
"vue-count-to": "~1.0.13", "vue-count-to": "~1.0.13",
"vue-infinite-scroll": "^2.0.2", "vue-infinite-scroll": "^2.0.2",
"vue-izitoast": "^1.2.1", "vue-izitoast": "^1.2.1",
"vue-scrollto": "^2.17.1",
"vue-sweetalert-icons": "~4.2.0", "vue-sweetalert-icons": "~4.2.0",
"vuex-i18n": "~1.13.1", "vuex-i18n": "~1.13.1",
"xregexp": "^4.2.4", "xregexp": "^4.2.4",

View File

@ -4235,6 +4235,11 @@ bcrypt-pbkdf@^1.0.0:
dependencies: dependencies:
tweetnacl "^0.14.3" tweetnacl "^0.14.3"
bezier-easing@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
integrity sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY=
bfj@^6.1.1: bfj@^6.1.1:
version "6.1.1" version "6.1.1"
resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.1.tgz#05a3b7784fbd72cfa3c22e56002ef99336516c48" resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.1.tgz#05a3b7784fbd72cfa3c22e56002ef99336516c48"
@ -15315,6 +15320,13 @@ vue-router@~3.0.7:
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.7.tgz#b36ca107b4acb8ff5bc4ff824584059c23fcb87b" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.7.tgz#b36ca107b4acb8ff5bc4ff824584059c23fcb87b"
integrity sha512-utJ+QR3YlIC/6x6xq17UMXeAfxEvXA0VKD3PiSio7hBOZNusA1jXcbxZxVEfJunLp48oonjTepY8ORoIlRx/EQ== integrity sha512-utJ+QR3YlIC/6x6xq17UMXeAfxEvXA0VKD3PiSio7hBOZNusA1jXcbxZxVEfJunLp48oonjTepY8ORoIlRx/EQ==
vue-scrollto@^2.17.1:
version "2.17.1"
resolved "https://registry.yarnpkg.com/vue-scrollto/-/vue-scrollto-2.17.1.tgz#cd62ee0b98cf7e2ba9fd94f029addcd093978a48"
integrity sha512-uxOJXg6cZL88B+hTXRHDJMR+gHGiaS70ZTNk55fE5Z2TdwyIx9K/IHoNeTrtBrM6u3FASAIymKjZaQLmDf8Ykg==
dependencies:
bezier-easing "2.1.0"
vue-server-renderer@^2.6.10: vue-server-renderer@^2.6.10:
version "2.6.10" version "2.6.10"
resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz#cb2558842ead360ae2ec1f3719b75564a805b375" resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.10.tgz#cb2558842ead360ae2ec1f3719b75564a805b375"