Cómo crear una página estática para portafolio con CSS y JavaScript
() translation by (you can also view the original English article)
En este tutorial utilizaremos todo el poder de flexbox y aprenderemos a crear una simple, pero atractiva página estática para portafolio en HTML. También crearemos un gráfico de columnas responsivas sin usar ninguna biblioteca externa de JavaScript, SVG, o el elemento canvas. ¡Únicamente con CSS!
Aquí está el proyecto que vamos a crear (haz clic en el enlace Skills [Habilidades] en la esquina superior):
Nota: Este tutorial asume que tienes algunos conocimientos de flexbox. Si estás comenzando, Anna Monus tiene una gran colección de guías para principiantes:
1. Inicia con el marcado de la página
El marcado de la página es bastante simple; una cabecera, un título, un enlace mailto
y una sección:
1 |
<body class="position-fixed d-flex flex-column text-white bg-red"> |
2 |
<header class="page-header"> |
3 |
<nav class="d-flex justify-content-between"> |
4 |
<a href="" class="logo">...</a> |
5 |
<ul class="d-flex"> |
6 |
<li>
|
7 |
<a href="">...</a> |
8 |
</li>
|
9 |
<!-- possibly more list items here -->
|
10 |
</ul>
|
11 |
</nav>
|
12 |
</header>
|
13 |
<h1 class="position-absolute w-100 text-center heading">...</h1> |
14 |
<a class="position-absolute contact" href="">...</a> |
15 |
<section class="position-absolute d-flex align-items-center justify-content-center text-black bg-white skills-section" data-slideIn="to-top"> |
16 |
<!-- content here -->
|
17 |
</section>
|
18 |
</body>
|
Dentro de la sección, colocamos un botón de cierre y un elemento de envoltura con dos listas. Estas listas son responsables de crear el gráfico de columnas:
1 |
<section class="position-absolute d-flex align-items-center justify-content-center text-black bg-white skills-section" data-slideIn="to-top"> |
2 |
<button class="position-absolute skills-close" aria-label="Close Skills Section">✕</button> |
3 |
<div class="d-flex chart-wrapper"> |
4 |
<ul class="chart-levels"> |
5 |
<li>Expert</li> |
6 |
<li>Advanced</li> |
7 |
<li>Intermediate</li> |
8 |
<li>Beginner</li> |
9 |
<li>Novice</li> |
10 |
</ul>
|
11 |
<ul class="d-flex justify-content-around align-items-end flex-grow-1 text-center bg-black chart-skills"> |
12 |
<li class="position-relative bg-red" data-height="80%"> |
13 |
<span class="position-absolute w-100">CSS</span> |
14 |
</li>
|
15 |
<li class="position-relative bg-red" data-height="60%"> |
16 |
<span class="position-absolute w-100">HTML</span> |
17 |
</li>
|
18 |
<li class="position-relative bg-red" data-height="68%"> |
19 |
<span class="position-absolute w-100">JavaScript</span> |
20 |
</li>
|
21 |
<li class="position-relative bg-red" data-height="52%"> |
22 |
<span class="position-absolute w-100">Python</span> |
23 |
</li>
|
24 |
<li class="position-relative bg-red" data-height="42%"> |
25 |
<span class="position-absolute w-100">Ruby</span> |
26 |
</li>
|
27 |
</ul>
|
28 |
</div>
|
29 |
</section>
|
Nota: Más allá de las clases específicas de los elementos, nuestro marcado contiene un número de clases de utilidad (auxiliares). Usaremos esta metodología para mantener nuestro CSS lo más DRY (No te repitas, por sus siglas en inglés. También se le conoce como: Una vez y solo una) posible. No obstante, por razones de legibilidad, dentro del CSS no agruparemos las reglas comunes de CSS.
2. Define algunos estilos básicos
Cumpliendo con lo que acabamos de comentar, ahora especificamos algunas reglas de reinicio junto con una serie de clases auxiliares:
1 |
:root { |
2 |
--black: #1a1a1a; |
3 |
--white: #fff; |
4 |
--red: #e21838; |
5 |
--transition-delay: 0.85s; |
6 |
--transition-delay-step: 0.3s; |
7 |
}
|
8 |
|
9 |
* { |
10 |
padding: 0; |
11 |
margin: 0; |
12 |
}
|
13 |
|
14 |
ul { |
15 |
list-style: none; |
16 |
}
|
17 |
|
18 |
a { |
19 |
text-decoration: none; |
20 |
color: inherit; |
21 |
}
|
22 |
|
23 |
button { |
24 |
background: none; |
25 |
border: none; |
26 |
cursor: pointer; |
27 |
outline: none; |
28 |
}
|
29 |
|
30 |
.d-flex { |
31 |
display: flex; |
32 |
}
|
33 |
|
34 |
.flex-column { |
35 |
flex-direction: column; |
36 |
}
|
37 |
|
38 |
.justify-content-center { |
39 |
justify-content: center; |
40 |
}
|
41 |
|
42 |
.justify-content-between { |
43 |
justify-content: space-between; |
44 |
}
|
45 |
|
46 |
.justify-content-around { |
47 |
justify-content: space-around; |
48 |
}
|
49 |
|
50 |
.align-items-center { |
51 |
align-items: center; |
52 |
}
|
53 |
|
54 |
.align-items-end { |
55 |
align-items: flex-end; |
56 |
}
|
57 |
|
58 |
.flex-grow-1 { |
59 |
flex-grow: 1; |
60 |
}
|
61 |
|
62 |
.w-100 { |
63 |
width: 100%; |
64 |
}
|
65 |
|
66 |
.position-relative { |
67 |
position: relative; |
68 |
}
|
69 |
|
70 |
.position-fixed { |
71 |
position: fixed; |
72 |
}
|
73 |
|
74 |
.position-absolute { |
75 |
position: absolute; |
76 |
}
|
77 |
|
78 |
.text-center { |
79 |
text-align: center; |
80 |
}
|
81 |
|
82 |
.text-black { |
83 |
color: var(--black); |
84 |
}
|
85 |
|
86 |
.text-white { |
87 |
color: var(--white); |
88 |
}
|
89 |
|
90 |
.bg-black { |
91 |
background: var(--black); |
92 |
}
|
93 |
|
94 |
.bg-white { |
95 |
background: var(--white); |
96 |
}
|
97 |
|
98 |
.bg-red { |
99 |
background: var(--red); |
100 |
}
|
Las nomenclaturas para nuestras clases auxiliares están inspiradas en los nombres de las clases de Bootstrap 4.
3. Estiliza el diseño de la página
El diseño de la página será tan simple como esto:



