Slide 1

Slide 1 text

Frontend Engineer @ Upside • Google Code-In Winner • ACS @ UJ kawka.me • github.com/letelete • linkedin.com/in/brunokawka Animating the web Jak nie tracić głowy, kiedy nasze animacje traca klatki? Bruno Kawka ,

Slide 2

Slide 2 text

Should I even bother with animations?

Slide 3

Slide 3 text

Performance issues on low-end devices Motion sickness / Overstimulation / Eye fatigue Animations equals Accessibility problem Should I even bother with animations?

Slide 4

Slide 4 text

Should I even bother with animations? motherfuckingwebsite.com Should I even bother with animations?

Slide 5

Slide 5 text

Should I even bother with animations? Enhancing the UX Imitating the real world Should I even bother with animations?

Slide 6

Slide 6 text

Enhancing the UX Should I even bother with animations? 10 3860 Explain core app mechanics

Slide 7

Slide 7 text

Enhancing the UX Should I even bother with animations? 10 3860 Explain core app mechanics

Slide 8

Slide 8 text

Enhancing the UX Should I even bother with animations? 9 3860 Explain core app mechanics

Slide 9

Slide 9 text

Enhancing the UX Should I even bother with animations? 9 3861 Explain core app mechanics

Slide 10

Slide 10 text

Enhancing the UX Should I even bother with animations? 9 3861 Explain core app mechanics

Slide 11

Slide 11 text

Explain core app mechanics Should I even bother with animations? 3861 9 Enhancing the UX

Slide 12

Slide 12 text

Guide users over contextful navigation with Hero transitions Should I even bother with animations? • Communicate direction of fl ow • Make it clear which elements are related from page to page. • Combine them with data fetching for faster perception of performance. Enhancing the UX https://developer.chrome.com/docs/web-platform/view-transitions

Slide 13

Slide 13 text

Explain relationship between elements Should I even bother with animations? • Inform about the connection between two components Enhancing the UX https://m3.material.io/styles/motion/transitions/applying-transitions

Slide 14

Slide 14 text

Assure user about the state of the important event with Should I even bother with animations? Enhancing the UX https://tympanus.net/Development/ProgressButtonStyles

Slide 15

Slide 15 text

Describe upcoming changes - Skeletons Enhancing the UX Should I even bother with animations? • Hint at where content will appear once it's loaded • Hack perception of time - reduce perceived latency and stabilize layouts as content loads https://m3.material.io/styles/motion/transitions/transition-patterns

Slide 16

Slide 16 text

Should I even bother with animations? Enhancing the UX Imitating the real world Should I even bother with animations?

Slide 17

Slide 17 text

Transitions Should I even bother with animations? Transitions are fundamental to a great user experience because they help users understand how an app works. Well-designed transitions make an experience feel high quality and expressive. Google Material Design

Slide 18

Slide 18 text

Spring-physics animations Should I even bother with animations? https://react-spring-visualizer.com

Slide 19

Slide 19 text

Spring-physics animations Should I even bother with animations? https://react-spring-visualizer.com

Slide 20

Slide 20 text

Should I even bother with animations? The state of Web APIs

Slide 21

Slide 21 text

The state of Web APIs The state of Web APIs https://developer.chrome.com/docs/web-platform/view-transitions Hero transitions API (The View Transitions API)

Slide 22

Slide 22 text

View transition API The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

Slide 23

Slide 23 text

View transition API The state of Web APIs const spaNavigate = (data) => { document.startViewTransition(() => updateTheDOMSomehow(data)); } Capturing the state Captures current page (takes a screenshot)

Slide 24

Slide 24 text

View transition API The state of Web APIs const spaNavigate = (data) => { document.startViewTransition(() => updateTheDOMSomehow(data)); } Captures current page (takes a screenshot) Add elements / change class names / change styles / … Modifying the DOM

Slide 25

Slide 25 text

View transition API The state of Web APIs ::view-transition └─ ::view-transition-group(root) └─ ::view-transition-image-pair(root) ├─ ::view-transition-old(root) └─ ::view-transition-new(root) Generating pseudo-element tree

