Skip to content

Commit

Permalink
Android hardware artifacting fix (#38)
Browse files Browse the repository at this point in the history
* Add perspective to transform array for Android platform
* Add renderToHardwareTextureAndroid prop to fix artifacting on some Android devices
* Add missing semicolons
* Only assign renderToHardwareTextureAndroid = true while animating
* Apply suggestions from code review
Co-authored-by: Vincent Catillon <[email protected]>
* Remove renderToHardwareTextureAndroid from top level Explosion prop
* Remove state based renderToHardwareTextureAndroid enabled
Co-authored-by: Vincent Catillon <[email protected]>
  • Loading branch information
leezumstein authored Mar 3, 2021
1 parent c4c9ac8 commit 2802713
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 9 deletions.
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ module.exports = {
'node_modules/(?!(.*-)?react(.*-)?(native)(-.*)?)',
'node_modules/core-js'
],
setupFiles: [
'<rootDir>/node_modules/react-native/jest/setup.js',
'<rootDir>/jestSetup.js'
],
collectCoverage: true,
coverageReporters: ['lcov', 'text', 'html'],
collectCoverageFrom: [
Expand Down
27 changes: 27 additions & 0 deletions jestSetup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @flow

jest.mock('react-native', () => {
const ReactNative = jest.requireActual('react-native');
const { Platform } = ReactNative;

jest.spyOn(Platform, 'select');
const MockPlatform = {
...Platform,
OS: 'ios',
};
Platform.select.mockImplementation(specifics => {
const { OS } = MockPlatform
if (OS in specifics) {
return specifics[OS];
} else if ('default' in specifics) {
return specifics.default;
}
return undefined;
})

return Object.setPrototypeOf({
Platform: MockPlatform,
}, ReactNative);
});

jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
42 changes: 36 additions & 6 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// @flow

import * as React from 'react';
import { Animated, Platform } from 'react-native';
import renderer from 'react-test-renderer';

import ConfettiCannon, {DEFAULT_EXPLOSION_SPEED, DEFAULT_FALL_SPEED} from '..';

describe('index', () => {
beforeEach(() => {
jest.useFakeTimers();
Platform.OS = 'ios';
});

it('should trigger animations callbacks', () => {
Expand Down Expand Up @@ -64,7 +66,7 @@ describe('index', () => {
expect(handleAnimationEnd).toHaveBeenCalledTimes(1);
});

it('should not start is autoStart is disabled', () => {
it('should not start if autoStart is disabled', () => {
const handleAnimationStart = jest.fn();

renderer.create(
Expand Down Expand Up @@ -108,7 +110,7 @@ describe('index', () => {
const handleAnimationResume = jest.fn();
const handleAnimationStop = jest.fn();
const handleAnimationEnd = jest.fn();
const ref = jest.fn();
const ref = jest.fn<[ConfettiCannon | null], void>();

renderer.create(
<ConfettiCannon
Expand All @@ -119,28 +121,27 @@ describe('index', () => {
onAnimationResume={handleAnimationResume}
onAnimationStop={handleAnimationStop}
onAnimationEnd={handleAnimationEnd}
// $FlowFixMe this is a mock
ref={ref}
/>
);

const [confettiCannon] = ref.mock.calls[0];

confettiCannon.start();
confettiCannon && confettiCannon.start();

expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(0);
expect(handleAnimationStop).toHaveBeenCalledTimes(0);
expect(handleAnimationEnd).toHaveBeenCalledTimes(0);

confettiCannon.stop();
confettiCannon && confettiCannon.stop();

expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(0);
expect(handleAnimationStop).toHaveBeenCalledTimes(1);
expect(handleAnimationEnd).toHaveBeenCalledTimes(0);

confettiCannon.resume();
confettiCannon && confettiCannon.resume();

expect(handleAnimationStart).toHaveBeenCalledTimes(1);
expect(handleAnimationResume).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -230,4 +231,33 @@ describe('index', () => {

expect(confettis1).toEqual(confettis2);
});

it('should include the perspective transform on the Android platform', () => {
Platform.OS = 'android';

const origin = {x: -10, y: 0};
const count = 1000;

const component = renderer.create(
<ConfettiCannon count={count} origin={origin} />
);
const confetti = component.root.find(el => el.props.testID === 'confetti-1');

expect(confetti.props.transform).toEqual(expect.arrayContaining([{ perspective: 100 }]));
});

it('should set "renderToHardwareTextureAndroid" prop to true for confetti animated view', () => {
const origin = {x: -10, y: 0};
const count = 1000;

const component = renderer.create(
<ConfettiCannon count={count} origin={origin} />
);

const confettiAnimatedView = component.root
.find(el => el.props.testID === 'confetti-1')
.findByType(Animated.View);

expect(confettiAnimatedView.props.renderToHardwareTextureAndroid).toEqual(true);
});
});
8 changes: 6 additions & 2 deletions src/components/confetti.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ type Interpolations = Array<{
translateY?: Animated.Interpolation,
rotate?: Animated.Interpolation,
rotateX?: Animated.Interpolation,
rotateY?: Animated.Interpolation
rotateY?: Animated.Interpolation,
perspective?: number
}>;

type Props = {|
Expand All @@ -34,7 +35,10 @@ class Confetti extends React.PureComponent<Props> {
const style = { width, height, backgroundColor: color, transform, opacity};

return (
<Animated.View style={[styles.confetti, containerStyle]}>
<Animated.View
pointerEvents="none"
renderToHardwareTextureAndroid={true}
style={[styles.confetti, containerStyle]}>
<Animated.View style={[isRounded && styles.rounded, style]} />
</Animated.View>
);
Expand Down
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow

import * as React from 'react';
import { Animated, Dimensions, Easing } from 'react-native';
import { Animated, Dimensions, Easing, Platform } from 'react-native';
import type { CompositeAnimation } from 'react-native/Libraries/Animated/src/AnimatedImplementation';
import type { EndResult } from 'react-native/Libraries/Animated/src/animations/Animation';

Expand Down Expand Up @@ -216,6 +216,10 @@ class Explosion extends React.PureComponent<Props, State> {
const containerTransform = [{translateX: left}, {translateY: top}];
const transform = [{rotateX}, {rotateY}, {rotate: rotateZ}, {translateX}];

if (Platform.OS === 'android') {
transform.push({ perspective: 100 });
}

return (
<Confetti
color={item.color}
Expand Down

0 comments on commit 2802713

Please sign in to comment.