Aquí están los requisitos del diseño:
- La página debe ser a pantalla completa.
- El logotipo se sitúa en la esquina superior izquierda de la página, el menú en la esquina superior derecha y el enlace
mailto
en la esquina inferior derecha. - El título está centrado de forma horizontal y vertical.
- La sección que contiene el gráfico está inicialmente oculta (fuera de la pantalla).
Aquí están los estilos correspondientes para hacer todo esto:
1 |
body { |
2 |
top: 0; |
3 |
right: 0; |
4 |
bottom: 0; |
5 |
left: 0; |
6 |
font: 1rem/1.5 "Montserrat", sans-serif; |
7 |
overflow: hidden; |
8 |
}
|
9 |
|
10 |
.page-header { |
11 |
padding: 20px; |
12 |
border-bottom: 1px solid #e93451; |
13 |
}
|
14 |
|
15 |
.page-header li:not(:last-child) { |
16 |
margin-right: 20px; |
17 |
}
|
18 |
|
19 |
.page-header .logo { |
20 |
font-size: 1.2rem; |
21 |
z-index: 1; |
22 |
transition: color 0.3s; |
23 |
}
|
24 |
|
25 |
.window-opened .page-header .logo { |
26 |
color: var(--black); |
27 |
transition-delay: 0.8s; |
28 |
}
|
29 |
|
30 |
.heading { |
31 |
top: 50%; |
32 |
left: 50%; |
33 |
transform: translate(-50%, -50%); |
34 |
font-size: 2.5rem; |
35 |
}
|
36 |
|
37 |
.contact { |
38 |
bottom: 20px; |
39 |
right: 20px; |
40 |
}
|
41 |
|
42 |
.skills-section { |
43 |
top: 0; |
44 |
right: 0; |
45 |
bottom: 0; |
46 |
left: 0; |
47 |
transform: translateX(100%); |
48 |
}
|
El progreso hasta ahora
¡Esto es lo que hemos creado hasta el momento!
4. Alternando la sección
Como se comentó anteriormente, al principio la sección está oculta. Se hará visible con un agradable efecto deslizante cada vez que hagamos clic en el enlace del menú. Haremos esto añadiendo la clase window-opened
al elemento body
y alterando el CSS según sea necesario. De la misma manera, la sección desaparecerá tan pronto como hagamos clic en el botón de cerrar.
Como bonificación, nos daremos la facultad de establecer la dirección de la animación de deslizamiento. Podemos pasar el atributo personalizado data-slideIn
a la sección que determinará la posición inicial de su animación. Los posibles valores de los atributos son: to-top
, to-bottom
, y to-right
. De manera predeterminada, la sección aparece de derecha a izquierda.
Aquí están los estilos asociados:
1 |
.skills-section { |
2 |
top: 0; |
3 |
right: 0; |
4 |
bottom: 0; |
5 |
left: 0; |
6 |
transform: translateX(100%); |
7 |
transition: transform 1s; |
8 |
}
|
9 |
|
10 |
.window-opened .skills-section { |
11 |
transform: none; |
12 |
}
|
13 |
|
14 |
[data-slideIn="to-top"] { |
15 |
transform: translateY(100%); |
16 |
}
|
17 |
|
18 |
[data-slideIn="to-bottom"] { |
19 |
transform: translateY(-100%); |
20 |
}
|
21 |
|
22 |
[data-slideIn="to-right"] { |
23 |
transform: translateX(-100%); |
24 |
}
|
Y el código JavaScript necesario para alternar su estado:
1 |
const skillsLink = document.querySelector(".page-header li:nth-child(1) a"); |
2 |
const skillsClose = document.querySelector(".skills-close"); |
3 |
const windowOpened = "window-opened"; |
4 |
|
5 |
skillsLink.addEventListener("click", (e) => { |
6 |
e.preventDefault(); |
7 |
document.body.classList.toggle(windowOpened); |
8 |
});
|
9 |
|
10 |
skillsClose.addEventListener("click", () => { |
11 |
document.body.classList.toggle(windowOpened); |
12 |
});
|
4. Estiliza el gráfico
En este punto, veremos más de cerca el contenido de nuestra sección. Primero tenemos el botón de cierre situado en la parte superior derecha de la sección.
Aquí está su marcado:
1 |
<button class="position-absolute skills-close" aria-label="Close Skills Section">✕</button> |
Y sus estilos:
1 |
.skills-close { |
2 |
top: 20px; |
3 |
right: 20px; |
4 |
font-size: 2rem; |
5 |
}
|
A continuación tenemos el gráfico en sí. También revisemos nuevamente su estructura:
1 |
<div class="d-flex chart-wrapper"> |
2 |
<ul class="chart-levels"> |
3 |
<li>Expert</li> |
4 |
... |
5 |
</ul>
|
6 |
|
7 |
<ul class="d-flex justify-content-around align-items-end flex-grow-1 text-center bg-black chart-skills"> |
8 |
<li class="position-relative bg-red" data-height="80%"> |
9 |
<span class="position-absolute w-100">CSS</span> |
10 |
</li>
|
11 |
... |
12 |
</ul>
|
13 |
</div>
|
Aquí están los puntos clave con respecto a este marcado:
- Configuramos el elemento
.chart-wrapper
como un contenedor flexible con dos listas como elementos flexibles. - La segunda lista, que mide el conocimiento de una determinada habilidad, es en sí misma un contenedor flexible. Le damos
flex-grow: 1
para crecer y ocupar todo el espacio disponible.
Las reglas iniciales de CSS para nuestro gráfico:
1 |
.chart-wrapper { |
2 |
width: calc(100% - 40px); |
3 |
max-width: 500px; |
4 |
}
|
5 |
|
6 |
.chart-levels li { |
7 |
padding: 15px; |
8 |
}
|
En este momento, veremos más de cerca los elementos de la segunda lista.
Puntos a recordar:
- Todos tienen un ancho del 12%. Los distribuimos uniformemente a través del eje principal dando
justify-content: space-around
a la lista principal. - Deben estar situados en la parte inferior de su contenedor y, por lo tanto, establecemos
align-items: flex-end
a la lista principal. - Su altura inicial es 0. Tan pronto como la sección se hace visible, su altura se anima y recibe un valor igual al valor de su atributo
data-height
. Solo ten en cuenta que debemos reescribir los valores de altura deseados en nuestro CSS porque establecerheight: attr(data-height)
no funciona :(
Aquí están los estilos relacionados:
1 |
:root { |
2 |
...
|
3 |
--transition-delay: 0.85s; |
4 |
--transition-delay-step: 0.3s; |
5 |
}
|
6 |
|
7 |
.chart-skills li { |
8 |
width: 12%; |
9 |
height: 0; |
10 |
border-top-left-radius: 10px; |
11 |
border-top-right-radius: 10px; |
12 |
transition: height 0.65s cubic-bezier(0.51, 0.91, 0.24, 1.16); |
13 |
}
|
14 |
|
15 |
.window-opened .chart-skills li:nth-child(1) { |
16 |
height: 80%; |
17 |
transition-delay: var(--transition-delay); |
18 |
}
|
19 |
|
20 |
.window-opened .chart-skills li:nth-child(2) { |
21 |
height: 60%; |
22 |
transition-delay: calc( |
23 |
var(--transition-delay) + var(--transition-delay-step) |
24 |
);
|
25 |
}
|
26 |
|
27 |
.window-opened .chart-skills li:nth-child(3) { |
28 |
height: 68%; |
29 |
transition-delay: calc( |
30 |
var(--transition-delay) + var(--transition-delay-step) * 2 |
31 |
);
|
32 |
}
|
33 |
|
34 |
.window-opened .chart-skills li:nth-child(4) { |
35 |
height: 52%; |
36 |
transition-delay: calc( |
37 |
var(--transition-delay) + var(--transition-delay-step) * 3 |
38 |
);
|
39 |
}
|
40 |
|
41 |
.window-opened .chart-skills li:nth-child(5) { |
42 |
height: 42%; |
43 |
transition-delay: calc( |
44 |
var(--transition-delay) + var(--transition-delay-step) * 4 |
45 |
);
|
46 |
}
|
Como puedes ver en el código anterior, utilizamos las variables de CSS transition-delay
y transition-delay-step
junto con la función calc()
CSS para controlar la velocidad de los efectos de transición. Microsoft Edge no soporta esas operaciones matemáticas, así que si necesitas admitirlo, pasa algunos valores estáticos como este:
1 |
.window-opened .chart-skills li:nth-child(2) { |
2 |
transition-delay: 1.15s; |
3 |
}
|
4 |
|
5 |
.window-opened .chart-skills li:nth-child(3) { |
6 |
transition-delay: 1.45s; |
7 |
}
|
Para generar la cantidad de conocimiento estipulada para una cierta tecnología, usaremos el pseudo-elemento ::before
.

Este valor se extrae del atributo data-height
que se asigna a los elementos de la segunda lista.
Aquí están los estilos que realizan este trabajo:
1 |
.chart-skills li::before { |
2 |
content: attr(data-height); |
3 |
position: absolute; |
4 |
top: 10px; |
5 |
left: 0; |
6 |
width: 100%; |
7 |
font-size: 0.85rem; |
8 |
color: var(--white); |
9 |
}
|
Por último, añadimos algunos estilos al elemento span
que está situado dentro de cada uno de los elementos de la lista. Este elemento se comporta como una etiqueta y almacena los nombres de la tecnología.

Los estilos correspondientes:
1 |
.chart-skills span { |
2 |
bottom: 0; |
3 |
left: 0; |
4 |
transform: translateY(40px) rotate(45deg); |
5 |
}
|
5. Volviéndola responsiva
¡Ya casi hemos terminado! Como último punto, asegurémonos de que la página tenga una apariencia sólida en todas las pantallas. Aplicaremos dos reglas específicamente para las pantallas estrechas:
1 |
@media screen and (max-width: 600px) { |
2 |
html { |
3 |
font-size: 12px; |
4 |
}
|
5 |
|
6 |
.chart-levels li { |
7 |
padding: 15px 10px 15px 0; |
8 |
}
|
9 |
}
|
Una nota importante aquí es que en nuestros estilos hemos utilizado rem para ajustar el tamaño de las fuente. Este enfoque es realmente útil porque los tamaños de las fuentes no son absolutos y sus valores dependen del valor del elemento raíz. Por lo tanto, si disminuimos el tamaño de la fuente del elemento raíz como en el código anterior, los tamaños de la fuente relacionados con rem disminuirán de forma dinámica. ¡Buen trabajo amigos!
El estado final de nuestro proyecto:
6. Compatibilidad con navegadores
La demostración funciona bien en todos los navegadores y dispositivos recientes.
Como ya se comentó anteriormente, Microsoft Edge aún no soporta operaciones matemáticas con propiedades personalizadas dentro de la función calc()
. Para superar este problema, deberás usar valores codificados en su lugar.
Conclusión
En este tutorial, mejoramos nuestras habilidades con flexbox creando una atractiva página estática para portafolios. Incluso nos desafiamos a nosotros mismos, pues hemos aprendido a crear un gráfico de columnas responsivo sin usar ninguna biblioteca externa de JavaScript, SVG, o el elemento canvas
. ¡Solamente con CSS!
Espero que hayas disfrutado este tutorial tanto como yo disfruté escribiéndolo, y que esta demostración te inspire para desarrollar el sitio para tu portafolio. Me encantaría ver tu trabajo, ¡asegúrate de compartirlo con nosotros!
Lecturas adicionales
- FlexboxCómo crear una página responsiva a pantalla completa con FlexboxGeorge Martsoukos
- CSSCómo crear un gráfico de dona (rosquilla) en semicírculo con CSSGeorge Martsoukos
- InspiraciónMás de 15 de los mejores temas de WordPress para portafolios de creativosBrenda Barron
- WordPressMás de 18 de los mejores temas de WordPress para portafolios personales de 2018Brenda Barron