Slide 26

Slide 26 text

View transition API The state of Web APIs ::view-transition └─ ::view-transition-group(root) └─ ::view-transition-image-pair(root) ├─ ::view-transition-old(root) └─ ::view-transition-new(root) Generating pseudo-element tree Old captured state, and live representation of the new view

Slide 27

Slide 27 text

View transition API The state of Web APIs ::view-transition └─ ::view-transition-group(root) └─ ::view-transition-image-pair(root) ├─ ::view-transition-old(root) └─ ::view-transition-new(root) Customizing the transition with CSS selectors ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 5s; }

Slide 28

Slide 28 text

View transition API The state of Web APIs Identifying elements across the transition .image { view-transition-name: hero-image; } ├ │ │ │ └─ ::view-transition-group(hero-image) └─ ::view-transition-image-pair(hero-image) ├─ ::view-transition-old(hero-image) └─ ::view-transition-new(hero-image) ::view-transition └─ ::view-transition-group(root) └─ ::view-transition-image-pair(root) ├─ ::view-transition-old(root) └─ ::view-transition-new(root)

Slide 29

Slide 29 text

View transition API The state of Web APIs Hero! Hero! Hero! .image { view-transition-name: hero-image; } ├ │ │ │ └─ ::view-transition-group(hero-image) └─ ::view-transition-image-pair(hero-image) ├─ ::view-transition-old(hero-image) └─ ::view-transition-new(hero-image) ::view-transition └─ ::view-transition-group(root) └─ ::view-transition-image-pair(root) ├─ ::view-transition-old(root) └─ ::view-transition-new(root)

Slide 30

Slide 30 text

View transition API The state of Web APIs Hero! Hero! Hero! .image { view-transition-name: hero-image; } ├ │ │ │ └─ ::view-transition-group(hero-image) └─ ::view-transition-image-pair(hero-image) ├─ ::view-transition-old(hero-image) └─ ::view-transition-new(hero-image) ::view-transition └─ ::view-transition-group(root) └─ ::view-transition-image-pair(root) ├─ ::view-transition-old(root) └─ ::view-transition-new(root) image.onclick = async () => { image.style.viewTransitionName = 'hero-image'; document.startViewTransition(() => { image.style.viewTransitionName = ''; }); };

Slide 31

Slide 31 text

View transition API The state of Web APIs Resources https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API https://developer.chrome.com/docs/web-platform/view-transitions

Slide 32

Slide 32 text

Intersection Observer API The state of Web APIs Observe changes within an intersection of two elements • Lazy-load content as page is scrolled / in fi nite scrolling • Deciding whether to perform tasks or animation processes based on the user perception

Slide 33

Slide 33 text

Intersection Observer API The state of Web APIs const observer = new IntersectionObserver((entries) => { Initialize IntersectionObserver });

Slide 34

Slide 34 text

Intersection Observer API The state of Web APIs Viewport Observed const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }); const observer = new IntersectionObserver((entries) => { });

Slide 35

Slide 35 text

Intersection Observer API The state of Web APIs Viewport Observe const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }); const observer = new IntersectionObserver((entries) => { }); observer.observe(document.querySelector('.Observed'));

Slide 36

Slide 36 text

Intersection Observer API The state of Web APIs const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }); Observed Viewport observer.observe(document.querySelector('.Observed')); });

Slide 37

Slide 37 text

Intersection Observer API The state of Web APIs const con fi g = { threshold: 0.75, }; const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }, con fi g); observer.observe(document.querySelector('.Observed')); Viewport Observed

Slide 38

Slide 38 text

Intersection Observer API The state of Web APIs const con fi g = { threshold: 0.75, }; const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }, con fi g); observer.observe(document.querySelector('.Observed')); Observed Viewport 75%

Slide 39

Slide 39 text

Intersection Observer API The state of Web APIs const con fi g = { rootMargin: “-100px”, }; const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }, con fi g); observer.observe(document.querySelector('.Observed')); Viewport 100px 100px Observed

Slide 40

