mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 11:58:53 +00:00
⚡️(frontend) reduce re-renders of the ParticipantTile focus overlay
Optimize the idle mouse handling and the FocusOverlay component so they no longer trigger frequent re-renders of the ParticipantTile focus. State related to hover and focus is now scoped closer to where it is used, keeping updates local instead of propagating up the tile.
This commit is contained in:
@@ -114,6 +114,10 @@ const config: Config = {
|
||||
clipPath: 'polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0)',
|
||||
},
|
||||
},
|
||||
overlayIn: {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 0.6 },
|
||||
},
|
||||
},
|
||||
tokens: defineTokens({
|
||||
/* we take a few things from the panda preset but for now we clear out some stuff.
|
||||
|
||||
+74
-76
@@ -1,7 +1,7 @@
|
||||
import { css } from '@/styled-system/css'
|
||||
import { HStack } from '@/styled-system/jsx'
|
||||
import { TrackReferenceOrPlaceholder } from '@livekit/components-core'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
import { Track } from 'livekit-client'
|
||||
import { useCanMute } from '@/features/rooms/livekit/hooks/useCanMute'
|
||||
import { FocusButton } from './FocusButton'
|
||||
@@ -11,49 +11,34 @@ import { ZoomButton } from './ZoomButton'
|
||||
|
||||
const MOUSE_IDLE_TIME = 3000
|
||||
|
||||
export const ParticipantTileFocus = ({
|
||||
trackRef,
|
||||
hasKeyboardFocus,
|
||||
}: {
|
||||
trackRef: TrackReferenceOrPlaceholder
|
||||
type FadeOverlayProps = {
|
||||
children: ReactNode
|
||||
hasKeyboardFocus: boolean
|
||||
}) => {
|
||||
const [hovered, setHovered] = useState(false)
|
||||
const [opacity, setOpacity] = useState(0)
|
||||
}
|
||||
|
||||
const FadeOverlay = ({ children, hasKeyboardFocus }: FadeOverlayProps) => {
|
||||
const [active, setActive] = useState(false)
|
||||
const idleTimerRef = useRef<number | null>(null)
|
||||
const [isIdleRef, setIsIdleRef] = useState(false)
|
||||
|
||||
const isVisible = hasKeyboardFocus || (hovered && !isIdleRef)
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible) {
|
||||
// Wait for next frame to ensure element is mounted
|
||||
requestAnimationFrame(() => {
|
||||
setOpacity(0.6)
|
||||
})
|
||||
} else {
|
||||
setOpacity(0)
|
||||
}
|
||||
}, [isVisible])
|
||||
|
||||
const handleMouseMove = () => {
|
||||
if (idleTimerRef.current) {
|
||||
window.clearTimeout(idleTimerRef.current)
|
||||
}
|
||||
idleTimerRef.current = window.setTimeout(() => {
|
||||
setIsIdleRef(true)
|
||||
}, MOUSE_IDLE_TIME)
|
||||
setIsIdleRef(false)
|
||||
const clearIdleTimer = () => {
|
||||
if (idleTimerRef.current) window.clearTimeout(idleTimerRef.current)
|
||||
}
|
||||
|
||||
const participant = trackRef.participant
|
||||
const armIdleTimer = () => {
|
||||
clearIdleTimer()
|
||||
idleTimerRef.current = window.setTimeout(() => {
|
||||
setActive(false)
|
||||
}, MOUSE_IDLE_TIME)
|
||||
}
|
||||
|
||||
const isScreenShare = trackRef.source == Track.Source.ScreenShare
|
||||
const isLocal = trackRef.participant.isLocal
|
||||
const handleActivity = () => {
|
||||
setActive(true)
|
||||
armIdleTimer()
|
||||
}
|
||||
|
||||
const canMute = useCanMute(participant)
|
||||
useEffect(() => clearIdleTimer, [])
|
||||
|
||||
const isVisible = hasKeyboardFocus || active
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
@@ -66,49 +51,62 @@ export const ParticipantTileFocus = ({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
})}
|
||||
data-visible={isVisible || undefined}
|
||||
aria-hidden={!isVisible}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseEnter={handleActivity}
|
||||
onMouseMove={handleActivity}
|
||||
onMouseLeave={() => {
|
||||
clearIdleTimer()
|
||||
setActive(false)
|
||||
}}
|
||||
>
|
||||
{isVisible && (
|
||||
<div
|
||||
className={css({
|
||||
backgroundColor: 'primaryDark.50',
|
||||
transition: 'opacity 200ms linear',
|
||||
zIndex: 1,
|
||||
borderRadius: '0.25rem',
|
||||
display: 'flex',
|
||||
_hover: {
|
||||
opacity: '0.95 !important',
|
||||
},
|
||||
})}
|
||||
style={{ opacity }}
|
||||
>
|
||||
<HStack
|
||||
gap={0.5}
|
||||
className={css({
|
||||
padding: '0.5rem',
|
||||
_hover: {
|
||||
opacity: '1 !important',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<FocusButton trackRef={trackRef} />
|
||||
{!isScreenShare ? (
|
||||
<>
|
||||
{participant.isLocal ? (
|
||||
<EffectsButton />
|
||||
) : (
|
||||
canMute && <MuteButton participant={participant} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!isLocal && <ZoomButton trackRef={trackRef} />
|
||||
)}
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
{isVisible && children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ParticipantTileFocus = ({
|
||||
trackRef,
|
||||
hasKeyboardFocus,
|
||||
}: {
|
||||
trackRef: TrackReferenceOrPlaceholder
|
||||
hasKeyboardFocus: boolean
|
||||
}) => {
|
||||
const participant = trackRef.participant
|
||||
const isScreenShare = trackRef.source == Track.Source.ScreenShare
|
||||
const isLocal = participant.isLocal
|
||||
const canMute = useCanMute(participant)
|
||||
|
||||
return (
|
||||
<FadeOverlay hasKeyboardFocus={hasKeyboardFocus}>
|
||||
<div
|
||||
className={css({
|
||||
backgroundColor: 'primaryDark.50',
|
||||
zIndex: 1,
|
||||
borderRadius: '0.25rem',
|
||||
display: 'flex',
|
||||
opacity: 0.6,
|
||||
animation: 'overlayIn 200ms linear 300ms backwards',
|
||||
_hover: {
|
||||
opacity: 0.95,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<HStack gap={0.5} padding={0.5}>
|
||||
<FocusButton trackRef={trackRef} />
|
||||
{!isScreenShare ? (
|
||||
<>
|
||||
{isLocal ? (
|
||||
<EffectsButton />
|
||||
) : (
|
||||
canMute && <MuteButton participant={participant} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!isLocal && <ZoomButton trackRef={trackRef} />
|
||||
)}
|
||||
</HStack>
|
||||
</div>
|
||||
</FadeOverlay>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user