Website
Esteban Campos / August 20, 2020
6 min read • ––– views
I created this website, because i always desire to have a portfolio but also a way to display the code that I normally work. That code usually just hit my github account. But i really want to keep working on my own stuff and feel rewarded with my progress.
I used a free template for the website part. For the blog, I based my efforts on the excelent portfolio of leerob.
I used some techs on top and remove vanilla CSS and jQuery completely. My selected technologies:
At the moment the website has been done with theme-ui
and the blog part with chakra-ui
due my heritage of the leerob work. My plan is to migrate completely to chakra-ui
due it contains some really usefuls components but keep two different themes.
MDX Support#
The MDX support is essential to me because I decided to start a blog too. MDX
allows me to create and publish new articles easily, as well as improve maintainability over time. I decide to add some plugins that would allow me to add some extra info like next-mdx-enhanced. So, some info date or title can be added as:
---
title: "Rails Conf 2020"
publishedAt: "2020-28-01"
summary: "Overview of the Rails Conf 2020 version 2"
---
And later, that data can be easily tracked:
import React from "react";
export default (frontMatter) => {
return ({ children }) => (
<>
<h1>{frontMatter.title}</h1>
{children}
</>
);
};
};
Thanks to babel-plugin-import-glob-array, I can make the file system the source of truth.
import { frontMatter as blogPosts } from './blog/**/*.mdx';
const Blog = () => (
blogPosts.map((frontMatter) => (
<BlogPost key={frontMatter.title} {...frontMatter} />
))}
);
MDX Plugins#
MDX uses remark and rehype under the hood and allows you to provide external plugins to hook into the compilation process. Some of the plugins that i'm using:
- remark-autolink-headings: Add a link to the headings. It works in conjuntion with
remark-slug
. - remark-slug: Add anchors headings based on the titles.
- remark-code-titles: Allows to add titles to the code blocks:
js:hello.js
- reading-time: One of my favorites. Add an estimation of the reading time of the content.
Syntax Highlighting and dark/light mode#
Using mdx-prism and two prism themes for dark/light mode, I got two different ways to display the blog.
import { css } from "@emotion/core";
import { theme } from "@chakra-ui/core";
const prismBaseTheme = css`
// Base styling
`;
export const prismLightTheme = css`
// Light mode
`;
export const prismDarkTheme = css`
// Dark mode
`;
const { colorMode } = useColorMode();
const prismTheme = colorMode === "light" ? prismLightTheme : prismDarkTheme;
Website challengues#
Resume data#
Almost all the static data of the website is contained in a JSON structure. So, each section it's straighlty easy to add new info. Let's check for example Favorites Tech
section.
favoriteTechs: [
{
name: "React/React Native",
image: "react.png",
description: "",
},
];
That will be rendered as:
Nav menu#
The list of our Menu Titles (Sections) as keys, with their Y-pixel position on the page as the values 'home' generically references the top of the page.
const menuItems = {
home: 0,
about: null,
resume: null,
portfolio: null,
};
The next step is determine where to set Anchor Points for each section included on the menuItems
. The process is basically look for the IDs that match the items on menu. The website contains a layout similar to:
<header id="home" />
<div id="about" />
<div id="resume" />
<div id="portfolio" />
The next function scroll down to find each id and it updates menuItems
with the Y-pixel
.
const getAnchorPoints = () => {
const curScroll = window.scrollY - 100;
for (const key in menuItems) {
menuItems[key] =
document.getElementById(key).getBoundingClientRect().top + curScroll;
}
};
Now, we need to add a hook to trigger getAnchorPoints
when it's necessary. The MutationObserver allows us to watch for a few different events, including page resizing when new elements might be added to the page, potentially changing the location of our anchor points.
We also listen to the scroll event in order to update based on our user's scroll depth.
useEffect(() => {
// initial run
getAnchorPoints();
// run on page resizing
const observer = new MutationObserver(getAnchorPoints);
observer.observe(document.getElementById("__next"), {
childList: true,
subtree: true,
});
return () => {
observer.disconnect();
};
}, []);
Some extra sugar#
To add the active class, I'm using reactState.
const [activeItem, setActiveItem] = useState("home");
Then, we can define the function handleScroll. Iterate through our sections object to find which section matches with the current scrollDepth of the user.
NOTE: This code assumes that the sections object is built with an 'ordered' list of sections, with the lowest depth (top) section first and greatest depth (bottom) section last.
const handleScroll = () => {
const curPos = window.scrollY;
let curSection = null;
if (curPos < menuItems["about"]) {
curSection = "home";
} else if (curPos < menuItems["resume"]) {
curSection = "about";
} else if (curPos < menuItems["portfolio"]) {
curSection = "resume";
} else {
curSection = "portfolio";
}
setActiveItem(curSection);
We need to add a hook to detect scroll
:
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
Fade animation#
The original version of the template change the opacity of the header when the scrolling pass from the first to the second section. Also, in some moment on that transition add some animation to hide the nav menu when we scroll a little but appears again in the second section. It's just a silly but nice animation that i want to keep.
The important is to check the current position:
- If the current position is still in the first section but it's kinda close to the second section then hide the nav menu.
- If the current position is currently on the first section, display the menu but with transparency.
- Otherwise change the opacity to opaque and always display the nav menu.
const handleScroll = () => {
const curPos = window.scrollY;
// menuItems store the Y-position of each section and it is recalculated on screen dimension change.
// So it uses a percentage of the size of the sections not a bad approach.
const topSection = menuItems["resume"];
// 1. First section but approaching the second section
if (curPos > topSection * 0.2 && curPos < 768) {
showNav(false);
} else {
// 2. On the top of the first section
if (curPos < topSection * 0.2) {
setNavOpaque(false);
showNav(true);
} else {
// 3. Second section and below
setNavOpaque(true);
showNav(true);
}
}
};