Slide 40 text

Intersection Observer API The state of Web APIs const con fi g = { rootMargin: “-100px”, }; const observer = new IntersectionObserver((entries) => { entries.forEach(({ target, isIntersecting }) => { target.style.backgroundColor = isIntersecting ? 'yellow' : 'transparent'; }); }, con fi g); observer.observe(document.querySelector('.Observed')); Observed Viewport 100px

Slide 41

Slide 41 text

Sometimes, all you need is the linear interpolation The state of Web APIs

Slide 42

Slide 42 text

Linear interpolation: Clamp The state of Web APIs Given min and max, return the closest value within the bounds clamp(24, 20, 30) // 24 clamp(12, 20, 30) // 20 clamp(32, 20, 30) // 30 const clamp = (val, min, max) => Math.min(Math.max(val, min), max);

Slide 43

Slide 43 text

Linear interpolation: Lerp The state of Web APIs Value between two numbers at a specified, decimal midpoint lerp(20, 80, 0); // 20 lerp(20, 80, 1); // 80 lerp(20, 80, 0.5); // 50 const lerp = (min, max, val) => min * (1 - val) + max * val;

Slide 44

Slide 44 text

Linear interpolation: Inv. Lerp The state of Web APIs Inversed Lerp - Decimal midpoint specified between two invlerp(50, 100, 75); // 0.5 invlerp(50, 100, 25); // 0 invlerp(50, 100, 125); // 1 const invlerp = (min, max, val) => clamp((val - min) / (max - min), 0, 1);

Slide 45

Slide 45 text

Linear interpolation: Range The state of Web APIs Map one data range into another range(10, 100, 2000, 20000, 50) // 10000 const range = (min1, max1, min2, max2, val) => { return lerp(min2, max2, invlerp(min1, max1, val)); }

Slide 46

Slide 46 text

Animatable CSS properties The state of Web APIs A CSS property is animatable if its value can be made to change over a given amount of time. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties

Slide 47

Slide 47 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties -moz-outline-radius -moz-outline-radius-bottomleft -moz-outline-radius-bottomright -moz-outline-radius-topleft -moz-outline-radius-topright -ms-grid-columns -ms-grid-rows -webkit-line-clamp -webkit-text-fill-color -webkit-text-stroke -webkit-text-stroke-color accent-color all backdrop-filter background background-color background-position background-size

Slide 48

Slide 48 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties block-size border border-block-end border-block-end-color border-block-end-width border-block-start border-block-start-color border-block-start-width border-bottom border-bottom-color border-bottom-left-radius border-bottom-right-radius border-bottom-width border-color border-end-end-radius border-end-start-radius border-image-outset border-image-slice

Slide 49

Slide 49 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties border-image-width border-inline-end border-inline-end-color border-inline-end-width border-inline-start border-inline-start-color border-inline-start-width border-left border-left-color border-left-width border-radius border-right border-right-color border-right-width border-start-end-radius border-start-start-radius border-top border-top-color

Slide 50

Slide 50 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties border-top-left-radius border-top-right-radius border-top-width border-width bottom box-shadow caret caret-color caret-shape clip clip-path color column-count column-gap column-rule column-rule-color column-rule-width column-width

Slide 51

Slide 51 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties columns contain-intrinsic-block-size contain-intrinsic-height contain-intrinsic-inline-size contain-intrinsic-size contain-intrinsic-width filter flex flex-basis flex-grow flex-shrink font font-size font-size-adjust font-stretch font-variation-settings font-weight gap

Slide 52

Slide 52 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties grid-column-gap grid-gap grid-row-gap grid-template-columns grid-template-rows height inline-size input-security inset inset-block inset-block-end inset-block-start inset-inline inset-inline-end inset-inline-start left letter-spacing line-clamp

Slide 53

Slide 53 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties line-height margin margin-block margin-block-end margin-block-start margin-bottom margin-inline margin-inline-end margin-inline-start margin-left margin-right margin-top mask mask-border mask-position mask-size max-block-size max-height

Slide 54

