Slide 1

Slide 1 text

Shared Element Transition with React Native @narendra_shetty

Slide 2

Slide 2 text

Narendra Shetty Front-End Engineer @ Booking.com Amsterdam

Slide 3

Slide 3 text

What is Shared Element Transition?

Slide 4

Slide 4 text

What is Transition?

Slide 5

Slide 5 text

What is Transition? Transition is an animation which guides users to focus on what you want

Slide 6

Slide 6 text

What is Transition? Transition is an animation which guides users to focus on what you want Keep Users from getting lost Transitions between different screens involves enter and exit transitions

Slide 7

Slide 7 text

What is Shared Element Transition?

Slide 8

Slide 8 text

What is Shared Element Transition? As the name suggest, there is a single element being shared and moved from one screen to another during transition

Slide 9

Slide 9 text

What is Shared Element Transition? As the name suggest, there is a single element being shared and moved from one screen to another during transition This emphasizes continuity between transitions and breaks screen boundaries as the user navigates the app. This forces the human eye to focus on the content and its representation

Slide 10

Slide 10 text

Few good examples

Slide 11

Slide 11 text

Few good examples

Slide 12

Slide 12 text

Does React Native support shared elements?

Slide 13

Slide 13 text

Does React Native support shared elements? NO! :(

Slide 14

Slide 14 text

App

Slide 15

Slide 15 text

App Grid Detail Text Image Text Image

Slide 16

Slide 16 text

App Grid Detail Text Image Text Image

Slide 17

Slide 17 text

App Grid Detail Text Image Text Image

Slide 18

Slide 18 text

Goal

Slide 19

Slide 19 text

Goal

Slide 20

Slide 20 text

Goal Pure JavaScript Reasonably smooth animation

Slide 21

Slide 21 text

Dissecting the animation

Slide 22

Slide 22 text

Dissecting the animation

Slide 23

Slide 23 text

Dissecting the animation

Slide 24

Slide 24 text

Do you really believe that there is a single image being shared between the grid and detail screens?

Slide 25

Slide 25 text

Do you really believe that there is a single image being shared between the grid and detail screens? NO! :)

Slide 26

Slide 26 text

Dissecting the animation

Slide 27

Slide 27 text

Things to know

Slide 28

Slide 28 text

Things to know import { Animated } from 'react-native';

Slide 29

Slide 29 text

Things to know import { Animated } from 'react-native'; let AnimatedValue = new Animated.Value(0);

Slide 30

Slide 30 text

Things to know import { Animated } from 'react-native'; Animated.timing(AnimatedValue, { toValue: 1, duration: 3000 }).start(); let AnimatedValue = new Animated.Value(0);

Slide 31

Slide 31 text

Things to know import { Animated } from 'react-native'; let AnimatedValue = new Animated.Value(0); Animated.timing(AnimatedValue, { toValue: 1, duration: 3000 }).start(); AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [1, 0] });

Slide 32

Slide 32 text

Implementation

Slide 33

Slide 33 text

Grid Screen Detail Screen

Slide 34

Slide 34 text

Slide 35

Slide 35 text

Without Transition

Slide 36

Slide 36 text

Exit Grid Entry Detail

Slide 37

Slide 37 text

Adding Exit Transition

Slide 38

Slide 38 text

Adding Exit Transition { opacity: 1 }

Slide 39

Slide 39 text

Adding Exit Transition { opacity: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }) }

Slide 40

Slide 40 text

Adding Exit Transition Animated.timing(AnimatedValue, { toValue: 1, duration: 3000, }).start(); Trigger { opacity: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }) }

Slide 41

Slide 41 text

With exit transition on Grid screen

Slide 42

Slide 42 text

Adding Entry Transition

Slide 43

Slide 43 text

{ transform: [{ translateY: 0 }] } Adding Entry Transition

Slide 44

Slide 44 text

{ transform: [{ translateY: AnimatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [100, 100, 0] }) }] } Adding Entry Transition

Slide 45

Slide 45 text

Adding Entry Transition { transform: [{ translateY: AnimatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [100, 100, 0] }) }] } Animated.timing(AnimatedValue, { toValue: 1, duration: 3000, }).start(); Trigger

Slide 46

Slide 46 text

With exit and entry transition on grid and detail screen

Slide 47

Slide 47 text

Adding Transition Layer

Slide 48

Slide 48 text

Grid Screen Detail Screen

Slide 49

Slide 49 text

Grid Screen Detail Screen Transition Layer x: 0 y: 600 width: 200 height: 112 x: 0 y: 0 width: 360 height: 300

Slide 50

Slide 50 text

Grid Screen Detail Screen Transition Layer x: 0 y: 600 width: 200 height: 112 x: 0 y: 0 width: 360 height: 300

Slide 51

Slide 51 text

Grid Screen Detail Screen Transition Layer

Slide 52

Slide 52 text

Animation in Transition Layer

Slide 53

Slide 53 text

Animation in Transition Layer By interpolating on width, height, top and left

Slide 54

Slide 54 text

{ position: 'absolute', width: source.width, height: source.height, left: source.pageX, top: source.pageY }

Slide 55

Slide 55 text

{ position: 'absolute', width: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.width, destination.width] }) }

Slide 56

Slide 56 text

{ position: 'absolute', width: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.width, destination.width] }), height: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.height, destination.height] }) }

Slide 57

Slide 57 text

{ position: 'absolute', width: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.width, destination.width] }), height: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.height, destination.height] }), left: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.pageX, destination.pageX] }) }

Slide 58

Slide 58 text

{ position: 'absolute', width: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.width, destination.width] }), height: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.height, destination.height] }), left: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.pageX, destination.pageX] }), top: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [source.pageY, destination.pageY] }) }

Slide 59

Slide 59 text

By interpolating on width, height, top and left

Slide 60

Slide 60 text

Performance Analysis

Slide 61

Slide 61 text

Performance Analysis Simulating a busy app

Slide 62

Slide 62 text

Performance Analysis Simulating a busy app setInterval(() => { for (let i = 0; i < 5e8; i++) {} }, 1000);

Slide 63

Slide 63 text

Simulating a busy app setInterval(() => { for (let i = 0; i < 5e8; i++) {} }, 1000);

Slide 64

Slide 64 text

JS thread Native thread Click Uses requestAnimationFrame Generates new input value Generates new output value Passed as props to the View View is updated using setNativeProps Passed to Native over the bridge Native View is updated

Slide 65

Slide 65 text

useNativeDriver

Slide 66

Slide 66 text

useNativeDriver Animated.timing(AnimatedValue, { toValue: 1, duration: 3000 }).start();

Slide 67

Slide 67 text

Animated.timing(AnimatedValue, { toValue: 1, duration: 3000, useNativeDriver: true }).start(); useNativeDriver

Slide 68

Slide 68 text

JS thread Native thread Uses Native Driver Generates new input value Generates new output value Serializes animated nodes Native View is updated Click Passed to Native over the bridge

Slide 69

Slide 69 text

useNativeDriver can only animate non-layout properties, things like transform and opacity will work but flexbox and position properties won't. Disadvantage

Slide 70

Slide 70 text

Animation in Transition Layer By interpolating on Transform and using useNativeDriver

Slide 71

Slide 71 text

By interpolating on Transform and using useNativeDriver translateX translateY scale

Slide 72

Slide 72 text

Grid Screen Detail Screen

Slide 73

Slide 73 text

const sourceAspectRatio = source.width / source.height; const destAspectRatio = dest.width / dest.height;

Slide 74

Slide 74 text

