-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0c0e683
commit 8f3670a
Showing
5 changed files
with
172 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
Binary file not shown.
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 @@ | ||
export * from './typography-metaball.screen'; |
114 changes: 114 additions & 0 deletions
114
src/screens/typography-metaball/typography-metaball.screen.tsx
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,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> | ||
); | ||
}; |
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,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, | ||
}; | ||
}; |