Slide 54 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties max-inline-size max-lines max-width min-block-size min-height min-inline-size min-width object-position offset offset-anchor offset-distance offset-path offset-position offset-rotate opacity order outline outline-color

Slide 55

Slide 55 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties outline-offset outline-width padding padding-block padding-block-end padding-block-start padding-bottom padding-inline padding-inline-end padding-inline-start padding-left padding-right padding-top perspective perspective-origin right rotate row-gap

Slide 56

Slide 56 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties scale scroll-margin scroll-margin-block scroll-margin-block-end scroll-margin-block-start scroll-margin-bottom scroll-margin-inline scroll-margin-inline-end scroll-margin-inline-start scroll-margin-left scroll-margin-right scroll-margin-top scroll-padding scroll-padding-block scroll-padding-block-end scroll-padding-block-start scroll-padding-bottom scroll-padding-inline

Slide 57

Slide 57 text

A CSS property is animatable if its value can be made to change over a given amount of time. Animatable CSS properties The state of Web APIs https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties scroll-padding-inline-end scroll-padding-inline-start scroll-padding-left scroll-padding-right scroll-padding-top scroll-snap-coordinate scroll-snap-destination scroll-timeline scrollbar-color shape-image-threshold shape-margin shape-outside tab-size text-decoration text-decoration-color text-decoration-thickness text-emphasis text-emphasis-color

Slide 58

Slide 58 text

Animatable CSS properties The state of Web APIs text-indent text-shadow text-underline-offset top transform transform-origin translate vertical-align visibility width word-spacing z-index zoom A CSS property is animatable if its value can be made to change over a given amount of time. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties

Slide 59

Slide 59 text

.class { transition: ; will-change: ; ⚠ } Animatable CSS properties The state of Web APIs Just use transition.

Slide 60

Slide 60 text

.class { transition: ; will-change: ; ⚠ } Animatable CSS properties The state of Web APIs Just use transition. Unless… justify-content 😰

Slide 61

Slide 61 text

t Animatable CSS properties Unless… justify-content 😰 https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content Just use transition. Animation discrete

Slide 62

Slide 62 text

Animating layout changes The state of Web APIs Problem: Recalculating layout positions in every frame 60 fps = 60 recalculations per second 60 fps = 16.7 milliseconds to recalculate styles, layout, and paint (1000ms / 60fps ~= 16.7)

Slide 63

Slide 63 text

The rendering waterfall Recalculate Layou Paint The state of Web APIs

Slide 64

Slide 64 text

The rendering waterfall Recalculate Layou Paint Takes DOM Tree, and CSS styles Tree Creates Render Tree The state of Web APIs

Slide 65

Slide 65 text

The rendering waterfall Recalculate Layou Paint Revalidates parts of render tree. Calculates node dimensions. A significant cost to performance. The state of Web APIs

Slide 66

Slide 66 text

The rendering waterfall The state of Web APIs Recalculate Layou Pain Triggers whenever something changed its visibility or appearance.

Slide 67

Slide 67 text

The rendering waterfall The state of Web APIs Recalculate Layou Paint .class { transition: width ; }

Slide 68

Slide 68 text

The rendering waterfall The state of Web APIs Recalculate Layou Paint .class { transition: width ; }

Slide 69

Slide 69 text

The rendering waterfall The state of Web APIs Recalculate Layou Paint .class { transition: color ; }

Slide 70

Slide 70 text

The rendering waterfall The state of Web APIs Recalculate Layou Paint .class { transition: color ; }

Slide 71

Slide 71 text

The rendering waterfall The state of Web APIs Recalculate Layou Paint .class { transition: transform ; }

Slide 72

Slide 72 text

The rendering waterfall The state of Web APIs Recalculate Layou Paint .class { transition: transform ; }

Slide 73

Slide 73 text

Cheap animatable properties The state of Web APIs transform opacity scale Recalculate Layou Paint No repaint triggered. Their updates are handled in the composition.

Slide 74

Slide 74 text

