Skip to content

Commit

Permalink
Merge pull request #6 from AlirezaHadjar/feat/optimization
Browse files Browse the repository at this point in the history
Feat/optimization
  • Loading branch information
AlirezaHadjar authored Dec 28, 2024
2 parents 5f4ffaf + 8fbc07a commit 5a5c1f5
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 78 deletions.
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
root: true,
extends: '@react-native',
rules: {
'@typescript-eslint/no-shadow': 'off',
'react/react-in-jsx-scope': 'off',
'curly': 'off',
},
};
1 change: 1 addition & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function App() {
fallDuration={8000}
verticalSpacing={20}
cannonsPositions={cannonPositions}
sizeVariation={0.1}
fadeOutOnEnd
/>
<Button title="Resume" onPress={() => confettiRef.current?.resume()} />
Expand Down
7 changes: 1 addition & 6 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,4 @@ pre-commit:
run: npx eslint {staged_files}
types:
glob: "*.{js,ts, jsx, tsx}"
run: npx tsc
commit-msg:
parallel: true
commands:
commitlint:
run: npx commitlint --edit
run: npx tsc
90 changes: 59 additions & 31 deletions src/Confetti.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import { StyleSheet, useWindowDimensions, View } from 'react-native';
Expand All @@ -26,7 +27,7 @@ import {
withSequence,
withTiming,
} from 'react-native-reanimated';
import { generateBoxesArray } from './utils';
import { generateBoxesArray, generateEvenlyDistributedValues } from './utils';
import {
DEFAULT_AUTOSTART_DELAY,
DEFAULT_BLAST_DURATION,
Expand All @@ -44,6 +45,7 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
{
count = DEFAULT_BOXES_COUNT,
flakeSize = DEFAULT_FLAKE_SIZE,
sizeVariation = 0,
fallDuration = DEFAULT_FALL_DURATION,
blastDuration = DEFAULT_BLAST_DURATION,
colors = DEFAULT_COLORS,
Expand Down Expand Up @@ -87,16 +89,29 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
const columnsNum = Math.floor(containerWidth / flakeSize.width);
const rowsNum = Math.ceil(count / columnsNum);
const rowHeight = flakeSize.height + verticalSpacing;
const columnWidth = flakeSize.width;
const verticalOffset =
-rowsNum * rowHeight * (hasCannons ? 0.2 : 1) +
verticalSpacing -
RANDOM_INITIAL_Y_JIGGLE;
const textureSize = {
width: columnWidth * columnsNum,
height: rowHeight * rowsNum,
};
const [boxes, setBoxes] = useState(() => generateBoxesArray(count, colors));

const sizeSteps = 10;
const sizeVariations = useMemo(() => {
const sizeVariations = [];
for (let i = 0; i < sizeSteps; i++) {
const variationScale = -1 + (2 * i) / (sizeSteps - 1);
const multiplier = 1 + sizeVariation * variationScale;

sizeVariations.push({
width: flakeSize.width * multiplier,
height: flakeSize.height * multiplier,
});
}
return sizeVariations;
}, [sizeSteps, sizeVariation, flakeSize]);

const [boxes, setBoxes] = useState(() =>
generateBoxesArray(count, colors.length, sizeVariations.length)
);

const pause = () => {
running.value = false;
Expand All @@ -111,9 +126,13 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
const refreshBoxes = useCallback(() => {
'worklet';

const newBoxes = generateBoxesArray(count, colors);
const newBoxes = generateBoxesArray(
count,
colors.length,
sizeVariations.length
);
runOnJS(setBoxes)(newBoxes);
}, [count, colors]);
}, [count, colors, sizeVariations.length]);

const JSOnStart = () => onAnimationStart?.();
const JSOnEnd = () => onAnimationEnd?.();
Expand Down Expand Up @@ -231,29 +250,37 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoplay]);

const height = sizeVariations.reduce((acc, size) => acc + size.height, 0);
const maxWidth = Math.max(...sizeVariations.map((size) => size.width));

