-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
17 changed files
with
900 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { CSSProperties, ReactNode, memo } from 'react'; | ||
import { LaserShine, useLaserShine } from './LaserShine'; | ||
import Orbit from './Orbit'; | ||
import { useStyles } from './style'; | ||
|
||
export interface ContainerProps { | ||
back?: string; | ||
foil?: string; | ||
mask?: string; | ||
children?: ReactNode; | ||
className?: string; | ||
loading?: boolean; | ||
} | ||
|
||
const Container = memo<ContainerProps>(({ back, foil, mask, children, className, loading }) => { | ||
const { styles, cx } = useStyles(); | ||
const { style: shineStyle, onMouseMove, onMouseOut } = useLaserShine(); | ||
|
||
return ( | ||
<Orbit | ||
classNames={{ | ||
container: cx(`${className} ${mask ? 'masked' : ''}`, styles.container), | ||
rotator: cx(styles.rotator), | ||
}} | ||
styles={{ | ||
container: { | ||
...shineStyle, | ||
'--mask': `url(${mask ?? ''})`, | ||
'--foil': `url(${foil ?? ''})`, | ||
width: 380, | ||
} as CSSProperties, | ||
content: { | ||
display: 'grid', | ||
}, | ||
}} | ||
onMouseMove={onMouseMove} | ||
onMouseOut={onMouseOut} | ||
> | ||
<img | ||
className={cx(styles.back, loading && styles.backLoading)} | ||
src={back} | ||
loading="lazy" | ||
width="660" | ||
height="921" | ||
/> | ||
<div className={cx(styles.front, loading && styles.fontLoading)}> | ||
{children} | ||
<LaserShine mask={!!mask} className={styles.shine} /> | ||
<div className={cx('card__glare', styles.glare)} /> | ||
</div> | ||
</Orbit> | ||
); | ||
}); | ||
|
||
export default Container; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { animated } from '@react-spring/web'; | ||
import { CSSProperties, memo } from 'react'; | ||
import { DivProps } from 'react-layout-kit'; | ||
import { useStyles } from './style'; | ||
|
||
export interface LaserShineProps extends DivProps { | ||
mask?: boolean; | ||
className?: string; | ||
style?: CSSProperties; | ||
} | ||
|
||
export const LaserShine = memo<LaserShineProps>(({ mask, className, ...res }) => { | ||
const { styles, cx } = useStyles(); | ||
|
||
console.log(className); | ||
return ( | ||
<animated.div | ||
className={cx( | ||
styles.composeShine, | ||
mask ? styles.maskedShine : styles.noMaskedShine, | ||
className, | ||
)} | ||
{...res} | ||
/> | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './LaserShine'; | ||
export * from './useLaserShine'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import { createStyles } from 'antd-style'; | ||
|
||
export const useStyles = createStyles(({ css, cx }) => { | ||
const shine = css` | ||
background-image: var(--foil), | ||
repeating-linear-gradient( | ||
0deg, | ||
var(--sunpillar-clr-1) calc(var(--space) * 1), | ||
var(--sunpillar-clr-2) calc(var(--space) * 2), | ||
var(--sunpillar-clr-3) calc(var(--space) * 3), | ||
var(--sunpillar-clr-4) calc(var(--space) * 4), | ||
var(--sunpillar-clr-5) calc(var(--space) * 5), | ||
var(--sunpillar-clr-6) calc(var(--space) * 6), | ||
var(--sunpillar-clr-1) calc(var(--space) * 7) | ||
), | ||
repeating-linear-gradient( | ||
var(--angle), | ||
#0e152e 0%, | ||
hsl(180, 10%, 60%) 3.8%, | ||
hsl(180, 29%, 66%) 4.5%, | ||
hsl(180, 10%, 60%) 5.2%, | ||
#0e152e 10%, | ||
#0e152e 12% | ||
), | ||
radial-gradient( | ||
farthest-corner circle at var(--pointer-x) var(--pointer-y), | ||
hsla(0, 0%, 0%, 0.1) 12%, | ||
hsla(0, 0%, 0%, 0.15) 20%, | ||
hsla(0, 0%, 0%, 0.25) 120% | ||
); | ||
background-position: center center, 0% var(--background-y), | ||
calc(var(--background-x) + (var(--background-y) * 0.2)) var(--background-y), | ||
var(--background-x) var(--background-y); | ||
background-blend-mode: soft-light, hue, hard-light; | ||
background-size: var(--imgsize), 200% 700%, 300% 100%, 200% 100%; | ||
filter: brightness(calc((var(--pointer-from-center) * 0.05) + 0.8)) contrast(1.75) saturate(1.2); | ||
`; | ||
|
||
const shineAfter = css` | ||
content: ''; | ||
--space: 5%; | ||
--angle: 133deg; | ||
--imgsize: cover; | ||
background-image: var(--foil), | ||
repeating-linear-gradient( | ||
0deg, | ||
var(--sunpillar-clr-1) calc(var(--space) * 1), | ||
var(--sunpillar-clr-2) calc(var(--space) * 2), | ||
var(--sunpillar-clr-3) calc(var(--space) * 3), | ||
var(--sunpillar-clr-4) calc(var(--space) * 4), | ||
var(--sunpillar-clr-5) calc(var(--space) * 5), | ||
var(--sunpillar-clr-6) calc(var(--space) * 6), | ||
var(--sunpillar-clr-1) calc(var(--space) * 7) | ||
), | ||
repeating-linear-gradient( | ||
var(--angle), | ||
#0e152e 0%, | ||
hsl(180, 10%, 60%) 3.8%, | ||
hsl(180, 29%, 66%) 4.5%, | ||
hsl(180, 10%, 60%) 5.2%, | ||
#0e152e 10%, | ||
#0e152e 12% | ||
), | ||
radial-gradient( | ||
farthest-corner circle at var(--pointer-x) var(--pointer-y), | ||
hsla(0, 0%, 0%, 0.1) 12%, | ||
hsla(0, 0%, 0%, 0.15) 20%, | ||
hsla(0, 0%, 0%, 0.25) 120% | ||
); | ||
background-blend-mode: soft-light, hue, hard-light; | ||
background-position: center center, 0% var(--background-y), | ||
calc((var(--background-x) + (var(--background-y) * 0.2)) * -1) calc(var(--background-y) * -1), | ||
var(--background-x) var(--background-y); | ||
background-size: var(--imgsize), 200% 400%, 195% 100%, 200% 100%; | ||
filter: brightness(calc((var(--pointer-from-center) * 0.4) + 0.85)) contrast(2) saturate(0.5); | ||
mix-blend-mode: exclusion; | ||
`; | ||
|
||
const shineBefore = css` | ||
content: ''; | ||
-webkit-mask-image: none; | ||
mask-image: none; | ||
background-position: center; | ||
background-size: cover; | ||
z-index: 1; | ||
background-image: radial-gradient( | ||
farthest-corner circle at var(--pointer-x) var(--pointer-y), | ||
hsl(0, 0%, 100%) 0%, | ||
hsla(0, 0%, 0%, 0) 80% | ||
); | ||
mix-blend-mode: screen; | ||
opacity: 0.5; | ||
`; | ||
|
||
const masked = css` | ||
/** masking image for cards which are masked **/ | ||
mask-image: var(--mask); | ||
mask-size: cover; | ||
mask-position: center center; | ||
`; | ||
|
||
const nomasked = css` | ||
--mask: none; | ||
--foil: none; | ||
--imgsize: 20%; | ||
background-blend-mode: color-burn, hue, hard-light; | ||
filter: brightness(calc((var(--pointer-from-center) * 0.05) + 0.6)) contrast(1.5) saturate(1.2); | ||
`; | ||
|
||
return { | ||
composeShine: cx( | ||
'aha-shine', | ||
css` | ||
--space: 5%; | ||
--angle: 133deg; | ||
--imgsize: cover; | ||
--sunpillar-1: hsl(2, 100%, 73%); | ||
--sunpillar-2: hsl(53, 100%, 69%); | ||
--sunpillar-3: hsl(93, 100%, 69%); | ||
--sunpillar-4: hsl(176, 100%, 76%); | ||
--sunpillar-5: hsl(228, 100%, 74%); | ||
--sunpillar-6: hsl(283, 100%, 73%); | ||
--sunpillar-clr-1: var(--sunpillar-1); | ||
--sunpillar-clr-2: var(--sunpillar-2); | ||
--sunpillar-clr-3: var(--sunpillar-3); | ||
--sunpillar-clr-4: var(--sunpillar-4); | ||
--sunpillar-clr-5: var(--sunpillar-5); | ||
--sunpillar-clr-6: var(--sunpillar-6); | ||
display: grid; | ||
transform: translateZ(1px); | ||
overflow: hidden; | ||
z-index: 3; | ||
mix-blend-mode: color-dodge; | ||
opacity: var(--card-opacity); | ||
&:before { | ||
--sunpillar-clr-1: var(--sunpillar-5); | ||
--sunpillar-clr-2: var(--sunpillar-6); | ||
--sunpillar-clr-3: var(--sunpillar-1); | ||
--sunpillar-clr-4: var(--sunpillar-2); | ||
--sunpillar-clr-5: var(--sunpillar-3); | ||
--sunpillar-clr-6: var(--sunpillar-4); | ||
grid-area: 1/1; | ||
transform: translateZ(1px); | ||
border-radius: var(--card-radius); | ||
} | ||
&:after { | ||
--sunpillar-clr-1: var(--sunpillar-6); | ||
--sunpillar-clr-2: var(--sunpillar-1); | ||
--sunpillar-clr-3: var(--sunpillar-2); | ||
--sunpillar-clr-4: var(--sunpillar-3); | ||
--sunpillar-clr-5: var(--sunpillar-4); | ||
--sunpillar-clr-6: var(--sunpillar-5); | ||
transform: translateZ(1.2px); | ||
grid-area: 1/1; | ||
border-radius: var(--card-radius); | ||
} | ||
${shine}; | ||
&:before { | ||
${shineBefore} | ||
} | ||
&:after { | ||
${shineAfter} | ||
} | ||
`, | ||
), | ||
|
||
maskedShine: css` | ||
${masked} | ||
&:before, | ||
&:after { | ||
${masked} | ||
} | ||
`, | ||
noMaskedShine: css` | ||
${nomasked} | ||
&:after { | ||
${nomasked} | ||
} | ||
`, | ||
}; | ||
}); |
62 changes: 62 additions & 0 deletions
62
src/HolographicCard/components/LaserShine/useLaserShine.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { useSpring } from '@react-spring/web'; | ||
import { CSSProperties } from 'react'; | ||
import { adjust, clamp, round } from '../../utils/math'; | ||
|
||
const randomSeed = { | ||
x: Math.random(), | ||
y: Math.random(), | ||
}; | ||
|
||
const cosmosPosition = { | ||
x: Math.floor(randomSeed.x * 734), | ||
y: Math.floor(randomSeed.y * 1280), | ||
}; | ||
|
||
export const useLaserShine = (delay = 500) => { | ||
const [{ background, glare }, api] = useSpring(() => ({ | ||
background: [0, 50], | ||
glare: [50, 50, 0], | ||
})); | ||
|
||
const onMouseMove = (e: any) => { | ||
const rect = e.target.getBoundingClientRect(); | ||
const absolute = { | ||
x: e.clientX - rect.left, | ||
y: e.clientY - rect.top, | ||
}; | ||
const percent = { | ||
x: clamp(round((100 / rect.width) * absolute.x)), | ||
y: clamp(round((100 / rect.height) * absolute.y)), | ||
}; | ||
|
||
api.start({ | ||
background: [adjust(percent.x, 0, 100, 37, 63), adjust(percent.y, 0, 100, 33, 67)], | ||
glare: [round(percent.x), round(percent.y), 1], | ||
}); | ||
}; | ||
|
||
const onMouseOut = () => { | ||
setTimeout(() => { | ||
api.start({ glare: [50, 50, 0], background: [50, 50] }); | ||
}, delay); | ||
}; | ||
|
||
const style = { | ||
'--pointer-x': glare.to((x) => `${x}%`), | ||
'--pointer-y': glare.to((_, y) => `${y}%`), | ||
'--pointer-from-center': glare.to((x, y) => | ||
clamp(Math.sqrt((y - 50) * (y - 50) + (x - 50) * (x - 50)) / 50, 0, 1), | ||
), | ||
|
||
'--pointer-from-top': glare.to((_, y) => y / 100), | ||
'--pointer-from-left': glare.to((x) => x / 100), | ||
'--card-opacity': glare.to((_, __, o) => o), | ||
'--background-x': background.to((x) => `${x}%`), | ||
'--background-y': background.to((_, y) => `${y}%`), | ||
'--seedx': randomSeed.x, | ||
'--seedy': randomSeed.y, | ||
'--cosmosbg': `${cosmosPosition.x}px ${cosmosPosition.y}px`, | ||
} as CSSProperties; | ||
|
||
return { onMouseMove, onMouseOut, style }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import { animated, useSpring } from '@react-spring/web'; | ||
import { CSSProperties, MouseEventHandler, ReactNode, forwardRef } from 'react'; | ||
import { clamp, round } from '../../utils/math'; | ||
import { useStyles } from './styles'; | ||
|
||
export interface OrbitProps { | ||
children?: ReactNode; | ||
className?: string; | ||
style?: CSSProperties; | ||
|
||
classNames?: { | ||
container?: string; | ||
translator?: string; | ||
rotator?: string; | ||
content?: string; | ||
}; | ||
/** | ||
* @default: 400 | ||
*/ | ||
width?: number; | ||
/** | ||
* @default: 560 | ||
*/ | ||
height?: number; | ||
styles?: { | ||
container?: CSSProperties; | ||
translator?: CSSProperties; | ||
rotator?: CSSProperties; | ||
content?: CSSProperties; | ||
}; | ||
/** | ||
* 延迟恢复动画的时间,单位:ms | ||
* @default: 500 | ||
*/ | ||
delay?: number; | ||
/** | ||
* 可旋转方向 | ||
* @default both | ||
*/ | ||
orbitDirection?: 'both' | 'vertical' | 'horizontal'; | ||
/** | ||
* @default: 1 | ||
*/ | ||
sensitivity?: number; | ||
damping?: number; | ||
onMouseMove?: MouseEventHandler<HTMLDivElement>; | ||
onMouseOut?: MouseEventHandler<HTMLDivElement>; | ||
/** | ||
* @default: false | ||
*/ | ||
followPointer?: boolean; | ||
} | ||
|
||
const Orbit = forwardRef<HTMLDivElement, OrbitProps>( | ||
( | ||
{ | ||
children, | ||
className, | ||
classNames = {}, | ||
styles: outStyles = {}, | ||
style, | ||
width = 400, | ||
height = 560, | ||
delay = 500, | ||
onMouseOut, | ||
onMouseMove, | ||
sensitivity = 1, | ||
orbitDirection = 'both', | ||
followPointer, | ||
...res | ||
}, | ||
ref, | ||
) => { | ||
const { styles, cx } = useStyles(); | ||
|
||
const [{ rotate }, api] = useSpring(() => ({ | ||
rotate: [0, 0], | ||
})); | ||
|
||
const handleInteract = (e: any) => { | ||
const rect = e.target.getBoundingClientRect(); | ||
|
||
const absolute = { | ||
x: e.clientX - rect.left, | ||
y: e.clientY - rect.top, | ||
}; | ||
const percent = { | ||
x: clamp(round((100 / rect.width) * absolute.x)), | ||
y: clamp(round((100 / rect.height) * absolute.y)), | ||
}; | ||
const center = { x: percent.x - 50, y: percent.y - 50 }; | ||
|
||
let x = 0, | ||
y = 0; | ||
|
||
const calcX = () => | ||
// 使用 followPointer 时,移动跟随鼠标方向 | ||
round((followPointer ? 1 : -1) * (center.x / 3.5) * sensitivity); | ||
|
||
const calcY = () => round((center.y / 2) * sensitivity); | ||
|
||
switch (orbitDirection) { | ||
case 'both': | ||
x = calcX(); | ||
y = calcY(); | ||
break; | ||
|
||
case 'horizontal': | ||
x = calcX(); | ||
break; | ||
|
||
case 'vertical': | ||
y = calcY(); | ||
break; | ||
} | ||
|
||
api.start({ rotate: [x, y] }); | ||
}; | ||
|
||
const handleInteractEnd = (delayTime: number) => { | ||
setTimeout(() => { | ||
api.start({ rotate: [0, 0] }); | ||
}, delayTime); | ||
}; | ||
|
||
return ( | ||
<animated.div | ||
className={cx(styles.container, classNames.container)} | ||
style={ | ||
{ | ||
'--card-aspect': width / height, | ||
'--rotate-x': rotate.to((x) => `${x}deg`), | ||
'--rotate-y': rotate.to((_, y) => `${y}deg`), | ||
width, | ||
height, | ||
...outStyles.container, | ||
} as CSSProperties | ||
} | ||
ref={ref} | ||
{...res} | ||
> | ||
<div className={cx(styles.translator, classNames.translator)} style={outStyles.translator}> | ||
<div | ||
className={cx(styles.rotator, className, classNames.rotator)} | ||
onMouseMove={(event) => { | ||
handleInteract(event); | ||
onMouseMove?.(event); | ||
}} | ||
onMouseOut={(event) => { | ||
handleInteractEnd(delay); | ||
onMouseOut?.(event); | ||
}} | ||
style={{ ...style, ...outStyles.rotator }} | ||
> | ||
<div className={cx(styles.content, classNames.content)} style={outStyles.content}> | ||
{children} | ||
</div> | ||
</div> | ||
</div> | ||
</animated.div> | ||
); | ||
}, | ||
); | ||
|
||
export default Orbit; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { createStyles } from 'antd-style'; | ||
|
||
export const useStyles = createStyles(({ css, cx }) => { | ||
const prefix = `aha-orbit`; | ||
|
||
const contour = css` | ||
//--card-radius: 4.55% / 3.5%; | ||
aspect-ratio: var(--card-aspect); | ||
border-radius: var(--card-radius); | ||
`; | ||
|
||
const common = css` | ||
display: grid; | ||
// TODO: 看下600px 是否是动态值 | ||
perspective: 600px; | ||
will-change: transform, box-shadow; | ||
transform-origin: center; | ||
`; | ||
|
||
return { | ||
container: cx( | ||
`${prefix}-container`, | ||
css` | ||
/* place the card on a new transform layer and | ||
make sure it has hardward acceleration... we gun'need that! */ | ||
transform: translate3d(0px, 0px, 0.01px); | ||
transform-style: preserve-3d; | ||
/* make sure the card is above others if it's scaled up */ | ||
z-index: calc(var(--card-scale) * 2); | ||
/* every little helps! */ | ||
will-change: transform, visibility, z-index; | ||
${contour}; | ||
/* outline is a little trick to anti-alias */ | ||
outline: 1px solid transparent; | ||
& * { | ||
outline: 1px solid transparent; | ||
} | ||
`, | ||
), | ||
rotator: cx( | ||
`${prefix}-rotator`, | ||
css` | ||
${contour} | ||
${common} | ||
transform: rotateY(var(--rotate-x)) rotateX(var(--rotate-y)); | ||
transform-style: preserve-3d; | ||
/* performance */ | ||
pointer-events: auto; | ||
/* overflow: hidden; <-- this improves perf on mobile, but breaks backface visibility. */ | ||
/* isolation: isolate; <-- this improves perf, but breaks backface visibility on Chrome. */ | ||
`, | ||
), | ||
translator: cx( | ||
`${prefix}-translator`, | ||
css` | ||
${common}; | ||
width: auto; | ||
position: relative; | ||
transform: translate3d(var(--translate-x), var(--translate-y), 0.1px) | ||
scale(var(--card-scale)); | ||
`, | ||
), | ||
content: css` | ||
height: 100%; | ||
`, | ||
}; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { createStyles } from 'antd-style'; | ||
|
||
export const useStyles = createStyles(({ css, cx }) => { | ||
const perf = css` | ||
will-change: transform, opacity, background-image, background-size, background-position, | ||
background-blend-mode, filter; | ||
`; | ||
|
||
const shadow = css` | ||
transition: box-shadow 0.4s ease, opacity 0.33s ease-out; | ||
box-shadow: rgba(255, 255, 255, 0.1) 0px 1px 1px 0px inset, | ||
rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px; | ||
`; | ||
|
||
const view = css` | ||
width: 100%; | ||
grid-area: 1/1; | ||
aspect-ratio: var(--card-aspect); | ||
border-radius: var(--card-radius); | ||
image-rendering: optimizeQuality; | ||
-webkit-transform-style: preserve-3d; | ||
transform-style: preserve-3d; | ||
pointer-events: none; | ||
overflow: hidden; | ||
`; | ||
|
||
return { | ||
container: css` | ||
--card-radius: 4.55% / 3.5%; | ||
--card-back: hsl(220, 52%, 6%); | ||
--sunpillar-1: hsl(2, 100%, 73%); | ||
--sunpillar-2: hsl(53, 100%, 69%); | ||
--sunpillar-3: hsl(93, 100%, 69%); | ||
--sunpillar-4: hsl(176, 100%, 76%); | ||
--sunpillar-5: hsl(228, 100%, 74%); | ||
--sunpillar-6: hsl(283, 100%, 73%); | ||
--sunpillar-clr-1: var(--sunpillar-1); | ||
--sunpillar-clr-2: var(--sunpillar-2); | ||
--sunpillar-clr-3: var(--sunpillar-3); | ||
--sunpillar-clr-4: var(--sunpillar-4); | ||
--sunpillar-clr-5: var(--sunpillar-5); | ||
--sunpillar-clr-6: var(--sunpillar-6); | ||
--space: 5%; | ||
--angle: 133deg; | ||
--imgsize: cover; | ||
`, | ||
|
||
active: cx(shadow), | ||
shine: cx( | ||
perf, | ||
css` | ||
${view} | ||
`, | ||
), | ||
glare: cx( | ||
perf, | ||
css` | ||
/* make sure the glare doesn't clip */ | ||
transform: translateZ(1.41px); | ||
overflow: hidden; | ||
${view}; | ||
background-image: radial-gradient( | ||
farthest-corner circle at var(--pointer-x) var(--pointer-y), | ||
hsl(0, 0%, 75%) 5%, | ||
hsl(200, 5%, 35%) 60%, | ||
hsl(320, 40%, 10%) 150% | ||
); | ||
//background-size: 120% 150%; | ||
background-position: center center; | ||
//mix-blend-mode: hard-light; | ||
opacity: calc(var(--card-opacity) * 0.75); | ||
mix-blend-mode: multiply; | ||
filter: brightness(1.5) contrast(1.4) saturate(1); | ||
background-size: 170% 170%; | ||
`, | ||
), | ||
|
||
front: css` | ||
& * { | ||
backface-visibility: hidden; | ||
} | ||
opacity: 1; | ||
backface-visibility: hidden; | ||
transition: opacity 0.33s ease-out; | ||
transform: translate3d(0px, 0px, 0.01px); | ||
display: grid; | ||
${view} | ||
`, | ||
back: css` | ||
background-color: var(--card-back); | ||
transform: rotateY(180deg) translateZ(1px); | ||
backface-visibility: visible; | ||
${view} | ||
`, | ||
fontLoading: css` | ||
opacity: 0; | ||
`, | ||
backLoading: css` | ||
transform: rotateY(0deg); | ||
`, | ||
|
||
rotator: css` | ||
${shadow} | ||
img { | ||
height: auto; | ||
transform: translate3d(0px, 0px, 0.01px); | ||
} | ||
`, | ||
}; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { HolographicCard } from '@ant-design/pro-editor'; | ||
import { Button } from 'antd'; | ||
import { Center } from 'react-layout-kit'; | ||
|
||
const Demo = () => ( | ||
<HolographicCard> | ||
<Center gap={16} style={{ height: '100%' }}> | ||
<Button type={'primary'}>主按钮</Button> | ||
<Button>普通按钮</Button> | ||
</Center> | ||
</HolographicCard> | ||
); | ||
|
||
export default Demo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { HolographicCard } from '@ant-design/pro-editor'; | ||
import { Center } from 'react-layout-kit'; | ||
|
||
const Demo = () => { | ||
return ( | ||
<Center height={800}> | ||
<HolographicCard | ||
mask={'https://gw.alipayobjects.com/zos/kitchen/nbf5vouUl/illusion.png'} | ||
img="https://registry.npmmirror.com/@v-idol/vidol-agent-sample-b/1.0.0/files/cover.png" | ||
/> | ||
</Center> | ||
); | ||
}; | ||
|
||
export default Demo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { HolographicCard } from '@ant-design/pro-editor'; | ||
import { Center } from 'react-layout-kit'; | ||
|
||
const Demo = () => ( | ||
<HolographicCard> | ||
<Center gap={16} style={{ height: '100%' }} /> | ||
</HolographicCard> | ||
); | ||
|
||
export default Demo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
--- | ||
title: HolographicCard 全息卡片 | ||
group: | ||
title: 复合组件 | ||
order: 10 | ||
--- | ||
|
||
# HolographicCard | ||
|
||
搭配 Orbit + LaserShine 实现: | ||
|
||
## 效果预览 | ||
|
||
<code src="./demos/default.tsx"></code> | ||
|
||
## 不带图片 | ||
|
||
<code src="./demos/pure.tsx"></code> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { createStyles } from 'antd-style'; | ||
import { ReactNode, memo, useEffect, useState } from 'react'; | ||
|
||
import Container from './components/Container'; | ||
|
||
const useStyles = createStyles(({ css }) => ({ | ||
img: css` | ||
width: 100%; | ||
grid-area: 1/1; | ||
aspect-ratio: var(--card-aspect); | ||
border-radius: var(--card-radius); | ||
image-rendering: optimizeQuality; | ||
-webkit-transform-style: preserve-3d; | ||
transform-style: preserve-3d; | ||
`, | ||
})); | ||
|
||
export interface HolographicCardProps { | ||
img?: string; | ||
back?: string; | ||
foil?: string; | ||
mask?: string; | ||
children?: ReactNode; | ||
} | ||
|
||
const HolographicCard = memo<HolographicCardProps>(({ img = '', mask, children }) => { | ||
const [loading, setLoading] = useState(true); | ||
const { styles } = useStyles(); | ||
useEffect(() => { | ||
if (children) | ||
setTimeout(() => { | ||
setLoading(false); | ||
}, 500); | ||
}, []); | ||
|
||
return ( | ||
<Container mask={mask} loading={loading}> | ||
{children ? ( | ||
<div | ||
className={'card_children_container'} | ||
style={{ | ||
height: '100%', | ||
position: 'absolute', | ||
width: '100%', | ||
}} | ||
> | ||
{children} | ||
</div> | ||
) : ( | ||
<img | ||
className={styles.img} | ||
src={img} | ||
onLoad={() => { | ||
setTimeout(() => { | ||
setLoading(false); | ||
}, 500); | ||
}} | ||
loading="lazy" | ||
width="660" | ||
height="921" | ||
/> | ||
)} | ||
</Container> | ||
); | ||
}); | ||
|
||
export default HolographicCard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { createWithEqualityFn } from 'zustand/traditional'; | ||
|
||
type ActiveCardStore = { | ||
activeCard: HTMLDivElement | undefined | null; | ||
setActiveCard: (card: HTMLDivElement | undefined | null) => void; | ||
}; | ||
|
||
export const useActiveCard = createWithEqualityFn<ActiveCardStore>((set) => ({ | ||
activeCard: undefined, | ||
setActiveCard: (card) => set(() => ({ activeCard: card })), | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* return a value that has been rounded to a set precision | ||
* @param {Number} value the value to round | ||
* @param {Number} precision the precision (decimal places), default: 3 | ||
* @returns {Number} | ||
*/ | ||
const round = (value: number, precision: number = 3) => parseFloat(value.toFixed(precision)); | ||
|
||
/** | ||
* return a value that has been limited between min & max | ||
* @param {Number} value the value to clamp | ||
* @param {Number} min minimum value to allow, default: 0 | ||
* @param {Number} max maximum value to allow, default: 100 | ||
* @returns {Number} | ||
*/ | ||
const clamp = (value: number, min = 0, max = 100) => { | ||
return Math.min(Math.max(value, min), max); | ||
}; | ||
|
||
/** | ||
* return a value that has been re-mapped according to the from/to | ||
* - for example, adjust(10, 0, 100, 100, 0) = 90 | ||
* @param {Number} value the value to re-map (or adjust) | ||
* @param {Number} fromMin min value to re-map from | ||
* @param {Number} fromMax max value to re-map from | ||
* @param {Number} toMin min value to re-map to | ||
* @param {Number} toMax max value to re-map to | ||
* @returns {Number} | ||
*/ | ||
const adjust = (value: number, fromMin: number, fromMax: number, toMin: number, toMax: number) => { | ||
return round(toMin + ((toMax - toMin) * (value - fromMin)) / (fromMax - fromMin)); | ||
}; | ||
|
||
// const degToRad = () => { | ||
// | ||
// } | ||
export { adjust, clamp, round }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters