Skip to content
Closed
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fadad8b
Initial support
m-bert Oct 19, 2023
f239f83
Remove non-null assertion
m-bert Oct 19, 2023
f09e13a
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Oct 23, 2023
b0edf9a
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Oct 24, 2023
e458ca5
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Oct 31, 2023
3b292d8
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Oct 31, 2023
c7adc62
Merge main
m-bert Nov 30, 2023
f3a9c16
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 1, 2023
ffbc27b
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 1, 2023
4a4b065
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 1, 2023
323030c
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 2, 2023
a513e37
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 4, 2023
e5c2009
Fix addPxToTranslate
m-bert Dec 5, 2023
013ed28
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 6, 2023
b6d9c81
Merge main
m-bert Dec 12, 2023
30b7329
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 21, 2023
8053df8
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Dec 27, 2023
cffa11d
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Jan 12, 2024
f411dc2
Merge main
m-bert Jan 16, 2024
e63dc05
Adjust timestamp
m-bert Jan 16, 2024
a2fa728
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Feb 14, 2024
da28547
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Mar 7, 2024
b06d7fe
Apply easing
m-bert Mar 7, 2024
e32df62
Change formatting
m-bert Mar 7, 2024
2fbefc2
Merge conditions
m-bert Mar 7, 2024
92c1666
Fix formatting
m-bert Mar 7, 2024
6e9b4b2
Remove Circular dependencies
m-bert Mar 7, 2024
bee509b
Move easing to separate file
m-bert Mar 11, 2024
7c53e10
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Mar 11, 2024
fc05b98
Add use strict
m-bert Mar 11, 2024
f47e160
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Apr 8, 2024
dfac9d7
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Apr 23, 2024
c3f7dfe
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert Apr 24, 2024
4930ef0
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert May 24, 2024
d2c7978
Merge branch 'main' into @mbert/add-custom-keyframes
m-bert May 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/reanimated2/layoutReanimation/web/Easing.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

// Those are the easings that can be implemented using Bezier curves.
// Others should be done as CSS animations
export const WebEasings = {
linear: [0, 0, 1, 1],
ease: [0.42, 0, 1, 1],
quad: [0.11, 0, 0.5, 0],
cubic: [0.32, 0, 0.67, 0],
sin: [0.12, 0, 0.39, 0],
circle: [0.55, 0, 1, 0.45],
exp: [0.7, 0, 0.84, 0],
};

export type WebEasingsNames = keyof typeof WebEasings;
21 changes: 19 additions & 2 deletions src/reanimated2/layoutReanimation/web/animationParser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

import type { TransformsStyle } from 'react-native';
import { WebEasings } from './Easing.web';
import type { WebEasingsNames } from './Easing.web';

