The Future of Web Animation

A presentation at An Event Apart Denver in October 2019 in Denver, CO, USA by Sarah Drasner

Slide 1

Slide 1

Animation on the bleeding edge

Slide 2

Slide 2

Animation on the bleeding edge

Slide 3

Slide 3

@sarah_edo

Slide 4

Slide 4

Communicating @sarah_edo

Slide 5

Slide 5

? @sarah_edo

Slide 6

Slide 6

Happiness sadness Fear curiosity @sarah_edo

Slide 7

Slide 7

Happiness sadness Fear curiosity @sarah_edo

Slide 8

Slide 8

@sarah_edo

Slide 9

Slide 9

@sarah_edo

Slide 10

Slide 10

Anything can happen

Slide 11

Slide 11

SARAH DRASNER @SARAH_EDO

Slide 12

Slide 12

page transitions

Slide 13

Slide 13

@sarah_edo

Slide 14

Slide 14

Vue Single File Components <template> <div></div> </template> <script> export default {} </script> <style scoped> </style> @sarah_edo

Slide 15

Slide 15

<nuxt-link to=”/product”>Product</nuxt-link> Livecode Nuxt Resource @sarah_edo

Slide 16

Slide 16

@sarah_edo

Slide 17

Slide 17

Transition hook already available name=“page” .page-enter-active, .page-leave-active { transition: all .25s ease-out; } .page-enter, .page-leave-active { opacity: 0; transform: scale(0.95); transform-origin: 50% 50%; } @sarah_edo

Slide 18

Slide 18

@sarah_edo

Slide 19

Slide 19

