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