export interface ReanimatedWebTransformProperties {
translateX?: string;
Expand All @@ -16,7 +18,7 @@ export interface ReanimatedWebTransformProperties {
skewX?: string;
}

interface AnimationStyle {
export interface AnimationStyle {
opacity?: number;
transform?: ReanimatedWebTransformProperties[];
}
Expand All @@ -41,9 +43,24 @@ export function convertAnimationObjectToKeyframes(
let keyframe = `@keyframes ${animationObject.name} { `;

for (const [timestamp, style] of Object.entries(animationObject.style)) {
keyframe += `${timestamp}% { `;
const step =
timestamp === 'from' ? 0 : timestamp === 'to' ? 100 : timestamp;

keyframe += `${step}% { `;

for (const [property, values] of Object.entries(style)) {
if (property === 'easing') {
const easingName = (
values.name in WebEasings ? values.name : 'linear'
) as WebEasingsNames;

keyframe += `animation-timing-function: cubic-bezier(${WebEasings[
easingName
].toString()});`;

continue;
}

if (property !== 'transform') {
keyframe += `${property}: ${values}; `;
continue;
Expand Down
35 changes: 27 additions & 8 deletions src/reanimated2/layoutReanimation/web/animationsManager.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
'use strict';

import type { AnimationConfig, AnimationNames, CustomConfig } from './config';
import type {
AnimationConfig,
AnimationNames,
CustomConfig,
KeyframeDefinitions,
} from './config';
import { Animations } from './config';
import type {
AnimatedComponentProps,
LayoutAnimationStaticContext,
} from '../../../createAnimatedComponent/commonTypes';
import { LayoutAnimationType } from '../animationBuilder/commonTypes';
import type { StyleProps } from '../../commonTypes';
import { createAnimationWithExistingTransform } from './createAnimation';
import {
createAnimationWithExistingTransform,
createCustomKeyFrameAnimation,
} from './createAnimation';
import {
extractTransformFromStyle,
getProcessedConfig,
Expand All @@ -19,6 +27,7 @@ import {
import { areDOMRectsEqual } from './domUtils';
import type { TransformsStyle } from 'react-native';
import type { TransitionData } from './animationParser';
import { Keyframe } from '../animationBuilder';
import { makeElementVisible } from './componentStyle';

function chooseConfig<ComponentProps extends Record<string, unknown>>(
Expand All @@ -39,11 +48,11 @@ function chooseConfig<ComponentProps extends Record<string, unknown>>(

function checkUndefinedAnimationFail(
initialAnimationName: string,
isLayoutTransition: boolean
needsCustomization: boolean
) {
// This prevents crashes if we try to set animations that are not defined.
// We don't care about layout transitions since they're created dynamically
if (initialAnimationName in Animations || isLayoutTransition) {
// We don't care about layout transitions or custom keyframes since they're created dynamically
if (initialAnimationName in Animations || needsCustomization) {
return false;
}

Expand Down Expand Up @@ -96,14 +105,15 @@ function tryGetAnimationConfigWithTransform<
typeof config.constructor;

const isLayoutTransition = animationType === LayoutAnimationType.LAYOUT;
const isCustomKeyframe = config instanceof Keyframe;
const initialAnimationName =
typeof config === 'function'
? config.presetName
: (config.constructor as ConstructorWithStaticContext).presetName;

const shouldFail = checkUndefinedAnimationFail(
initialAnimationName,
isLayoutTransition
isLayoutTransition || isCustomKeyframe
);

if (shouldFail) {
Expand All @@ -112,15 +122,24 @@ function tryGetAnimationConfigWithTransform<

const transform = extractTransformFromStyle(props.style as StyleProps);

const animationName =
transform && animationType !== LayoutAnimationType.EXITING
let animationName = initialAnimationName;

if (isCustomKeyframe) {
animationName = createCustomKeyFrameAnimation(
(config as CustomConfig).definitions as KeyframeDefinitions,
transform
);
} else {
animationName = transform
? createAnimationWithExistingTransform(initialAnimationName, transform)
: initialAnimationName;
}

const animationConfig = getProcessedConfig(
animationName,
animationType,
config as CustomConfig,
isLayoutTransition || isCustomKeyframe,
initialAnimationName as AnimationNames
);

Expand Down
12 changes: 7 additions & 5 deletions src/reanimated2/layoutReanimation/web/componentUtils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use strict';

import type { TransformsStyle } from 'react-native';
import { Animations, TransitionType, WebEasings } from './config';
import { Animations, TransitionType } from './config';
import type {
AnimationCallback,
AnimationConfig,
AnimationNames,
CustomConfig,
WebEasingsNames,
} from './config';
import { WebEasings } from './Easing.web';
import type { WebEasingsNames } from './Easing.web';
import { convertTransformToString } from './animationParser';
import type { TransitionData } from './animationParser';
import { TransitionGenerator } from './createAnimation';
Expand Down Expand Up @@ -66,10 +67,10 @@ export function getReducedMotionFromConfig(config: CustomConfig) {

function getDurationFromConfig(
config: CustomConfig,
isLayoutTransition: boolean,
needsCustomization: boolean,
animationName: AnimationNames
): number {
const defaultDuration = isLayoutTransition
const defaultDuration = needsCustomization
? 0.3
: Animations[animationName].duration;

Expand Down Expand Up @@ -111,14 +112,15 @@ export function getProcessedConfig(
animationName: string,
animationType: LayoutAnimationType,
config: CustomConfig,
needsCustomization: boolean,
initialAnimationName: AnimationNames
): AnimationConfig {
return {
animationName,
animationType,
duration: getDurationFromConfig(
config,
animationType === LayoutAnimationType.LAYOUT,
needsCustomization,
initialAnimationName
),
delay: getDelayFromConfig(config),
Expand Down
18 changes: 4 additions & 14 deletions src/reanimated2/layoutReanimation/web/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ import {
} from './animation/Stretch.web';
import { ZoomIn, ZoomInData, ZoomOut, ZoomOutData } from './animation/Zoom.web';

import type { AnimationData } from './animationParser';
import type { AnimationData, AnimationStyle } from './animationParser';

export type AnimationCallback = ((finished: boolean) => void) | null;

export type KeyframeDefinitions = Record<number, AnimationStyle>;

export interface AnimationConfig {
animationName: string;
animationType: LayoutAnimationType;
Expand All @@ -59,6 +61,7 @@ export interface CustomConfig {
reduceMotionV?: ReduceMotion;
callbackV?: AnimationCallback;
reversed?: boolean;
definitions?: KeyframeDefinitions;
}

export enum TransitionType {
Expand Down Expand Up @@ -111,18 +114,5 @@ export const Animations = {
...RollOut,
};

// Those are the easings that can be implemented using Bezier curves.
// Others should be done as CSS animations
export const WebEasings = {
linear: [0, 0, 1, 1],
ease: [0.42, 0, 1, 1],
quad: [0.11, 0, 0.5, 0],
cubic: [0.32, 0, 0.67, 0],
sin: [0.12, 0, 0.39, 0],
circle: [0.55, 0, 1, 0.45],
exp: [0.7, 0, 0.84, 0],
};

export type AnimationNames = keyof typeof Animations;
export type LayoutTransitionsNames = keyof typeof AnimationsData;
export type WebEasingsNames = keyof typeof WebEasings;
50 changes: 42 additions & 8 deletions src/reanimated2/layoutReanimation/web/createAnimation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

import { Animations, AnimationsData, TransitionType } from './config';
import type { KeyframeDefinitions } from './config';
import { convertAnimationObjectToKeyframes } from './animationParser';
import type {
AnimationData,
Expand All @@ -13,12 +14,12 @@ import { SequencedTransition } from './transition/Sequenced.web';
import { FadingTransition } from './transition/Fading.web';
import { insertWebAnimation } from './domUtils';

type TransformType = NonNullable<TransformsStyle['transform']>;

// Translate values are passed as numbers. However, if `translate` property receives number, it will not automatically
// convert it to `px`. Therefore if we want to keep existing transform we have to add 'px' suffix to each of translate values
// that are present inside transform.
function addPxToTranslate(
existingTransform: NonNullable<TransformsStyle['transform']>
) {
function addPxToTranslate(existingTransform: TransformType) {
type RNTransformProp = (typeof existingTransform)[number];

// @ts-ignore `existingTransform` cannot be string because in that case
Expand All @@ -27,7 +28,7 @@ function addPxToTranslate(
(transformProp: RNTransformProp) => {
const newTransformProp: ReanimatedWebTransformProperties = {};
for (const [key, value] of Object.entries(transformProp)) {
if (key.includes('translate')) {
if (key.includes('translate') && typeof value === 'number') {
// @ts-ignore After many trials we decided to ignore this error - it says that we cannot use 'key' to index this object.
// Sadly it doesn't go away after using cast `key as keyof TransformProperties`.
newTransformProp[key] = `${value}px`;
Expand Down Expand Up @@ -71,13 +72,13 @@ function addExistingTransform(
*/
export function createAnimationWithExistingTransform(
animationName: string,
existingTransform: NonNullable<TransformsStyle['transform']>,
layoutTransition?: AnimationData
existingTransform: TransformType,
customData?: AnimationData
) {
let newAnimationData;

if (layoutTransition) {
newAnimationData = layoutTransition;
if (customData) {
newAnimationData = customData;
} else {
if (!(animationName in Animations)) {
return '';
Expand All @@ -100,6 +101,39 @@ export function createAnimationWithExistingTransform(
return keyframeName;
}

export function createCustomKeyFrameAnimation(
keyframeDefinitions: KeyframeDefinitions,
transform: TransformsStyle['transform']
) {
for (const value of Object.values(keyframeDefinitions)) {
if (value.transform) {
value.transform = addPxToTranslate(value.transform as TransformType);
}
}

const animationData: AnimationData = {
name: '',
style: keyframeDefinitions,
duration: -1,
};

if (transform) {
return createAnimationWithExistingTransform(
animationData.name,
transform,
animationData
);
}

animationData.name = generateNextCustomKeyframeName();

const parsedKeyframe = convertAnimationObjectToKeyframes(animationData);

insertWebAnimation(animationData.name, parsedKeyframe);

return animationData.name;
}

let customKeyframeCounter = 0;

function generateNextCustomKeyframeName() {
Expand Down