Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 905b970

Browse files
jpleclercJean-Philippe Leclercantfu
authored
fix(useColorMode, useDark): fix full page reflows when calling useColorMode and useDark (#4001)
Co-authored-by: Jean-Philippe Leclerc <[email protected]> Co-authored-by: Anthony Fu <[email protected]> Co-authored-by: Anthony Fu <[email protected]>
1 parent adbe017 commit 905b970

File tree

2 files changed

+77
-11
lines changed

2 files changed

+77
-11
lines changed

packages/core/useColorMode/index.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
2-
import { ref } from 'vue-demi'
2+
import { nextTick, ref } from 'vue-demi'
33
import { nextTwoTick } from '../../.test'
44
import { usePreferredDark } from '../usePreferredDark'
55
import { useColorMode } from '.'
@@ -123,4 +123,51 @@ describe('useColorMode', () => {
123123
expect(mode.system.value).toBe('light')
124124
expect(mode.state.value).toBe('light')
125125
})
126+
127+
it('should call classList.add/classList.remove only if mode changed', async () => {
128+
const target = document.createElement('div')
129+
130+
const mode = useColorMode({ selector: target, initialValue: 'light' })
131+
132+
await nextTick()
133+
134+
const addClass = vi.spyOn(target.classList, 'add')
135+
const removeClass = vi.spyOn(target.classList, 'remove')
136+
137+
mode.value = 'light'
138+
139+
await nextTick()
140+
141+
expect(addClass).not.toHaveBeenCalled()
142+
expect(removeClass).not.toHaveBeenCalled()
143+
144+
mode.value = 'dark'
145+
146+
await nextTick()
147+
148+
expect(addClass).toHaveBeenCalled()
149+
expect(removeClass).toHaveBeenCalled()
150+
})
151+
152+
it('should call setAttribute only if mode changed', async () => {
153+
const target = document.createElement('div')
154+
155+
const mode = useColorMode({ selector: target, initialValue: 'light', attribute: 'data-color-mode' })
156+
157+
await nextTick()
158+
159+
const setAttr = vi.spyOn(target, 'setAttribute')
160+
161+
mode.value = 'light'
162+
163+
await nextTick()
164+
165+
expect(setAttr).not.toHaveBeenCalled()
166+
167+
mode.value = 'dark'
168+
169+
await nextTick()
170+
171+
expect(setAttr).toHaveBeenCalled()
172+
})
126173
})

packages/core/useColorMode/index.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export type UseColorModeReturn<T extends string = BasicColorMode> =
100100
state: ComputedRef<T | BasicColorMode>
101101
}
102102

103+
const CSS_DISABLE_TRANS = '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'
104+
103105
/**
104106
* Reactive color mode with auto data persistence.
105107
*
@@ -152,13 +154,9 @@ export function useColorMode<T extends string = BasicColorMode>(
152154
if (!el)
153155
return
154156

155-
let style: HTMLStyleElement | undefined
156-
if (disableTransition) {
157-
style = window!.document.createElement('style')
158-
const styleString = '*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}'
159-
style.appendChild(document.createTextNode(styleString))
160-
window!.document.head.appendChild(style)
161-
}
157+
const classesToAdd = new Set<string>()
158+
const classesToRemove = new Set<string>()
159+
let attributeToChange: { key: string, value: string } | null = null
162160

163161
if (attribute === 'class') {
164162
const current = value.split(/\s/g)
@@ -167,13 +165,34 @@ export function useColorMode<T extends string = BasicColorMode>(
167165
.filter(Boolean)
168166
.forEach((v) => {
169167
if (current.includes(v))
170-
el.classList.add(v)
168+
classesToAdd.add(v)
171169
else
172-
el.classList.remove(v)
170+
classesToRemove.add(v)
173171
})
174172
}
175173
else {
176-
el.setAttribute(attribute, value)
174+
attributeToChange = { key: attribute, value }
175+
}
176+
177+
if (classesToAdd.size === 0 && classesToRemove.size === 0 && attributeToChange === null)
178+
// Nothing changed so we can avoid reflowing the page
179+
return
180+
181+
let style: HTMLStyleElement | undefined
182+
if (disableTransition) {
183+
style = window!.document.createElement('style')
184+
style.appendChild(document.createTextNode(CSS_DISABLE_TRANS))
185+
window!.document.head.appendChild(style)
186+
}
187+
188+
for (const c of classesToAdd) {
189+
el.classList.add(c)
190+
}
191+
for (const c of classesToRemove) {
192+
el.classList.remove(c)
193+
}
194+
if (attributeToChange) {
195+
el.setAttribute(attributeToChange.key, attributeToChange.value)
177196
}
178197

179198
if (disableTransition) {

0 commit comments

Comments
 (0)