hand Inicio
hand JSBloqs
hand GutenBloqs
Qué?
nochedía

DESARROLLO WEB con
REACT y WORDPRESS

Apúntate a la newsletter (escribo algo de tanto en cuanto)
Wallpaper)

CSS y JavaScript para conseguir gradientes animados

1200 palabras
5 minutos
July 9, 2020
curso, cursoscssjavascriptgatsby

No podemos generar gradientes animados directamente

Pero sí podemos simularlos con css, o fabricarlos con JavaScript

Prólogo

Conseguir un gradiente animado no es trivial

Podría serlo, pero en css los gradientes no son colores sino imágenes (por eso no dices background-color: linear-gradient... sino que utilizas la forma background: line...)

Y cómo lo podemos hacer? O lo simulamos con css puro, o lo hacemos de verdad con JavaScript

Vamos allá

  1. Prólogo
  2. Gradientes por css
  3. Gradientes con JavaScript

Gradientes por css

Los gradientes estáticos por css son extraordinariamente potentes en el sentido de que es el propio navegador el que nos renderiza esas imágenes y por lo tanto el rendimiento de nuestra página no se verá afectado

Por cierto, tienes una pequeña herramienta css-gradients para generar gradientes aleatorios por si necesitas inspiración

Cómo los podemos animar?

Más fácil de lo que parece, simplemente dibujando un gradiente gigante y ... moverlo

[ El código utiliza @emotion así que en los codesandbox que toquen no te olvides de añadir las dependencias @emotion/styled y @emotion/core ]

jsx
import React from 'react'
import styled from '@emotion/styled'
export default () => {
return <Background />
}
const Background = styled.div`
width: 100%;
height: 100vh;
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab, #ff1ef8);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
@keyframes gradientBG {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
`
Ver en CodeSandBox

Se trata de un panel 4 veces más grande que nuestro viewport y que al moverlo nos da la impresión de que estamos cambiando el gradiente

Brillante y tremendamente barato, pero cuando acostumbras la vista es verdad que terminas detectando que es un panel que va oscilando

Importa? Eso ya depende de lo que busques con tu página web y tu gradiente animado

Ojo, un detalle importante pero no relacionado con el gradiente

Si te fijas, ese gradiente ocupa todo el largo de la pantalla con height: 100vh;

Esto no funciona como debería en móviles por el impacto que tiene la barra de navegación que a veces aparece y a veces no

Para solucionarlo basta con añadir un custom hook que nos calcule el heigh de verdad y nos lo vuelve como variable css, y a partir de ahí listos

jsx
import React, { useEffect } from 'react'
import styled from '@emotion/styled'
// custom hook para fijar la altura de la página en una variable `css`
const useReplace100vh = () => {
const setCssVar = () =>
window.scrollY === 0 && document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`)
useEffect(() => setCssVar(), [])
// cada vez que la ventana cambie de tamaño volvemos a fijar la variable
// pero lo desactivo para no desviarnos del tema y tener que poner también el hook aquí
// useWindowResize(setCssVar, 500)
return ''
}
export default () => {
useReplace100vh()
return <Background />
}
const Background = styled.div`
width: 100%;
min-height: calc(var(--vh, 1vh) * 100);
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab, #ff1ef8);
background-size: 400% 400%;
animation: gradientBG 15s ease infinite;
@keyframes gradientBG {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
`
Ver en CodeSandBox

Básicamente ponemos como altura el equivalente a los 100vh de antes pero a partir de la variable --vh que hemos fijado con useReplace100vh

Esa variable se basa en innerHeight, y esto sí tiene en cuenta la potencial barra superior de un navegador en móviles

Esto nos sirve por si queremos que ese fondo ocupe toda la pantalla, lo comento y me olvido pero que lo sepas

A partir de aquí, se trata de

  • Definir un gradiente con background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab, #ff1ef8);

  • Definir su tamaño con background-size: 400% 400%;

  • Definir un loop eterno que mueva ese gradiente con background-position

Fácil, eficiente y efectivo

Las animaciones en css constan de 2 momentos

  1. Definimos como se anima
css
{
animation: gradientBG 15s ease infinite;
}

Con el nombre que queramos (aquí gradientBG), la duración, el tipo de movimiento y si queremos que haya un loop infinito pues con infinite

El tipo de movimiento se define con curvas bezier pero la mayoría de veces nos bastará y sobrará con las que están predefinidas, como ease

Éstas son las predefinidas

image

Y si quieres curvas más sofisticadas puedes utilizar las de easings.net)

  1. Definimos la animación
css
{
@keyframes gradientBG {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
}

Con la palabra clave @keyframes y seguido del nombre que antes hemos puesto

Luego definimos los porcentajes de la animación que nos parezcan, fíjate que aquí el 0% y el 100% son iguales, esto nos permitirá hacer un loop infinito sin que se perciba el inicio y final de la animación

Y lo que hacemos es cambiar la posición del background, donde establecemos la x y la y

Aquí por lo tanto estaremos siempre moviéndonos en el eje de las x

Podemos sofisticarlo un poco más y añadir un sol que nos aparece lentamente con radial-gradient

css
{
background: radial-gradient(circle closest-side at 5% 5%, #c77e7e, #688ea7);
}

Aquí le estamos definiendo un círculo que termine cuando toque su lado más cercano (con closest-side), y luego utilizamos los colores indicados

Puedes ver la diferencia entre closest-side y farthest-side en esta imagen

image

Pero lo suyo es que lo pruebes en el codesandbox

jsx
import React from 'react'
import styled from '@emotion/styled'
export default () => {
return (
<>
<Background />
<BackgroundImg />
</>
)
}
const Background = styled.div`
width: 100%;
height: 100vh;
background: radial-gradient(circle closest-side at 15% 15%, #ffc600, #ffffff);
background-size: 400% 400%;
animation: gradientBG 1s ease infinite;
@keyframes gradientBG {
0% {
background-position: 35% 20%;
}
50% {
background-position: 10% 20%;
}
100% {
background-position: 35% 20%;
}
}
`
const BackgroundImg = styled.div`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
background-image: url(http://source.unsplash.com/nD2WzCZrlLE/2551x1701), radial-gradient(circle farthest-corner at 10%
20%, #fdc168 0%, #fb8080 90%);
background-size: cover;
mix-blend-mode: hard-light;
background-blend-mode: multiply;
`
Ver en CodeSandBox

Primero defino un fondo inmovil con <BackgroundImg>, lo hago con background-image, y añado una imagen de fondo y un gradiente radial

Y luego, éstas dos imágenes las mezclo con mix-blend-mode: hard-light; y background-blend-mode: multiply;

Y después, defino mí gradiente animado con <Background> donde en lugar de un fondo dibujo una suerte de sol

En el codesandbox verás como el sol aparece y desaparece muy rápidamente, pone un tiempo mucho más largo y ya lo tendrás

background-blend-mode se utiliza para mezclar background-image que esten en una misma instrucción

Si se quiere mezclar <div> distintos se utiliza mix-blend-mode

Y si lo que quieres es volver a la animación inicial, tan sencillo como cambiar los componentes

Gradientes con JavaScript

Para generar gradientes reales y no mover un panel gigante (que si tienes la vista acostumbrada los detectas, pero quién la tiene?) necesitamos JavaScript

Lo que haremos será modificar los colores

Cómo funcionan los colores en JavaScript

Si has escogido tus colores con el Chrome Developer Tools habrás descubierto que un mismo color se puede expresar de distintas maneras

css
{
color: #ff6500;
color: rgb(255, 101, 0);
color: hsl(24, 100%, 50%);
}

El primero de todo es el hex, el que a mi más me gusta por ser más conciso y que representa lo mismo que el rgb pero en formato hexadecimal

Utilizando la función rgb() es por lo tanto lo mismo, y se trata de determinar el Red Green y Blue que especifica eso mismo, el rojo, verde y azul

Y el último es el hsl() por Hue, Saturation, Lightness, conceptos que si has ajustado tus fotos con algún programa especializado (yo soy de darktable) sabrás qué significan

  • El Hue nos determina el color, un valor de 0 a 360 que representa una rueda de colores
  • El Saturation nos determina lo intenso del color, una saturación de 0 es convertir el color en gris
  • Y el brillo, más del 50% implica añadir blanco (es decir, del 50 a 100 añadimos blanco, y menos del 50% añadimos negro)

*Podemos utilizar las funciones rgba() y hsla() y añadir un cuarto parámetro que implicará la opacidad

Entonces, si queremos un gradiente que pase por toda la tonalidad de colores lo más sencillo es utilizar la notación hsl() y cambiar solamente el Hue

Lo primero que haremos será definir el gradiente y pasárselo al componente <Background>

jsx
import React, { useState, useEffect } from 'react'
import styled from '@emotion/styled'
export default () => {
// definimos el gradiente en una lista
const hsl = [
[0, 100, 50],
[360, 100, 50],
]
// función para convertir esa lista de antes en un gradiente
const hsl_fix = arr =>
`hsl(${arr[0][0]},${arr[0][1]}%,${arr[0][2]}%),
hsl(${arr[1][0]},${arr[1][1]}%,${arr[1][2]}%)`
// función para cambiar de color, el gradiente y el estado
const change_color = () => {
const next = color + 1
setColor(next)
hsl[0][0] = next
setGradient(`0deg, ${hsl_fix(hsl)}`)
}
// almacenamos el gradiente en un estado
const [gradient, setGradient] = useState(`0deg, ${hsl_fix(hsl)}`)
// almacenamos los colores
const [color, setColor] = useState(0)
// cada vez que color nos cambie, volvemos a cambiar el color
useEffect(() => {
if (color < 180) window.requestAnimationFrame(change_color)
}, [color])
return <Background gradient={gradient} />
}
const Background = styled.div`
width: 100%;
height: 100vh;
background: linear-gradient(${props => props.gradient});
background-size: 100% 100%;
`
Ver en CodeSandBox

La función change_color nos cambia el color, en concreto el color del inicio y el primer número del color expresado en hsl

Cuando ejecutamos esa función?

Desde siempre, empezamos con el useState donde lo inicializamos, y a partir de aquí tenemos el useEffect que se nos ejecuta cada vez que color vuelva a cambiar

Esto lo hacemos hasta que color alcance los 180

Y utilizando window.requestAnimationFrame nos aseguramos que ese cambio no va demasiado rápido sino que se espera a que el navegador haya terminado con sus renderizados

Cómo podríamos mejorarlo?

Cambiando todos los valores del hsl, y a distintas velocidades, donde por velocidad se entiende la magnitud del cambio (cambiar de 1 en 1 o de 10 en 10)

Y además, en lugar de partir siempre desde el mismo gradiente, vamos a empezar desde un punto aleatorio

  1. Definimos un estado inicial random
  2. Lo cambiamos a distintas velocidades
  3. Ajustamos para que los valores siempre estén entre 0 y 255

El problema es que cambiando todos los valores de hsl al final el efecto es algo repetitivo, por lo que una opción mejor es fijar las dos últimas variables y cambiar sólo la primera (que es el color propiamente dicho)

jsx
import React, { useState, useEffect } from 'react'
import styled from '@emotion/styled'
// función que nos devuelve un número random entre 0 y 255
const rn = () => Math.floor(Math.random() * 255)
export default () => {
// definimos el gradiente en un estado
const [hsl, setHsl] = useState([
[rn(), rn(), rn()],
[rn(), rn(), rn()],
])
// definimos la magnitud de los cambios del primer valor
const speed = 1
// función para convertir esa lista de antes en un gradiente
const hsl_fix = arr =>
`hsl(${arr[0][0]},${arr[0][1]}%,${arr[0][2]}%),
hsl(${arr[1][0]},${arr[1][1]}%,${arr[1][2]}%)`
// almacenamos el gradiente en un estado
const [gradient, setGradient] = useState(`0deg, ${hsl_fix(hsl)}`)
// función para cambiar de color, el gradiente y el estado
const change_color = () => {
// copiamos y cambiamos el estado actual
const nextHsl = hsl.map(el => {
el[0] += speed
if (el[0] > 255) {
el[0] = 0
el[1] = rn()
el[2] = rn()
}
return el
})
setHsl(nextHsl) // actualizamos el estado
setGradient(`0deg, ${hsl_fix(nextHsl)}`) // actualizamos el gradiente
}
// cada vez que color nos cambie, volvemos a cambiar el color
useEffect(() => {
window.requestAnimationFrame(change_color)
}, [gradient])
return <Background gradient={gradient} />
}
const Background = styled.div`
width: 100%;
height: 100vh;
background: linear-gradient(${props => props.gradient});
background-size: 100% 100%;
`
Ver en CodeSandBox

El resultado final es un gradiente que no es continuo sino que cuando termina empieza de nuevo dando un efecto curioso

Si quisiésemos un efecto continuo posiblemente nos interesase cambiar de hsl a rgb y no reiniciar ningún valor sino que cuando éste llegase a 255 simplemente invertir el cambio para que volviese a 0 y así mantener un cambio suave y contínuo

Y si queremos implementarlo vía canvas, siempre podemos utilizar o explorar la fantástica librería granim.js

🙋‍♂️

Qué tal el curso?

👌 Bien 🙌🙌
👍 Bien, pero algunas cosas podrían explicarse mejor 😬
🤷‍♂️ Da por sentadas demasiadas cosas 😒
🤷‍♂️ A ver, hay poca chicha 😬
🤷‍♂️ Los ejemplos no son muy claros 🙇‍♂️
🤷‍♂️ No se entiende, está mal escrito 👎
✍️ Hay errores, revísalo en cuanto puedas 🙏
Enviar Feedback ✍️
El texto está en blanco!
Gracias por enviarme tu opinión
👍

Si quieres explorar más cursos y más entradas en el blog, los tienes todos en la página principal, y si el contenido te ha ayudado dame las gracias por ejemplo por twitter con este enlace 👍

Privacidad
by kuworking.com
[ 2020 >> kuworking ]