Creación de un formulario de inicio de sesión animado para TouchID | Programar Plus

Me encontré con este increíble disparo de Dribbble de Jakub Reis hace un tiempo. Me llamó la atención y supe que solo tenía que intentar recrearlo en código. En ese momento, no sabía cómo. Probé un montón de cosas diferentes y, aproximadamente un año después, finalmente logré hacer esta demostración.

Aprendí un par de cosas en el camino, así que permíteme llevarte en un pequeño viaje de lo que hice para hacer esto porque es posible que también aprendas una o dos cosas.

Consulte la pantalla de apertura del lápiz para una aplicación bancaria de Kirill Kiyutin (@kiyutink) en CodePen.

Paso 1: divide el trabajo en partes

Vi el GIF original muchas veces. Mi objetivo era dividir la animación en trozos pequeños y digeribles y pude dividirla de esta manera:

  • Llenar la huella dactilar
  • Quitar la huella dactilar
  • Animar el camino termina
  • Transforma una de las líneas curvas de huellas dactilares en una horizontal
  • Animar la “bala” que lanza la línea
  • Transforma la cadena en un gráfico
  • Animar pequeñas partículas explosivas
  • Animar el saldo de la cuenta
  • Y otras pequeñas animaciones con algunas transiciones CSS menores

Lo sé, parece mucho, ¡pero podemos hacer esto!

Paso 2: desarme la demostración original fotograma a fotograma

Necesitaba extraer tanta información como pudiera del GIF original para tener una buena comprensión de la animación, así que lo dividí en cuadros individuales. De hecho, hay muchos servicios que pueden hacer esto por nosotros. Usé uno en ezgif.com pero fácilmente podría haber sido otra cosa. De cualquier manera, esto nos permite obtener detalles como los colores, tamaños y proporciones de todos los diferentes elementos que necesitamos crear.

Ah, y todavía tenemos que convertir la huella digital en un SVG. Nuevamente, hay muchas aplicaciones que nos ayudarán aquí. Usé Adobe Illustrator para rastrear la huella digital con la herramienta de lápiz para obtener este conjunto de rutas:

Consulte el Pen css-t. rutas de Kirill Kiyutin (@kiyutink) en CodePen.

Pasaremos por el mismo proceso con el gráfico de líneas que aparece hacia el final de la animación, por lo que también podríamos mantener abierto el editor de vectores. 🙂

Paso 3: implementar las animaciones

Explicaré cómo funcionan las animaciones en la pluma final, pero también puede encontrar algunos de los enfoques fallidos que tomé a lo largo del camino al final del artículo.

Me centraré en las partes importantes aquí y puede consultar las demostraciones para obtener el código completo.

Llenando la huella dactilar

Creemos la estructura HTML de la pantalla del teléfono y la huella digital.

<div class="demo">
  <div class="demo__screen demo__screen--clickable">
    <svg class="demo__fprint" viewBox="0 0 180 320">  
      <!-- removes-forwards and removes-backwards classes will be helpful later on -->
      <path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--pinkish" d="M46.1,214.3c0,0-4.7-15.6,4.1-33.3"/>
      <path class="demo__fprint-path demo__fprint-path--removes-backwards demo__fprint-path--purplish" d="M53.5,176.8c0,0,18.2-30.3,57.5-13.7"/>
      <path class="demo__fprint-path demo__fprint-path--removes-forwards demo__fprint-path--pinkish" d="M115.8,166.5c0,0,19.1,8.7,19.6,38.4"/>
      <!-- ... and about 20 more paths like this -->
    </svg>
  

Los estilos son bastante simples hasta ahora. Tenga en cuenta que estoy usando Sass durante toda la demostración; me parece que ayuda a mantener el trabajo limpio y ayuda con algunos de los levantamientos más pesados ​​que tenemos que hacer.

// I use a $scale variable to quickly change the scaling of the whole pen, so I can focus on the animation and decide on the size later on.
$scale: 1.65;
$purplish-color: #8742cc;
$pinkish-color: #a94a8c;
$bg-color: #372546;

