Skip to content

Commit 4c23c8b

Browse files
committed
优化导航菜单动效执行性能
1 parent 3fe319e commit 4c23c8b

File tree

1 file changed

+135
-120
lines changed

1 file changed

+135
-120
lines changed

src/layouts/components/Menu/sub.vue

Lines changed: 135 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -22,106 +22,107 @@ const rootMenu = inject(rootMenuInjectionKey)!
2222
2323
const index = props.menu.path ?? rootMenu.getUseId(props.menu)
2424
25-
const opened = computed(() => {
26-
return rootMenu.openedMenus.includes(props.uniqueKey.at(-1)!)
27-
})
25+
const opened = computed(() => rootMenu.openedMenus.includes(props.uniqueKey.at(-1)!))
2826
2927
const transitionEvent = computed(() => {
30-
return rootMenu.isMenuPopup
31-
? {
32-
enter(el: HTMLElement) {
33-
if (el.offsetHeight > window.innerHeight) {
34-
el.style.height = `${window.innerHeight}px`
35-
}
36-
},
37-
afterEnter: () => {},
38-
beforeLeave: (el: HTMLElement) => {
39-
el.style.maxHeight = `${el.offsetHeight}px`
40-
el.style.overflow = 'hidden'
41-
},
42-
leave: (el: HTMLElement) => {
43-
el.style.maxHeight = '0'
44-
},
45-
afterLeave(el: HTMLElement) {
46-
el.style.maxHeight = ''
47-
el.style.overflow = ''
48-
},
49-
}
50-
: CSS.supports('height', 'calc-size(auto, size)')
51-
? {}
52-
: {
53-
enter(el: HTMLElement) {
54-
requestAnimationFrame(() => {
55-
el.dataset.height = el.offsetHeight.toString()
56-
el.style.maxHeight = '0'
57-
void el.offsetHeight
58-
el.style.maxHeight = `${el.dataset.height}px`
59-
el.style.overflow = 'hidden'
60-
})
61-
},
62-
afterEnter(el: HTMLElement) {
63-
el.style.maxHeight = ''
64-
el.style.overflow = ''
65-
},
66-
enterCancelled(el: HTMLElement) {
67-
el.style.maxHeight = ''
68-
el.style.overflow = ''
69-
},
70-
beforeLeave(el: HTMLElement) {
71-
el.style.maxHeight = `${el.offsetHeight}px`
72-
el.style.overflow = 'hidden'
73-
},
74-
leave(el: HTMLElement) {
75-
el.style.maxHeight = '0'
76-
},
77-
afterLeave(el: HTMLElement) {
78-
el.style.maxHeight = ''
79-
el.style.overflow = ''
80-
},
81-
leaveCancelled(el: HTMLElement) {
82-
el.style.maxHeight = ''
83-
el.style.overflow = ''
84-
},
28+
if (rootMenu.isMenuPopup) {
29+
return {
30+
enter(el: HTMLElement) {
31+
if (el.offsetHeight > window.innerHeight) {
32+
el.style.height = `${window.innerHeight}px`
8533
}
34+
},
35+
afterEnter: () => {},
36+
beforeLeave: (el: HTMLElement) => {
37+
el.style.maxHeight = `${el.offsetHeight}px`
38+
el.style.overflow = 'hidden'
39+
},
40+
leave: (el: HTMLElement) => {
41+
el.style.maxHeight = '0'
42+
},
43+
afterLeave(el: HTMLElement) {
44+
el.style.maxHeight = ''
45+
el.style.overflow = ''
46+
},
47+
}
48+
}
49+
if (CSS.supports('height', 'calc-size(auto, size)')) {
50+
return {}
51+
}
52+
return {
53+
enter(el: HTMLElement) {
54+
requestAnimationFrame(() => {
55+
el.dataset.height = el.offsetHeight.toString()
56+
el.style.maxHeight = '0'
57+
void el.offsetHeight
58+
el.style.maxHeight = `${el.dataset.height}px`
59+
el.style.overflow = 'hidden'
60+
})
61+
},
62+
afterEnter(el: HTMLElement) {
63+
el.style.maxHeight = ''
64+
el.style.overflow = ''
65+
},
66+
enterCancelled(el: HTMLElement) {
67+
el.style.maxHeight = ''
68+
el.style.overflow = ''
69+
},
70+
beforeLeave(el: HTMLElement) {
71+
el.style.maxHeight = `${el.offsetHeight}px`
72+
el.style.overflow = 'hidden'
73+
},
74+
leave(el: HTMLElement) {
75+
el.style.maxHeight = '0'
76+
},
77+
afterLeave(el: HTMLElement) {
78+
el.style.maxHeight = ''
79+
el.style.overflow = ''
80+
},
81+
leaveCancelled(el: HTMLElement) {
82+
el.style.maxHeight = ''
83+
el.style.overflow = ''
84+
},
85+
}
8686
})
8787
8888
const transitionClass = computed(() => {
89-
return rootMenu.isMenuPopup
90-
? {
91-
enterActiveClass: 'ease-in-out duration-300',
92-
enterFromClass: 'opacity-0 translate-x-4',
93-
enterToClass: 'opacity-100',
94-
leaveActiveClass: 'ease-in-out duration-300',
95-
leaveFromClass: 'opacity-100',
96-
leaveToClass: 'opacity-0',
97-
}
98-
: {
99-
enterActiveClass: 'ease-in-out duration-300',
100-
enterFromClass: cn('opacity-0 translate-y-4 scale-95 blur-4', CSS.supports('height', 'calc-size(auto, size)') && 'h-0'),
101-
enterToClass: 'opacity-100 translate-y-0 scale-100 blur-0',
102-
leaveActiveClass: 'ease-in-out duration-300',
103-
leaveFromClass: 'opacity-100 translate-y-0 scale-100 blur-0',
104-
leaveToClass: cn('opacity-0 translate-y-4 scale-95 blur-4', CSS.supports('height', 'calc-size(auto, size)') && 'h-0'),
105-
}
89+
if (rootMenu.isMenuPopup) {
90+
return {
91+
enterActiveClass: 'ease-in-out duration-300',
92+
enterFromClass: 'opacity-0 translate-x-4',
93+
enterToClass: 'opacity-100',
94+
leaveActiveClass: 'ease-in-out duration-300',
95+
leaveFromClass: 'opacity-100',
96+
leaveToClass: 'opacity-0',
97+
}
98+
}
99+
return {
100+
enterActiveClass: 'ease-in-out duration-300',
101+
enterFromClass: cn('opacity-0 translate-y-4 scale-95 blur-4', CSS.supports('height', 'calc-size(auto, size)') && 'h-0'),
102+
enterToClass: 'opacity-100 translate-y-0 scale-100 blur-0',
103+
leaveActiveClass: 'ease-in-out duration-300',
104+
leaveFromClass: 'opacity-100 translate-y-0 scale-100 blur-0',
105+
leaveToClass: cn('opacity-0 translate-y-4 scale-95 blur-4', CSS.supports('height', 'calc-size(auto, size)') && 'h-0'),
106+
}
106107
})
107108
108-
const hasChildren = computed(() => {
109-
return props.menu.children?.some((item: any) => item.meta?.menu !== false) ?? false
110-
})
109+
const hasChildren = computed(() => props.menu.children?.some((item: any) => item.meta?.menu !== false) ?? false)
111110
112111
function handleClick() {
113-
if (rootMenu.isMenuPopup && hasChildren.value) {
114-
return
115-
}
116-
if (props.menu.meta?.link) {
112+
if (
113+
(rootMenu.isMenuPopup && hasChildren.value)
114+
|| props.menu.meta?.link
115+
) {
117116
return
118117
}
119-
if (hasChildren.value) {
120-
rootMenu.handleSubMenuClick(index, props.uniqueKey)
121-
}
122-
else {
123-
rootMenu.handleMenuItemClick(index)
124-
}
118+
requestAnimationFrame(() => {
119+
if (hasChildren.value) {
120+
rootMenu.handleSubMenuClick(index, props.uniqueKey)
121+
}
122+
else {
123+
rootMenu.handleMenuItemClick(index)
124+
}
125+
})
125126
}
126127
127128
let timeout: (() => void) | undefined
@@ -136,32 +137,44 @@ function handleMouseenter() {
136137
if (hasChildren.value) {
137138
rootMenu.openMenu(index, props.uniqueKey)
138139
nextTick(() => {
139-
const el = itemRef.value?.ref
140-
const subMenuEl = subMenuRef.value?.$el
141-
if (!el || !subMenuEl) {
142-
return
143-
}
144-
let top = 0
145-
let left = 0
146-
if (rootMenu.props.mode === 'vertical' || props.level !== 0) {
147-
top = el.getBoundingClientRect().top + el.scrollTop
148-
left = el.getBoundingClientRect().left + el.getBoundingClientRect().width
149-
if (top + subMenuEl.offsetHeight > window.innerHeight) {
150-
top = Math.max(0, window.innerHeight - subMenuEl.offsetHeight)
140+
requestAnimationFrame(() => {
141+
const el = itemRef.value?.ref
142+
const subMenuEl = subMenuRef.value?.$el
143+
if (!el || !subMenuEl) {
144+
return
151145
}
152-
}
153-
else {
154-
top = el.getBoundingClientRect().top + el.getBoundingClientRect().height
155-
left = el.getBoundingClientRect().left
156-
if (top + subMenuEl.offsetHeight > window.innerHeight) {
157-
subMenuEl.style.height = `${window.innerHeight - top}px`
146+
const rect = el.getBoundingClientRect()
147+
const { top, left, width, height } = rect
148+
let menuTop = 0
149+
let menuLeft = 0
150+
if (rootMenu.props.mode === 'vertical' || props.level !== 0) {
151+
menuTop = top + el.scrollTop
152+
menuLeft = left + width
153+
// 处理边界情况
154+
if (menuTop + subMenuEl.offsetHeight > window.innerHeight) {
155+
menuTop = Math.max(0, window.innerHeight - subMenuEl.offsetHeight)
156+
}
158157
}
159-
}
160-
if (left + subMenuEl.offsetWidth > document.documentElement.clientWidth) {
161-
left = el.getBoundingClientRect().left - el.getBoundingClientRect().width
162-
}
163-
subMenuEl.style.top = `${top}px`
164-
subMenuEl.style.insetInlineStart = `${left}px`
158+
else {
159+
menuTop = top + height
160+
menuLeft = left
161+
// 处理边界情况
162+
if (menuTop + subMenuEl.offsetHeight > window.innerHeight) {
163+
subMenuEl.style.height = `${window.innerHeight - menuTop}px`
164+
}
165+
}
166+
// 处理边界情况
167+
if (menuLeft + subMenuEl.offsetWidth > document.documentElement.clientWidth) {
168+
menuLeft = left - width
169+
}
170+
// 设置样式
171+
Object.assign(subMenuEl.style, {
172+
top: `${menuTop}px`,
173+
insetInlineStart: `${menuLeft}px`,
174+
willChange: 'transform',
175+
transform: 'translateZ(0)',
176+
})
177+
})
165178
})
166179
}
167180
else {
@@ -178,14 +191,16 @@ function handleMouseleave() {
178191
rootMenu.mouseInMenu = []
179192
timeout?.()
180193
;({ stop: timeout } = useTimeoutFn(() => {
181-
if (rootMenu.mouseInMenu.length === 0) {
182-
rootMenu.closeMenu(props.uniqueKey)
183-
}
184-
else {
185-
if (hasChildren.value) {
186-
!rootMenu.mouseInMenu.includes(props.uniqueKey.at(-1)!) && rootMenu.closeMenu(props.uniqueKey.at(-1)!)
194+
requestAnimationFrame(() => {
195+
if (rootMenu.mouseInMenu.length === 0) {
196+
rootMenu.closeMenu(props.uniqueKey)
187197
}
188-
}
198+
else {
199+
if (hasChildren.value) {
200+
!rootMenu.mouseInMenu.includes(props.uniqueKey.at(-1)!) && rootMenu.closeMenu(props.uniqueKey.at(-1)!)
201+
}
202+
}
203+
})
189204
}, 300))
190205
}
191206
</script>
@@ -195,7 +210,7 @@ function handleMouseleave() {
195210
<Teleport v-if="hasChildren" to="body" :disabled="!rootMenu.isMenuPopup">
196211
<Transition v-bind="transitionClass" v-on="transitionEvent">
197212
<FaScrollArea
198-
v-if="opened" ref="subMenuRef" :scrollbar="false" :mask="rootMenu.isMenuPopup" :class="cn('sub-menu static h-[calc-size(auto,size)] rounded-lg', {
213+
v-if="opened" ref="subMenuRef" :scrollbar="false" :mask="rootMenu.isMenuPopup" :class="cn('sub-menu static h-[calc-size(auto,size)] rounded-lg will-change-transform', {
199214
'bg-[var(--g-sub-sidebar-bg)]': rootMenu.isMenuPopup,
200215
'border shadow-xl fixed z-3000 w-[200px]': rootMenu.isMenuPopup,
201216
'mx-1': rootMenu.isMenuPopup && (rootMenu.props.mode === 'vertical' || level !== 0),

0 commit comments

Comments
 (0)