Análisis detallado de CSS: matrix3d() para obtener una barra de desplazamiento personalizada perfecta para el marco.

Las barras de desplazamiento personalizadas son muy poco frecuentes. Esto se debe principalmente las barras de desplazamiento son uno de los bits restantes de la Web que no estilable (los estoy mirando, selector de fecha). Puedes usar JavaScript para compilar el tuyo, pero eso es costoso y es bajo la fidelidad y la latencia. En este artículo, veremos matrices de CSS poco convencionales para crear un desplazador personalizado que no requiera ningún tipo JavaScript mientras te desplazas, solo algo de código de configuración.

A modo de resumen

¿No te importan los pequeños detalles? Solo tienes que mirar la Demostración del gato Nyan y obtener la biblioteca? Encontrarás el código de la demostración en nuestra Repositorio de GitHub.

LAM;WRA (largo y matemático; se leerá de todos modos)

Hace un tiempo, construimos un desplazador con paralaje (¿Leíste ese artículo? Es muy bueno y vale la pena tu tiempo). Remitiendo elementos con CSS 3D , los elementos se mueven más lento que nuestra velocidad de desplazamiento real.

Resumen

Empecemos con un resumen de cómo funcionó el desplazador de paralaje.

Como se muestra en la animación, logramos el efecto de paralaje enviando elementos “hacia atrás” en un espacio 3D, a lo largo del eje Z. Desplazarse por un documento es efectivamente un traslación a lo largo del eje Y. Si nos desplazamos hacia abajo, digamos 100 px, cada se traducirá hacia arriba en 100 px. Eso se aplica a todos los elementos, incluso los que están "más allá". Sin embargo, porque están más lejos de la cámara, su movimiento observado en pantalla será de menos de 100 px, lo que generará el efecto de paralaje deseado.

Por supuesto, mover un elemento al espacio también hará que parezca más pequeño, que corregimos escalando nuevamente el elemento. Descubrimos los cálculos exactos cuando creamos desplazamiento de paralaje, así que no repetiré todos los detalles.

Paso 0: ¿Qué queremos hacer?

Barras de desplazamiento Eso es lo que vamos a compilar. Pero, ¿alguna vez has pensado sobre lo que hacen? Claro que no. Las barras de desplazamiento son un indicador de cuánto del contenido disponible es visible actualmente y cuánto progreso que hiciste como lector. Si te desplazas hacia abajo, también se moverá la barra de desplazamiento para indican que estás avanzando hacia el final. Si todo el contenido encaja en la viewport, la barra de desplazamiento suele estar oculta. Si el contenido es el doble de la altura del viewport, el que ocupa 1⁄2 de la altura del viewport. Contenido cuyo valor equivale a 3 veces la altura de el viewport ajusta la barra de desplazamiento a 1⁄3 del viewport, etc. Verás el patrón. En lugar de desplazarse, también puedes hacer clic y arrastrar la barra de desplazamiento para desplazarte para agilizar el sitio. Es una cantidad sorprendente de comportamiento para una persona con ese elemento. Vamos a pelear una batalla a la vez.

Paso 1: Invierte

Bien, podemos hacer que los elementos se muevan más lento que la velocidad de desplazamiento con CSS 3D. transformaciones como se describe en el artículo sobre desplazamiento con paralaje. ¿Podemos revertir la dirección? Resulta que sí podemos, y ese es nuestro camino para crear con una barra de desplazamiento personalizada y perfecta para lograr un fotograma perfecto. Para entender cómo funciona, debemos abordar algunos conceptos básicos sobre CSS 3D.

Para obtener cualquier tipo de proyección de perspectiva en el sentido matemático, lo más probablemente termines usando coordenadas homogéneas. No entraré en detalles sobre qué son ni por qué funcionan, pero pueden pensar en como coordenadas 3D con una cuarta coordenada adicional llamada w. Esta coordenada debe ser 1, excepto si quieres que haya una distorsión de perspectiva. Mié no te preocupes por los detalles de w, ya que no usaremos ningún recurso distinto de 1. Por lo tanto, a partir de ahora, todos los puntos corresponden a vectores de 4 dimensiones. [x, y, z, w=1] y, por lo tanto, las matrices necesitan para que también sea 4x4.

Una ocasión en la que puedes ver que CSS usa coordenadas homogéneas en el es cuando defines tus propias matrices 4x4 en una propiedad de transformación usando la función matrix3d(). matrix3d toma 16 argumentos (porque la matriz es 4x4), especificando una columna después de la otra. Así que podemos usar esta función para para especificar manualmente las rotaciones, las traslaciones, etc. es perder la coordenada w.

Antes de poder usar matrix3d(), necesitamos un contexto 3D, porque sin un contexto 3D, no habría ninguna distorsión de perspectiva ni necesidad de coordenadas homogéneas. Para crear un contexto en 3D, necesitamos un contenedor con un perspective y algunos elementos dentro que podemos transformar en la nueva creado espacio 3D. Para ejemplo:

Fragmento de código CSS que distorsiona un div usando el código CSS
    de perspectiva.

El motor de CSS procesa los elementos dentro de un contenedor de perspectiva de la siguiente manera:

  • Convertir las esquinas (vértices) de un elemento en coordenadas homogéneas [x,y,z,w], en relación con el contenedor de perspectiva
  • Aplica todas las transformaciones de los elementos como matrices de derecha a izquierda.
  • Si el elemento de perspectiva se puede desplazar, aplica una matriz de desplazamiento.
  • Aplica la matriz de perspectiva.

La matriz de desplazamiento es una traslación a lo largo del eje y. Si nos desplazamos hacia abajo 400 px, todos los elementos se deben mover hacia arriba 400 px. La matriz de perspectiva es una que “jala” apunta más cerca del punto de fuga cuanto más atrás en 3D espacio donde están. Esto logra el efecto de hacer que las cosas parezcan más pequeñas cuando están más atrás y también los hace "mover más lento" cuando se los traduce. Por lo tanto, si se aleja un elemento, una traducción de 400 px provocará que el elemento para mover solo 300 px en la pantalla.

Si quieres conocer todos los detalles, consulta el spec de los CSS de transformación de datos, pero, para los fines de este artículo, simplifiquemos algoritmo anterior.

La caja está dentro de un contenedor de perspectiva con el valor p para perspective. Supongamos que el contenedor es desplazable y se desplaza hacia abajo n píxeles.

La matriz de perspectiva multiplica la matriz de desplazamiento por la matriz de transformación de los elementos
  equivale a una matriz de identidad de cuatro por cuatro con menos uno sobre p en la cuarta fila
  tercera columna multiplicada por una matriz de identidad de cuatro por cuatro con menos n en la segunda
  fila cuarta columna multiplicada por la matriz de transformación del elemento.

La primera es la de perspectiva, y la segunda, el desplazamiento de salida. En resumen: el trabajo de la matriz de desplazamiento es hacer que un elemento se mueva hacia arriba cuando se se desplazan hacia abajo, de ahí el signo negativo.

Para nuestra barra de desplazamiento, queremos lo opuesto; queremos que nuestro elemento mover hacia abajo cuando nos desplacemos hacia abajo Podemos usar el truco en los siguientes casos: Invertimos la coordenada w de las esquinas de nuestro cuadro. Si la coordenada w es -1. Todas las traducciones se aplicarán en la dirección opuesta. Entonces, ¿cómo lo eso? El motor de CSS se encarga de convertir las esquinas de nuestro cuadro en coordenadas homogéneas, y establece w en 1. ¡Es hora de brillar matrix3d()!

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    );
}

Esta matriz no hará nada más que anular w. Cuando el motor CSS tiene convierte cada esquina en un vector de la forma [x,y,z,1], la matriz conviértela en [x,y,z,-1].

Una matriz de identidad de cuatro por cuatro con menos uno sobre p en la cuarta fila
  tercera columna multiplicada por una matriz de identidad de cuatro por cuatro con menos n en la segunda
  fila cuarta columna multiplicada por cuatro por cuatro en la matriz de identidad con menos uno en la
  cuarta fila, cuarta columna por vector de cuatro dimensiones x, y, z, 1 es igual a cuatro
  por una matriz de identidad con menos uno sobre p en la tercera fila de la tercera columna,
  menos n en la segunda fila, cuarta columna y menos uno en la cuarta fila
  la cuarta columna equivale a un vector de cuatro dimensiones x, y más n, z, menos z sobre
  p menos 1.

Enumeramos un paso intermedio para mostrar el efecto de nuestra transformación de elementos de salida. Si no te sientes cómodo con la matemática de matrices, no hay problema. La Eureka momento es que en la última línea terminamos agregando el desplazamiento n a nuestra y en lugar de restarlo. El elemento se traducirá hacia abajo. si nos desplazamos hacia abajo.

Sin embargo, si solo colocamos esta matriz en nuestro ejemplo, el elemento no se mostrará. Esto se debe a que las especificaciones de CSS requieren que vértice con w < 0 bloquea la renderización del elemento. Y como nuestra actual es 0, p es 1, w será -1.

Por suerte, podemos elegir el valor de z. Para asegurarnos de obtener w=1, necesitamos para establecer z = -2.

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

He aquí, nuestro regresó la caja.

Paso 2: Haz que se mueva

Ahora nuestra caja está ahí y tiene el mismo aspecto que sin transformaciones de datos. En este momento, el contenedor de perspectiva no se puede desplazar, por lo que no podemos pero sabemos que nuestro elemento irá en otra dirección cuando desplazado. Hagamos que el contenedor se desplace, ¿de acuerdo? Podemos agregar una elemento separador que ocupa espacio:

<div class="container">
    <div class="box"></div>
    <span class="spacer"></span>
</div>

<style>
/* … all the styles from the previous example … */
.container {
    overflow: scroll;
}
.spacer {
    display: block;
    height: 500px;
}
</style>

Y ahora desplázate por el cuadro. El cuadro rojo se mueve hacia abajo.

Paso 3: Asígnale un tamaño

Tenemos un elemento que se mueve hacia abajo cuando la página se desplaza hacia abajo. Qué difícil es un poco, realmente. Ahora necesitamos aplicar un estilo para que se vea como una barra de desplazamiento y hacerlo un poco más interactivo.

Una barra de desplazamiento suele estar formada por un círculo y un "recorrido", mientras que el segmento no es siempre visibles. La altura del pulgar es directamente proporcional a la porción de la el contenido sea visible.

<script>
    const scroller = document.querySelector('.container');
    const thumb = document.querySelector('.box');
    const scrollerHeight = scroller.getBoundingClientRect().height;
    thumb.style.height = /* ??? */;
</script>

scrollerHeight es la altura del elemento desplazable, mientras que scroller.scrollHeight es la altura total del contenido desplazable. scrollerHeight/scroller.scrollHeight es la fracción del contenido que se sean visibles. La proporción del espacio vertical que cubre el pulgar debe ser igual a proporción del contenido visible:

pulgar del estilo de estilo de punto de altura sobre desplazamiento
  sobre altura de desplazamiento con punto del pulgar solo si es y solo si el estilo del pulgar hacia arriba es de los puntos
  es igual a la altura de la barra de desplazamiento por la altura de la barra de desplazamiento sobre el punto de desplazamiento
  altura.
<script>
    // …
    thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
    // Accommodate for native scrollbars
    thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

El tamaño del pulgar es que se vean bien, pero se mueve demasiado rápido. Aquí es donde podemos tomar nuestra técnica de la desplazador de paralaje. Si movemos el elemento más atrás, se moverá más lento mientras el desplazamiento. Podemos corregir el tamaño agrandándolo. Pero ¿cuánto debemos impulsar exactamente? Hagamos algo de matemáticas, ya lo adivinaste. Esta es la última vez, muy prometedores.

Lo esencial es que queremos que el borde inferior del pulgar se alinea con el borde inferior del elemento desplazable cuando el usuario se desplaza por completo. fuera de servicio. En otras palabras: si nos desplazamos scroller.scrollHeight - scroller.height píxeles, queremos que el pulgar traducido por scroller.height - thumb.height Para cada píxel de desplazador, Queremos que nuestro dedo se mueva una fracción de un píxel:

El factor es igual a la altura de los puntos de desplazamiento menos la altura de los puntos del pulgar sobre la barra de desplazamiento
  altura de desplazamiento de puntos menos altura de punto de desplazamiento.

Ese es nuestro factor de escala. Ahora debemos convertir el factor de escala en un traslación en el eje z, algo que ya hicimos en el desplazamiento con paralaje . Según el sección relevante de la especificación: El factor de escala es igual a p/(p − z). Podemos resolver esta ecuación para z a descubrir cuánto necesitamos trasladar el pulgar en el eje z. Pero mantén debido a nuestros trucos de coordinación, -2px adicionales a lo largo de z. Además, ten en cuenta que las transformaciones de un elemento se aplican De derecha a izquierda, todas las traducciones antes de nuestra matriz especial no se invertirá, sin embargo, todas las traducciones que aparezcan después de nuestra matriz especial sí lo harán. Veamos ¡codifica esto!

<script>
    // ... code from above...
    const factor =
    (scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
    thumb.style.transform = `
    matrix3d(
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, -1
    )
    scale(${1/factor})
    translateZ(${1 - 1/factor}px)
    translateZ(-2px)
    `;
</script>

Tenemos un scrollbar. Y es solo un elemento del DOM al que podemos aplicar diseño de la forma que queramos. Una cosa que es en términos de accesibilidad es hacer que el pulgar responda a hacer clic y arrastrar, ya que muchos usuarios están acostumbrados a interactuar con una barra de desplazamiento de esa manera. Para no alargar esta entrada de blog, no voy a explicar los detalles de esa parte. Consulta la código de biblioteca para obtener más detalles si quieres ver cómo se hace.

¿Qué sucede con iOS?

Ah, mi viejo amigo Safari iOS. Al igual que con el desplazamiento con paralaje, nos encontramos con problema. Debido a que nos desplazamos sobre un elemento, debemos especificar -webkit-overflow-scrolling: touch, pero eso causa acoplamiento 3D y toda nuestra el efecto de desplazamiento deja de funcionar. Resolvimos este problema en la barra de desplazamiento con paralaje. detectando Safari en iOS y recurriendo a position: sticky como solución alternativa haremos lo mismo aquí. Consulta la artículo de paralaje para refrescar tu memoria.

¿Qué ocurre con la barra de desplazamiento del navegador?

En algunos sistemas, tendremos que lidiar con una barra de desplazamiento nativa permanente. Históricamente, la barra de desplazamiento no se podía ocultar (excepto con un pseudoselector no estándar). Así que, para ocultarlo, tenemos que recurrir a un hackeo (sin matemáticas). Completamos nuestra elemento de desplazamiento en un contenedor con overflow-x: hidden y haz que el el elemento de desplazamiento sea más ancho que el contenedor. La barra de desplazamiento nativa del navegador es ahora fuera de la vista.

Fin

Al unir todos estos elementos, podemos crear una imagen personalizada perfecta para un marco barra de desplazamiento, como la de nuestra Demostración del gato Nyan.

Si no ves el gato Nyan, estás experimentando que encontramos y presentamos mientras compilas esta demostración (haz clic en el pulgar para que aparezca el gato Nyan). Chrome es muy bueno para evitar el trabajo innecesario como pintar o animar cosas fuera de la pantalla. La mala noticia es que las travesuras de las matrices hacen creer a Chrome que el GIF del gato Nyan realmente está fuera de la pantalla. Esperamos que esto se solucione pronto.

Ahí lo tienes. Eso fue mucho trabajo. Te felicito por leer toda la más grande. Estas son algunas un verdadero truco para hacerlo, y probablemente rara vez valga la pena el esfuerzo excepto cuando una barra de desplazamiento personalizada es una parte esencial de la experiencia. Sin embargo, es bueno saber que es posible, ¿no? El hecho de que sea tan difícil la barra de desplazamiento personalizada muestra que hay trabajo por hacer en el lado del CSS. ¡Pero no temas! En el futuro, Houdini AnimationWorklet hará lo siguiente: mucho más sencillos con los efectos vinculados con desplazamiento de fotograma perfecto.