¡Te doy la bienvenida a un nuevo reto de CSS! Esta vez vamos a hacer el rediseño de Instagram y consumiremos su API para mostrar información real.
Compártenos el resultado final en los comentarios y/o en Twitter con el hashtag #RetosPlatziCSS. 😉
Antes de comenzar, los cursos de Platzi que te recomiendo para realizar este reto son los siguientes:
Para este reto te propongo que realices siguientes 5 pasos:
💡 Recomendación: Evita copiar y pegar el código. Trata de transcribir cada detalle y entender muy bien su funcionamiento (sin afán, tómate tu tiempo). Solo de esta forma podrás convertirte en un(a) experto(a) en maquetación con CSS.
ʕ•́ᴥ•̀ʔっ ¡Empecemos!
Para obtener el token de acceso, Instagram nos pedirá un enlace de nuestro proyecto (en un ratito te muestro para qué). Así que, lo primero que debemos hacer es crear un repositorio en GitHub y hacer el deploy con GitHub Pages. En mi caso, creé un repositorio llamado instagram-redesign-app
así:
Para hacer el deploy con GitHub Pages, lo primero que debemos hacer es ir a la pestaña de Configuraciones:
En Configuraciones, debemos ir a la sección que dice GitHub Pages y darle click al enlace que dice Check it out here! (o, ¡Compruébalo aquí! en español):
Aquí debemos cambiar la fuente que dice None por main:
Seguido de esto, GitHub nos mostrará el enlace de nuestro sitio que nos servirá para obtener nuestro token y poder acceder a la información de nuestro usuario en Instagram:
Luego de tener desplegado el repositorio de nuestro proyecto obtendremos el token de acceso siguiendo los 5 pasos de este tutorial de Facebook for Developers.
Aquí te enseño como me quedó mi configuración final en donde obtengo el client_id
(identificador de la app en Instagram), el client_secret
(clave secreta de la app de Instagram) y el redirect_uri
(URI de redireccionamiento de OAuth válidos):
Con el client_id
y el redirect_uri
debemos armar la siguiente URL (que encuentras en el paso número 4 del tutorial):
https://api.instagram.com/oauth/authorize?client_id={app-id}&redirect_uri={redirect-uri}&scope=user_profile,user_media&response_type=code
Esta URL la pegas en el navegador. Allí te saldrá una ventanita para autorizar y, apenas autorices, te debe redirigir a una página como esta:
Si te surgen inconvenientes en este paso, no dudes en escribirnos en los comentarios 😊
Ahora con el código que te da Instagram al hacer la redirección (como pudiste ver en la imagen) vas a hacer un POST a la API de Instagram (https://api.instagram.com/oauth/access_token) desde Postman, así:
Al tener listos los valores de las keys del body client_id
, client_secret
, grant_type
, redirect_uri
y code
, le vas a dar click en Send e Instagram te dará el access_token
y el user_id
para poder hacer las peticiones más adelante sobre tu perfil.
Con el token que obtuvimos anteriormente podremos hacer diferentes peticiones a la API de Instagram. En nuestro caso, queremos obtener 2 cosas: la primera, información del usuario (como el username y cantidad de publicaciones); y la segunda, información del contenido multimedia del usuario (como fotos y videos publicados).
Instagram nos provee la siguiente documentación para obtener perfiles de usuario y contenido multimedia de usuario. Lo que debemos hacer nosotros es usar Postman (o el mismo navegador) para realizar ambas peticiones.
Para hacer la petición GET en Postman a la API https://graph.instagram.com/me
(para obtener el id
y el username
del usuario) debemos dar click en donde dice params
y agregar dos keys: fiels
y access_token
, y como valores: id,username
y tu_access_token
respectivamente. Así:
Ahora vamos a hacer la petición al contenido multimedia desde el navegador (también lo puedes hacer desde Postman, pero quiero mostrarte otro camino). Para ello escribimos la URL completa (https://graph.instagram.com/me?fields=id,username,media_count&access_token=tu_access_token
) en la barra de búsqueda, damos enter y listo, tendríamos algo como esto:
Ya hicimos las dos peticiones a la API de Instagram para obtener la información que necesitamos: username
y contenido multimedia.
Ahora es momento de hacer estas peticiones en nuestro proyecto con JavaScript y para ello utilizaremos azync/await.
Inicialmente añadiremos estas dos variables:
const BASE_API = 'https://graph.instagram.com/me'const ACCESS_TOKEN = tu_access_token
Luego crearemos dos funciones: una para hacer la petición a la API que nos entrega la información del usuario (username
y media_count
); y otra para hacer la petición a la API que nos entrega la información multimedia del usuario (id
y media_url
).
La función que nos provee la información del usuario quedaría de la siguiente forma (puse un console.log
para que veas en consola la respuesta):
asyncfunctiongetUserInfo() {
const response = await fetch(`${BASE_API}?fields=username,media_count&access_token=${ACCESS_TOKEN}`)
const userInfo = await response.json()
console.log(userInfo)
return userInfo
}
getUserInfo()
💡 Para comprender muchísimo mejor las líneas que acabamos de escribir, te recomiendo ver la clase: Conociendo Async/await del Curso de Asincronismo con JavaScript.
La función que nos provee la información multimedia del usuario quedaría de la siguiente forma (puse un console.log
para que veas en consola la respuesta):
asyncfunctiongetUserMediaInfo() {
const response = await fetch(`${BASE_API}/media?fields=id,media_url&access_token=${ACCESS_TOKEN}`)
const userMediaInfo = await response.json()
console.log(userMediaInfo)
return userMediaInfo
}
getUserMediaInfo()
Para poder visualizar esa información, debemos preparar nuestro HTML de la siguiente manera:
<h1id="username"></h1><pid="posts"></p><divid="photos"></div>
Y, desde JavaScript accedemos a él así:
const username = document.getElementById('username')
const posts = document.getElementById('posts')
const photos = document.getElementById('photos')
Para comprender muchísimo mejor las 3 líneas que acabamos de escribir, te recomiendo ver esta clase sobre Cómo acceder al DOM con JavaScript del Curso Práctico de Maquetación y Animaciones con CSS.
A continuación, a nuestra función getUserInfo
le añadimos las dos siguientes líneas:
username.innerHTML = userInfo.username
posts.innerHTML = userInfo.media_count
Quedando finalmente de la siguiente manera:
asyncfunctiongetUserInfo() {
const response = await fetch(`${BASE_API}?fields=username,media_count&access_token=${ACCESS_TOKEN}`)
const userInfo = await response.json()
console.log(userInfo)
username.innerHTML = userInfo.username
posts.innerHTML = userInfo.media_count
return userInfo
}
getUserInfo()
Finalmente debemos cambiar la forma en la que ejecutamos nuestra función getUserMediaInfo
de la siguiente manera para añadirle un ancho a cada imagen (width
), una fuente a cada imagen (src
), y las diferentes imágenes al contenedor principal (photos
):
getUserMediaInfo().then(media => {
media.data.map((mediaInfo) => {
const img = document.createElement('img')
img.style.width = '100px'img.src = mediaInfo.media_url
photos.appendChild(img)
})
})
Así obtendrías un resultado como este:
"El término Neomorfismo es la combinación de la palabra Nuevo y del término Eskeumorfismo, acuñado por Steve Jobs para describir el aspecto “tradicional” que tenían sus interfaces en las primeras versiones de Iphone. ¡Cómo olvidar estas interfaces!
Siguiendo el segundo principio de la Heurística de Nielsen, esta tendencia trata de imitar de la manera más fiel posible los objetos y artefactos con los que interactuamos en la vida real. Esto se vio reflejado en herramientas como la calculadora, iBook y su vitrina con libros o la app de notas, la que parecía evidentemente un block de anotaciones." Neomorfismo: la nueva tendencia en diseño UI.
Hablando de tendencias: Crea tu planeador semanal con CSS Grid, Flexbox y Glassmorphism
Buscando en Behance diseños neomórficos me encontré con este rediseño de Instagram por Parisa Behboodi el cuál usaremos como guía para embellecer las peticiones que hicimos en el paso anterior.
Pero, antes de comenzar, para obtener un resultado neomórfico con HTML y CSS debemos jugar con sombras y con luz así como nos indica este tutorial (aunque también podemos obtener este efecto usando una herramienta online como Neumorphism.io y añadirlo a nuestro código).
Teniendo en cuenta lo anterior, nuestro HTML quedaría así:
<div class="neumorphism"></div>
nuestro CSS así:
:root {
--gray: #EAEBF3;
}
body {
background: var(--gray);
display: grid;
place-items: center;
}
.neumorphism {
width: 100px;
height: 100px;
border-radius: 50%;
margin: 20px;
box-shadow: 9px9px16pxrgb(163 177 198 / 60%),
-9px -9px16pxrgb(255 255 255 / 50%);
}
Y el resultado final:
También puedes verlo en CodePen.
Teniendo ya listo el efecto neomorfico con HTML y CSS podemos comenzar con la maquetación de nuestro perfil de Instagram. Para ello te propongo dividir toda la pantalla en pequeñas partes: Header, Posts, Highlights y Photos.
Teniendo en cuenta estas divisiones, podemos comenzar a maquetar sección por sección.
💡 Por cierto, te recomiendo icons8 para los íconos y Google Fonts para la tipografía.
Este Header en particular puede maquetarse de varias formas. Para este caso, te propongo que usemos CSS Grid para crear 5 columnas y ubicar cada uno de los elementos allí así como te muestro en la siguiente imagen:
Teniendo en cuenta esta estructura, analicemos cómo podríamos abordar los elementos 1, 2 y 3 desde CSS.
El elemento 1 necesitaría:
El elemento 2 necesitaría:
Y el elemento 3 necesitaría:
Nuestros estilos generales quedarían de la siguiente forma:
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@300&display=swap');
:root {
--gray: #EAEBF3;
--dark-gray: #A5A5A5;
--black: #000000;
--text-sm: 10px;
--text-md: 12px;
--text-lg: 14px;
}
body {
background: var(--gray);
font-family: 'Source Sans 3', sans-serif;
}
.text-sm {
font-size: var(--text-sm);
text-align: center;
margin: 0;
}
.text-md {
font-size: var(--text-md);
text-align: center;
margin: 0;
}
.text-lg {
font-size: var(--text-lg);
text-align: center;
margin: 0;
}
.relative {
position: relative;
}
.center {
display: grid;
place-items: center;
}
.icon-sm {
width: 16px;
}
.icon-md {
width: 24px;
}
El HTML nos quedaría así:
<header class="header-container">
<img class="icon-sm" src="https://img.icons8.com/external-becris-lineal-becris/50/000000/external-edit-mintab-for-ios-becris-lineal-becris.png"/>
<div class="icon-neumorphism center relative">
<div class="notification"></div>
<svg class="icon-sm" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"width="30"height="30"
viewBox="0 0 172 172"
style=" fill:#000000;"><g fill="none"fill-rule="nonzero"stroke="none"stroke-width="1"stroke-linecap="butt"stroke-linejoin="miter"stroke-miterlimit="10"stroke-dasharray=""stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none"text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z"fill="none"></path><g fill="#a5a5a5"><path d="M28.66667,28.66667c-6.33533,0 -11.46667,5.13133 -11.46667,11.46667v86c0,6.33533 5.13133,11.46667 11.46667,11.46667h22.93333v22.93333c0,3.16643 2.5669,5.73333 5.73333,5.73333c1.9417,-0.00362 3.74963,-0.98976 4.80391,-2.62031l19.56276,-26.04636h61.63333c6.33533,0 11.46667,-5.13133 11.46667,-11.46667v-86c0,-6.33533 -5.13133,-11.46667 -11.46667,-11.46667z"></path></g></g></svg>
</div>
<div class="center">
<div class="profile-pic-neumorphism center">
<div class="story center">
<img class="profile-picture" src="https://scontent-bog1-1.cdninstagram.com/v/t51.2885-15/18300095_278312585963307_9179315967434424320_n.jpg?_nc_cat=106&ccb=1-5&_nc_sid=8ae9d6&_nc_ohc=Zio5O-jBLXkAX9Cx181&_nc_ht=scontent-bog1-1.cdninstagram.com&edm=ANo9K5cEAAAA&oh=81d971eb6ea000a5465982e3d27741d0&oe=61B8D7A3" alt="Mike">
</div>
</div>
<h1 id="username"class="text-lg"></h1>
</div>
<div class="icon-neumorphism center">
<svg class="icon-sm" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"width="30"height="30"
viewBox="0 0 172 172"
style=" fill:#000000;"><g fill="none"fill-rule="nonzero"stroke="none"stroke-width="1"stroke-linecap="butt"stroke-linejoin="miter"stroke-miterlimit="10"stroke-dasharray=""stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none"text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z"fill="none"></path><g fill="#a5a5a5"><path d="M54.72422,22.93333c-20.726,0 -37.52422,16.79822 -37.52422,37.52422c0,36.07642 41.84746,70.54796 61.39817,85.89922c0.01862,0.01499 0.03728,0.02992 0.05599,0.04479c0.13396,0.10517 0.31608,0.25498 0.44792,0.35833l0.0112,-0.0112c1.98358,1.4988 4.40056,2.31232 6.88672,2.31797c2.48999,-0.00322 4.91127,-0.81686 6.89792,-2.31797v0.0112c0.03859,-0.03025 0.09561,-0.07038 0.13437,-0.10078c0.02948,-0.02312 0.0712,-0.05517 0.10079,-0.07839c0.03755,-0.03335 0.07487,-0.06694 0.11198,-0.10078c19.49011,-15.2961 61.55495,-49.85989 61.55495,-86.02239c0,-20.726 -16.79822,-37.52422 -37.52422,-37.52422c-19.80867,0 -31.27578,17.2 -31.27578,17.2c0,0 -11.46711,-17.2 -31.27578,-17.2z"></path></g></g></svg>
</div>
<img class="icon-sm"class="icon" src="https://img.icons8.com/ios/50/000000/settings--v1.png"/>
</header>
Y el CSS nos quedaría así:
.header-container {
display: grid;
grid-template-columns: 16px50px150px50px16px;
margin-top: 20px;
}
.notification {
position: absolute;
width: 8px;
height: 8px;
background: red;
border-radius: 50%;
top: 2px;
right: 2px;
}
.profile-pic-neumorphism {
width: 100px;
height: 100px;
border-radius: 50%;
margin: 16px0;
box-shadow: 9px9px16pxrgb(163 177 198 / 60%),
-9px -9px16pxrgb(255 255 255 / 50%);
}
.icon-neumorphism {
align-self: end;
width: 35px;
height: 35px;
border-radius: 50%;
margin-bottom: 20px;
justify-self: center;
box-shadow: 9px9px16pxrgb(163 177 198 / 60%),
-9px -9px16pxrgb(255 255 255 / 50%);
}
.story {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(to right, red, purple);
}
.profile-picture {
width: 72px;
height: 72px;
border-radius: 50%;
border: 3px solid var(--gray);
background: var(--gray);
}
Obteniendo un resultado como el siguiente:
Analicemos lo que necesitamos para esta sección de Posts:
Para ello, nuestro HTML nos quedaría así:
<sectionclass="posts-container"><pclass="text-box text-md"><spanid="posts"></span><span>Posts</span></p><pclass="text-box text-md"><span>14</span><span>Followers</span></p><pclass="text-box text-md"><span>83</span><span>Following</span></p></section>
Y el CSS nos quedaría así:
.posts-container {
display: flex;
justify-content: space-evenly;
align-items: center;
width: 282px;
height: 45px;
border-radius: 8px;
box-shadow: 9px9px16pxrgb(163 177 198 / 60%),
-9px -9px16pxrgb(255 255 255 / 50%);
margin-top: 16px;
}
.text-box {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
}
.text-boxspan + span {
color: var(--dark-gray);
}
Obteniendo un resultado como el siguiente:
Analicemos lo que necesitamos para esta sección de Highlights:
Para ello, nuestro HTML nos quedaría así:
<ulclass="highlights-container"><li><divclass="center"><imgclass="icon-sm"src="https://img.icons8.com/external-kmg-design-outline-color-kmg-design/32/000000/external-shopping-bag-e-commerce-kmg-design-outline-color-kmg-design-1.png"/></div><pclass="text-sm">Shopping</p></li><li><divclass="center"><imgclass="icon-sm"src="https://img.icons8.com/external-vitaliy-gorbachev-lineal-color-vitaly-gorbachev/60/000000/external-carrot-vegetable-vitaliy-gorbachev-lineal-color-vitaly-gorbachev.png"/></div><pclass="text-sm">Life Style</p></li><li><divclass="center"><imgclass="icon-sm"src="https://img.icons8.com/external-filled-outline-icons-pause-08/64/000000/external-clothes-autumn-filled-outline-icons-pause-08.png"/></div><pclass="text-sm">Trend ?!</p></li><li><divclass="center"><imgclass="icon-sm"src="https://img.icons8.com/external-vitaliy-gorbachev-lineal-color-vitaly-gorbachev/60/000000/external-plane-summer-vitaliy-gorbachev-lineal-color-vitaly-gorbachev.png"/></div><pclass="text-sm">Travel</p></li><li><divclass="center"><imgclass="icon-sm"src="https://img.icons8.com/external-bearicons-outline-color-bearicons/64/000000/external-Dog-chinese-new-year-bearicons-outline-color-bearicons.png"/></div><pclass="text-sm">My Dog</p></li></ul>
Y el CSS nos quedaría así:
.highlights-container {
list-style-type: none;
display: flex;
padding: 0;
}
.highlights-containerdiv {
width: 40px;
height: 40px;
border-radius: 50%;
box-shadow: 9px9px16pxrgb(163 177 198 / 60%),
-9px -9px16pxrgb(255 255 255 / 50%);
margin: 8px;
}
Obteniendo un resultado como el siguiente:
Analicemos lo que necesitamos para esta sección de Photos:
Para ello, nuestro HTML nos quedaría así:
<section id="photos"class="photos-container"></section>
Y el CSS nos quedaría así:
.photos-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 4px;
width: 282px;
}
.photos-containerimg {
border-radius: 8px;
width: 90px;
height: 90px;
object-fit: cover;
}
Obteniendo un resultado como el siguiente:
¡Este paso es mi favorito! Compártenos en los comentarios y/o en Twitter con el hashtag #RetosPlatziCSS el resultado final del rediseño de tu Perfil de Instagram. 😄
Aquí te comparto mi codepen y también mi repositorio de GitHub con el resultado final.
Además, cuéntanos qué otro tipo de retos te gustaría encontrar en esta sección.
#NuncaParesDeAprender
Listo mi resultado.
Una pequeña corrección que quisiera sugerir en el código de la función “getUserMediaInfo” para que las imágenes obtengan el ancho adecuado:
getUserMediaInfo().then(media => { media.data.map((mediaInfo) => { const img = document.createElement('img') // img.style.width = "90px" // Simplemente no usemos esta línea :-) img.src = mediaInfo.media_url photos.appendChild(img) }) })```
Que gran reto, la mejor manera de aprender son estos tipos de practicas.
Graciasss… Así es, Iván !!! ✨🚀
Este tutorial es la excelencia pura!
Es con todo el amor !!! 💚
Me encanta!
🥰 🥰 🥰
Tremendo tutorial, muy detallado!! Lo voy a poner en práctica. Tesa!!
Gracias, Juancho !!! 🥰 Feliz de ver tu resultadooo ✨
Qué reto tan increible!
Gracias, Jean !!! ✨🚀
Me encanta!
🥰 🥰 🥰
¡Te amo, teff!
Y yo más a ustedes !!! 😊 💚
Yo tengo un problema 😦.
Al realizar la petición post con la info del cient_id etc, la petición retorna un bad request mencionando lo siguiente:
{ "error_type": "OAuthException", "code": 400, "error_message": "This grant type is not supported" }
Me mandas pantallacito con la petición que estás haciendo para revisarlo ? 😊
Está buenísimo!
Muchas gracias por compartir! 😄
Manos a la obra! 👨🏻💻