const sourceAspectRatio = source.width / source.height; const destAspectRatio = dest.width / dest.height; if (sourceAspectRatio - destAspectRatio > 0) { // Landscape image } else { // Portrait image }

Slide 75

Slide 75 text

if (sourceAspectRatio - destAspectRatio > 0) { // Landscape image const newWidth = sourceAspectRatio * dest.height; } else { // Portrait image const newHeight = dest.width / sourceAspectRatio; } const sourceAspectRatio = source.width / source.height; const destAspectRatio = dest.width / dest.height;

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

const sourceAspectRatio = source.width / source.height; const destAspectRatio = dest.width / dest.height; if (sourceAspectRatio - destAspectRatio > 0) { // Landscape image } else { // Portrait image }

Slide 78

Slide 78 text

if (sourceAspectRatio - destAspectRatio > 0) { // Landscape image dest.posX -= (newWidth - dest.width) / 2; } else { // Portrait image dest.posY -= (newHeight - dest.height) / 2; } const sourceAspectRatio = source.width / source.height; const destAspectRatio = dest.width / dest.height;

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

const openingInitScale = source.width / newWidth;

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

const translateInitX = source.posX + source.width / 2; const translateInitY = source.posY + source.height / 2;

Slide 86

Slide 86 text

const translateInitX = source.posX + source.width / 2; const translateInitY = source.posY + source.height / 2; const translateDestX = dest.posX + dest.width / 2; const translateDestY = dest.posY + dest.height / 2;

Slide 87

Slide 87 text

const translateInitX = source.posX + source.width / 2; const translateInitY = source.posY + source.height / 2; const translateDestX = dest.posX + dest.width / 2; const translateDestY = dest.posY + dest.height / 2; openingInitTranslateX = translateInitX - translateDestX; openingInitTranslateY = translateInitY - translateDestY;

Slide 88

Slide 88 text

Slide 89

Slide 89 text

Animated.timing(AnimatedValue, { toValue: 1, duration: 3000, useNativeDriver: true }).start();

Slide 90

Slide 90 text

{ position: 'absolute', width: destination.width, height: destination.height, left: destination.posX, top: destination.posY }

Slide 91

Slide 91 text

{ position: 'absolute', width: destination.width, height: destination.height, left: destination.posX, top: destination.posY, transform: [ { translateX: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [openingInitTranslateX, 0] }) }, { translateY: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [openingInitTranslateY, 0] }) }, { scale: AnimatedValue.interpolate({ inputRange: [0, 1], outputRange: [openingInitScale, 1] }) } ] }

Slide 92

Slide 92 text

By interpolating on transform and using useNativeDriver

Slide 93

Slide 93 text

Performance Analysis Simulating a busy app

Slide 94

Slide 94 text

Performance Analysis Simulating a busy app setInterval(() => { for (let i = 0; i < 5e8; i++) {} }, 1000);

Slide 95

Slide 95 text

Simulating a busy app setInterval(() => { for (let i = 0; i < 5e8; i++) {} }, 1000);

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

Hiding the source and destination image during transition

Slide 98

Slide 98 text

Hiding the source and destination image during transition { opacity: AnimatedValue.interpolate({ inputRange: [0.005, 0.01], outputRange: [1, 0] }) }

Slide 99

Slide 99 text

Hiding the source and destination image during transition { opacity: AnimatedValue.interpolate({ inputRange: [0, 0.99, 0.995], outputRange: [0, 0, 1] }) }

Slide 100

Slide 100 text

After hiding source and destination image during transition

Slide 101

Slide 101 text

Handling back button

Slide 102

Slide 102 text

Animated.timing(AnimatedValue, { toValue: 1, duration: 3000, useNativeDriver: true }).start(); Animated.timing(AnimatedValue, { toValue: 0, duration: 3000, useNativeDriver: true }).start();

Slide 103

Slide 103 text

Handling Back button

Slide 104

Slide 104 text

@narendrashetty

Slide 105

Slide 105 text

@narendrashetty

Slide 106

Slide 106 text

https://expo.io/@narendrashetty/ photo-gallery

Slide 107

Slide 107 text

Narendra Shetty @narendra_shetty https://expo.io/@narendrashetty/photo-gallery