// The main container
.demo {
  background: linear-gradient(45deg, lighten($pinkish-color, 10%), lighten($purplish-color, 10%));
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 0;
  user-select: none;
  overflow: hidden;
  position: relative;
  
  // The screen that holds the login component
  &__screen {
    position: relative;
    background-color: $bg-color;
    overflow: hidden;
    flex-shrink: 0;
    &--clickable {
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
  }
  
  // Styles the fingerprint SVG paths
  &__fprint-path {
    stroke-width: 2.5px;
    stroke-linecap: round;
    fill: none;
    stroke: white;
    visibility: hidden;
    transition: opacity 0.5s ease;
    
    &--pinkish {
      stroke: $pinkish-color;
    }
    
    &--purplish {
      stroke: $purplish-color;
    }    
  }
  
  // Sizes positions the fingerprint SVG
  &__fprint {
    width: 180px * $scale;
    height: 320px * $scale;
    position: relative;
    top: 20px * $scale;
    overflow: visible;
    // This is going to serve as background to show "unfilled" paths. we're gonna remove it at the moment where the filling animation is over
    background-image: url('https://kiyutink.github.io/svg/fprintBackground.svg');
    background-size: cover;
    
    &--no-bg {
      background-image: none;
    }
  }
}

Ahora la parte difícil: hacer que la huella digital sea interactiva. Puede leer sobre la animación de las líneas SVG aquí. Ese es el método que usaremos para completar cada ruta individual.

Creemos una clase que describa un elemento de ruta para que sea más fácil manipular las rutas más adelante.

class Path {
  constructor(selector, index) {
    this.index = index;
    this.querySelection = document.querySelectorAll(selector)[index];
    this.length = this.querySelection.getTotalLength();
    this.$ = $(selector).eq(index);
    this.setDasharray();
    this.removesForwards = this.$.hasClass('demo__fprint-path--removes-forwards');
  }
  
  setDasharray() {
    this.$.css('stroke-dasharray', `${this.length} ${this.length + 2}`);
    return this;
  }
  
  offset(ratio) {
    this.$.css('stroke-dashoffset', -this.length * ratio + 1);
    return this;
  }
  
  makeVisible() {
    this.$.css('visibility', 'visible');
    return this;
  }
}

La idea general es esta: Cree una instancia de esta clase para cada ruta que tengamos en la huella digital y modifíquela en cada cuadro. Los caminos comenzarán con una relación de desplazamiento de -1 (completamente invisible) y luego aumentará la relación de compensación (a la que nos referiremos como “compensación” de aquí en adelante) en un valor constante en cada cuadro hasta que lleguen a 0 (completamente visible). La animación de llenado habrá terminado en este punto.

Si nunca ha animado nada con este enfoque cuadro por cuadro, aquí hay una demostración muy simple para ayudarlo a comprender cómo funciona:

Vea la prueba de concepto de la animación raf de Pen 60fps de Kirill Kiyutin (@kiyutink) en CodePen.

También deberíamos manejar el caso en el que el usuario deja de tocar o presionar el botón del mouse. En este caso, animaremos en la dirección opuesta (restando un valor constante del desplazamiento de cada cuadro hasta que llegue a -1 de nuevo).

Creemos la función que calcula el incremento de desplazamiento para cada fotograma; esto será útil más adelante.

function getPropertyIncrement(startValue, endValue, transitionDuration) {
  // We animate at 60 fps
  const TICK_TIME = 1000 / 60;
  const ticksToComplete = transitionDuration / TICK_TIME;
  return (endValue - startValue) / ticksToComplete;
}

¡Ahora es el momento de animar! Mantendremos las rutas de huellas digitales en una sola matriz:

let fprintPaths = [];

// We create an instance of Path for every existing path. 
// We don't want the paths to be visible at first and then 
// disappear after the JavaScript runs, so we set them to 
// be invisible in CSS. That way we can offset them first 
// and then make them visible.
for (let i = 0; i < $(fprintPathSelector).length; i++) {
  fprintPaths.push(new Path(fprintPathSelector, i));
  fprintPaths[i].offset(-1).makeVisible();
}

Revisaremos esa matriz para cada fotograma de la animación, animando las rutas una por una:

let fprintTick = getPropertyIncrement(0, 1, TIME_TO_FILL_FPRINT);

function fprintFrame(timestamp) {
  
  // We don't want to paint if less than 1000 / 65 ms elapsed 
  // since the last frame (because there are faster screens 
  // out there and we want the animation to look the same on 
  // all devices). We use 65 instead of 60 because, even on 
  // 60 Hz screens, `requestAnimationFrame` can sometimes be called 
  // a little sooner, which can result in a skipped frame.
  if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
    lastRafCallTimestamp = timestamp;
    curFprintPathsOffset += fprintTick * fprintProgressionDirection;
    offsetAllFprintPaths(curFprintPathsOffset);
  }
  
  // Schedule the next frame if the animation isn't over
  if (curFprintPathsOffset >= -1 && curFprintPathsOffset <= 0) {
    isFprintAnimationInProgress = true;
    window.requestAnimationFrame(fprintFrame);
  }
  
  // The animation is over. We can schedule next animation steps
  else if (curFprintPathsOffset > 0) {
    curFprintPathsOffset = 0;
    offsetAllFprintPaths(curFprintPathsOffset);
    isFprintAnimationInProgress = false;
    isFprintAnimationOver = true;
    // Remove the background with grey paths
    $fprint.addClass('demo__fprint--no-bg');
    // Schedule the next animation step - transforming one of the paths into a string 
    // (this function is not implemented at this step yet, but we'll do that soon)
    startElasticAnimation();
    // Schedule the fingerprint removal (removeFprint function will be implemented in the next section)
    window.requestAnimationFrame(removeFprint);
  }
  // The fingerprint is back to the original state (the user has stopped holding the mouse down)
  else if (curFprintPathsOffset < -1) {
    curFprintPathsOffset = -1;
    offsetAllFprintPaths(curFprintPathsOffset);
    isFprintAnimationInProgress = false;
  }
}