Animating layout changes The state of Web APIs Problem: Recalculating layout positions in every frame 60 fps = 60 recalculations per second 60 fps = 16.7 milliseconds to recalculate styles, layout, and paint

Slide 75

Slide 75 text

Animating layout changes The state of Web APIs Problem: Recalculating layout positions in every frame 60 fps = 60 recalculations per second 60 fps = 16.7 milliseconds to recalculate styles, layout, and paint Solution: Use properties that are rendered in their own layer Don’t trigger repaint, and layout recalculation

Slide 76

Slide 76 text

The state of Web APIs Animating layout changes Do expensive work cheaply using the FLIP technique (First Last Invert Play)

Slide 77

Slide 77 text

x: 434 y: 262.5 The state of Web APIs How FLIP works? First - the initial state of the element involved in the transition .container { display: fl ex; gap: 1rem; }

Slide 78

Slide 78 text

The state of Web APIs How FLIP works? First - the initial state of the element involved in the transition x1: 216 y1: 0 .container { display: fl ex; gap: 1rem; }

Slide 79

Slide 79 text

The state of Web APIs How FLIP works? Last - the final state of the element x1: 216 y1: 0 x2: 0 y2: 216 } fl ex-direction: column; .container { display: fl ex; gap: 1rem;

Slide 80

Slide 80 text

Inverse - transform element to its initial (First) position using cheap CSS animatable properties The state of Web APIs How FLIP works? } fl ex-direction: column; .container { display: fl ex; gap: 1rem; transform: translateX x1: 216 y1: 0 x2: 0 y2: 216 (x1 - x2, y1 - y2)

Slide 81

Slide 81 text

Inverse - transform element to its initial (First) position using cheap CSS animatable properties The state of Web APIs How FLIP works? x1: 216 y1: 0 x2: 0 y2: 216 } fl ex-direction: column; .container { display: fl ex; gap: 1rem; transform: translateX (216, -216)

Slide 82

Slide 82 text

Play - switch on transitions for changed properties and remove the inversion changes The state of Web APIs How FLIP works? } x1: 216 y1: 0 x2: 0 y2: 216 transition: transform 0.5s ease; fl ex-direction: column; .container { display: fl ex; gap: 1rem; transform: translateX (216, -216)

Slide 83

Slide 83 text

Play - switch on transitions for changed properties and remove the inversion changes The state of Web APIs How FLIP works? x1: 216 y1: 0 x2: 0 y2: 216 } transition: transform 0.5s ease; fl ex-direction: column; .container { display: fl ex; gap: 1rem;

Slide 84

Slide 84 text

Use scale to perform analogical transformations. Compare First and Last with centered transform origins The state of Web APIs FLIP vs geometry

Slide 85

Slide 85 text

Use scale to perform analogical transformations. Compare First and Last with centered transform origins The state of Web APIs FLIP vs geometry

Slide 86

Slide 86 text

Use scale to perform analogical transformations. Compare First and Last with centered transform origins The state of Web APIs FLIP vs geometry

Slide 87

Slide 87 text

When scaling, children will enlarge due to parent transformation. To avoid it, on every frame, scale children relatively to the parent The state of Web APIs FLIP: Beware of child distortions Child Child

Slide 88

Slide 88 text

When scaling, children will enlarge due to parent transformation. To avoid it, on every frame, scale children relatively to the parent The state of Web APIs FLIP: Beware of child distortions Child Child Children scales down relatively to the parent scaling up childScale = 1 / parentScale

Slide 89

Slide 89 text

When scaling, children will enlarge due to parent transformation. To avoid it, on every frame, scale children relatively to the parent The state of Web APIs FLIP: Beware of child distortions Child Child Children scales down relatively to the parent scaling up 48pt 48pt childScale = 1 / parentScale

Slide 90

Slide 90 text

You probably need libraries. The state of Web APIs Framer-motion GSAP Spring Lottie Rive Three.js (WebGL) HeadlessUI (Tailwind)

Slide 91

Slide 91 text

Framer-motion, GSAP, … The state of Web APIs You’ll generally use one of these. Great for micro-interactions, orchestration, transitions, timeline-based animations

Slide 92

Slide 92 text

Lottie, Rive The state of Web APIs After Effects (or Rive) animations in real time, controlled with code

Slide 93

Slide 93 text

Lottie, Rive The state of Web APIs After Effects (or Rive) animations in real time, controlled with code

Slide 94

Slide 94 text

Lottie, Rive The state of Web APIs After Effects animations in real time

Slide 95

Slide 95 text

Three.js (WebGL) The state of Web APIs

Slide 96

Slide 96 text

Three.js (WebGL) The state of Web APIs

Slide 97

Slide 97 text

Three.js (WebGL) The state of Web APIs

Slide 98

Slide 98 text

The state of Web APIs Output Reference export const Button = ({ onClick }) => { return ( {'play'} ); }; Three.js (WebGL)

Slide 99

Slide 99 text

The state of Web APIs Output Reference export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(true); const { transform } = useMemo( () => ({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }), [isHovering] ); const handleMouseEnter = (e) => { e.preventDefault(); setIsHovering(true); }; const handleMouseLeave = (e) => { e.preventDefault(); setIsHovering(false); }; return (
{‘play'}
// shadows ); }; Three.js (WebGL)

Slide 100

Slide 100 text

The state of Web APIs Output Reference import { useSpring, animated } from ‘@react-spring/web'; export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(true); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)', }); const handleMouseEnter = (e) => { e.preventDefault(); setIsHovering(true); }; const handleMouseLeave = (e) => { e.preventDefault(); setIsHovering(false); }; return ( {‘play'} // shadows ); }; Three.js (WebGL)

Slide 101

Slide 101 text

The state of Web APIs Output Reference import { useSpring, animated } from ‘@react-spring/web'; export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(true); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)', }); const handleMouseEnter = (e) => { e.preventDefault(); setIsHovering(true); }; const handleMouseLeave = (e) => { e.preventDefault(); setIsHovering(false); }; return ( {‘play'} // shadows ); }; Three.js: The scene (where you arrange objects) ButtonCanvas

Slide 102

Slide 102 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The scene (where you arrange objects)

Slide 103

Slide 103 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The meshes (objects present in your scene) Sphere

Slide 104

Slide 104 text

The state of Web APIs Output Reference const states = { collapsed: { position: [-0.5, -0.8, -1], opacity: 0, }, expanded: { position: [-1, -1.25, 1], opacity: 1, }, }; Three.js: The meshes (objects present in your scene)

Slide 105

Slide 105 text

The state of Web APIs Output Reference const states = { collapsed: { position: [-0.5, -0.8, -1], opacity: 0, }, expanded: { position: [-1, -1.25, 1], opacity: 1, }, }; Three.js: The meshes (objects present in your scene) export const Sphere = () => { const { isHovering } = useContext(ButtonContext); };

Slide 106

Slide 106 text

The state of Web APIs Output Reference const states = { collapsed: { position: [-0.5, -0.8, -1], opacity: 0, }, expanded: { position: [-1, -1.25, 1], opacity: 1, }, }; Three.js: The meshes (objects present in your scene) export const Sphere = () => { const { isHovering } = useContext(ButtonContext); }; const { position } = useSpring( isHovering ? states.expanded : states.collapsed );

Slide 107

Slide 107 text

The state of Web APIs Output Reference const states = { collapsed: { position: [-0.5, -0.8, -1], opacity: 0, }, expanded: { position: [-1, -1.25, 1], opacity: 1, }, }; Three.js: The meshes (objects present in your scene) export const Sphere = () => { const { isHovering } = useContext(ButtonContext); }; return ( ); const { position } = useSpring( isHovering ? states.expanded : states.collapsed );

Slide 108

Slide 108 text

The state of Web APIs Output Reference const states = { collapsed: { position: [-0.5, -0.8, -1], opacity: 0, }, expanded: { position: [-1, -1.25, 1], opacity: 1, }, }; export const Sphere = () => { const { isHovering } = useContext(ButtonContext); const { position, opacity } = useSpring( isHovering ? states.expanded : states.collapsed ); return ( ); }; Three.js: The materials (styles definitions for your meshes)

Slide 109

Slide 109 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 110

Slide 110 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 111

Slide 111 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 112

Slide 112 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 113

Slide 113 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 114

Slide 114 text

The state of Web APIs Output Reference Three.js: The lights (control how your materials act) import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( );

Slide 115

Slide 115 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 116

Slide 116 text

The state of Web APIs Output Reference import { Canvas } from ‘@react-three/ fi ber'; export const ButtonCanvas = () => ( ); Three.js: The lights (control how your materials act)

Slide 117

Slide 117 text

Should I even bother with animations? The state of Web APIs Optimizing the animation

Slide 118

Slide 118 text

Will-change may be a pure evil Optimizing the animation .class { transition: ; will-change: ; ⚠ }

Slide 119

Slide 119 text

Will-change may be a pure evil Optimizing the animation .class { transition: ; will-change: ; ⚠ } Creates composition layer

Slide 120

Slide 120 text

Don’ts: Premature Optimization Optimizing the animation “The real problem is that programmers have spent far too much time worrying about ef fi ciency in the wrong places and at the wrong times; premature optimization is the root of all evil in programming.” Donald Knuth

Slide 121

Slide 121 text

Framework-wise optimization Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; return ( ... ... ); };

Slide 122

Slide 122 text

Framework-wise optimization Optimizing the animation const states = { ... }; export const Sphere = () => { const { isHovering } = useContext(ButtonContext); const { position, opacity } = useSpring(isHovering ? states.expanded : states.collapsed); return ( ); };

Slide 123

Slide 123 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const { position, opacity } = useSpring(isHovering ? states.expanded : states.collapsed); return ( ); };

Slide 124

Slide 124 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const { position, opacity } = useSpring(isHovering ? states.expanded : states.collapsed); return ( ); };