Js hooks export default { transition: { mode: ‘out-in’, css: false, enter (el, done) { let tl = new TimelineMax({ onComplete: done }), spt = new SplitText(‘h1’, { type: ‘chars’ }), chars = spt.chars; TweenMax.set(chars, { transformPerspective: 600, perspective: 300, transformStyle: ‘preserve-3d’ }) } tl.add(‘start’) tl.from(el, 0.8, { scale: 0.9, transformOrigin: ‘50% 50%’, ease: Sine.easeOut }, ‘start’) … tl.timeScale(1.5) @sarah_edo

Slide 20

Slide 20

@sarah_edo

Slide 21

Slide 21

@sarah_edo

Slide 22

Slide 22

Repo https://github.com/sdras/nuxt-type @sarah_edo

Slide 23

Slide 23

Native-like page transitions on the web

Slide 24

Slide 24

@sarah_edo

Slide 25

Slide 25

Cognitive Load Kathy Sierra: Badass, Making Users Awesome @sarah_edo

Slide 26

Slide 26

@sarah_edo

Slide 27

Slide 27

@sarah_edo

Slide 28

Slide 28

@sarah_edo

Slide 29

Slide 29

In store/index.js import Vuex from ‘vuex’ const createStore = () =>” { return new Vuex.Store({ state: { page: ‘index’ }, mutations: { updatePage(state, pageName) { state.page = pageName } } }) } export default createStore @sarah_edo

Slide 30

Slide 30

In middleware/pages.js: export default function(context) { // go tell the store to update the page context.store.commit(‘updatePage’, context.route.name) } register the middleware in nuxt.config.js: module.exports = { … router: { middleware: ‘pages’ } } @sarah_edo

Slide 31

Slide 31

In layouts/default.vue: <template> <div> <app-navigation /> <nuxt/> </div> </template> In AppNavTransition.vue: <h2 key=”profile-name” class=”profile-name”> <span v-if=”page ===$$ ‘group’” class=”user-trip”>{{ selectedUser.trip }}</span> <span v-else>{{ selectedUser.name }}</span> </h2> @sarah_edo

Slide 32

Slide 32

Small codepen poc @sarah_edo

Slide 33

Slide 33

<transition-group /> Flip under the hood Rosario’s article @sarah_edo

Slide 34

Slide 34

Transition Group wraps it… <transition-group name=”layout” tag=“nav”> <p>I am content!</p> </transition-group> Styles: .layout-move { transition: all 0.4s ease; } @sarah_edo

Slide 35

Slide 35

.profile { .follow { transform: translate3d(-215px, -80px, 0); } .profile-photo { transform: translate3d(-20px, -100px, 0) scale(0.75); } .profile-name { transform: translate3d(140px, -125px, 0) scale(0.75); color: white; } .side-icon { transform: translate3d(0, -40px, 0); background: rgba(255, 255, 255, 0.9); } .calendar { opacity: 1; } } @sarah_edo

Slide 36

Slide 36

@sarah_edo

Slide 37

Slide 37

My Demo vue/nuxt page-transitions.com github.com/sdras/page-transitions-travelapp Simona Cotin fork angular/typescript github.com/simonaco/page-transitions-travelapp Shawn wang (swyx) fork react/next github.com/sw-yx/page-transitions-react-travelapp @sarah_edo

Slide 38

Slide 38

Animate CSS Grid github.com/aholachek/animate-css-grid/ @sarah_edo

Slide 39

Slide 39

Next-level responsive

Slide 40

Slide 40

@sarah_edo

Slide 41

Slide 41

@sarah_edo

Slide 42

Slide 42

@sarah_edo

Slide 43

Slide 43

@joshcarpenter @sarah_edo

Slide 44

Slide 44

@joshcarpenter @sarah_edo

Slide 45

Slide 45

@joshcarpenter @sarah_edo

Slide 46

Slide 46

@joshcarpenter @sarah_edo

Slide 47

Slide 47

Index page design inspired from this dribbble shot @sarah_edo

Slide 48

Slide 48

methods: { startDrag() { this.dragging = true this.x = this.y = 0 }, stopDrag() { this.dragging = false }, coords(e) { if (this.page ===$$ ‘vrview’ && this.dragging) { this.x = (e.clientX || e.touches[0].clientX) / 60 + 4 this.y = (e.clientY || e.touches[0].clientY) / 100 + 5 } }, … } @sarah_edo

Slide 49

Slide 49

<div v-if=”page ===$$ ‘vrview’” :style=”{ perspectiveOrigin: `${x}% ${y}%`, transform: `rotateY(${x * 4}deg)` }”> <app-nav /> </div> <app-nav v-else/> <nuxt /> <div class=”movearound” v-if=”page ===$$ ‘indview’ || page ===$$ ‘vrview’” :style=”{ perspectiveOrigin: `${x/2}% ${y/2}%`, transform: `rotateY(${x*4}deg) translate3d(0, 0, 0)` }”> <app-bottommain :x=”x” :y=”y” /> </div> @sarah_edo

Slide 50

Slide 50

this.scene = new THREE.Scene() var geometry = new THREE.SphereBufferGeometry(500, 60, 40) // invert the geometry on the x-axis so that all of the faces point inward geometry.scale(-1, 1, 1) var material = new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load(imgsrc) }) mesh = new THREE.Mesh(geometry, material) this.scene.add(mesh) @sarah_edo

Slide 51

Slide 51

update() { lat = Math.max(-85, Math.min(85, lat)) phi = THREE.Math.degToRad(90 - lat) theta = THREE.Math.degToRad(lon) camera.target.x = 500 * Math.sin(phi) * Math.cos(theta) camera.target.y = 500 * Math.cos(phi) camera.target.z = 500 * Math.sin(phi) * Math.sin(theta) camera.lookAt(camera.target) renderer.render(scene, camera) } @sarah_edo

Slide 52

Slide 52

div { width: 500px; height: 500px; } border-radius: 50%; } @media vr { div { material: grass; } } @sarah_edo

Slide 53

Slide 53

sphere { material: envmap(0.9) reflectivity(0.5); } threejs.org/examples @sarah_edo

Slide 54

Slide 54

@joshcarpenter @sarah_edo

Slide 55

Slide 55

@sarah_edo

Slide 56

Slide 56

@sarah_edo

Slide 57

Slide 57

https://hotel-chooser-aframe.glitch.me/ https://aframe.io/examples/showcase/360-image-gallery/ @sarah_edo

Slide 58

Slide 58

<!— 360-degree image. —>& <a-sky id=”image-360” radius=”10” src=”#hotel1”></a-sky> <!— Image links. —>& <a-entity id=”links” layout=”type: line; margin: 1.5” position=”-2 0 -3”> <a-entity template=”src: #link” data-src=”#hotel2” data-thumb=”#king”></a-entity> <a-entity template=”src: #link” data-src=”#hotel1” data-thumb=”#patagonia”></a-entity> <a-entity template=”src: #link” data-src=”#hotel3” data-thumb=”#empire”></a-entity> <a-entity template=”src: #link” data-src=”#hotel4” data-thumb=”#cabin”></a-entity> </a-entity> <a-plane position=”0.2 0 -4” width=”8” height=”2.5” color=”#FFF” material=”transparent: true; opacity: 0.2” shadow></a-plane>

@sarah_edo

Slide 59

Slide 59

<!— Camera + cursor. —>& <a-entity camera look-controls> <a-cursor id=”cursor” animation__click=”property:scale;startEvents:click;from:0.1 0.1 0.1;to:1 1 1;dur:150” animation__fusing=”property:fusing;startEvents:fusing;from:1 1 1; to:0.1 0.1 0.1;dur:1500” event-set__1=”_event: mouseenter; color: red” event-set__2=”_event: mouseleave; color: black” fuse=”true” raycaster=”objects: .link”></a-cursor> </a-entity>

@sarah_edo

Slide 60

Slide 60

supermedium.com @sarah_edo

Slide 61

Slide 61

supermedium.com/supercraft @sarah_edo

Slide 62

Slide 62

ARKit 2.0 @OsFalmer @sarah_edo

Slide 63

Slide 63

@sarah_edo

Slide 64

Slide 64

@sarah_edo

Slide 65

Slide 65

Daniel @danielkuntz0 @sarah_edo

Slide 66

Slide 66

AR Expedition @sarah_edo

Slide 67

Slide 67

static 2d is an abstraction

Slide 68

Slide 68

Minh Pham on Dribbble @sarah_edo

Slide 69

Slide 69

@sarah_edo

Slide 70

Slide 70

@sarah_edo

Slide 71

Slide 71

How Virtual Aditi Reality Benefits Seniors Khazanchi @sarah_edo

Slide 72

Slide 72

seaheroquest.com @sarah_edo

Slide 73

Slide 73

Angie Wang @okchickadee @sarah_edo

Slide 74

Slide 74

The Art of Journey @sarah_edo

Slide 75

Slide 75

@AysSomething @sarah_edo

Slide 76

Slide 76

@sarah_edo

Slide 77

Slide 77

@sarah_edo

Slide 78

Slide 78

@sarah_edo

Slide 79

Slide 79

https://medium.com/inborn-experience/easy-way-to-make-360-vr-designs-c00ee2910b22 Volodymyr Kurbatov @sarah_edo

Slide 80

Slide 80

@sarah_edo

Slide 81

Slide 81

@sarah_edo

Slide 82

Slide 82

@sarah_edo

Slide 83

Slide 83

@sarah_edo

Slide 84

Slide 84

@sarah_edo

Slide 85

Slide 85

@sarah_edo

Slide 86

Slide 86

@sarah_edo

Slide 87

Slide 87

Hands-free experiences

Slide 88

Slide 88

Slide 89

Slide 89

@sarah_edo

Slide 90

Slide 90

@sarah_edo

Slide 91

Slide 91

@sarah_edo

Slide 92

Slide 92

@sarah_edo

Slide 93

Slide 93

@sarah_edo

Slide 94

Slide 94

SLOW DOWN @sarah_edo

Slide 95

Slide 95

BIOFEEDBACK

Slide 96

Slide 96

@sarah_edo

Slide 97

Slide 97

@sarah_edo

Slide 98

Slide 98

In store.js (Vuex) export default new Vuex.Store({ state: { counter: 0, intent: ‘None’, intensity: ‘None’, score: 0, // idle - awaiting user input // listening - listening to user input // fetching - fetching user data from the API uiState: ‘idle’, zoom: 3 }, getters: { intentStr: state => { var str = state.intent str = str.replace(/\b(App.)\b/gi, ”) return str }, @sarah_edo

Slide 99

Slide 99

In store.js (Vuex) actions: { getSpeech({ dispatch, commit, state }) { commit(‘setUiState’, ‘listening’) //keep recording speech all the time or activate it//for the first screen no, press a button. second screen yes. state.intent === ‘None’ ? (recognition.continuous = true) : (recognition.continuous = false) recognition.start() recognition.onresult = function(event) { const last = event.results.length - 1 const phrase = event.results[last][0].transcript dispatch(‘getUnderstanding’, phrase) } }, … @sarah_edo

Slide 100

Slide 100

In store.js (Vuex) getUnderstanding({ commit }, utterance) { commit(‘setUiState’, ‘fetching’) const url = https://westus.api.cognitive.microsoft.com/luis/v2.0/apps /4aba2274-c5df-4b0d-8ff7-57658254d042 https: axios({ method: ‘get’, url, params: { verbose: true, timezoneOffset: 0, q: utterance }, headers: { ‘Content-Type’: ‘application/json’, ‘Ocp-Apim-Subscription-Key’: ‘XXXXXXXXXXXXXXXXXXX’ } }) @sarah_edo

Slide 101

Slide 101

In store.js (Vuex) .then(({ data }) => { console.log(‘axios result’, data) if (altMaps.hasOwnProperty(data.query)) { commit(‘newIntent’, { intent: altMaps[data.query], score: 1 }) } else { commit(‘newIntent’, data.topScoringIntent) } commit(‘setUiState’, ‘idle’) commit(‘setZoom’) }) .catch(err => { console.error(‘axios error’, err) }) @sarah_edo

Slide 102

Slide 102

@sarah_edo

Slide 103

Slide 103

@sarah_edo

Slide 104

Slide 104

In store.js (Vuex) mutations: { newIntent: (state, {intent, score}) => { if (intent.includes(‘Intensity’)) { state.intensity = intent if (intent.includes(‘More’)) { state.counter++ } else if (intent.includes(‘Less’)) { state.counter-} } else { state.intent = intent } state.score = score }, … } @sarah_edo

Slide 105

Slide 105

In Base.vue (Components) createShapes() { this.bufferCamera.position.z = this.shapeZoom if (this.torusKnot !== null) { this.torusKnot.material.dispose() this.torusKnot.geometry.dispose() this.bufferScene.remove(this.torusKnot) } var shape = new THREE.TorusKnotGeometry( this.tConfig.a, this.tConfig.b, this.tConfig.c, this.tConfig.d ), material … this.torusKnot = new THREE.Mesh(shape, material) this.torusKnot.material.needsUpdate = true this.bufferScene.add(this.torusKnot) }, @sarah_edo

Slide 106

Slide 106

In Base.vue (Components) animate() { this.storeRAF = requestAnimationFrame(this.animate) this.bufferScene.rotation.x += 0.01 this.bufferScene.rotation.y += 0.02 this.renderer.render( this.bufferScene, this.bufferCamera, this.bufferTexture ) this.renderer.render(this.scene, this.camera) }, @sarah_edo

Slide 107

Slide 107

In Base.vue (Components) watch: { shapeZoom() { this.createShapes() cancelAnimationFrame(this.storeRAF) this.animate() } }, Repo: github.com/sdras/three-vue-pattern @sarah_edo

Slide 108

Slide 108

@sarah_edo

Slide 109

Slide 109

Charlie Gerard, @devdevcharlie @sarah_edo

Slide 110

Slide 110

“We are in a rut. A rut shaped like this” -Jen Simmons @sarah_edo

Slide 111

Slide 111

Slide 112

Slide 112

Take up space

Slide 113

Slide 113

Think bigger

Slide 114

Slide 114

Thank you! @sarah_edo