Y adjuntaremos algunos oyentes de eventos a la demostración:

$screen.on('mousedown touchstart', function() {
  fprintProgressionDirection = 1;
  // If the animation is already in progress,
  // we don't schedule the next frame since it's 
  // already scheduled in the `fprintFrame`. Also, 
  // we obviously don't schedule it if the animation 
  // is already over. That's why we have two separate 
  // flags for these conditions.
  if (!isFprintAnimationInProgress && !isFprintAnimationOver)
    window.requestAnimationFrame(fprintFrame);
})

// On `mouseup` / `touchend` we flip the animation direction
$(document).on('mouseup touchend', function() {
  fprintProgressionDirection = -1;
  if (!isFprintAnimationInProgress && !isFprintAnimationOver)
    window.requestAnimationFrame(fprintFrame);
})

… ¡Y ahora deberíamos haber terminado con el primer paso! Así es como se ve nuestro trabajo en este paso:

Consulte el Pen css-t. paso 1 de Kirill Kiyutin (@kiyutink) en CodePen.

Quitar la huella dactilar

Esta parte es bastante similar a la primera, solo que ahora tenemos que tener en cuenta el hecho de que algunos de los caminos se eliminan en una dirección y el resto en la otra. Es por eso que agregamos el --removes-forwards modificador anterior.

Primero, tendremos dos matrices adicionales: una para las rutas que se eliminan hacia adelante y otra para las que se eliminan hacia atrás:

const fprintPathsFirstHalf = [];
const fprintPathsSecondHalf = [];

for (let i = 0; i < $(fprintPathSelector).length; i++) {
  // ...
  if (fprintPaths[i].removesForwards)
    fprintPathsSecondHalf.push(fprintPaths[i]);
  else
    fprintPathsFirstHalf.push(fprintPaths[i]);
}

… y escribiremos una función que los desvíe en la dirección correcta:

function offsetFprintPathsByHalves(ratio) {    
  fprintPathsFirstHalf.forEach(path => path.offset(ratio));
  fprintPathsSecondHalf.forEach(path => path.offset(-ratio));
}

También vamos a necesitar una función que dibuje los marcos:

function removeFprintFrame(timestamp) {
  // Drop the frame if we're faster than 65 fps
  if (timestamp - lastRafCallTimestamp >= 1000 / 65) {
    curFprintPathsOffset += fprintTick * fprintProgressionDirection;
    offsetFprintPathsByHalves(curFprintPathsOffset);
    lastRafCallTimestamp = timestamp;
  }
  // Schedule the next frame if the animation isn't over
  if (curFprintPathsOffset >= -1)
    window.requestAnimationFrame(removeFprintFrame);
  else {
    // Due to the floating point errors, the final offset might be 
    // slightly less than -1, so if it exceeds that, we'll just 
    // assign -1 to it and animate one more frame
    curFprintPathsOffset = -1;
    offsetAllFprintPaths(curFprintPathsOffset);
  }
}

function removeFprint() {
  fprintProgressionDirection = -1;
  window.requestAnimationFrame(removeFprintFrame);
}

Ahora todo lo que queda es llamar removeFprint cuando terminemos de llenar la huella digital:

function fprintFrame(timestamp) {
  // ...
  else if (curFprintPathsOffset > 0) {
    // ...
    window.requestAnimationFrame(removeFprint);
  }
  // ...
}

Revisemos nuestro trabajo ahora:

Consulte el Pen css-t. parte 2 de Kirill Kiyutin (@kiyutink) en CodePen.

Animando el final del camino

Puede ver que, como la huella digital está casi eliminada, algunos de sus caminos son más largos de lo que eran al principio. Los moví por caminos separados que comienzan a animarse en el momento adecuado. Podría incorporarlos a las rutas existentes, pero sería mucho más difícil y, a 60 fps, no haría prácticamente ninguna diferencia.

Vamos a crearlos:

<path class="demo__ending-path demo__ending-path--pinkish" d="M48.4,220c-5.8,4.2-6.9,11.5-7.6,18.1c-0.8,6.7-0.9,14.9-9.9,12.4c-9.1-2.5-14.7-5.4-19.9-13.4c-3.4-5.2-0.4-12.3,2.3-17.2c3.2-5.9,6.8-13,14.5-11.6c3.5,0.6,7.7,3.4,4.5,7.1"/>
<!-- and 5 more paths like this -->

… Y aplica algunos estilos básicos:

&__ending-path {
  fill: none;
  stroke-width: 2.5px;
  stroke-dasharray: 60 1000;
  stroke-dashoffset: 61;
  stroke-linecap: round;
  will-change: stroke-dashoffset, stroke-dasharray, opacity;
  transform: translateZ(0);
  transition: stroke-dashoffset 1s ease, stroke-dasharray 0.5s linear, opacity 0.75s ease;
  
  &--removed {
    stroke-dashoffset: -130;
    stroke-dasharray: 5 1000;
  }
  
  &--transparent {
    opacity: 0;
  }
  
  &--pinkish {
    stroke: $pinkish-color;
  }
  
  &--purplish {
    stroke: $purplish-color;
  }
}

Ahora, tenemos que agregar el --removed modificador para fluir estos caminos en el momento adecuado:

function removeFprint() {
  $endingPaths.addClass('demo__ending-path--removed');
  setTimeout(() => {
    $endingPaths.addClass('demo__ending-path--transparent');
  }, TIME_TO_REMOVE_FPRINT * 0.9);
  // ...
}

Ahora nuestro trabajo realmente está comenzando a tomar forma:

Consulte el Pen css-t. parte 3 de Kirill Kiyutin (@kiyutink) en CodePen.

Transformando la huella digital

De acuerdo, encontré que esta parte es realmente difícil de hacer por mi cuenta, pero es realmente fácil de implementar con el complemento morphSVG de GSAP.

Creemos las rutas invisibles (bueno, una ruta y una línea para ser exactos 🙂) que serán los fotogramas clave de nuestra cadena:

<line id='demo__straight-path' x1="0" y1="151.3" x2="180" y2="151.3"/>
<path class="demo__hidden-path" id='demo__arc-to-top' d="M0,148.4c62.3-13.5,122.3-13.5,180,0"/>

Luego usaremos morphSVG para hacer la transición de la ruta entre los fotogramas clave:

const $elasticPath = $('#demo__elastic-path');