Slide 125

Slide 125 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const { position, opacity } = useSpring(isHovering ? states.expanded : states.collapsed); return ( ); };

Slide 126

Slide 126 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const springApi = useSpringRef(); const { position, opacity } = useSpring({ ref: springApi, from: states.collapsed, }); useEffect(() => { springApi.start({ to: isHovering ? states.expanded : states.collapsed }); }, [isHovering, springApi]); return ( ); };

Slide 127

Slide 127 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const springApi = useSpringRef(); const { position, opacity } = useSpring({ ref: springApi, from: states.collapsed, }); useEffect(() => { springApi.start({ to: isHovering ? states.expanded : states.collapsed }); }, [isHovering, springApi]); return ( ); };

Slide 128

Slide 128 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const springApi = useSpringRef(); const { position, opacity } = useSpring({ ref: springApi, from: states.collapsed, }); useEffect(() => { springApi.start({ to: isHovering ? states.expanded : states.collapsed }); }, [isHovering, springApi]); return ( ); };

Slide 129

Slide 129 text

Framework-wise optimization Optimizing the animation const states = { ... }; let rerenders = 0; export const Sphere = () => { console.log(rerenders++); const { isHovering } = useContext(ButtonContext); const springApi = useSpringRef(); const { position, opacity } = useSpring({ ref: springApi, from: states.collapsed, }); useEffect(() => { springApi.start({ to: isHovering ? states.expanded : states.collapsed }); }, [isHovering, springApi]); return ( ); };

