Logotipo de Zephyrnet

Cómo crear formas y patrones ondulados en CSS

Fecha:

La ola es probablemente una de las formas más difíciles de hacer en CSS. Siempre tratamos de aproximarlo con propiedades como border-radius y muchos números mágicos hasta que consigamos algo que se sienta un poco cerca. Y eso es incluso antes de entrar en patrones ondulados, que son más difíciles.

"¡SVG!" podría decir, y probablemente tenga razón, que es una mejor manera de hacerlo. Pero veremos que CSS puede hacer buenas olas y el código no tiene que ser del todo loco. ¿Y adivina qué? tengo un generador en línea para hacerlo aún más trivial!

Si juegas con el generador, puedes ver que el CSS que escupe es solo dos gradientes y una propiedad de máscara CSS: solo esas dos cosas y podemos hacer cualquier tipo de forma de onda o patrón. Sin mencionar que podemos controlar fácilmente el tamaño y la curvatura de las olas mientras lo hacemos.

Algunos de los valores pueden verse como “números mágicos” pero en realidad hay una lógica detrás de ellos y diseccionaremos el código y descubriremos todos los secretos detrás de la creación de olas.

Este artículo es una continuación de uno anterior donde construí todo tipo de diferentes bordes en zig-zag, con alcance, festoneados y, sí, ondulados. Recomiendo encarecidamente consultar ese artículo, ya que utiliza la misma técnica que cubriremos aquí, pero con mayor detalle.

Las matemáticas detrás de las ondas

Estrictamente hablando, no hay una fórmula mágica detrás de las formas onduladas. Cualquier forma con curvas que suben y bajan puede llamarse onda, por lo que no nos limitaremos a matemáticas complejas. En cambio, reproduciremos una onda usando los conceptos básicos de geometría.

Comencemos con un ejemplo simple usando dos formas circulares:

Tenemos dos círculos con el mismo radio uno al lado del otro. ¿Ves esa línea roja? Cubre la mitad superior del primer círculo y la mitad inferior del segundo. Ahora imagina que tomas esa línea y la repites.

Una línea roja ondulada en forma de ondas.

Ya vemos la ola. Ahora vamos a llenar la parte de abajo (o la de arriba) para obtener lo siguiente:

Patrón de onda roja.

¡Tada! Tenemos una forma ondulada y una que podemos controlar usando una variable para los radios del círculo. Esta es una de las olas más fáciles que podemos hacer y es la que mostré en this Artículo anterior

Agreguemos un poco de complejidad tomando la primera ilustración y moviendo un poco los círculos:

Dos círculos grises con dos líneas discontinuas que se bisectan que indican el espaciado.

Todavía tenemos dos círculos con los mismos radios pero ya no están alineados horizontalmente. En este caso, la línea roja ya no cubre la mitad del área de cada círculo, sino un área más pequeña. Esta área está limitada por la línea roja discontinua. Esa línea cruza el punto donde ambos círculos se encuentran.

Ahora toma esa línea y repítela y obtendrás otra ola, una más suave.

Una línea ondulada roja.
Un patrón de onda roja.

Creo que entiendes la idea. Al controlar la posición y el tamaño de los círculos, podemos crear cualquier onda que queramos. Incluso podemos crear variables para ellos, que llamaré P y S, respectivamente.

Probablemente haya notado que, en el generador en línea, controlamos la onda usando dos entradas. Se asignan a las variables anteriores. S es el "Tamaño de la onda" y P es la “curvatura de la onda”.

Yo definiré P as P = m*S donde m es la variable que ajustas al actualizar la curvatura de la onda. Esto nos permite tener siempre la misma curvatura, incluso si actualizamos S.

m puede ser cualquier valor entre 0 y 2. 0 nos dará el primer caso particular donde ambos círculos están alineados horizontalmente. 2 es una especie de valor máximo. Podemos ir más grandes, pero después de algunas pruebas descubrí que cualquier cosa por encima 2 produce malas formas planas.

¡No olvidemos el radio de nuestro círculo! Eso también se puede definir usando S y P Me gusta esto:

R = sqrt(P² + S²)/2

Cuándo P es igual a 0, tendremos R = S/2.

¡Tenemos todo para comenzar a convertir todo esto en gradientes en CSS!

Creando degradados

Nuestras ondas usan círculos, y cuando hablamos de círculos hablamos de gradientes radiales. Y dado que dos círculos definen nuestra onda, lógicamente usaremos dos gradientes radiales.

Comenzaremos con el caso particular donde P es igual a 0. Aquí está la ilustración del primer gradiente:

Este gradiente crea la primera curvatura mientras llena toda el área del fondo, el "agua" de la ola, por así decirlo.

.wave {
  --size: 50px;

  mask: radial-gradient(var(--size) at 50% 0%, #0000 99%, red 101%) 
    50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

La --size La variable define el radio y el tamaño del degradado radial. Si lo comparamos con el S variable, entonces es igual a S/2.

Ahora agreguemos el segundo gradiente:

El segundo gradiente no es más que un círculo para completar nuestra ola:

radial-gradient(var(--size) at 50% var(--size), blue 99%, #0000 101%) 
  calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%

Si buscas el articulo anterior verás que simplemente estoy repitiendo lo que ya hice allí.

Seguí ambos artículos pero las configuraciones de gradiente no son las mismas.

Eso es porque podemos llegar al mismo resultado usando diferentes configuraciones de gradiente. Notarás una ligera diferencia en la alineación si comparas ambas configuraciones, pero el truco es el mismo. Esto puede resultar confuso si no está familiarizado con los degradados, pero no se preocupe. Con un poco de práctica, se acostumbrará a ellos y descubrirá por sí mismo que diferentes sintaxis pueden conducir al mismo resultado.

Aquí está el código completo para nuestra primera ola:

.wave {
  --size: 50px;

  mask:
    radial-gradient(var(--size) at 50% var(--size),#000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--size) at 50% 0px, #0000 99%, #000 101%) 
      50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

Ahora tomemos este código y ajústelo donde introducimos una variable que lo hace completamente reutilizable para crear cualquier ola que queramos. Como vimos en la sección anterior, el truco principal es mover los círculos para que no estén más alineados, así que actualicemos la posición de cada uno. Moveremos el primero hacia arriba y el segundo hacia abajo.

Nuestro código se verá así:

.wave {
  --size: 50px;
  --p: 25px;

  mask:
    radial-gradient(var(--size) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--size) at 50% calc(-1*var(--p)), #0000 99%, #000 101%) 
      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
}

he introducido un nuevo --p variable que se usa para definir la posición central de cada círculo. El primer gradiente está usando 50% calc(-1*var(--p)), por lo que su centro se mueve hacia arriba mientras que el segundo está usando calc(var(--size) + var(--p)) para moverlo hacia abajo.

Una demostración vale más que mil palabras:

Los círculos no están alineados ni se tocan entre sí. Los espaciamos mucho sin cambiar sus radios, por lo que perdimos nuestra ola. Pero podemos arreglar las cosas usando las mismas matemáticas que usamos antes para calcular el nuevo radio. Recuérdalo R = sqrt(P² + S²)/2. En nuestro caso, --size es igual a S/2; lo mismo para --p que también es igual a P/2 ya que estamos moviendo ambos círculos. Entonces, la distancia entre sus puntos centrales es el doble del valor de --p para esto:

R = sqrt(var(--size) * var(--size) + var(--p) * var(--p))

Eso nos da un resultado de 55.9px.

¡Nuestra ola está de regreso! Insertemos esa ecuación en nuestro CSS:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p) * var(--p) + var(--size)*var(--size));

  mask:
    radial-gradient(var(--R) at 50% calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0 / calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(-1*var(--p)), #0000 99%, #000 101%) 
      50% var(--size)/calc(4 * var(--size)) 100% repeat-x;
}

Este es un código CSS válido. sqrt() es parte de la especificación, pero en el momento en que escribo esto, no hay soporte de navegador para ello. Eso significa que necesitamos una pizca de JavaScript o Sass para calcular ese valor hasta que seamos más amplios. sqrt() apoyo.

Esto es genial: todo lo que se necesita son dos gradientes para obtener una onda genial que puedes aplicar a cualquier elemento usando el mask propiedad. No más prueba y error: ¡todo lo que necesita es actualizar dos variables y listo!

Invirtiendo la ola

¿Qué pasa si queremos que las olas vayan en la otra dirección, donde estamos llenando el "cielo" en lugar del "agua". Lo creas o no, todo lo que tenemos que hacer es actualizar dos valores:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p) * var(--p) + var(--size) * var(--size));

  mask:
    radial-gradient(var(--R) at 50% calc(100% - (var(--size) + var(--p))), #000 99%, #0000 101%)
      calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(100% + var(--p)), #0000 99%, #000 101%) 
      50% calc(100% - var(--size)) / calc(4 * var(--size)) 100% repeat-x;
}

Todo lo que hice allí fue agregar un desplazamiento igual a 100%, resaltado arriba. Aquí está el resultado:

Podemos considerar una sintaxis más amigable usando valores de palabras clave para hacerlo aún más fácil:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size) * var(--size));

  mask:
    radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      calc(50% - 2 * var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% bottom var(--size) / calc(4 * var(--size)) 100% repeat-x;
}

Estamos usando el left y bottom palabras clave para especificar los lados y el desplazamiento. De forma predeterminada, el navegador tiene como valor predeterminado left y top — por eso usamos 100% para mover el elemento al fondo. En realidad, lo estamos moviendo desde el top by 100%, por lo que es realmente lo mismo que decir bottom. ¡Mucho más fácil de leer que las matemáticas!

Con esta sintaxis actualizada, todo lo que tenemos que hacer es intercambiar bottom para top — o viceversa — para cambiar la dirección de la onda.

Y si desea obtener ondas superiores e inferiores, combinamos todos los gradientes en una sola declaración:

.wave {
  --size: 50px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  mask:
    /* Gradient 1 */
    radial-gradient(var(--R) at left 50% bottom calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      left calc(50% - 2*var(--size)) bottom 0 / calc(4 * var(--size)) 51% repeat-x,
    /* Gradient 2 */
    radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% bottom var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x,
    /* Gradient 3 */
    radial-gradient(var(--R) at left 50% top calc(var(--size) + var(--p)), #000 99%, #0000 101%) 
      left calc(50% - 2 * var(--size)) top 0 / calc(4 * var(--size)) 51% repeat-x,
    /* Gradient 4 */
    radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), #0000 99%, #000 101%) 
      left 50% top var(--size) / calc(4 * var(--size)) calc(51% - var(--size)) repeat-x;
}

Si revisas el código, verás que además de combinar todos los degradados, también he reducido su altura de 100% a 51% para que ambos cubran la mitad del elemento. Sí, 51%. Necesitamos ese pequeño porcentaje adicional para una pequeña superposición que evite las lagunas.

¿Qué pasa con los lados izquierdo y derecho?

¡Es tu tarea! Tome lo que hicimos con los lados superior e inferior e intente actualizar los valores para obtener los valores derecho e izquierdo. No te preocupes, es fácil y lo único que tienes que hacer es intercambiar valores.

Si tienes problemas, siempre puedes usar el generador en línea para comprobar el código y visualizar el resultado.

lineas onduladas

Anteriormente, hicimos nuestra primera ola usando una línea roja y luego llenamos la parte inferior del elemento. ¿Qué tal esa línea ondulada? ¡Eso también es una ola! Aún mejor es si podemos controlar su grosor con una variable para poder reutilizarlo. ¡Vamos a hacerlo!

No vamos a empezar de cero sino que vamos a tomar el código anterior y actualizarlo. Lo primero que debe hacer es actualizar las paradas de color de los degradados. Ambos degradados parten de un color transparente a uno opaco, o viceversa. Para simular una línea o borde, debemos comenzar desde transparente, ir a opaco y luego volver a transparente nuevamente:

#0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%

Creo que ya adivinaste que el --b variable es lo que estamos usando para controlar el grosor de la línea. Apliquemos esto a nuestros gradientes:

Sí, el resultado está lejos de ser una línea ondulada. Pero mirando de cerca, podemos ver que un gradiente está creando correctamente la curvatura inferior. Entonces, todo lo que realmente necesitamos hacer es rectificar el segundo gradiente. En lugar de mantener un círculo completo, hagamos uno parcial como el otro degradado.

Todavía lejos, ¡pero tenemos las dos curvaturas que necesitamos! Si revisa el código, verá que tenemos dos gradientes idénticos. La única diferencia es su posicionamiento:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
  mask:
    radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g)) 
      calc(50% - 2*var(--size)) 0/calc(4*var(--size)) 100%,
    radial-gradient(var(--R) at left 50% top    calc(-1*var(--p)), var(--_g)) 
      50% var(--size)/calc(4*var(--size)) 100%;
}

Ahora necesitamos ajustar el tamaño y la posición para la forma final. Ya no necesitamos que el degradado sea de altura completa, por lo que podemos reemplazar 100% con este:

/* Size plus thickness */
calc(var(--size) + var(--b))

No hay lógica matemática detrás de este valor. Solo necesita ser lo suficientemente grande para la curvatura. Veremos su efecto en el patrón en un momento. Mientras tanto, también actualicemos la posición para centrar verticalmente los degradados:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: sqrt(var(--p)*var(--p) + var(--size)*var(--size));

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;  
  mask:
    radial-gradient(var(--R) at left 50% bottom calc(-1*var(--p)), var(--_g)) 
      calc(50% - 2*var(--size)) 50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat,
    radial-gradient(var(--R) at left 50% top calc(-1 * var(--p)), var(--_g)) 50%
      50%/calc(4 * var(--size)) calc(var(--size) + var(--b)) no-repeat;
}

Todavía no estoy del todo allí:

Un gradiente necesita moverse un poco hacia abajo y el otro un poco hacia arriba. Ambos necesitan moverse la mitad de su altura.

¡Estamos casi alli! Necesitamos una pequeña solución para que el radio tenga una superposición perfecta. Ambas líneas deben compensarse por la mitad del borde (--b) espesor:

¡Lo conseguimos! Una línea ondulada perfecta que podemos ajustar fácilmente controlando algunas variables:

.wave {
  --size: 50px;
  --b: 10px;
  --p: 25px;
  --R: calc(sqrt(var(--p) * var(--p) + var(--size) * var(--size)) + var(--b) / 2);

  --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
  mask:
    radial-gradient(var(--R) at left 50% bottom calc(-1 * var(--p)), var(--_g)) 
     calc(50% - 2*var(--size)) calc(50% - var(--size)/2 - var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x,
    radial-gradient(var(--R) at left 50% top calc(-1*var(--p)),var(--_g)) 
     50%  calc(50% + var(--size)/2 + var(--b)/2) / calc(4 * var(--size)) calc(var(--size) + var(--b)) repeat-x;
}

Sé que la lógica toma un poco de comprensión. Eso está bien y, como dije, crear una forma ondulada en CSS no es fácil, sin mencionar las complicadas matemáticas detrás de esto. es por eso que el generador en línea es un salvavidas: puede obtener fácilmente el código final incluso si no comprende completamente la lógica detrás de él.

patrones ondulados

¡Podemos hacer un patrón a partir de la línea ondulada que acabamos de crear!

¡Oh no, el código del patrón será aún más difícil de entender!

¡De nada! Ya tenemos el código. Todo lo que tenemos que hacer es eliminar repeat-x de lo que ya tenemos, y tada. 🎉

Un bonito patrón ondulado. ¿Recuerdas la ecuación que dije que revisaríamos?

/* Size plus thickness */
calc(var(--size) + var(--b))

Bueno, esto es lo que controla la distancia entre las líneas en el patrón. Podemos convertirlo en una variable, pero no hay necesidad de mayor complejidad. Ni siquiera estoy usando una variable para eso en el generador. Quizás cambie eso más tarde.

Aquí está el mismo patrón yendo en una dirección diferente:

Le estoy proporcionando el código en esa demostración, pero me gustaría que lo analizara y entendiera qué cambios hice para que eso sucediera.

Simplificando el código

En todas las demostraciones anteriores, siempre definimos el --size y --p independientemente. Pero, ¿recuerdas que mencioné anteriormente que el generador en línea evalúa P como igual a m*S, Donde m controla la curvatura de la onda? Al definir un multiplicador fijo, podemos trabajar con una onda en particular y el código puede volverse más fácil. Esto es lo que necesitaremos en la mayoría de los casos: una forma ondulada específica y una variable para controlar su tamaño.

Actualicemos nuestro código e introduzcamos el m variable:

.wave {
  --size: 50px;
  --R: calc(var(--size) * sqrt(var(--m) * var(--m) + 1));

  mask:
    radial-gradient(var(--R) at 50% calc(var(--size) * (1 + var(--m))), #000 99%, #0000 101%) 
      calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
    radial-gradient(var(--R) at 50% calc(-1 * var(--size) * var(--m)), #0000 99%, #000 101%) 
      50% var(--size) / calc(4 * var(--size)) 100% repeat-x;
  }

Como puede ver, ya no necesitamos el --p variable. lo reemplacé con var(--m)*var(--size), y optimizó algunas de las matemáticas en consecuencia. Ahora, si queremos trabajar con una forma ondulada en particular, podemos omitir el --m variable y reemplazarlo con un valor fijo. Intentemos .8 por ejemplo.

--size: 50px;
--R: calc(var(--size) * 1.28);

mask:
  radial-gradient(var(--R) at 50% calc(1.8 * var(--size)), #000 99%, #0000 101%) 
    calc(50% - 2*var(--size)) 0/calc(4 * var(--size)) 100%,
  radial-gradient(var(--R) at 50% calc(-.8 * var(--size)), #0000 99%, #000 101%) 
    50% var(--size) / calc(4 * var(--size)) 100% repeat-x;

¿Ves cómo el código es más fácil ahora? Solo una variable para controlar su ola, además ya no necesita depender de ella sqrt() que no tiene soporte para navegador!

Puede aplicar la misma lógica a todas las demostraciones que vimos, incluso para las líneas onduladas y el patrón. Comencé con una explicación matemática detallada y di el código genérico, pero es posible que necesite un código más fácil en un caso de uso real. Esto es lo que estoy haciendo todo el tiempo. Rara vez uso el código genérico, pero siempre considero una versión simplificada, especialmente porque, en la mayoría de los casos, estoy usando algunos valores conocidos que no necesitan almacenarse como variables. (Spoiler de alerta: ¡Compartiré algunos ejemplos al final!)

Limitaciones de este enfoque

Matemáticamente, el código que creamos debería darnos formas y patrones ondulados perfectos, pero en realidad, nos enfrentaremos a algunos resultados extraños. Entonces, sí, este método tiene sus limitaciones. Por ejemplo, el generador en línea es capaz de producir malos resultados, especialmente con líneas onduladas. Parte del problema se debe a una combinación particular de valores donde el resultado se codifica, como usar un valor grande para el grosor del borde en comparación con el tamaño:

Para los otros casos, es el problema relacionado con algún redondeo lo que resultará en desalineación y espacios entre las ondas:

Dicho esto, sigo pensando que el método que cubrimos sigue siendo bueno porque produce ondas suaves en la mayoría de los casos, y podemos evitar fácilmente los malos resultados jugando con diferentes valores hasta que lo consigamos perfecto.

Terminando

Espero que después de este artículo, ya no tengas que buscar a tientas con prueba y error para construir una forma o patrón ondulado. Además al generador en línea, ¡tienes todos los secretos matemáticos para crear cualquier tipo de ola que quieras!

El artículo termina aquí, pero ahora tiene una herramienta poderosa para crear diseños elegantes que usan formas onduladas. Aquí tienes inspiración para que empieces...

¿Y usted? ¡Usa mi generador en línea (o escribe el código manualmente si ya aprendiste todas las matemáticas de memoria) y muéstrame tus creaciones! Tengamos una buena colección en la sección de comentarios.

punto_img

Información más reciente

punto_img

Habla con nosotros!

¡Hola! ¿Le puedo ayudar en algo?