Skip to content

Commit cc830a2

Browse files
Akatuorochgeo
andauthored
Fix toggles (#2428)
Fixes impl-variant toggle behavior in regards to: - layout shift on load - outline inconsistencies (both node and java headers shown on load) Good page for testing: http://localhost:5173/docs/guides/databases/postgres#deploy Chrome Performance CLS: 0.01 -> 0.00 --------- Co-authored-by: Christian Georgi <[email protected]>
1 parent 86379b1 commit cc830a2

File tree

5 files changed

+62
-56
lines changed

5 files changed

+62
-56
lines changed

.vitepress/config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ const config = defineConfig({
7676
['link', { rel: 'icon', href: base+'favicon.ico' }],
7777
['link', { rel: 'shortcut icon', href: base+'favicon.ico' }],
7878
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: base+'logos/cap.png' }],
79-
['script', { src: base+'script.js' } ]
79+
// Inline script to restore impl-variant selection immediately (before first paint)
80+
['script', { id: 'check-impl-variant' }, `{const p=new URLSearchParams(location.search),v=p.get('impl-variant')||localStorage.getItem('impl-variant');if(v)document.documentElement.classList.add(v)}`]
8081
],
8182

8283
vite: {

.vitepress/theme/Layout.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts" setup>
2+
import { onMounted } from 'vue'
23
import { useData } from 'vitepress'
34
import DefaultTheme from 'vitepress/theme'
45
import ShortcutsList from './components/ShortcutsList.vue'
@@ -13,6 +14,12 @@ const archiveVersion = import.meta.env.VITE_CAPIRE_VERSION
1314
const { Layout } = DefaultTheme
1415
const { frontmatter } = useData()
1516
17+
onMounted(() => {
18+
// Enable transitions after hydration to prevent flickering during initial load
19+
requestAnimationFrame(() => {
20+
document.documentElement.classList.add('transitions-ready')
21+
})
22+
})
1623
</script>
1724

1825
<template>

.vitepress/theme/components/implvariants/ImplVariants.vue

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup>
2-
import { onMounted, computed, ref, watchEffect } from 'vue'
2+
import { onMounted, onUnmounted, computed, ref, watch } from 'vue'
33
import { useData } from 'vitepress'
44
import VPSwitch from '../VPSwitch.vue'
55
import IconNode from './IconNode.vue'
@@ -11,26 +11,33 @@ const checked = ref(false)
1111
const toggle = typeof localStorage !== 'undefined' ? useVariant() : () => {}
1212
const knownImplVariants = ['node', 'java']
1313
14+
let outlineObserver = null
15+
1416
onMounted(() => {
1517
if (!supportsVariants.value) return
1618
17-
let check = currentCheckState()
18-
// Persist value even intially. If query param was used, users expect to get this value from now on, even if not using the query anymore.
19+
const check = currentCheckState()
20+
// Persist value initially. If query param was used, users expect to get this value from now on.
1921
const variantNew = check ? 'java' : 'node'
2022
localStorage.setItem('impl-variant', variantNew)
2123
22-
setClass(check)
24+
syncState(check)
25+
observeOutline()
26+
})
2327
24-
// Scroll hash element into view. Needed on first page load if variant is changed by query param.
25-
scrollTo(window.location.hash?.slice(1))
28+
onUnmounted(() => {
29+
if (outlineObserver) {
30+
outlineObserver.disconnect()
31+
outlineObserver = null
32+
}
2633
})
2734
28-
function scrollTo(id) {
29-
const elem = document.getElementById(id)
30-
if (elem) {
31-
setTimeout(() => { elem?.scrollIntoView(true) }, 20)
35+
watch(supportsVariants, (supports) => {
36+
if (supports) {
37+
syncState(currentCheckState())
38+
observeOutline()
3239
}
33-
}
40+
})
3441
3542
function currentCheckState() {
3643
const url = new URL(window.location)
@@ -39,76 +46,61 @@ function currentCheckState() {
3946
return localStorage.getItem('impl-variant') === 'java'
4047
}
4148
42-
function setClass(check) {
49+
function syncState(check) {
4350
checked.value = check
4451
45-
for (let swtch of document.getElementsByClassName('SwitchImplVariant')) {
46-
swtch.classList[check ? 'add' : 'remove']('checked')
52+
for (const swtch of document.getElementsByClassName('SwitchImplVariant')) {
53+
swtch.classList.toggle('checked', check)
4754
}
48-
for (let container of document.getElementsByClassName('SwitchImplVariantContainer')) {
55+
for (const container of document.getElementsByClassName('SwitchImplVariantContainer')) {
4956
container.title = check ? 'Java content. Toggle to see Node.js.' : 'Node.js content. Toggle to see Java.'
5057
}
5158
5259
markOutlineItems()
53-
toggleContent(check ? 'java' : 'node')
54-
5560
}
5661
5762
function useVariant() {
5863
function toggle() {
5964
let check = currentCheckState()
60-
setClass((check = !check))
65+
check = !check
66+
6167
const variantNew = check ? 'java' : 'node'
6268
localStorage.setItem('impl-variant', variantNew)
6369
70+
toggleContent(variantNew)
71+
syncState(check)
72+
6473
if (supportsVariants.value) {
6574
const url = new URL(window.location)
6675
url.searchParams.set('impl-variant', variantNew)
6776
window.history.replaceState({}, '', url)
6877
}
69-
7078
}
7179
return toggle
7280
}
7381
74-
function animationsOff(cb) {
75-
let css
76-
css = document.createElement('style')
77-
css.appendChild(
78-
document.createTextNode(
79-
`:not(.VPSwitchAppearance):not(.VPSwitchAppearance *) {
80-
-webkit-transition: none !important;
81-
-moz-transition: none !important;
82-
-o-transition: none !important;
83-
-ms-transition: none !important;
84-
transition: none !important;
85-
}`
86-
))
87-
document.head.appendChild(css)
88-
89-
cb()
90-
91-
// keep unused declaration, used to force the browser to redraw
92-
// eslint-disable-next-line no-unused-vars
93-
const _ = window.getComputedStyle(css).opacity
94-
document.head.removeChild(css)
95-
}
96-
97-
watchEffect(() => {
98-
if (!supportsVariants.value) return
99-
setTimeout(() => { // otherwise DOM is not ready
100-
if (typeof document !== 'undefined') {
101-
animationsOff(() => setClass(currentCheckState()) )
102-
}
103-
}, 20)
104-
})
105-
10682
function toggleContent(variant) {
10783
const htmlClassList = document.documentElement.classList
10884
knownImplVariants.forEach(v => htmlClassList.remove(v))
10985
htmlClassList.add(variant)
11086
}
11187
88+
function observeOutline() {
89+
if (outlineObserver) return
90+
91+
const aside = document.querySelector('.VPDocAside')
92+
if (!aside) return
93+
94+
markOutlineItems()
95+
outlineObserver = new MutationObserver(() => {
96+
markOutlineItems()
97+
})
98+
outlineObserver.observe(aside, {
99+
childList: true,
100+
subtree: true
101+
})
102+
}
103+
112104
// Only mark outline items here, as these are not part of the generated HTML,
113105
// but are created on the fly with JS.
114106
// All other DOM content is handled at build time on MD level (see md-attrs-propagate.ts)

.vitepress/theme/styles.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
html {
1111
&.java .node { display: none; }
1212
&.node .java { display: none; }
13+
14+
:not(.transitions-ready) {
15+
:not(.VPSwitchAppearance):not(.VPSwitchAppearance *) {
16+
-webkit-transition: none !important;
17+
-moz-transition: none !important;
18+
-o-transition: none !important;
19+
-ms-transition: none !important;
20+
transition: none !important;
21+
}
22+
}
1323
}
1424

1525

public/script.js

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)