Slide 130

Slide 130 text

Framework-wise optimization Optimizing the animation export const Sphere = () => { console.log(rerenders++); const { subscribeHoverChange } = useContext(ButtonContext); const springApi = useSpringRef(); const { position, opacity } = useSpring({ ref: springApi, from: states.collapsed, }); useEffect(() => { const handleOnHover = (isHovering) => { springApi.start({ to: isHovering ? states.expanded : states.collapsed }); }; subscribeHoverChange(handleOnHover); }, [subscribeHoverChange, springApi]); return ( ); };

Slide 131

Slide 131 text

Framework-wise optimization Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; return ( ... ... ); };

Slide 132

Slide 132 text

Framework-wise optimization Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = (callback) => { subscribers.current.push(callback); }; return ( ... ... ); };

Slide 133

Slide 133 text

Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = (callback) => { subscribers.current.push(callback); }; return ( ... ... ); }; Framework-wise optimization

Slide 134

Slide 134 text

Framework-wise optimization Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = (callback) => { subscribers.current.push(callback); }; return ( ... ... );

Slide 135

Slide 135 text

Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = (callback) => { subscribers.current.push(callback); }; return ( ... ... ); Framework-wise optimization

Slide 136

Slide 136 text

Optimizing the animation export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = useCallback((callback) => { subscribers.current.push(callback); }, []); return ( ... ... ); Framework-wise optimization

