THE Future of WEB Animation

@sarah_edo

Communicating @sarah_edo

? @sarah_edo

Happiness sadness Fear curiosity @sarah_edo

Happiness sadness Fear curiosity @sarah_edo

@sarah_edo

@sarah_edo

Anything can happen

SARAH DRASNER @SARAH_EDO

Hooks Composition Components css-tricks.com/what-hooks-mean-for-vue/

@sarah_edo

src/hooks/preventScroll.js import { useDestroyed, useMounted } from “vue-hooks”; export function preventscroll() { const preventDefault = (e) =>” { e = e || window.event; if (e.preventDefault) e.preventDefault(); e.returnValue = false; } // keycodes for left, up, right, down const keys = { 37: 1, 38: 1, 39: 1, 40: 1 }; const preventDefaultForScrollKeys = (e) =>” { if (keys[e.keyCode]) { preventDefault(e); return false; } } … } @sarah_edo

src/hooks/preventScroll.js useMounted(() =>” { if (window.addEventListener) // older FF window.addEventListener(‘DOMMouseScroll’, preventDefault, false); window.onwheel = preventDefault; // modern standard window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE window.touchmove = preventDefault; // mobile window.touchstart = preventDefault; // mobile document.onkeydown = preventDefaultForScrollKeys; }); @sarah_edo

src/hooks/preventScroll.js useDestroyed(() =>” { if (window.removeEventListener) window.removeEventListener(‘DOMMouseScroll’, preventDefault, false); //firefox window.addEventListener(‘DOMMouseScroll’, (e) =>” { e.stopPropagation(); }, true); window.onmousewheel = document.onmousewheel = null; window.onwheel = null; window.touchmove = null; window.touchstart = null; document.onkeydown = null; }); @sarah_edo

src/components/BaseAppDetails.vue import { preventscroll } from “./../hooks/preventscroll.js”; import { lettering } from “./../hooks/lettering.js”; export default { … hooks() { preventscroll(); lettering(); } } @sarah_edo

• • They allows us to pass state from one to the other. They make it explicit where logic is coming from. css-tricks.com/what-hooks-mean-for-vue/ @sarah_edo

@sarah_edo

src/hooks/windowWidth.js import { useData, useMounted } from ‘vue-hooks’; export function windowwidth() { const data = useData({ width: 0 }) useMounted(() =>” { data.width = window.innerWidth }) // this is something we can consume with the other hook return { data } } @sarah_edo

src/hooks/logoLettering.js import { Elastic, TweenMax } from “gsap”; import { useMounted } from ‘vue-hooks’; //the data comes from the other hook export function logolettering(data) { useMounted(function () { if (data.data.width > 1200) { const logoname = this.$refs.logoname.childNodes; Splitting({ target: logoname, by: “chars” }); TweenMax.staggerFromTo( logoname, 5, { opacity: 0, transformOrigin: “50% 50% -30px”, cycle: { color: [“red”, “purple”, “teal”], rotationY(i) { return i * 50 } } }, … @sarah_edo

src/components/BaseAppNavigation.vue import AppLogo from “./AppLogo.vue”; import { logolettering } from “./../hooks/logolettering.js”; import { windowwidth } from “./../hooks/windowwidth.js”; export default { components: { AppLogo }, hooks() { logolettering(windowwidth()); } }; @sarah_edo

Article css-tricks.com/what-hooks-mean-for-vue/ Demo sdras.github.io/vue-hooks-foodapp/ Repo github.com/sdras/vue-hooks-foodapp @sarah_edo

page transitions

@sarah_edo

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

@sarah_edo

@sarah_edo

@sarah_edo

Templates in the pages directory <nuxt-link to=”/product”>Product</nuxt-link> @sarah_edo

@sarah_edo

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%; } .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

@sarah_edo

@sarah_edo

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

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

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

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

Transition Group wraps it… <transition-group name=”layout” tag=”g”> <rect class=”items rect” ref=”rect” key=”rect” width=”171” height=”171”/> … </transition-group> Styles: .items, .list-move { transition: all 0.4s ease; } .active { .rect { transform: translate3d(0, 30px, 0); } … } @sarah_edo

//animations .place { .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

@sarah_edo

Demo vue/nuxt page-transitions.com github.com/sdras/page-transitions-travelapp Article css-tricks.com/native-like-animations-for-pagetransitions-on-the-web/ @sarah_edo

@sarah_edo

@sarah_edo

@sarah_edo

@joshcarpenter @sarah_edo

@joshcarpenter @sarah_edo

@joshcarpenter @sarah_edo

@joshcarpenter @sarah_edo

Index page design inspired from this dribbble shot @sarah_edo

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

<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

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

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

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

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

@joshcarpenter @sarah_edo

@sarah_edo

@sarah_edo

ARKit 2.0 @OsFalmer @sarah_edo

@sarah_edo

Daniel @danielkuntz0 @sarah_edo

AR Expedition @sarah_edo

static 2d is an abstraction

Minh Pham on Dribbble @sarah_edo

@sarah_edo

@sarah_edo

Voice controlled Experiences on the web

@sarah_edo

Made in collaboration with Brian Holt @holtbt @sarah_edo

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

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

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

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

@sarah_edo

@sarah_edo

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

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

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

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

@sarah_edo

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

Take up space

Think bigger

Thank you! @sarah_edo