Merged in fix/dtmc-perf-fixes (pull request #2296)
fix: performance fixes for dtmc animation Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
import {
|
||||
type CSSProperties,
|
||||
type MouseEvent,
|
||||
type TouchEvent,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react"
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
@@ -27,6 +22,7 @@ export default function DigitalTeamMemberCardContent({
|
||||
const cardRef = useRef<HTMLDivElement>(null)
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const [coords, setCoords] = useState({ x: 0, y: 0 })
|
||||
const shimmerRef = useRef<HTMLDivElement>(null)
|
||||
const [rect, setRect] = useState<DOMRect>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
@@ -38,8 +34,6 @@ export default function DigitalTeamMemberCardContent({
|
||||
y: 0,
|
||||
toJSON() {},
|
||||
})
|
||||
let ticking = false
|
||||
let animationFrame = 0
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(
|
||||
@@ -56,82 +50,67 @@ export default function DigitalTeamMemberCardContent({
|
||||
}
|
||||
}, [])
|
||||
|
||||
function requestTick(
|
||||
evt: MouseEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>
|
||||
) {
|
||||
if (!ticking) {
|
||||
animationFrame = requestAnimationFrame(update(evt))
|
||||
function onInteractionMove(e: React.MouseEvent | React.TouchEvent) {
|
||||
let x, y
|
||||
if ("touches" in e) {
|
||||
x = e.touches[0].clientX - rect.left
|
||||
y = e.touches[0].clientY - rect.top
|
||||
} else {
|
||||
x = e.clientX - rect.left
|
||||
y = e.clientY - rect.top
|
||||
}
|
||||
ticking = true
|
||||
}
|
||||
|
||||
function onInteractionMove(
|
||||
evt: MouseEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>
|
||||
) {
|
||||
requestTick(evt)
|
||||
}
|
||||
|
||||
function update(
|
||||
evt: MouseEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>
|
||||
) {
|
||||
return () => {
|
||||
let x, y
|
||||
if ("touches" in evt) {
|
||||
x = evt.touches[0].clientX - rect.left
|
||||
y = evt.touches[0].clientY - rect.top
|
||||
} else {
|
||||
x = evt.clientX - rect.left
|
||||
y = evt.clientY - rect.top
|
||||
}
|
||||
setCoords({ x, y })
|
||||
}
|
||||
}
|
||||
|
||||
function getShimmerStyle(): CSSProperties {
|
||||
const { x, y } = coords
|
||||
const oppositeX = 100 - (x / rect.width) * 100
|
||||
const oppositeY = 100 - (y / rect.height) * 100
|
||||
return {
|
||||
background: `radial-gradient(circle at ${oppositeX}% ${oppositeY}%, rgb(233 171 163 / 40%) 0%, rgb(255 255 255 / 0%) 50%)`,
|
||||
opacity: isHovering ? 1 : 0,
|
||||
}
|
||||
}
|
||||
|
||||
function getSkewStyle(): CSSProperties {
|
||||
const { x, y } = coords
|
||||
const centerX = rect.width / 2
|
||||
const centerY = rect.height / 2
|
||||
const rotateY = ((x - centerX) / centerX) * 5 // Max 5 degrees
|
||||
const rotateX = ((centerY - y) / centerY) * 5 // Max 5 degrees
|
||||
|
||||
return {
|
||||
transform: isHovering
|
||||
? `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
|
||||
: "rotateX(0) rotateY(0)",
|
||||
const rotateY = ((x - centerX) / centerX) * 8
|
||||
const rotateX = ((centerY - y) / centerY) * 8
|
||||
|
||||
// Update shimmer position to be in the opposite corner
|
||||
if (shimmerRef.current) {
|
||||
// Calculate opposite position (invert percentage within bounds)
|
||||
const oppositeX = 100 - (x / rect.width) * 100
|
||||
const oppositeY = 100 - (y / rect.height) * 100
|
||||
|
||||
shimmerRef.current.style.background = `radial-gradient(
|
||||
circle at ${oppositeX}% ${oppositeY}%,
|
||||
rgba(233, 171, 163, 0.4) 0%,
|
||||
rgba(255, 255, 255, 0) 50%
|
||||
)`
|
||||
}
|
||||
setCoords({ x: rotateX, y: rotateY })
|
||||
}
|
||||
|
||||
function handleInteractionStart() {
|
||||
function onInteractionStart() {
|
||||
setIsHovering(true)
|
||||
}
|
||||
|
||||
function handleInteractionEnd() {
|
||||
function onInteractionEnd() {
|
||||
setIsHovering(false)
|
||||
cancelAnimationFrame(animationFrame)
|
||||
setCoords({ x: 0, y: 0 })
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.cardContainer}
|
||||
onMouseEnter={handleInteractionStart}
|
||||
onMouseMove={onInteractionMove}
|
||||
onMouseLeave={handleInteractionEnd}
|
||||
onTouchStart={handleInteractionStart}
|
||||
ref={cardRef}
|
||||
onTouchStart={onInteractionStart}
|
||||
onTouchMove={onInteractionMove}
|
||||
onTouchEnd={handleInteractionEnd}
|
||||
onTouchEnd={onInteractionEnd}
|
||||
onMouseEnter={onInteractionStart}
|
||||
onMouseMove={onInteractionMove}
|
||||
onMouseLeave={onInteractionEnd}
|
||||
>
|
||||
<div className={styles.card} ref={cardRef} style={getSkewStyle()}>
|
||||
<div className={styles.shimmer} style={getShimmerStyle()} />
|
||||
<div
|
||||
className={styles.card}
|
||||
style={{
|
||||
transform: `rotateX(${coords.x}deg) rotateY(${coords.y}deg)`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.shimmer}
|
||||
ref={shimmerRef}
|
||||
style={{ opacity: isHovering ? 1 : 0 }}
|
||||
/>
|
||||
<div className={styles.content}>
|
||||
<Typography variant="Tag/sm">
|
||||
<div className={styles.top}>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
.shimmer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
@@ -35,16 +36,18 @@
|
||||
}
|
||||
|
||||
.card {
|
||||
touch-action: none;
|
||||
overflow: hidden;
|
||||
border-radius: var(--Corner-radius-lg);
|
||||
box-shadow: 0 2px 1px rgb(255 255 255 / 11%) inset;
|
||||
padding: var(--Space-x2);
|
||||
height: 400px;
|
||||
width: 327px;
|
||||
max-width: 100%;
|
||||
color: var(--Text-Brand-OnPrimary-3-Accent);
|
||||
background-color: var(--Surface-Brand-Primary-1-OnSurface-Default);
|
||||
box-shadow: 0 4px 44px rgb(0 0 0 / 25%);
|
||||
box-shadow:
|
||||
0 2px 1px rgb(255 255 255 / 11%) inset,
|
||||
0 4px 44px rgb(0 0 0 / 25%);
|
||||
transition: transform 0.3s ease-out;
|
||||
will-change: transform;
|
||||
user-select: none;
|
||||
|
||||
Reference in New Issue
Block a user