Slide 137

Slide 137 text

Optimizing the animation const MemoizedButtonCanvas = memo(ButtonCanvas); export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = useCallback((callback) => { subscribers.current.push(callback); }, []); return ( ... ... ); }; Framework-wise optimization

Slide 138

Slide 138 text

Optimizing the animation const MemoizedButtonCanvas = memo(ButtonCanvas); export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = useCallback((callback) => { subscribers.current.push(callback); }, []); return ( ... ... ); }; Framework-wise optimization

Slide 139

Slide 139 text

Optimizing the animation const MemoizedButtonCanvas = memo(ButtonCanvas); export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = useCallback((callback) => { subscribers.current.push(callback); }, []); const props = useMemo(() => ({ subscribeHoverChange }), [subscribeHoverChange]); return ( ... ... ); Framework-wise optimization

Slide 140

Slide 140 text

Optimizing the animation const MemoizedButtonCanvas = memo(ButtonCanvas); export const Button = ({ onClick }) => { const [isHovering, setIsHovering] = useState(false); const subscribers = useRef([]); const notifySubscribers = useCallback( () => subscribers.current.forEach((fn) => fn(isHovering)), [isHovering] ); useEffect(() => notifySubscribers(), [notifySubscribers]); const { transform } = useSpring({ transform: isHovering ? 'scale(1.0)' : 'scale(0.8)' }); const handleMouseEnter = (...) => { ...; setIsHovering(true); }; const handleMouseLeave = (...) => { ...; setIsHovering(false); }; const subscribeHoverChange = useCallback((callback) => { subscribers.current.push(callback); }, []); const props = useMemo(() => ({ subscribeHoverChange }), [subscribeHoverChange]); return ( ... ... ); Framework-wise optimization

Slide 141

Slide 141 text

Should I even bother with animations? The state of Web APIs Optimizing the animation Accessible animations

Slide 142

Slide 142 text

Let users decide Accessible animations All motions enabled? Perfect. Only gentle motions? No problem. All motions disabled? Sure. Give users more control over your product

Slide 143

Slide 143 text

Let users decide Accessible animations Give users more control over your product

Slide 144

Slide 144 text

Let users decide Accessible animations Give users more control over your product

Slide 145

Slide 145 text

Prefers reduced motion Accessible animations @media (prefers-reduced-motion) { /* styles to apply if the user's settings are set to reduced motion */ } Has user requested the system to disable non-essential

Slide 146

Slide 146 text

Prefers reduced motion Accessible animations @media (prefers-reduced-motion) { /* styles to apply if the user's settings are set to reduced motion */ } Has user requested the system to disable non-essential

Slide 147

Slide 147 text

GIF? I want to see the frame. Accessible animations Nyan Cat Replace GIF with static image on prefers-reduced-motion

Slide 148

Slide 148 text

Accessible animations Nyan Cat Replace GIF with static image on prefers-reduced-motion

Slide 149

Slide 149 text

Don’ts: Overwriting native behaviors Accessible animations - Keep in mind platform-specific guidelines - Keep in mind Accessibility (eg. screen readers) - Don’t overwrite scroll!

Slide 150

Slide 150 text

Frontend Engineer @ Upside • Google Code-In Winner • ACS @ UJ kawka.me • github.com/letelete • linkedin.com/in/brunokawka Thank you! Bruno Kawka