const ELASTIC_TRANSITION_TIME_TO_STRAIGHT = 250;
const WOBBLE_TIME = 1000;

function startElasticAnimation() {
  $elasticPath.css('stroke-dasharray', 'none');
  const elasticAnimationTimeline = new TimelineLite();
  
  elasticAnimationTimeline
    .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_STRAIGHT / 1000, {
      delay: TIME_TO_REMOVE_FPRINT / 1000 * 0.7,
      morphSVG: '#demo__arc-to-top'
    })
    .to('#demo__elastic-path', WOBBLE_TIME / 1000, {
      morphSVG: '#demo__straight-path',
      // I played with the easing a bit to get that "vibration" effect
      ease: Elastic.easeOut.config(1, 0.3)
    })
}

Llamaremos a esta función dentro del fprintFrame una vez que se llena la huella digital:

function fprintFrame(timestamp) {
  // ...
  else if (curFprintPathsOffset > 0) {
    // ...
    startElasticAnimation();
    // ...
  }
  // ...
}

El resultado es este:

Consulte el Pen css-t. parte 4 de Kirill Kiyutin (@kiyutink) en CodePen.

Animando la bala flotante

Para esto, utilicé algunas animaciones CSS sencillas y sencillas. Elegí las funciones de sincronización para emular la gravedad. Puedes jugar con las funciones de cronometraje aquí o aquí.

Creemos un div:

<div class="demo__bullet"></div>

… y aplicarle algunos estilos:

&__bullet {
  position: absolute;
  width: 4px * $scale;
  height: 4px * $scale;
  background-color: white;
  border-radius: 50%;
  top: 210px * $scale;
  left: 88px * $scale;
  opacity: 0;
  transition: all 0.7s cubic-bezier(0.455, 0.030, 0.515, 0.955);
  will-change: transform, opacity;
  
  // This will be applied after the bullet has descended, to create a transparent "aura" around it
  &--with-aura {
    box-shadow: 0 0 0 3px * $scale rgba(255, 255, 255, 0.3);
  }
  // This will be applied to make the bullet go up
  &--elevated {
    transform: translate3d(0, -250px * $scale, 0);
    opacity: 1;
  }
  // This will be applied to make the bullet go down
  &--descended {
    transform: translate3d(0, 30px * $scale, 0);
    opacity: 1;
    transition: all 0.6s cubic-bezier(0.285, 0.210, 0.605, 0.910);
  }
}

Luego, lo unimos agregando y eliminando clases según las interacciones de un usuario:

const DELAY_TO_BULLET_AURA = 300;
const ELEVATION_TIME = 700;
const DELAY_AFTER_ELEVATION = 700;

const $bullet = $('.demo__bullet');

function elevateBullet() {
  $bullet.addClass('demo__bullet--elevated');
}

function descendBullet() {
  $bullet.addClass('demo__bullet--descended').removeClass('demo__bullet--elevated');
  animateBulletAura();
}
  
function animateBulletAura() {
  setTimeout(() => $bullet.addClass('demo__bullet--with-aura'), DELAY_TO_BULLET_AURA);
}

function animateBullet() {
  elevateBullet();
  $screen.removeClass('demo__screen--clickable');
  setTimeout(descendBullet, ELEVATION_TIME + DELAY_AFTER_ELEVATION);
}

Ahora, tenemos que llamar al animateBullet función:

function startElasticAnimation() {
  // ...
  animateBullet();
}

Aquí es donde estamos en este punto:

Consulte el Pen css-t. parte 5 de Kirill Kiyutin (@kiyutink) en CodePen.

Transformando la cadena en un gráfico

Ahora, convierta esa cadena en un gráfico donde puede caer la bala. Agregaremos otro fotograma clave a la animación morphSVG.

<path class="demo__hidden-path" id='demo__curve' d="M0,140.2c13.1-10.5,34.7-17,48.5-4.1c5.5,5.2,7.6,12.1,9.2,19.2c2.4,10.5,4.3,21,7.2,31.4c2.4,8.6,4.3,19.6,10.4,26.7c4.3,5,17.7,13.4,23.1,4.8c5.9-9.4,6.8-22.5,9.7-33c4.9-17.8,13-14.6,15.7-14.6c1.8,0,9,2.3,15.4,5.4c6.2,3,11.9,7.7,17.9,11.2c7,4.1,16.5,9.2,22.8,6.6"/>