const texture = useTexture(
<Group>
{boxes.map((box, index) => {
const { x, y } = getPosition(index);

return (
<Rect
key={index}
rect={rect(x, y, flakeSize.width, flakeSize.height)}
color={box.color}
/>
);
{colors.map((color, index) => {
return sizeVariations.map((size, sizeIndex) => {
return (
<Rect
key={`${index}-${sizeIndex}`}
rect={rect(0, index * size.height, size.width, size.height)}
color={color}
/>
);
});
})}
</Group>,
textureSize
{
width: maxWidth,
height: height * colors.length,
}
);

const sprites = boxes.map((_, index) => {
const { x, y } = getPosition(index);
return rect(x, y, flakeSize.width, flakeSize.height);
const sprites = boxes.map((box) => {
const colorIndex = box.colorIndex;
const sizeIndex = box.sizeIndex;
const size = sizeVariations[sizeIndex]!;
return rect(0, colorIndex * size.height, size.width, size.height);
});

const transforms = useRSXformBuffer(count, (val, i) => {
const transforms = useRSXformBuffer(boxes.length, (val, i) => {
'worklet';
const piece = boxes[i];
if (!piece) return;
Expand All @@ -263,8 +290,8 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
const { x, y } = getPosition(i); // Already includes random offsets

if (progress.value < 1 && aHasCannon.value) {
// Determine the corresponding index in initialBlasts based on i and count
const blastIndex = Math.floor((i / count) * cannonsPositions.length);
// Distribute confetti evenly across cannons by using modulo
const blastIndex = i % cannonsPositions.length;
const blastPosX = cannonsPositions[blastIndex]?.x || 0;
const blastPosY = cannonsPositions[blastIndex]?.y || 0;

Expand All @@ -276,13 +303,13 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(

tx = interpolate(
progress.value,
[0, 1],
[piece.blastThreshold, 1],
[blastPosX, initialX],
Extrapolation.CLAMP
);
ty = interpolate(
progress.value,
[0, 1],
[piece.blastThreshold, 1],
[blastPosY, initialY],
Extrapolation.CLAMP
);
Expand All @@ -302,7 +329,7 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
// Interpolate between randomX values for smooth left-right movement
const randomX = interpolate(
progress.value,
[1, 1.25, 1.5, 1.75, 2],
generateEvenlyDistributedValues(1, 2, piece.randomXs.length),
piece.randomXs, // Use the randomX array for horizontal movement
Extrapolation.CLAMP
);
Expand Down Expand Up @@ -337,9 +364,10 @@ export const Confetti = forwardRef<ConfettiMethods, ConfettiProps>(
Extrapolation.CLAMP
);
const scale = blastScale * oscillatingScale;
const size = sizeVariations[piece.sizeIndex]!;

const px = flakeSize.width / 2;
const py = flakeSize.height / 2;
const px = size.width / 2;
const py = size.height / 2;

// Apply the transformation, including the flipping effect and randomX oscillation
const s = Math.sin(rz) * scale;
Expand Down
93 changes: 56 additions & 37 deletions src/PIConfetti.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import {
Canvas,
Atlas,
} from '@shopify/react-native-skia';
import { forwardRef, useCallback, useImperativeHandle, useState } from 'react';
import {
forwardRef,
useCallback,
useImperativeHandle,
useMemo,
useState,
} from 'react';
import { StyleSheet, useWindowDimensions, View } from 'react-native';
import {
cancelAnimation,
Expand All @@ -34,6 +40,7 @@ export const PIConfetti = forwardRef<ConfettiMethods, PIConfettiProps>(
{
count = DEFAULT_BOXES_COUNT,
flakeSize = DEFAULT_FLAKE_SIZE,
sizeVariation = 0,
fallDuration = DEFAULT_FALL_DURATION,
blastDuration = DEFAULT_BLAST_DURATION,
colors = DEFAULT_COLORS,
Expand Down Expand Up @@ -66,17 +73,23 @@ export const PIConfetti = forwardRef<ConfettiMethods, PIConfettiProps>(
const containerHeight = _height || DEFAULT_SCREEN_HEIGHT;
const blastPosition = _blastPosition || { x: containerWidth / 2, y: 150 };

const columnsNum = Math.floor(containerWidth / flakeSize.width);
const rowsNum = Math.ceil(count / columnsNum);
const rowHeight = flakeSize.height + 0;
const columnWidth = flakeSize.width;
const sizeSteps = 10;
const sizeVariations = useMemo(() => {
const sizeVariations = [];
for (let i = 0; i < sizeSteps; i++) {
const variationScale = -1 + (2 * i) / (sizeSteps - 1);
const multiplier = 1 + sizeVariation * variationScale;

sizeVariations.push({
width: flakeSize.width * multiplier,
height: flakeSize.height * multiplier,
});
}
return sizeVariations;
}, [sizeSteps, sizeVariation, flakeSize]);

const textureSize = {
width: columnWidth * columnsNum,
height: rowHeight * rowsNum,
};
const [boxes, setBoxes] = useState(() =>
generatePIBoxesArray(count, colors)
generatePIBoxesArray(count, colors.length, sizeVariations.length)
);

const pause = () => {
Expand All @@ -95,9 +108,13 @@ export const PIConfetti = forwardRef<ConfettiMethods, PIConfettiProps>(
const refreshBoxes = useCallback(() => {
'worklet';

const newBoxes = generatePIBoxesArray(count, colors);
const newBoxes = generatePIBoxesArray(
count,
colors.length,
sizeVariations.length
);
runOnJS(setBoxes)(newBoxes);
}, [count, colors]);
}, [count, colors, sizeVariations.length]);

const JSOnStart = () => onAnimationStart?.();
const JSOnEnd = () => onAnimationEnd?.();
Expand Down Expand Up @@ -147,14 +164,6 @@ export const PIConfetti = forwardRef<ConfettiMethods, PIConfettiProps>(
restart,
}));

const getInitialPosition = (index: number) => {
'worklet';
const x = (index % columnsNum) * flakeSize.width;
const y = Math.floor(index / columnsNum) * rowHeight;

return { x, y };
};

const getPosition = (index: number) => {
'worklet';
const centerX = blastPosition.x; // Horizontal center of the container
Expand All @@ -172,29 +181,37 @@ export const PIConfetti = forwardRef<ConfettiMethods, PIConfettiProps>(
return { x, y };
};

const height = sizeVariations.reduce((acc, size) => acc + size.height, 0);
const maxWidth = Math.max(...sizeVariations.map((size) => size.width));

const texture = useTexture(
<Group>
{boxes.map((box, index) => {
const { x, y } = getInitialPosition(index);

return (
<Rect
key={index}
rect={rect(x, y, flakeSize.width, flakeSize.height)}
color={box.color}
/>
);
{colors.map((color, index) => {
return sizeVariations.map((size, sizeIndex) => {
return (
<Rect
key={`${index}-${sizeIndex}`}
rect={rect(0, index * size.height, size.width, size.height)}
color={color}
/>
);
});
})}
</Group>,
textureSize
{
width: maxWidth,
height: height * colors.length,
}
);

const sprites = boxes.map((_, index) => {
const { x, y } = getInitialPosition(index);
return rect(x, y, flakeSize.width, flakeSize.height);
const sprites = boxes.map((box) => {
const colorIndex = box.colorIndex;
const sizeIndex = box.sizeIndex;
const size = sizeVariations[sizeIndex]!;
return rect(0, colorIndex * size.height, size.width, size.height);
});

const transforms = useRSXformBuffer(count, (val, i) => {
const transforms = useRSXformBuffer(boxes.length, (val, i) => {
'worklet';
const piece = boxes[i];
if (!piece) return;
Expand Down Expand Up @@ -284,8 +301,10 @@ export const PIConfetti = forwardRef<ConfettiMethods, PIConfettiProps>(
);
const scale = blastScale * oscillatingScale;

const px = flakeSize.width / 2;
const py = flakeSize.height / 2;
const size = sizeVariations[piece.sizeIndex]!;

const px = size.width / 2;
const py = size.height / 2;

// Apply the transformation, including the flipping effect and randomX oscillation
const s = Math.sin(rz) * scale;
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ export type ConfettiProps = {
* @description An array of positions from which confetti flakes should blast.
*/
cannonsPositions?: Position[];
/**
* @description Controls the random size variation of confetti flakes. Value between 0 and 1.
* A value of 0.1 means flakes can randomly vary between 10% smaller to 10% larger than the base size.
* Recommended value is between 0 and 0.2
* @default 0
*/
sizeVariation?: number;
};

export type PIConfettiProps = Omit<
Expand Down
Loading

0 comments on commit 5a5c1f5

Please sign in to comment.