mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-27 12:39:49 +08:00
863f61d908
Co-authored-by: afc163 <afc163@gmail.com>
130 lines
3.1 KiB
TypeScript
130 lines
3.1 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { removeCSS, updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
|
|
|
|
import theme from '../../components/theme';
|
|
|
|
const viewTransitionStyle = `
|
|
::view-transition-old(root),
|
|
::view-transition-new(root) {
|
|
animation: none;
|
|
mix-blend-mode: normal;
|
|
}
|
|
|
|
.dark::view-transition-old(root) {
|
|
z-index: 1;
|
|
}
|
|
|
|
.dark::view-transition-new(root) {
|
|
z-index: 999;
|
|
}
|
|
|
|
::view-transition-old(root) {
|
|
z-index: 999;
|
|
}
|
|
|
|
::view-transition-new(root) {
|
|
z-index: 1;
|
|
}
|
|
`;
|
|
|
|
const useThemeAnimation = () => {
|
|
const {
|
|
token: { colorBgElevated },
|
|
} = theme.useToken();
|
|
|
|
const animateRef = useRef<{ colorBgElevated: string }>({ colorBgElevated });
|
|
|
|
const startAnimationTheme = (clipPath: string[], isDark: boolean) => {
|
|
updateCSS(
|
|
`
|
|
* {
|
|
transition: none !important;
|
|
}
|
|
`,
|
|
'disable-transition',
|
|
);
|
|
|
|
document.documentElement
|
|
.animate(
|
|
{
|
|
clipPath: isDark ? [...clipPath].reverse() : clipPath,
|
|
},
|
|
{
|
|
duration: 500,
|
|
easing: 'ease-in',
|
|
pseudoElement: isDark ? '::view-transition-old(root)' : '::view-transition-new(root)',
|
|
},
|
|
)
|
|
.addEventListener('finish', () => {
|
|
removeCSS('disable-transition');
|
|
});
|
|
};
|
|
|
|
const toggleAnimationTheme = (
|
|
event: React.MouseEvent<HTMLElement, MouseEvent>,
|
|
isDark: boolean,
|
|
) => {
|
|
// @ts-ignore
|
|
if (!(event && typeof document.startViewTransition === 'function')) {
|
|
return;
|
|
}
|
|
const time = Date.now();
|
|
const x = event.clientX;
|
|
const y = event.clientY;
|
|
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
|
|
updateCSS(
|
|
`
|
|
[data-prefers-color='dark'] {
|
|
color-scheme: light !important;
|
|
}
|
|
|
|
[data-prefers-color='light'] {
|
|
color-scheme: dark !important;
|
|
}
|
|
`,
|
|
'color-scheme',
|
|
);
|
|
document
|
|
// @ts-ignore
|
|
.startViewTransition(async () => {
|
|
// wait for theme change end
|
|
while (colorBgElevated === animateRef.current.colorBgElevated) {
|
|
await new Promise((resolve) => {
|
|
setTimeout(resolve, 1000 / 60);
|
|
});
|
|
}
|
|
const root = document.documentElement;
|
|
root.classList.remove(isDark ? 'dark' : 'light');
|
|
root.classList.add(isDark ? 'light' : 'dark');
|
|
})
|
|
.ready.then(() => {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`Theme transition finished in ${Date.now() - time}ms`);
|
|
const clipPath = [
|
|
`circle(0px at ${x}px ${y}px)`,
|
|
`circle(${endRadius}px at ${x}px ${y}px)`,
|
|
];
|
|
removeCSS('color-scheme');
|
|
startAnimationTheme(clipPath, isDark);
|
|
});
|
|
};
|
|
|
|
// inject transition style
|
|
useEffect(() => {
|
|
// @ts-ignore
|
|
if (typeof document.startViewTransition === 'function') {
|
|
updateCSS(viewTransitionStyle, 'view-transition-style');
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (colorBgElevated !== animateRef.current.colorBgElevated) {
|
|
animateRef.current.colorBgElevated = colorBgElevated;
|
|
}
|
|
}, [colorBgElevated]);
|
|
|
|
return toggleAnimationTheme;
|
|
};
|
|
|
|
export default useThemeAnimation;
|