La creación de: el sitio de animación SVG Million Devs de Netlify
El siguiente artículo captura el proceso de creación del micrositio Million Developers para Netlify. Este proyecto fue creado por algunas personas y hemos capturado algunas partes del proceso de construcción aquí, enfocándonos principalmente en los aspectos de animación, en caso de que alguno sea útil para otros que crean experiencias similares.
- Visita el micrositio de Million Developer
- repositorio de GitHub
Construyendo una aplicación Vue a partir de un SVG
La belleza de SVG es que puedes pensar en él, y en el sistema de coordenadas, como un gran juego de acorazado. Realmente estás pensando en términos de x, y, ancho y alto.
div app-login-result-sticky v-if="user.number" / app-github-corner / app-header / !-- this is one big SVG -- svg :viewBox="timelineAttributes.viewBox" !-- this is the desktop path -- path transform="translate(16.1 -440.3)" d="M951.5,7107..." / !-- this is the path for mobile -- app-mobilepath v-if="viewportSize === 'small'" / !-- all of the stations, broken down by year -- app2016 / app2017 / app2018 / app2019 / app2020 / !-- the 'you are here' marker, only shown on desktop and if you're logged in -- app-youarehere v-if="user.number viewportSize === 'large'" / /svg /div
Dentro del componente de aplicación más grande, tenemos el encabezado grande, pero como puedes ver, el resto es un SVG gigante. A partir de ahí, dividimos el resto del SVG gigante en varios componentes:
- Rutas tipo Candyland tanto para escritorio como para dispositivos móviles, mostradas condicionalmente por un estado en la tienda Vuex
- Hay 27 estaciones, sin incluir sus contrapartes de texto, y muchos componentes decorativos como arbustos, árboles y farolas, lo cual es mucho para realizar un seguimiento en un solo componente, por lo que están desglosados por año.
- El marcador “usted está aquí”, solo se muestra en el escritorio y si ha iniciado sesión
SVG es maravilloso flexible porque no solo podemos dibujar formas y rutas absolutas y relativas dentro de ese sistema de coordenadas, sino que también podemos dibujar SVG dentro de SVG. Solo necesitamos definir los SVG, x
y de esos SVG y podemos montarlos dentro del SVG más grande, que es exactamente lo que vamos a hacer con todos estos componentes para que podamos ajustar su ubicación cuando sea necesario. Dentro de los componentes significativos, puedes pensar en ellos un poco como en HTML.y
width
height
g
group
div
Así es como se ve esto dentro de los componentes del año:
template g !-- decorative components -- app-tree x="650" y="5500" / app-tree x="700" y="5550" / app-bush x="750" y="5600" / !-- station component -- app-virtual x="1200" y="6000" xSmall="50" ySmall="15100" / !-- text component, with slots -- app-text x="1400" y="6500" xSmall="50" ySmall="15600" num="20" url-slug="jamstack-conf-virtual" template v-slot:dateMay 27, 2020/template template v-slot:eventJamstack Conf Virtual/template /app-text ... /templatescript...export default { components: { // loading the decorative components in syncronously AppText, AppTree, AppBush, AppStreetlamp2, // loading the heavy station components in asyncronously AppBuildPlugins: () = import("@/components/AppBuildPlugins.vue"), AppMillion: () = import("@/components/AppMillion.vue"), AppVirtual: () = import("@/components/AppVirtual.vue"), },};.../script
Dentro de estos componentes, puede ver varios patrones:
- Tenemos arbustos y árboles para decoración que podemos esparcir a través de
x
yy
valores a través de accesorios. - Podemos tener componentes de estación individuales, que además tienen dos valores de ubicación diferentes, uno para dispositivos grandes y pequeños.
- Tenemos un componente de texto, que tiene tres disponibles
slots
, uno para la fecha y dos para dos líneas de texto diferentes. - También estamos cargando los componentes decorativos de forma sincrónica y cargando esas estaciones SVG más pesadas de forma asíncrona.
Animación SVG
La animación SVG se realiza con GreenSock (GSAP), con su nuevo complemento ScrollTrigger. Escribí una guía sobre cómo trabajar con GSAP para su última versión 3.0 a principios de este año. Si no está familiarizado con esta biblioteca, ese podría ser un buen lugar para comenzar.
Afortunadamente, trabajar con el complemento es sencillo; aquí está la base de la funcionalidad que necesitaremos:
import { gsap } from "gsap";import { ScrollTrigger } from "gsap/ScrollTrigger.js";import { mapState } from "vuex";gsap.registerPlugin(ScrollTrigger);export default { computed: { ...mapState([ "toggleConfig", "startConfig", "isAnimationDisabled", "viewportSize", ]), }, ... methods: { millionAnim() { let vm = this; let tl; const isScrollElConfig = { scrollTrigger: { trigger: `.million${vm.num}`, toggleActions: this.toggleConfig, start: this.startConfig, }, defaults: { duration: 1.5, ease: "sine", }, }; } }, mounted() { this.millionAnim(); },};
Primero, estamos importando gsap y el paquete que necesitamos, así como el estado de la tienda Vuex. Puse las toggleActions
configuraciones y start
en la tienda y las pasé a cada componente porque mientras trabajaba, necesitaba experimentar en qué punto de la interfaz de usuario quería activar las animaciones, esto me evitó tener que configurar cada componente por separado.
Esas configuraciones en la tienda se ven así:
export default new Vuex.Store({ state: { toggleConfig: `play pause none pause`, startConfig: `center 90%`, }}
Esta configuración se divide en
toggleConfig
: reproduce la animación cuando pasa por la página (otra opción es decir reiniciar y se volverá a activar si la vuelves a ver), se detiene cuando está fuera de la ventana gráfica (esto puede ayudar un poco con el rendimiento) y no No se Vuelve a activar en reversa al volver a subir la página.startConfig
Está indicando que cuando el centro del elemento esté al 90% por debajo de la altura de la ventana gráfica, se activa la animación para que comience.
Estos son los escenarios que decidimos para este proyecto, ¡hay muchos otros! Puedes entender todas las opciones con este vídeo.
Para esta animación en particular, necesitábamos tratarla de manera un poco diferente si se trataba de una animación de banner que no necesitaba activarse al desplazarse o si estaba más adelante en la línea de tiempo. Pasamos un accesorio y lo usamos para pasar esa configuración dependiendo del número de accesorios:
if (vm.num === 1) { tl = gsap.timeline({ defaults: { duration: 1.5, ease: "sine", }, });} else { tl = gsap.timeline(isScrollElConfig);}
Luego, para la animación en sí, estoy usando lo que se llama una etiqueta en la línea de tiempo. Puedes considerarlo como identificar un punto en el tiempo en el cabezal de reproducción en el que quizás quieras colgar animaciones o funcionalidades. Tenemos que asegurarnos de usar también el accesorio numérico para la etiqueta, por lo que mantenemos separadas las líneas de tiempo para el componente de encabezado y pie de página.
tl.add(`million${vm.num}`)....from( "#front-leg-r", { duration: 0.5, rotation: 10, transformOrigin: "50% 0%", repeat: 6, yoyo: true, ease: "sine.inOut", }, `million${vm.num}`).from( "#front-leg-l", { duration: 0.5, rotation: 10, transformOrigin: "50% 0%", repeat: 6, yoyo: true, ease: "sine.inOut", }, `million${vm.num}+=0.25`);
Están sucediendo muchas cosas en la animación del millón de desarrolladores, así que simplemente aislaré una parte del movimiento para analizarla: arriba tenemos a las chicas balanceando las piernas. Tenemos ambas piernas balanceándose por separado, ambas se repiten varias veces, y eso yoyo: true
le permite a GSAP saber que me gustaría que la animación revierta todas las demás alteraciones. Estamos rotando las piernas, pero lo que lo hace realista es que transformOrigin comienza en la parte superior central de la pierna, de modo que cuando gira, gira alrededor del eje de la rodilla, como lo hacen las rodillas
Agregar una palanca de animación
Queríamos brindarles a los usuarios la posibilidad de explorar el sitio sin animación, en caso de que tuvieran un trastorno vestibular, por lo que creamos una opción para alternar el estado de reproducción de la animación. El cambio no es nada especial: actualiza el estado en la tienda Vuex mediante una mutación, como era de esperar:
export default new Vuex.Store({ state: { ... isAnimationDisabled: false, }, mutations: { updateAnimationState(state) { state.isAnimationDisabled = !state.isAnimationDisabled }, ...})
Las actualizaciones reales ocurren en el componente superior de la aplicación, donde recopilamos todas las animaciones y activadores, y luego los ajustamos según el estado de la tienda. Tenemos watch
la isAnimationDisabled
propiedad para los cambios y, cuando ocurre uno, tomamos todas las instancias de animaciones de activación de desplazamiento en la aplicación. No .kill() las animaciones, cuál opción, porque si lo hiciéramos, no podríamos reiniciarlas.
En su lugar, configuramos su progreso en el cuadro final si las animaciones están deshabilitadas, o si las reiniciamos, configuramos su progreso en 0 para que puedan reiniciarse cuando estén configuradas para activarse en la página. Si hubiéramos usado .restart() aquí, todas las animaciones se habrían reproducido y no las veríamos activarse mientras seguíamos avanzando por la página. ¡Lo mejor de ambos mundos!
watch: { isAnimationDisabled(newVal, oldVal) { ScrollTrigger.getAll().forEach((trigger) = { let animation = trigger.animation; if (newVal === true) { animation animation.progress(1); } else { animation animation.progress(0); } }); }, },
Accesibilidad SVG
De ninguna manera soy un experto en accesibilidad, así que avíseme si me he equivocado aquí, pero investigué y probé bastante en este sitio, y estaba muy emocionado de que cuando probé en mi Macbook mediante voz en off, el La información pertinente del sitio era transitable, así que estoy compartiendo lo que hicimos para llegar allí.
Para el SVG inicial que abarcaba todo, no aplicamos una función para que el lector de pantalla lo atravesara. Para los árboles y arbustos, aplicamos role="img"
para que el lector de pantalla lo omitiera y en cualquiera de las estaciones más detalladas aplicamos un único id
y title
, que fue el primer elemento dentro del SVG. También aplicamos role="presentation"
.
svg ... role="presentation" aria-labelledby="analyticsuklaunch" titleLaunch of analytics/title
Aprendí mucho de esto en este artículo de Heather Migliorisi y en este fantástico artículo de Leonie Watson.
El texto dentro del SVG se anuncia a medida que avanza por la página, se encuentra el enlace y se lee todo el texto. Así es como se ve ese componente de texto, con los espacios mencionados anteriormente.
template a :href="`https://www.netlify.com/blog/2020/08/03/netlify-milestones-on-the-road-to-1-million-devs/#${urlSlug}`" svg :x="svgCoords.x" :y="svgCoords.y" viewBox="0 0 280 115.4" g :class="`textnode text${num}`" text transform="translate(7.6 14)" slot name="date"Jul 13, 2016/slot /text text transform="translate(16.5 48.7)" slot name="event"Something here/slot /text text transform="translate(16.5 70)" slot name="event2" / /text text transform="translate(164.5 104.3)"View Milestone/text /g /svg /a/template
Aquí hay un video de cómo suena esto si paso por el SVG en mi Mac:
Si tiene más sugerencias para mejorar, ¡háganoslo saber!
- Visita el micrositio de Million Developer
- repositorio de GitHub
El repositorio también es de código abierto si desea consultar el código o presentar un PR.
Un millón de gracias (juego de palabras) a mis compañeros de trabajo Zach Leatherman y Hugues Tennier que trabajaron en esto conmigo, sus aportes y su trabajo fueron invaluables para el proyecto, ¡solo existe a partir del trabajo en equipo para lograrlo! Y mucho respeto a Alejandro Álvarez, quien hizo el diseño e hizo un trabajo espectacular. Choca esos cinco por todos lados.
Deja un comentario