45 lines
1.4 KiB
React
45 lines
1.4 KiB
React
// Author: fruit.com.cn
|
|
import { useLayoutEffect, useRef } from 'react';
|
|
|
|
const DURATION = 420;
|
|
const FADE = 180;
|
|
|
|
export function HeroTransition({ data, onDone }) {
|
|
const ref = useRef(null);
|
|
|
|
useLayoutEffect(() => {
|
|
if (!data) return undefined;
|
|
const node = ref.current;
|
|
if (!node) return undefined;
|
|
const rect = data;
|
|
const scale = Math.max(window.innerWidth / rect.width, window.innerHeight / rect.height) * 1.04;
|
|
const tx = (window.innerWidth - rect.width * scale) / 2 - rect.left;
|
|
const ty = (window.innerHeight - rect.height * scale) / 2 - rect.top;
|
|
const expand = node.animate(
|
|
[
|
|
{ transform: 'translate(0, 0) scale(1)', borderRadius: '12px' },
|
|
{ transform: `translate(${tx}px, ${ty}px) scale(${scale})`, borderRadius: '0px' },
|
|
],
|
|
{ duration: DURATION, easing: 'cubic-bezier(.2, .7, .3, 1)', fill: 'forwards' },
|
|
);
|
|
expand.onfinish = () => {
|
|
const fade = node.animate([{ opacity: 1 }, { opacity: 0 }], { duration: FADE, fill: 'forwards' });
|
|
fade.onfinish = onDone;
|
|
};
|
|
return () => expand.cancel();
|
|
}, [data, onDone]);
|
|
|
|
if (!data) return null;
|
|
const rect = data;
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className="hero-transition"
|
|
aria-hidden="true"
|
|
style={{ left: rect.left, top: rect.top, width: rect.width, height: rect.height }}
|
|
>
|
|
{data.src && <img src={data.src} alt="" />}
|
|
</div>
|
|
);
|
|
}
|