Tutorial CSS: Cómo crear un objeto que baile
El pasado noviembre participé en Codevember 2017, una iniciativa basada en Inktober (ink + october), un reto temático dentro del ámbito de la ilustración llevado a cabo durante el mes de Octubre, donde cada participante debe realizar una ilustración diaria con la temática propuesta para ese día, dentro de sus 24h correspondientes.
De la misma forma, Codevember (Code + november) también es un reto creativo donde el participante debe crear una demo sobre un concepto diario, sólo que esta vez lo tiene que hacer utilizando tecnologías web, como por ejemplo, HTML5, CSS3 y/o Javascript.
En mi repositorio de GitHub Codevember2017 se pueden encontrar y visualizar las demos que publiqué en cada día del mes, con información sobre ellas y enlaces a CodePen/GitHub.
Concepto del día: «Pen»
En este artículo me gustaría explicar un poco, a modo de tutorial, el «cómo se hizo» de la demo correspondiente al Día 28: «Pen», explicado paso a paso.
Como su propia palabra indica, la idea original con «pen» era precisamente hacer una demo relacionada con bolígrafos, plumas, estilográficas o algo similar, pero preferí aprovechar el doble sentido de la palabra «pen» en este contexto, que es el nombre que se le da a las demos de la Plataforma Codepen. De esta forma, me pareció más original un navegador que mostrase un pen en blanco de Codepen.
Si además, lo caricaturizamos un poco añadiéndole algunos extras, eso podría quedar bien. ¡Y todo ello sin una línea de Javascript! ¡Vamos a ponernos a ello!
1. Análisis y estructura
Lo primero es tener la estructura a crear bien clara y definida. Si entramos a un pen en blanco en Codepen veremos perfectamente como es esta pantalla y los elementos que la componen. Tenemos que simplificar esa apariencia y convertirla en algo muy básico pero que tenga la misma esencia.
Así pues, definimos la estructura general de Codepen: lo dividiremos en varias secciones, una parte superior .top
que incluirá el titular y los botones, una parte central .mid
que contendrá las ventanas de HTML, CSS y JS, una parte inferior .preview
donde se previsualizan los contenidos y finalmente, una capa .bottom
como barra inferior.
Todo ello estará en el interior de una capa .body
, que a su vez estará en el interior de una capa .codepen
que utilizaremos para separar los pies del cuerpo más adelante. De momento, simplemente colocamos un comentario que más adelante sustituiremos:
<div class="codepen">
<div class="body">
<div class="top"></div>
<div class="mid"></div>
<div class="preview"></div>
<div class="bottom"></div>
</div>
<!-- feet -->
</div>
Pero todo esto es inútil si no le damos estilo. Para empezar, añadiremos algunas líneas para utilizar un color de fondo en toda la página (un gradiente, que siempre queda más atractivo) y precargamos la tipografía Lato que usaremos más adelante:
@import url('https://fonts.googleapis.com/css?family=Montserrat:600|Lato');
html {
background:linear-gradient(100deg, #444, #777);
}
También le daremos estilo a la estructura del modelo que hemos planificado antes. Básicamente, tamaños de ancho y/o alto, márgenes y colores de fondo para que sea bien visible la parte principal:
.body {
width:600px;
margin:2em auto; /* Para centrarlo todo */
box-shadow:0 0 15px #333;
}
.top {
height:40px;
background:#000;
}
.mid {
width:100%;
background:#343436;
min-height:165px;
}
.preview {
background:#FFF;
min-height:150px;
}
.bottom {
height:15px;
background:#343436;
}
Una vez hecho, tendremos el cuerpo de la ventana del navegador. No olvidar el box-shadow del .body
para darle un buen aspecto de suavidad y profundidad con una sombra (o varias si es necesario).
2. Ventanas HTML/CSS/JS
Para que empiece a tomar forma, es necesario colocar las tres ventanas A, B y C donde se escribe el código HTML, CSS y JS en Codepen. Les aplicaremos una clase .tab
común a las tres, para darle los estilos que deben tener todas. Posteriormente, si lo necesitaramos podríamos usar las otras clases para hacer referencia a una ventana específica. Todo ello, por supuesto, en el interior de la clase .mid
:
<div class="mid">
<div class="tab html"></div>
<div class="tab css"></div>
<div class="tab js"></div>
</div>
Con un border-top simulamos el título de cada ventana como si fuera una sombra, un efecto bastante bonito en nuestro ejemplo. Sin embargo, al ser elementos block cada ventana se colocará una debaja de otra. Para solucionar esto, establecemos la clase .mid
como un contenedor de elementos flexbox con display:flex
:
/* Sería más eficiente usar .tab, pero así se ve mejor */
.mid .tab {
width:100%;
height:125px;
margin:10px;
background:#1e1f21;
border-top:20px solid #111;
}
.mid {
/* ... */
display:flex;
}
Ya se va pareciendo más a Codepen. ¡Sigamos!
3. Franja superior
Otro de los detalles importantes de la página de Codepen que le da el aspecto reconocible son los botones de la parte superior-derecha, así como el título y el logo. Vamos a ponernos con ello, en el interior del elemento con clase .top
.
Si nos fijamos bien, establecemos dos capas iniciales: una contendrá el logotipo y el titular de la página, y la otra los botones y la foto de perfil del usuario, en este caso, la de mi perfil de CodePen :-P
Algo similar al boceto de la siguiente imagen:
Varios detalles:
- De momento, dentro de
.logo
no colocamos ningún logotipo. Más adelante veremos como colocar el logo con SVG, ahorrándonos imágenes externas y utilizando un SVG, que es más versátil. - En el interior de
.title
irá el título del pen de Codepen. Aprovecharemos y colocamos el atributocontentEditable
, para que el usuario pueda cambiar el texto si lo desea. - Finalmente, en
.buttons
colocamos tres botones y un enlace que será la foto de perfil (avatar) del usuario. En esto profundizaremos más adelante.
<div class="top">
<div>
<div class="logo"></div>
<div class="title" contentEditable>Untitled</div>
</div>
<div class="buttons">
<div class="button"></div>
<div class="button"></div>
<div class="button"></div>
<a class="avatar" href="https://codepen.io/manz/"></a>
</div>
</div>
Ahora lo necesario es darle forma y colocarlo todo, por lo que toca centrarse en el CSS. Por defecto, sólo haciendo los cambios de HTML ocurren varias cosas que hay que tener claras. Al establecer dos capas dentro de .top
, estas se colocan una debajo de otra, ya que son elementos de tipo block. Esto se puede ver fácilmente, si ponemos temporalmente un .top>div { background:fuchsia }
que cambia el color de fondo del elemento. Para evitar este comportamiento de colocación, haremos uso de nuevo de Flexbox:
.top {
/* ... */
display:flex;
justify-content:space-between;
align-items:center;
padding:0 15px;
}
.top > div {
display:flex;
}
Con flexbox, los hijos se comportan como elementos flexibles que se colocan en horizontal «automágicamente» y haciendo uso de justify-content:space-between
ya conseguimos lo que buscamos en el boceto previo.
Además, también habría que usar flexbox en las capas hijas de .top
, para colocar sus elementos hijos de la misma forma. De resto, sólo quedaría ir retocando el diseño en los demás elementos y puliendo detalles menores de diseño que veremos a continuación.
4. Titulo de la página
CodePen muestra el título de la página en la parte superior izquierda, seguido del icono de un lápiz. Para simular esta apariencia, podemos establecer una tipografía, color y otros como separarlo del borde (espaciado) u ocultar ese borde azul de los elementos interactivos (outline):
.top .title {
color:#FFF;
padding-left:15px;
font-family:Lato;
outline:0;
}
Podríamos dejar esta parte aquí, pero por hacerlo un poco más fiel a CodePen, podemos utilizar el emoji Right Upper Pencil para colocarlo a la derecha del texto de título. Para ello realizamos varios pasos:
-
Al elemento
.title
le asignamos una posición relativa. De este modo, todo lo que esté en su interior con posicionamiento absoluto, tomará este elemento como marco de referencia. -
Creamos un pseudoelemento
::after
que es el que contendrá el emoji del lápiz. Asignándole posicionamiento absoluto, podemos colocarlo justo después del texto. Si usamos el scale de transform, también podremos darle la vuelta (en el eje X y en el eje Y) para que aparezca igual que en CodePen:
.top .title {
/* ... */
position:relative;
}
.top .title::after {
position:absolute;
right:-20px;
top:1px;
content:"?";
color:#FFF;
transform:scale(-1);
}
5. Botones de Codepen
En la zona superior derecha, tenemos una parte donde hay que colocar varios botones. Para ello lo principal será darle estilo a estos botones, con la clase .button
:
- Le damos el mismo color que usan en Codepen.
- Redondeamos ligeramente las esquinas.
- Cambiamos tamaños y márgenes.
- Damos estilos diferentes al primer y segundo botón con
:nth-child
.
Nos quedaríamos con algo parecido a esto:
.buttons .button {
background:#343436;
width:70px;
height:23px;
margin:0 5px;
border-radius:4px;
border-top:2px solid #343436;
}
/* Primer botón: SAVE */
.button:nth-child(1) {
width:50px;
border-top-color:#fedd31;
}
/* Segundo botón: Settings */
.button:nth-child(2) {
width:60px;
}
Por último, hay que establecer el avatar del perfil del usuario que CodePen muestra en la parte superior derecha. Esto es muy sencillo. Simplemente le damos estilo a la etiqueta <a>
con clase .avatar
, asignándole un tamaño de 30x25 píxels y le establecemos nuestra imagen de fondo con background
:
.buttons .avatar {
width:30px;
height:25px;
border-radius:3px;
margin:0 5px;
background:#FFF url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/154065/profile/profile-80.jpg);
background-size:cover;
}
¡Esto ya tiene mejor pinta! Pero aún falta darle algo de personalidad… Vamos a antropomorfizar nuestra ventana del navegador de Codepen y a darle un aspecto más humano.
6. ¡Antropomorfosis!
En primer lugar, vamos a dibujarle unos ojitos y una boca. Insertar etiquetas HTML sobre lo que ya tenemos no sería una solución muy limpia, ya que estaríamos «ensuciando» lo que hemos hecho hasta ahora, así que vamos a utilizar pseudoelementos y posicionamiento para este fin.
En primer lugar, con ayuda de la posición absoluta, calc() y las unidades de viewport, colocamos a nuestro personaje en la parte baja de nuestra página. Hacemos esto porque, posteriormente, necesitaremos que aparezca justo «encima» del pie de ventana del navegador.
.codepen {
position:absolute;
left:calc(50vw - 300px);
bottom:32px;
height:472px;
}
Una vez hecho esto, vamos a crear los ojos. Para ello, utilizaremos el pseudoelemento ::before
para el ojo izquierdo y el pseudoelemento ::after
para el ojo derecho. Ambos sobre la zona central de clase .mid
. Lo interesante de este sistema es que utilizaremos la generación de contenido previamente no existente con CSS.
Con la propiedad content generamos un espacio en blanco al que le damos cierto estilo: unas dimensiones de ancho, alto, redondeamos las esquinas y coloreamos el fondo. Utilizamos un gradiente circular de negro (exterior) a blanco (interior) con cambio brusco (de golpe) sin difuminar, con lo que conseguimos mostrar un aparente circulo blanco dentro de un circulo negro, que simula ser un ojo negro con brillo blanco.
Por último, con posicionamiento absoluto, colocamos el ojo entre las ventanas B y C. En este momento, ambos ojos (::after y ::before) están situados en el mismo sitio con las mismas características, ya que comparten las mismas propiedades CSS. Por eso, establecemos un posicionamiento diferente en el .mid::before
, colocando el ojo entre la ventana A y B.
Además, con un transform:scale(-1,1)
hacemos el efecto espejo con el ojo, eliminando la sensación de estar clonado y haciendo que no mire hacia el mismo lado, sino en dirección opuesta.
/* Fijamos como marco de referencia: Los ojos se colocarán en base a este */
.mid {
position:relative;
}
/* Características comunes del ojo */
.mid::before,
.mid::after {
content:" ";
width:30px;
height:30px;
border-radius:50%;
background:radial-gradient(20px at 13px 10px, #FFF 20%, #000 30%);
/* Colocación */
position:absolute;
right:200px;
top:100px;
}
.mid::before {
left:200px;
right:none;
transform:scale(-1, 1);
}
Nos quedaría realizar una acción muy parecida para crear la boca del personaje. En este caso, creamos un rectángulo o cuadrado negro donde redondearemos las esquinas inferior izquierda e inferior derecha, obteniendo medio círculo que hará de boca del personaje.
.preview::after {
content:" ";
width:50px;
height:30px;
border-bottom-left-radius:50px;
border-bottom-right-radius:50px;
background:#000;
/* Colocación */
position:absolute;
left:calc(50% - 25px);
bottom:105px;
}
Por último, remarcar que la estamos colocando en base al elemento .mid
, ya que hemos establecido un posicionamiento absoluto.
7. Logo Codepen
Habíamos pospuesto la colocación del logo de Codepen en la página, teniendo una capa .logo
vacía, sin ningún contenido. En lugar de una imagen, vamos a colocar la siguiente etiqueta SVG en línea, donde tenemos el logotipo vectorial de Codepen:
<div class="logo">
<svg id="codepen-box" viewBox="0 0 100 100" width="100%" height="100%">
<path d="M100 34.2c-.4-2.6-3.3-4-5.3-5.3-3.6-2.4-7.1-4.7-10.7-7.1-8.5-5.7-17.1-11.4-25.6-17.1-2-1.3-4-2.7-6-4-1.4-1-3.3-1-4.8 0-5.7 3.8-11.5 7.7-17.2 11.5L5.2 29C3 30.4.1 31.8 0 34.8c-.1 3.3 0 6.7 0 10v16c0 2.9-.6 6.3 2.1 8.1 6.4 4.4 12.9 8.6 19.4 12.9 8 5.3 16 10.7 24 16 2.2 1.5 4.4 3.1 7.1 1.3 2.3-1.5 4.5-3 6.8-4.5 8.9-5.9 17.8-11.9 26.7-17.8l9.9-6.6c.6-.4 1.3-.8 1.9-1.3 1.4-1 2-2.4 2-4.1V37.3c.1-1.1.2-2.1.1-3.1 0-.1 0 .2 0 0zM54.3 12.3L88 34.8 73 44.9 54.3 32.4V12.3zm-8.6 0v20L27.1 44.8 12 34.8l33.7-22.5zM8.6 42.8L19.3 50 8.6 57.2V42.8zm37.1 44.9L12 65.2l15-10.1 18.6 12.5v20.1zM50 60.2L34.8 50 50 39.8 65.2 50 50 60.2zm4.3 27.5v-20l18.6-12.5 15 10.1-33.6 22.4zm37.1-30.5L80.7 50l10.8-7.2-.1 14.4z"/>
</svg>
</div>
Inicialmente, el logotipo aparecerá en negro y con unas dimensiones muy grandes, pero aprovecharemos las características de SVG para solucionarlo con las 3 líneas de CSS siguientes.
Le aplicamos estilo al contenedor del elemento SVG, el elemento con clase .logo
, donde se le indica que tenga unas dimensiones de 20x20 píxels y que utilizaremos la propiedad fill
para rellenar de blanco los elementos SVG que se encuentren dentro de la etiqueta HTML con clase .logo
:
.logo {
width:20px;
height:20px;
fill:#FFF;
}
De esta forma, el logotipo de Codepen ahora si tiene un tamaño aceptable y se coloca perfectamente en nuestra demo. ¡Además al ser vectorial es mucho más versátil si en algún momento tenemos que cambiar tamaños!
8. Let’s go dancing!
Si algo aprendí en codevember es que todo lo que baile, siempre gusta más. Así que, nos falta convertir a nuestro navegador en un excelente bailarín. Y si tiene piernas, pues mucho mejor.
Mediante Inkscape diseño unas patitas, que guardamos en SVG, y mediante SVGO o su versión gráfica online, SVGOMG, optimizamos la imagen al máximo para que ocupe lo menos posible.
Una vez la tengamos lista, reemplazamos la etiqueta <svg>
en cuestión por el comentario <!-- feet -->
que colocamos en el primer punto de esta guía, justo tras cerrar la capa .body
:
<svg class="feet" viewBox="0 0 50 40">
<path d="m 33.865999,2.4621617 c 5.91069,24.0121503 -1.10825,35.4641003 -1.10825,35.4641003 h 11.08253"/>
<path d="M 12.778709,2.4621617 C 6.8680187,26.474312 13.886959,37.926262 13.886959,37.926262 H 2.8044287"/>
</svg>
Aunque se podría indicar el CSS en la propia etiqueta HTML, mediante un atributo style
, he preferido separarlo y tenerlo de forma independiente, para que el código sea más modular y limpio, aplicando las propiedades CSS para SVG necesarias desde el estilo CSS:
.feet {
width:200px;
position:relative;
left:205px;
top:-90px;
z-index:-1;
}
.feet path {
fill:none;
stroke:#111;
stroke-width:4px;
stroke-linecap:round;
}
Propiedades como fill
, stroke
, stroke-width
o stroke-linecap
son estilos CSS para SVG que se aplican a la imagen SVG contenida en el elemento de clase .feet
.
¡Listo! ¡Ya tenemos las patitas! Sólo nos quedan dos cosas más para terminar.
9. Animación de baile
¡Nuestro personaje ya tiene piernas pero aún no baila! ¡Devuélveme mi dinero! Vamos a crear una animación CSS que simplemente se mueva de un lado a otro simulando que baile. Al tener cara de alegría y todo el cuerpo (salvo las patas) fuera del .body
, no tenemos más que aplicar la animación a dicha capa.
La animación en cuestión será un simple movimiento de izquierda a derecha y viceversa, utilizando la función de transformación skew (muchas veces traducida como sesgar) y moviendo ligeramente hacia arriba y abajo. Bien coordinada, el efecto visual es bastante llamativo:
html {
/* ... */
overflow:hidden; /* Ocultamos barra de desplazamiento */
}
.body {
/* ... */
animation:dancing .75s ease 2s infinite;
}
@keyframes dancing {
25% {
transform:translateY(20px) skew(-2deg);
}
0%, 50%, 100% {
transform:translateY(0) skew(0);
}
75% {
transform:translateY(20px) skew(2deg);
}
}
Le aplicamos dicha animación al .body
, estableciendo una duración de 0,75 segundos de forma infinita (no termina nunca). El retardo inicial de 2 segundos es porque la demo original no muestra las patitas y ojos inicialmente, sino que deja pasar unos segundos antes de mostrarlos mientras muestra el cartel superior de Codevember. En este tutorial actual no haría falta ya que no estamos realizando esa acción.
¡Ya lo tenemos casi listo, como se puede ver en la siguiente imagen!
Si analizamos los keyframes
, la animación alterna siempre en el estado normal del personaje (al principio, a la mitad y al final de la animación), moviéndose hacia arriba y hacia uno de los dos lados al 25% y al 75% de la animación. Esto hará que la animación sea fluida y no experimente cortes o movimientos bruscos.
10. ¿Y la música?
Bueno, pues sólo nos queda ponerle una canción en bucle que se escuche mientras el personaje baila. ¿Qué mejor que utilizar Bonetrousle, de Toby Fox, el tema del personaje Papyrus en el gran videojuego Undertale?
Podemos hacerlo utilizando la etiqueta <audio>
de HTML5, utilizando los formatos MP3 y OGG para hacer compatible el audio en cualquier tipo de navegador y sistema operativo:
<audio autoplay loop>
<source src="https://cdn.rawgit.com/ManzDev/codevember2017/master/assets/bonetrousle-tobyfox-by-bulby.mp3"/>
<source src="https://cdn.rawgit.com/ManzDev/codevember2017/master/assets/bonetrousle-tobyfox-by-bulby.ogg"/>
</audio>
¡Listo! ¡Ya tenemos nuestro ejemplo terminado y funcionando! Puedes verlo en el siguiente enlace de Codepen: Codevember2017 - Day 28: Pen o en el siguiente widget:
En el widget, el sonido está deshabilitado. Si quieres escucharlo con sonido, accede al pen de ejemplo: Codepen: Codevember2017 - Day 28: Pen.
¡Espero que les haya gustado y se animen a hacer algún personaje parecido y compartirlo en los comentarios! En mi GitHub de Codevember2017 tienen una lista de todas las demos que presenté y publiqué durante el mes de noviembre con algunas anotaciones. Animo a los lectores a preguntar cualquier duda en los comentarios, así como de otra demo de la lista.