Agregamos este fotograma clave a nuestra línea de tiempo de esta manera:

const DELAY_TO_CURVE = 350;
const ELASTIC_TRANSITION_TIME_TO_CURVED = 300;

function startElasticAnimation() {
  // ...
  elasticAnimationTimeline
    // ...
    .to('#demo__elastic-path', ELASTIC_TRANSITION_TIME_TO_CURVED / 1000, {
      delay: DELAY_TO_CURVE / 1000, 
      morphSVG: '#demo__curve'
    })
    // ...
}

Esto es lo que obtenemos:

Consulte el Pen css-t. parte 6 de Kirill Kiyutin (@kiyutink) en CodePen.

Explotando las partículas

Esta es una animación divertida. Primero, crearemos un par de nuevos divs que contienen las partículas que explotan:

<div class="demo__logo-particles">
    <div class="demo__logo-particle"></div>
    <!-- and several more of these -->
</div>
<div class="demo__money-particles">
    <div class="demo__money-particle"></div>
    <!-- and several more of these -->
</div>

Las dos explosiones son prácticamente iguales con la excepción de algunos parámetros. Ahí es donde los mixins de SCSS serán útiles. Podemos escribir la función una vez y usarla en nuestros divs.

@mixin particlesContainer($top) {
  position: absolute;
  width: 2px * $scale;
  height: 2px * $scale;
  left: 89px * $scale;
  top: $top * $scale;
  // We'll hide the whole container to not show the particles initially
  opacity: 0;

  &--visible {
    opacity: 1;
  }
}

// The $sweep parameter shows how far from the center (horizontally) the initial positions of the particles can be
@mixin particle($sweep, $time) {
  width: 1.5px * $scale;
  height: 1.5px * $scale;
  border-radius: 50%;
  background-color: white;
  opacity: 1;
  transition: all $time ease;
  position: absolute;
  will-change: transform;
  
  // Phones can't handle the particles very well :(
  @media (max-width: 400px) {
    display: none;
  }

  @for $i from 1 through 30 {
    &:nth-child(#{$i}) {
      left: (random($sweep) - $sweep / 2) * $scale + px;
      @if random(100) > 50 {
        background-color: $purplish-color;
      }
      @else {
        background-color: $pinkish-color;
      }
    }
    &--exploded:nth-child(#{$i}) {
      transform: translate3d((random(110) - 55) * $scale + px, random(35) * $scale + px, 0);
      opacity: 0;
    }
  }
}

Tenga en cuenta el comentario en el código de que las partículas no funcionan particularmente bien en dispositivos menos potentes, como teléfonos. Quizás haya otro enfoque aquí que resolvería esto si alguien tiene ideas y quiere intervenir.

Muy bien, usemos los mixins en los elementos:

&__logo-particles {
  @include particlesContainer(15px);
}

&__logo-particle {
  @include particle(50, 1.7s);
}

&__money-particles {
  @include particlesContainer(100px);
}

&__money-particle {
  @include particle(100, 1.5s);
}

Ahora agregamos las clases a los divs en el momento adecuado en JavaScript:

const DELAY_TO_ANIMATE_MONEY_PARTICLES = 300;
const DELAY_TO_ANIMATE_LOGO_PARTICLES = 500;

const $moneyParticles = $('.demo__money-particle');
const $moneyParticlesContainer = $('.demo__money-particles');
const $logoParticlesContainer = $('.demo__logo-particles');
const $logoParticles = $('.demo__logo-particle');

function animateMoneyParticles() {
  setTimeout(() => {
    $moneyParticlesContainer.addClass('demo__money-particles--visible')
    $moneyParticles.addClass('demo__money-particle--exploded');
  }, DELAY_TO_ANIMATE_MONEY_PARTICLES);    
}

function animateLogoParticles() {
  setTimeout(() => {
    $logoParticlesContainer.addClass('demo__logo-particles--visible')
    $logoParticles.addClass('demo__logo-particle--exploded');
  }, DELAY_TO_ANIMATE_LOGO_PARTICLES);    
}

