Last active
November 6, 2022 04:50
-
-
Save Swami-Laxmikant/342a8d7c28e5a4271d62ba74ff08ba32 to your computer and use it in GitHub Desktop.
Thanos snap effect on image using react native skia
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
/** | |
* Heavily inspired by Red Stapler's approach - https://redstapler.co/thanos-snap-effect-javascript-tutorial/ | |
*/ | |
import { | |
Blur, | |
Canvas, | |
Group, | |
Image, | |
Mask, | |
Path, | |
runTiming, | |
SkiaValue, | |
SkImage, | |
useComputedValue, | |
useImage, | |
useValue, | |
useValueEffect, | |
} from '@shopify/react-native-skia'; | |
import React, {useEffect, useMemo, useState} from 'react'; | |
let imgURI = 'https://images.pexels.com/photos/794494/pexels-photo-794494.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2'; | |
let W = 200, | |
H = W * 1.5; | |
let BOX_SIZE = 1; | |
let TOTAL_CANVAS = 35, | |
TOTAL_POINTS = W * H; | |
let ANGLE_DEVIATION = Math.PI / 6, // (-15 to 15 degrees) | |
MAX_TRASLATION = 55; | |
let BASE_DUR = 700, | |
VAR_DUR = 100, | |
BASE_DELAY = 60; | |
export default function Foo() { | |
let myImg = useImage(imgURI); | |
let masks = useMemo(getMasks, []); | |
let isStarted = useValue(false); | |
let blur = useValue(0); | |
useEffect(() => { | |
setTimeout(() => { | |
runTiming(blur, 0.3, {duration: BASE_DUR * 0.3}, () => { | |
isStarted.current = true; | |
}); | |
}, 400); | |
}, []); | |
return ( | |
<Canvas style={{flex: 1}}> | |
{myImg ? ( | |
<Group transform={[{translateX: 95}, {translateY: 140}]}> | |
{masks.map((mask: string, index: number) => ( | |
<MaskedImage | |
key={index} | |
isStarted={isStarted} | |
myImg={myImg!} | |
mask={mask} | |
index={index} | |
/> | |
))} | |
<Blur blur={blur} /> | |
</Group> | |
) : null} | |
</Canvas> | |
); | |
} | |
function MaskedImage({ | |
mask, | |
myImg, | |
isStarted, | |
index, | |
}: { | |
index: number; | |
mask: string; | |
myImg: SkImage; | |
isStarted: SkiaValue<boolean>; | |
}) { | |
let [isFaded, setIsFaded] = useState(false); | |
let opacity = useValue(1); | |
let translation = useValue(0); | |
let angle = useValue(0), | |
finalAngle = Math.random() * ANGLE_DEVIATION - ANGLE_DEVIATION / 2; | |
let config = { | |
duration: BASE_DUR + index * VAR_DUR, | |
easing: (t: number): number => t * t, | |
}; | |
useValueEffect(isStarted, () => { | |
setTimeout(() => { | |
runTiming(opacity, 0, config, () => setIsFaded(true)); | |
runTiming(translation, MAX_TRASLATION, config); | |
runTiming(angle, finalAngle, config); | |
}, index * BASE_DELAY); | |
}); | |
let transform = useComputedValue(() => { | |
return [ | |
{translateX: translation.current}, | |
{translateY: -translation.current}, | |
{rotate: angle.current}, | |
]; | |
}, [translation, angle]); | |
if (isFaded) { | |
return <></>; | |
} | |
return ( | |
<Group origin={{x: W / 2, y: H / 2}} transform={transform}> | |
<Mask mask={<Path path={mask} />}> | |
<Image | |
opacity={opacity} | |
x={0} | |
y={0} | |
width={W} | |
height={H} | |
image={myImg} | |
fit="cover" | |
/> | |
</Mask> | |
</Group> | |
); | |
} | |
let masks = new Array(TOTAL_CANVAS).fill(''); | |
function getMasks() { | |
for (let i = 0; i < TOTAL_POINTS; i++) { | |
let j = Math.floor((i / TOTAL_POINTS) * TOTAL_CANVAS); | |
let canvaIndex = weightedRandomDistrib(j); | |
let x = i % W; | |
let y = Math.floor(i / W); | |
masks[canvaIndex] += pahtMaker(x, y); | |
} | |
return masks; | |
} | |
function weightedRandomDistrib(peak: number): number { | |
let prob = [], | |
seq = [], | |
sum = 0; | |
for (let i = 0; i < TOTAL_CANVAS; i++) { | |
let p = Math.pow(TOTAL_CANVAS - Math.abs(peak - i), 3); | |
sum += p; | |
prob.push(p); | |
seq.push(i); | |
} | |
return weightedRandom(seq, prob, sum); | |
} | |
function pahtMaker(x: number, y: number, size: number = BOX_SIZE): string { | |
return `M${x} ${y}h${size}v${size}h-${size}Z`; | |
} | |
function weightedRandom( | |
values: number[], | |
weights: number[], | |
totalWeight: number, | |
): number { | |
let random = Math.random() * totalWeight; | |
let weight = 0; | |
for (let i = 0; i < values.length; i++) { | |
weight += weights[i]; | |
if (random < weight) { | |
return values[i]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment