portada

La programacion de nuestra web

¡Hola #Yobers!

Tras el anterior artículo donde os hablábamos del proceso de creativo detrás de nuestra web, continuamos con el siguiente paso: hacerla realidad.

Vamos a dividir la explicación en 3 partes:

  • Tecnología
  • Preparación del proyecto
  • Implementación

Tecnología

La web se trata de HTML estático, potenciado por Javascript. Para la generación, nos hemos apoyado en GatsbyJS, un framework basado en React. Básicamente se programan los componentes (tanto la lógica como el aspecto visual), para crear cada página a partir de los distintos data sources.

En nuestro caso, obtenemos los datos desde Sanity, que es un headless CMS (Content Management System, o sistema de gestión de contenido) que tras definir la estructura de los datos que se van a utilizar, genera un panel para gestionarlos, más adelante se explica con más detalle.

Para el hosting utilizamos Netlify, que automatiza la generación de los HTML estáticos que proporciona GatsbyJS, tras cada cambio que subamos a GitHub (donde tenéis el código completo de la web).

Preparación del proyecto

El proyecto se compone de 2 carpetas:

En web es donde configuramos GatsbyJS para que genere los ficheros de la página. Para ello es importante un fichero, gatsby-node.js, que es donde le decimos al framework qué páginas debe crear. Éste debe exportar una función llamada createPages. Para mayor legibilidad del código, su contenido se ha extraído a 3 funciones, cuyos nombres creo que son bastante autoexplicativos.

1
2
3
4
5
exports.createPages = async ({ graphql, actions, reporter }) => {
  await createBlogPostPages(graphql, actions, reporter)
  await createProjectPages(graphql, actions, reporter)
  await createProductPages(graphql, actions, reporter)
}

Vamos a explicar solo una de ellas, ya que las tres siguen la misma estructura.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
async function createBlogPostPages (graphql, actions, reporter) {
  const { createPage } = actions
  const result = await graphql(`
    {
      allSanityPost(filter: { slug: { current: { ne: null } } }) {
        edges {
          node {
            id
            publishedAt
            slug {
              current
            }
          }
        }
      }
    }
  `)
 
  if (result.errors) throw result.errors
 
  const postEdges = (result.data.allSanityPost || {}).edges || []
 
  postEdges.forEach((edge, index) => {
    const { id, slug = {}, publishedAt } = edge.node
    const dateSegment = format(publishedAt, 'YYYY/MM')
    const path = `/blog/${dateSegment}/${slug.current}/`
 
    reporter.info(`Creating blog post page: ${path}`)
 
    createPage({
      path,
      component: require.resolve('./src/templates/blog-post.js'),
      context: { id }
    })
  })
}

Para crear las páginas de este blog, lo primero que hay que hacer es pedirle a Sanity la lista de artículos que se han subido al CMS. Y esto se hace mediante la query GraphQL (esto da para otro artículo), donde se le piden todos los Post donde el slug no sea nulo.

Ahora toca crear una página para cada uno de los post que nos devuelve, y para ello se llama a la función createPage que nos proporciona Gatsby. A esta función hay que especificarle: el path (la URL), que generamos mediante los datos que tenemos del post, como la fecha previo formateo, y el slug (por eso hacíamos la petición especificando que este campo tuviese valor); el componente, que es la plantilla que hemos programado para ese tipo de página; y opcionalmente el contexto, que es información adicional que queremos que tenga la plantilla. En este caso a la plantilla de blog se le pasa el id, ya que esta plantilla se encargará de pedirle a Sanity toda la información necesaria para mostrar el Post.

Otro fichero importante es gatsby-config.js, donde especificaremos los plugins que se utilizarán en el proyecto:

  • react-helmet (para cambiar la información del head del HTML)
  • google-fonts (para bajarse la fuente Montserrat, que es la utilizada en la web)
  • google-analytics (introduce el script para obtener datos de tráfico)
  • mailchimp (gestión de la newsletter)

La segunda carpeta del proyecto es studio, donde se configura la estructura de Sanity. Para ello nos centramos en schemas, donde se encuetran todos los esquemas de los datos que necesitamos.

Para seguir con los ejemplos de los Post, nos vamos a centrar en post.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
export default {
  name: 'post',
  title: 'Blog Post',
  type: 'document',
  fields: [
    {
      name: 'title',
      title: 'Título',
      type: 'string'
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      description: 'Some frontend will require a slug to be set to be able to show the post',
      options: {
        source: 'title',
        maxLength: 96
      }
    },
    {
      name: 'publishedAt',
      title: 'Fecha de publicación',
      description: 'You can use this field to schedule post where you show them',
      type: 'datetime'
    },
    {
      name: 'excerpt',
      title: 'Descripción',
      type: 'blockText'
    },
    {
      name: 'authors',
      title: 'Autores',
      type: 'array',
      of: [{ type: 'postAuthor' }]
    },
    {
      name: 'cover',
      title: 'Cover',
      type: 'mainImage'
    },
    {
      name: 'categories',
      title: 'Categorías',
      type: 'array',
      of: [{ type: 'reference', to: { type: 'category' } }]
    },
    {
      name: 'body',
      title: 'Contenido',
      type: 'blockContent'
    }
  ],
  orderings: [
    {
      title: 'Publishing date new–>old',
      name: 'publishingDateAsc',
      by: [{ field: 'publishedAt', direction: 'asc' }, { field: 'title', direction: 'asc' }]
    },
    {
      title: 'Publishing date old->new',
      name: 'publishingDateDesc',
      by: [{ field: 'publishedAt', direction: 'desc' }, { field: 'title', direction: 'asc' }]
    }
  ],
  preview: {
    select: {
      title: 'title',
      publishedAt: 'publishedAt',
      image: 'cover'
    },
    prepare ({ title = 'No title', publishedAt, image }) {
      return {
        title,
        subtitle: publishedAt
          ? new Date(publishedAt).toLocaleDateString()
          : 'Missing publishing date',
        media: image
      }
    }
  }
}

Simplemente es un objeto donde se especifica el nombre del recurso, el título con el que aparecerá en el dashboard, el tipo, los campos que tendrá y la preview, en caso de queramos especificar qué campos queremos mostrar cuando nuestro recurso se muestre en una lista. Hay varios tipos definidos por defecto por Sanity: document, string, datetime, array… Pero además podemos usar los nuestros propios, como se hace por ejemplo con el campo authors, donde se puede hacer referencia al tipo de dato author, que tenemos creado en otro fichero de la misma manera que éste, y así Sanity los referenciará automáticamente.

Panel del creación de un post
Panel del creación de un post

Una vez lo tenemos todo, falta el proceso de subir la web y hacerla pública. Como hemos dicho, para ello utilizamos Netlify, donde configuramos que se hagan builds automáticas si ocurren dos cosas. O bien que se haya actualizado el contenido de Sanity, o que haya cambiado el código de GitHub. Todo esto hará que se vuelvan a generar los ficheros de la web.

Para ello hay que poner el webhook que te proporciona Netlify en Sanity y GitHub, que es lo que notifica a la plataforma de los cambios.

Implementación

Pasamos ahora a la parte visual, a la programación de la web.

Y comenzamos con el inicio, la sección Hero, donde destaca principalmente la animación de de las imágenes. Pero antes, hablemos de la estructura.

La disposición de los elementos está hecha con CSS Grid, que nos permite alinear los elementos en filas y columnas de manera sencilla. En el contenedor, aplicamos la clase hero, donde definimos el grid con 4 columnas y 4 filas.

1
2
3
4
5
6
7
8
9
10
.hero {
  display: grid;
  padding: 0;
  width: 100%;
  height: 100vh;
  margin-top: calc(var(--header-height) * -1);
  grid-template-columns: 1fr 3fr 3fr 5fr;
  grid-template-rows: var(--header-height) 4fr 3fr 1fr;
  grid-template-areas: '. . . .' 'nav title title .' 'nav description . .' 'contact contact . .';
}

Las unidades fr son flexibles, y dividen el espacio disponible, de acuerdo a su valor. 1fr es una parte del espacio, 2fr ocupa el doble, 3fr el triple… y así sucesivamente. Estas unidades se pueden combinar con valores absolutos, y el espacio restante será el que se divida. En las filas, por ejemplo, se utiliza la altura del header para la primera (obtenida de una variable).

Una vez tenemos definidas las filas y las columnas, podemos asignarle a cada área un nombre, que luego servirá para situar los elementos mediante la propiedad grid-area, como en el título, o bien mediante el número de filas y columnas, como en el carousel de imágenes (los números negativos son el número de la fila/columna, pero empezando a contar desde el final).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.hero h1 {
  grid-area: title;
  align-self: end;
  font-size: 5vw;
  font-weight: 800;
  margin: 0;
  z-index: 1;
}
.carousel {
  grid-column: 1 / -1;
  grid-row: 1 / -1;
  height: 100%;
  width: 100%;
  z-index: 0;
}

Para la versión móvil, basta con cambiar la grid-area en una media query para ajustar todo el layout:

1
2
3
4
5
6
7
8
9
@media (max-width: 800px) {
  .hero {
    grid-template-columns: repeat(1, 1fr);
    grid-template-rows: auto;
    grid-template-areas: 'title' 'img' 'description' 'contact';
    height: calc(100vh - var(--header-height));
    margin: 0;
  }
}

Para el carousel, utilizamos react-slick, y una vez cargados los svg, AnimeJS para la animación de los distintos elementos.

1
2
3
4
5
6
7
8
9
function animate (doc) {
  animateSquares(doc)
  animateDots(doc)
  animateClouds(doc)
  animateCode(doc)
  animateArrows(doc)
  animateLine(doc)
  animateCircles(doc)
}

Cada una de las funciones se encarga de animar un tipo de elementos. Como ejemplo explicaremos la animación de las barras de código , para no saturar el artículo, ya que todas las funciones siguen un patrón muy similar.

Lo primero que se hace es obtener los elementos que nos interesa animar, e iterar para cada uno de ellos. Aquí, el efecto que queremos conseguir es que las barras crezcan y decrezcan, por lo que hay que animar la propiedad width, a la que le hemos definido que anime desde 10px hasta la anchura total. Le aplicamos una duración y un retardo diferente a cada una de las barras, para que el efecto sea más aleatorio y no quede tan monótono. La propiedad alternate sirve para que la animación funcione en ambos sentidos, y que antes de volver a comenzar, vuelva al estado inicial también de forma progresiva.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function animateCode (svg) {
  const els = svg.querySelectorAll('.code rect')
  for (let i = 0; i < els.length; i++) {
    const el = els[i]
    const width = anime.get(el, 'width')
    anime({
      targets: el,
      duration: anime.random(1000, 2000),
      delay: (el, i) => i * 100,
      width: [10, width],
      loop: true,
      direction: 'alternate',
      easing: 'easeInOutSine',
      autoplay: true
    })
  }
}

La sección de Servicios no tiene mucho misterio, es un grid donde se colocan los elementos de forma escalonada, superponiendo un grid de color bajo una imagen, con una altura inferior.

Cabe destacar, sin embargo, la implementación de la imagen responsive del teclado, ya que en la versión de escritorio sí queda bien en horizontal, pero en móvil se perdía demasiado espacio, por lo que en dispositivos pequeños, la imagen se sustituye por un teclado rotado. Para ello se usa el elemento HTML picture, que te perimite definir una imagen para cada tamaño de pantalla:

1
2
3
4
<picture>
  <source media="(max-width: 800px)" srcSet={keyboardIMGsmall} />
  <img src={keyboardIMG} alt="A keyboard" />
</picture>
Versiones escritorio y móvil
Versiones escritorio y móvil

Llegamos a la parte más divertida de la web, la sección Equipo, donde hablamos sobre nosotros, y lo hacemos de una manera particular, en vez de ocupar espacio para cada uno de los integrantes, el contenido de la sección cambia según se va haciendo scroll.

La estructura es sencilla, un grid en el que la mitad se sitúan las imágenes de las personas, y en la otra mitad la información de la persona activa.

Para crear este efecto usamos scrollmagic, que nos facilita la interacción con los elementos de nuestra página basándose en la posición de la página.

Lo primero es crear un controlador, donde definimos cuándo comenzarán a surtir efecto las escenas que le asignaremos, aquí le decimos que ‘onLeave’, es decir, cuando el elemento vaya a salir por la parte de arriba de la página.

A partir de aquí vamos añadiendo escenas al controlador: la primera es la que se encarga de que la sección #team se quede fija en pantalla (no avance cuando haces scroll) durante 1200 píxeles.

Ahora, para cada uno de los miembros del equipo, creamos una escena, donde le indicamos que añada/quite la clase CSS active, según se encuentre dentro de esa duración. Así de 0-400, p1 (que es la clase que le hemos puesto a la persona 1) tendrá también la clase active, de 400-800 (ya que le hemos puesto un offset de 400) p2 será la active y de 800-1200, p3.

Hemos definido 400px por cada miembro, así que una vez llegamos al tercer y último miembro, la sección dejará de estar fija, ya que habremos llegado a los 1200 píxeles de la escena inicial, y continuaremos navegando por el resto de la web.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
this.controller = new ScrollMagic.Controller({
  globalSceneOptions: {
    triggerHook: 'onLeave'
  }
})

// Team scenes
new ScrollMagic.Scene({
  duration: 1200,
  triggerElement: '#team'
})
  .setPin('#team')
  .addTo(this.controller)

new ScrollMagic.Scene({
  triggerElement: '#team',
  duration: 400
})
  .setClassToggle('.p1', 'active')
  .addTo(this.controller)

new ScrollMagic.Scene({
  triggerElement: '#team',
  duration: 400,
  offset: 400
})
  .setClassToggle('.p2', 'active')
  .addTo(this.controller)
new ScrollMagic.Scene({
  triggerElement: '#team',
  duration: 400,
  offset: 800
})
  .setClassToggle('.p3', 'active')
  .addTo(this.controller)

Una vez tenemos el código javascript que gestiona las clases CSS según la posición del scroll, solo queda definir los estilos para cada estado. Así, cuando no tiene la clase active, cada persona tendrá oculto el fondo verde, y cuando la tenga, aparecerá y se le aplica una transformación de escala, para simular la selección. Además, las descripciones se ocultan o muestran también dependiendo de esa clase.

Como véis, el código no es muy complicado, pero se obtiene una interacción con la página diferente, que le otorga personalidad.

Finalmente, la sección de Contacto es básicamente un grid con una imagen en el background.

Destacar el mapa, que es un iframe de openstreetmap al que se le ha aplicado un clip-path para que tenga la forma redondeada en la parte superior.

Y hasta aquí todo el proceso que ha llevado la creación de Estudio Yobo, más adelante comentaremos cómo hemos incorporado la sección de tienda. Así que si no os lo queréis perder, no olvidéis suscribiros a la newsletter.

¡Hasta la semana que vienes Yobers!