Skip to content

Commit

Permalink
feat: add typography-metaball
Browse files Browse the repository at this point in the history
  • Loading branch information
daehyeonmun2021 committed Sep 17, 2023
1 parent 0c0e683 commit 8f3670a
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/screens/root.stack.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { createStackNavigator } from '@react-navigation/stack';
import { PixelatedImageScreen } from './pixelated-image';
import { TypographyMetaballScreen } from './typography-metaball/typography-metaball.screen';

export type RootStackParamList = {
PixelatedImageScreen: undefined;
TypographyMetaballScreen: undefined;
};

const Stack = createStackNavigator<RootStackParamList>();

export const RootStack = () => {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="TypographyMetaballScreen" component={TypographyMetaballScreen} />
<Stack.Screen name="PixelatedImageScreen" component={PixelatedImageScreen} />
</Stack.Navigator>
);
Expand Down
Binary file added src/screens/typography-metaball/Hind-Bold.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions src/screens/typography-metaball/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './typography-metaball.screen';
114 changes: 114 additions & 0 deletions src/screens/typography-metaball/typography-metaball.screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
Blur,
Canvas,
ColorMatrix,
Drawing,
Group,
Paint,
Skia,
useFont,
} from '@shopify/react-native-skia';
import { useWindowDimensions } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { makeParticle, makePointsFromString, randomInt, sample } from './util';

const TOTAL_PARTICLES = 500;
const FRICTION = 0.88;
const MOVE_SPEED = 0.92;

export const TypographyMetaballScreen = () => {
const { width: stageWidth, height: stageHeight } = useWindowDimensions();

const fontSize = stageWidth;
const font = useFont(require('./Hind-Bold.ttf'), fontSize);
if (!font) {
return null;
}

const strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
const stringPointsList = strings.map((str) =>
makePointsFromString(str, font, 15, stageWidth, stageHeight),
);

let sequence = 0;
const particles = [...Array(TOTAL_PARTICLES)].map(() => makeParticle(stringPointsList[sequence]));

const updateSequence = () => {
sequence = randomInt(0, stringPointsList.length - 1);
};

const updateParticles = () => {
for (let i = 0; i < particles.length; i++) {
const particle = particles[i];

const stringPoint = sample(stringPointsList[sequence]);
particle.savedX = stringPoint.x;
particle.savedY = stringPoint.y;

const dx = particle.x - particle.savedX;
const dy = particle.y - particle.savedY;
const dist = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const tx = particle.savedX + Math.cos(angle) * dist;
const ty = particle.savedY + Math.sin(angle) * dist;
const ax = tx - particle.savedX;
const ay = ty - particle.savedY;
particle.vx += ax;
particle.vy += ay;
}
};

const tap = Gesture.Tap()
.runOnJS(true)
.onEnd(() => {
updateSequence();
updateParticles();
});

return (
<GestureDetector gesture={tap}>
<Canvas
mode="continuous"
style={{
width: stageWidth,
height: stageHeight,
backgroundColor: '#297F2E',
}}
>
<Group
layer={
<Paint>
<Blur blur={8} />
<ColorMatrix
matrix={[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 60, -40]}
/>
</Paint>
}
>
<Drawing
drawing={({ canvas, paint }) => {
// paint.setStyle(PaintStyle.Stroke);
// paint.setStrokeWidth(3);
paint.setColor(Skia.Color('#f4c129'));

for (let i = 0; i < particles.length; i++) {
const particle = particles[i];

particle.x += (particle.savedX - particle.x) * MOVE_SPEED;
particle.y += (particle.savedY - particle.y) * MOVE_SPEED;

particle.vx *= FRICTION;
particle.vy *= FRICTION;

particle.x += particle.vx;
particle.y += particle.vy;

canvas.drawCircle(particle.x, particle.y, 15, paint);
}
}}
/>
</Group>
</Canvas>
</GestureDetector>
);
};
54 changes: 54 additions & 0 deletions src/screens/typography-metaball/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SkFont, SkPoint, Skia, StrokeCap, StrokeJoin } from '@shopify/react-native-skia';

export const sample = <T>(arr: T[]) => {
return arr[Math.floor(Math.random() * arr.length)];
};

export const randomInt = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};

export const makePointsFromString = (
str: string,
font: SkFont,
density: number,
stageWidth: number,
stageHeight: number,
) => {
const textPath = Skia.Path.MakeFromText(
str,
(stageWidth - font.getTextWidth(str)) / 2,
(stageHeight + font.getSize() / 2) / 2,
font,
)!;
textPath.stroke({
width: 1,
cap: StrokeCap.Round,
join: StrokeJoin.Round,
miter_limit: 1,
precision: 1,
});
textPath.dash(density, density, 0);
return [...Array(textPath.countPoints())].map((_, i) => textPath.getPoint(i));
};

type IParticle = {
x: number;
y: number;
savedX: number;
savedY: number;
vx: number;
vy: number;
};

export const makeParticle = (stringPoints: SkPoint[]): IParticle => {
const stringPoint = sample(stringPoints);
return {
x: stringPoint.x,
y: stringPoint.y,
savedX: stringPoint.x,
savedY: stringPoint.y,
vx: 0,
vy: 0,
};
};

0 comments on commit 8f3670a

Please sign in to comment.