function elevateBullet() {
  // ...
  animateMoneyParticles();
  animateLogoParticles();
}

Aquí es donde estamos:

Consulte el Pen css-t. parte 7 de Kirill Kiyutin (@kiyutink) en CodePen.

Animando el saldo de la cuenta

Cada dígito tendrá algunos números aleatorios por los que nos desplazaremos:

<div class="demo__money">
  <div class="demo__money-currency">$</div>
  <!-- every digit will be a div like this one -->
  <div class="demo__money-digit">
    1
    2
    3
    4
    5
    6
    7
    8
    1
  </div>
  // ...
</div>

Pondremos diferentes tiempos de transición en todos los dígitos para que las animaciones estén escalonadas. Podemos usar un bucle SCSS para eso:

&__money-digit {
  // ...
  // we start from 2 because the first child is the currency sign :)
  @for $i from 2 through 6 {
    &:nth-child(#{$i}) {
      transition: transform 0.1s * $i + 0.2s ease;
      transition-delay: 0.3s;
      transform: translate3d(0, -26px * $scale * 8, 0);
    }
    
    &--visible:nth-child(#{$i}) {
      transform: none;
    }
  }
}

Todo lo que queda es agregar las clases CSS en el momento adecuado:

const $money = $('.demo__money');
const $moneyDigits = $('.demo__money-digit');

function animateMoney() {
  $money.addClass('demo__money--visible');
  $moneyDigits.addClass('demo__money-digit--visible');
}

function descendBullet() {
  // ...
  animateMoney();
  // ...
}

Ahora siéntese y maravíllese con nuestro trabajo:

Consulte el Pen css-t. parte 8 de Kirill Kiyutin (@kiyutink) en CodePen.

El resto de las animaciones son bastante simples e involucran ligeras transiciones CSS, así que no entraré en ellas para ser breves. Puede ver todo el código final en la demostración completa.

Ver demostración

Algunas palabras finales

  • En mis primeros intentos intenté usar transiciones CSS para todo el trabajo de animación. Me resultó virtualmente imposible controlar el progreso y la dirección de la animación, así que abandoné esa idea en poco tiempo y esperé un mes más o menos antes de comenzar de nuevo. En realidad, si hubiera sabido en ese entonces que la API de animaciones web existía, habría intentado hacer uso de ella.
  • Intenté hacer la explosión con Canvas para un mejor rendimiento (usando este artículo como referencia), pero me resultó difícil controlar la velocidad de fotogramas con dos requestAnimationFrame cadenas Si sabes cómo hacer eso, entonces quizás puedas decírmelo en los comentarios (o escribir un artículo para Programar Plus🙂).
  • Después de que obtuve un primer prototipo funcional, estaba realmente descontento con su desempeño. Estaba alcanzando alrededor de 40-50 fps en una PC, sin mencionar los teléfonos en absoluto. Pasé mucho tiempo optimizando el código y este artículo fue de mucha ayuda.
  • Puede ver que el gráfico tiene un gradiente. Lo hice declarando un degradado directamente en el bloque defs de SVG:
<defs>
  <linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="0%">
    <stop offset="0%"   stop-color="#8742cc"/>
    <stop offset="100%" stop-color="#a94a8c"/>
  </linearGradient>
</defs>

… y luego lo aplicó en las propiedades de CSS:

fill: url(#linear);
stroke: url(#linear);

Todo el proceso de principio a fin, descubrir el tiro de Dribbble y terminar el trabajo, me llevó aproximadamente un año. Estaba tomando descansos de un mes aquí y allá porque no sabía cómo abordar un aspecto en particular o simplemente no tenía suficiente tiempo libre para trabajar en él. Todo el proceso fue una experiencia realmente valiosa y aprendí muchas cosas nuevas en el camino.

Dicho esto, la lección más importante que se puede aprender de esto es que no hay necesidad de rehuir una tarea ambiciosa o sentirse desanimado si no sabe cómo abordarla al principio. La web es un lugar grande y hay mucho espacio para resolver las cosas a medida que avanza.

(Visited 12 times, 1 visits today)