Skip to content

Commit

Permalink
Fix scrolling with offset
Browse files Browse the repository at this point in the history
RISDEV-2946
As scrollIntoView of Element.scrollIntoView() do not allow to use an offset. Window.scrollTo() though has options to both scroll to an offset position and to do so smoothly.
  • Loading branch information
leonie-koch committed Dec 15, 2023
1 parent b146b6d commit f629ee6
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ fileignoreconfig:
- filename: backend/src/test/kotlin/unit/application/service/GetFileServiceTest.kt
checksum: 8133265c6f388df84a3e0f61050428e8628853e0ba80b432e4723750a34b781e
- filename: compose.yaml
checksum: e36fa0e67d5919ad8d403dda4759a28d54f16320f50485acdea5fd79e5d2a98e
checksum: 819adfdca16189b30817f0af958088fd9d76d466d8cee377c5f05bb4ddec0b89
- filename: doc/norms/backend-api.yaml
checksum: df4fa1f7e6d7023f9a7a98d1052c2b88c1785366962004b3165453edcd5bf4db
- filename: doc/structurizr/workspace.json
checksum: 541780133a0bf013ac75f6a3bc402c77981a273654c5648eabeb32ccb271a834
- filename: frontend/Dockerfile.prod
checksum: 265a1ddc01dc3f5f8ec08f63b692c711cfefe947535d28ec3d8faa79a41f4ac6
checksum: ded78213b6e42141227ada2d018729b0bac907a31afb98e177d5840ed5fd88ba
- filename: frontend/FRONTEND_STYLEGUIDE.md
checksum: e6b8194eba2012f2a769c5a5d3577715a573b441a7b94e744c32b3149d0d3a93
- filename: frontend/src/main.ts
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/DocumentUnitCategories.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ const contentRelatedIndexing = computed({
})
const { hash: routeHash } = toRefs(route)
useScrollToHash(routeHash)
useScrollToHash(routeHash, 145)

This comment has been minimized.

Copy link
@HPrinz

HPrinz Dec 19, 2023

Contributor

Wollen wir der Magic Number noch einen Kommentar zur Bedeutung geben?

This comment has been minimized.

Copy link
@leonie-koch

leonie-koch Dec 19, 2023

Author Contributor

96a1c22

habs hier hinzugefügt, danke!

async function getOriginalDocumentUnit() {
if (fileAsHTML.value.length > 0) return
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/composables/useScrollToHash.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { watch } from "vue"
import type { Ref } from "vue"

export function useScrollToHash(routeHash: Ref<string | undefined>) {
export function useScrollToHash(
routeHash: Ref<string | undefined>,
offset?: number,
) {
function jumpToHash() {
// scrollIntoView with smooth behavior only works inside of a timeout
setTimeout(() => {
if (!routeHash.value) return
const idFromHash = routeHash.value.replace(/^#/, "")
const hashElement = document.getElementById(idFromHash)
hashElement?.scrollIntoView({ behavior: "smooth" })
const element = document.getElementById(idFromHash)
const headerOffset = offset ?? 0
const elementPosition = element ? element.getBoundingClientRect().top : 0
const offsetPosition = elementPosition + window.scrollY - headerOffset
window.scrollTo({
top: offsetPosition,
behavior: "smooth",
})
})
}

Expand Down
50 changes: 40 additions & 10 deletions frontend/test/composables/useScrollToHash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,47 @@ describe("useScrollToHash", async () => {
})

it("scrolls element into view if element with hash exists", async () => {
const hashElement = { scrollIntoView: vi.fn() }
const hashElement = { getBoundingClientRect: vi.fn(() => ({ top: 100 })) }
// @ts-expect-error It's not a proper HTML element but good enough for testing
vi.spyOn(document, "getElementById").mockReturnValue(hashElement)

const scrollToSpy = vi.fn()
Object.defineProperty(global.window, "scrollTo", { value: scrollToSpy })

useScrollToHash(ref("test-hash"))
vi.runAllTimers()

expect(document.getElementById).toHaveBeenCalledWith("test-hash")
expect(hashElement.scrollIntoView).toHaveBeenCalledOnce()

expect(scrollToSpy).toHaveBeenCalledWith({
top: 100,
behavior: "smooth",
})
})

it("removes the # from the route hash", async () => {
const hashElement = { scrollIntoView: vi.fn() }
const hashElement = { getBoundingClientRect: vi.fn(() => ({ top: 100 })) }
// @ts-expect-error It's not a proper HTML element but good enough for testing
vi.spyOn(document, "getElementById").mockReturnValue(hashElement)

useScrollToHash(ref("#test-hash"))
vi.runAllTimers()

expect(document.getElementById).toHaveBeenCalledWith("test-hash")
expect(hashElement.scrollIntoView).toHaveBeenCalledOnce()
})

it("does not scroll if no element exists for this hash", async () => {
const hashElement = { scrollIntoView: vi.fn() }
const hashElement = { getBoundingClientRect: vi.fn() }
vi.spyOn(document, "getElementById").mockReturnValue(null)

const scrollToSpy = vi.fn()
Object.defineProperty(global.window, "scrollTo", { value: scrollToSpy })

useScrollToHash(ref("unknown-hash"))
vi.runAllTimers()

expect(document.getElementById).toHaveBeenCalledWith("unknown-hash")
expect(hashElement.scrollIntoView).not.toHaveBeenCalledOnce()
expect(hashElement.getBoundingClientRect).not.toHaveBeenCalledOnce()
})

it("does nothing if given route has no hash", async () => {
Expand All @@ -55,22 +64,43 @@ describe("useScrollToHash", async () => {
})

it("detects new hash on route changes", async () => {
const hashElement = { scrollIntoView: vi.fn() }
const hashElement = { getBoundingClientRect: vi.fn(() => ({ top: 100 })) }
// @ts-expect-error It's not a proper HTML element but good enough for testing
vi.spyOn(document, "getElementById").mockReturnValue(hashElement)
const route = ref<string | undefined>(undefined)
const scrollToSpy = vi.fn()
Object.defineProperty(global.window, "scrollTo", { value: scrollToSpy })

useScrollToHash(route)
vi.runAllTimers()

expect(document.getElementById).not.toHaveBeenCalled()
expect(hashElement.scrollIntoView).not.toHaveBeenCalled()
expect(scrollToSpy).not.toHaveBeenCalled()

route.value = "yolo"
route.value = "new"
await nextTick()
vi.runAllTimers()

expect(document.getElementById).toHaveBeenCalled()
expect(hashElement.scrollIntoView).toHaveBeenCalled()
expect(scrollToSpy).toHaveBeenCalled()
})

it("scrolls with offset", async () => {
const hashElement = { getBoundingClientRect: vi.fn(() => ({ top: 100 })) }
// @ts-expect-error It's not a proper HTML element but good enough for testing
vi.spyOn(document, "getElementById").mockReturnValue(hashElement)

const scrollToSpy = vi.fn()
Object.defineProperty(global.window, "scrollTo", { value: scrollToSpy })

useScrollToHash(ref("test-hash"), 50)
vi.runAllTimers()

expect(document.getElementById).toHaveBeenCalledWith("test-hash")

expect(scrollToSpy).toHaveBeenCalledWith({
top: 50,
behavior: "smooth",
})
})
})

0 comments on commit f629ee6

Please sign in to comment.