Advertisement
  1. Web Design
  2. HTML/CSS
  3. JavaScript for Designers

Build a Static Portfolio With Advanced CSS Bar Chart

Scroll to top

In a previous post, I showed you how to build a beautiful fullscreen portfolio page. During that tutorial we also learned how to create a responsive CSS column chart. In this tutorial we’ll build another attractive static portfolio page, this time featuring a pure CSS bar chart without using any external JavaScript library, SVG, or the canvas element!

What We’re Building

Here’s the project we’ll be creating:

We have a lot of exciting things to cover, so let’s get started!

Note: This tutorial assumes some good CSS knowledge. For instance, you should be familiar with CSS positioning and flexbox basics.

1. Begin With the Page Markup

The page markup consists of a header and three fullscreen sections:

1
<header class="position-fixed text-lightblue page-header">...</header>
2
<section class="d-flex justify-content-center align-items-center vh-100">...</section>
3
<section class="d-flex vh-100 bg-lightwhite">...</section>
4
<section class="d-flex flex-column justify-content-center align-items-center vh-100 position-relative">...</section>

Note: Beyond the elements’ specific classes, our markup contains a number of utility (helper) classes. We’ll use this methodology to keep our CSS as DRY as possible. However, for the sake of readability, within the CSS we won’t group common CSS rules.

2. Define Some Basic Styles

Following what we’ve just discussed above, we first specify some reset rules along with a number of helper classes:

1
:root {
2
  --gray: #cbcfd3;
3
  --white: white;
4
  --black: #1a1a1a;
5
  --lightwhite: whitesmoke;
6
  --lightblue: #009dd3;
7
  --peach: #ff9469;
8
  --transition-delay: 0.3s;
9
  --transition-delay-step: 0.3s;
10
  --skills-width: 120px;
11
}
12
13
* {
14
  padding: 0;
15
  margin: 0;
16
  box-sizing: border-box;
17
}
18
19
ul {
20
  list-style: none;
21
}
22
23
a {
24
  text-decoration: none;
25
  color: inherit;
26
}
27
28
.d-block {
29
  display: block;
30
}
31
32
.d-flex {
33
  display: flex;
34
}
35
36
.flex-column {
37
  flex-direction: column;
38
}
39
40
.justify-content-center {
41
  justify-content: center;
42
}
43
44
.justify-content-between {
45
  justify-content: space-between;
46
}
47
48
.align-items-center {
49
  align-items: center;
50
}
51
52
.align-items-end {
53
  align-items: flex-end;
54
}
55
56
.flex-grow-1 {
57
  flex-grow: 1;
58
}
59
60
.vh-100 {
61
  height: 100vh;
62
}
63
64
.position-relative {
65
  position: relative;
66
}
67
68
.position-absolute {
69
  position: absolute;
70
}
71
72
.position-fixed {
73
  position: fixed;
74
}
75
76
.text-center {
77
  text-align: center;
78
}
79
80
.text-gray {
81
  color: var(--gray);
82
}
83
84
.text-lightblue {
85
  color: var(--lightblue);
86
}
87
88
.text-peach {
89
  color: var(--peach);
90
}
91
92
.bg-white {
93
  background: var(--white);
94
}
95
96
.bg-lightwhite {
97
  background: var(--lightwhite);
98
}
99
100
.h1 {
101
  font-size: 2.5rem;
102
}
103
104
.h2 {
105
  font-size: 2rem;
106
}

The naming conventions for our helper classes are inspired by Bootstrap 4’s class names.

3. Build the Page Header

The page header includes:

  • The logo
  • Main navigation
The header layoutThe header layoutThe header layout

Header HTML

1
<header class="position-fixed bg-white page-header">
2
  <nav class="d-flex justify-content-between align-items-center">
3
    <a href="" class="text-peach logo">
4
      <strong>DM</strong>
5
    </a>
6
    <ul class="d-flex">
7
      <li>
8
        <a href="#skills">Skills</a>
9
      </li>
10
      <li>
11
        <a href="#contact">Contact</a>
12
      </li>
13
    </ul>
14
  </nav>
15
</header>

Header CSS 

1
.page-header {
2
  left: 0;
3
  right: 0;
4
  top: 0;
5
  padding: 20px;
6
  z-index: 10;
7
}
8
9
.page-header .logo {
10
  font-size: 1.7rem;
11
}
12
13
.page-header li:not(:last-child) {
14
  margin-right: 20px;
15
}
16
17
.page-header a {
18
  font-size: 1.1rem;
19
}

4. Build the Hero Section

The first section of our page includes:

  • A heading
  • A call-to-action

Here’s what it will look like:

The layout of the first sectionThe layout of the first sectionThe layout of the first section

Section #1 HTML

1
<section class="d-flex justify-content-center align-items-center vh-100">
2
  <h1 class="text-center h1">...</h1> 
3
  <div class="position-absolute scroll-down">...</div>  
4
</section>

Section #1 CSS

The call-to-action includes a thin line which is animated infinitely, compelling the user to scroll down to see what’s beneath “the fold” (which may or may not exist).

Its styles:

1
.scroll-down {
2
  left: 50%;
3
  bottom: 0;
4
  transform: translateX(-50%);
5
  text-transform: uppercase;
6
  transition: all 0.5s;
7
}
8
9
.scroll-down.is-hidden {
10
  opacity: 0;
11
  visibility: hidden;
12
}
13
14
.scroll-down::after {
15
  content: '';
16
  display: block;
17
  margin: 3px auto 0;
18
  width: 1px;
19
  height: 60px;
20
  background: var(--black);
21
  transform-origin: bottom;
22
  animation: pulse 3.5s infinite linear;
23
}
24
25
@keyframes pulse {
26
  0% {
27
    transform: scaleY(1);
28
  }
29
  50% {
30
    transform: scaleY(0.65);
31
  }
32
  100% {
33
    transform: scaleY(1);
34
  }
35
}

Section #1 JavaScript

Initially the call-to-action will be visible. But when the user starts scrolling, it will disappear. More specifically, it will receive the is-hidden class which we have just defined in the styles above.

Here’s the required JavaScript code:

1
const scrollDown = document.querySelector(".scroll-down");
2
3
window.addEventListener("scroll", scrollHandler);
4
5
function scrollHandler() {
6
  window.pageYOffset > 0
7
    ? scrollDown.classList.add("is-hidden")
8
    : scrollDown.classList.remove("is-hidden");
9
}

5. Build the Section #2

The second section of our page includes:

  • A background image
  • The bar chart which demonstrates web skills

Here’s what it looks like:

The second section layoutThe second section layoutThe second section layout

Section #2 HTML

1
<section class="d-flex vh-100 bg-lightwhite" id="skills">
2
  <div class="position-relative flex-grow-1 bg-img"></div>
3
  <div class="d-flex justify-content-center align-items-center flex-grow-1">
4
    <div class="position-relative chart-wrapper">
5
      <ul class="d-flex flex-column chart-skills">
6
        <li class="position-relative">
7
          <span>CSS</span>
8
        </li>
9
        ...
10
      </ul>
11
      <ul class="d-flex position-absolute chart-levels">
12
        <li class="flex-grow-1 position-relative">
13
          <span class="position-absolute">Novice</span>
14
        </li>
15
        ...
16
      </ul> 
17
    </div>
18
  </div>
19
</section>

Style the Background Image

The left part of this section contains an image taken from Wordpress Code Backgrounds on Envato Elements. It will give us the atmosphere we’re looking for, whilst inspiring the colors we use elsewhere in the design:

WordPress Code BackgroundsWordPress Code BackgroundsWordPress Code Backgrounds
WordPress Code Backgrounds

Some key things:

  • The image will appear on screens wider than 900px and
  • we’ll use CSS (via the background-image property) and not HTML (via the img element) to place the image within the page. We choose this method because that gives us an easy way to enhance the image appearance. For example, we’ll skew it by taking advantage of its ::after pseudo-element. You could even play with its colors using background-blend-mode: luminosity, for instance.

Note: By default an img is an empty element and doesn’t have pseudo-elements.

The corresponding styles:

1
.bg-img {
2
  background: #fff url(bg-programming.jpg) no-repeat center / cover;
3
}
4
5
.bg-img::after {
6
  content: '';
7
  position: absolute;
8
  top: 0;
9
  bottom: 0;
10
  right: 0;
11
  width: 7rem;
12
  background: var(--lightwhite);
13
  transform: skew(-5deg);
14
  transform-origin: left bottom;
15
}
16
17
@media screen and (max-width: 900px) {
18
  .bg-img {
19
    display: none;
20
  }
21
}

Style the Chart

At this point we’ll concentrate on the most challenging part of our demo; how to construct the bar chart. 

The chart will have two axes. On the y-axis we’ll place the web skills (CSS, HTML, JavaScript, Python, Ruby). On the other x-axis we’ll put the skill levels (Novice, Beginner, Intermediate, Advanced, Expert).

The chart axesThe chart axesThe chart axes

The y-axis

In terms of markup, each skill is a list item placed inside the .chart-skills list. Next, each list item will hold its text within a span element, like this:

1
<ul class="chart-skills">
2
  <li class="position-relative">
3
    <span>CSS</span>
4
  </li>
5
  ...
6
</ul>

We define a fixed width for the span element equal to 120px. To avoid repetition and so we can easily change this value (as other values will depend on it), let’s store it inside a CSS variable:

1
:root {
2
  --skills-width: 120px;
3
}
4
5
.chart-wrapper .chart-skills span {
6
  display: inline-block;
7
  width: var(--skills-width);
8
  padding: 15px;
9
}

Each list item will contain its own ::before and ::after pseudo-elements:

1
/*CUSTOM VARIABLES HERE*/
2
3
.chart-wrapper .chart-skills li::before,
4
.chart-wrapper .chart-skills li::after {
5
  content: '';
6
  position: absolute;
7
  top: 25%;
8
  left: var(--skills-width);
9
  height: 50%;
10
  border-top-right-radius: 10px;
11
  border-bottom-right-radius: 10px;
12
  z-index: 2;
13
}

The ::after pseudo-element will have a light gray color and a static width which is calculated by subtracting the span width from the list item width:

1
.chart-wrapper .chart-skills li::after {
2
  width: calc(100% - var(--skills-width));
3
  background: rgba(211, 211, 211, 0.3);
4
}

Here’s how it looks:

The after pseudo-elementThe after pseudo-elementThe after pseudo-element

The initial width of all ::before pseudo-elements will be 0:

1
/*CUSTOM VARIABLES HERE*/
2
3
.chart-wrapper .chart-skills li::before {
4
  width: 0;
5
  background: var(--lightblue);
6
  transition: width 0.65s ease-out;
7
}

But as soon as the chart becomes visible in the viewport, their width will be animated and receive a value that will be determined by the associated skill level:

Add Class When in View

There are multiple ways to detect whether an element is visible in the viewport or not. Let’s take advantage of the following handy function extracted from an old, yet popular StackOverflow thread:

1
function isElementInViewport(el) {
2
  var rect = el.getBoundingClientRect();
3
  return (
4
    rect.top >= 0 &&
5
    rect.left >= 0 &&
6
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
7
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
8
  );
9
}

If you have read my previous tutorials, you might remember that I’ve also used this function to animate a vertical timeline.

So, back to the job in hand! When the chart becomes visible in the current viewport, we’ll give it the in-view class.

Here’s the JavaScript code that does this job:

1
const chartWrapper = document.querySelector(".chart-wrapper");
2
3
window.addEventListener("scroll", scrollHandler);
4
5
function scrollHandler() {
6
  if (isElementInViewport(chartWrapper)) chartWrapper.classList.add("in-view");
7
}

The ::before pseudo-elements should be animated sequentially. To give them the desired transition speed, we’ll use two other CSS variables along with the calc() CSS function.

Here are the CSS styles responsible for revealing those sudo-elements:

1
:root {
2
  ...
3
  --transition-delay: 0.3s;
4
  --transition-delay-step: 0.3s;
5
  --skills-width: 120px;
6
}
7
8
.chart-wrapper.in-view .chart-skills li:nth-child(1)::before {
9
  width: calc(90% - var(--skills-width));
10
  transition-delay: var(--transition-delay);
11
}
12
13
.chart-wrapper.in-view .chart-skills li:nth-child(2)::before {
14
  width: calc(75% - var(--skills-width));
15
  transition-delay: calc(
16
    var(--transition-delay) + var(--transition-delay-step)
17
  );
18
}
19
20
.chart-wrapper.in-view .chart-skills li:nth-child(3)::before {
21
  width: calc(62% - var(--skills-width));
22
  transition-delay: calc(
23
    var(--transition-delay) + var(--transition-delay-step) * 2
24
  );
25
}
26
27
.chart-wrapper.in-view .chart-skills li:nth-child(4)::before {
28
  width: calc(49% - var(--skills-width));
29
  transition-delay: calc(
30
    var(--transition-delay) + var(--transition-delay-step) * 3
31
  );
32
}
33
34
.chart-wrapper.in-view .chart-skills li:nth-child(5)::before {
35
  width: calc(38% - var(--skills-width));
36
  transition-delay: calc(
37
    var(--transition-delay) + var(--transition-delay-step) * 4
38
  );
39
}

Keep in mind that Microsoft Edge doesn’t support the above math operations, so if you need to support it, just pass some static values instead, like this:

1
.chart-wrapper.in-view .chart-skills li:nth-child(2)::before {
2
  transition-delay: 0.6s;
3
}
4
5
.chart-wrapper.in-view .chart-skills li:nth-child(3)::before {
6
  transition-delay: 0.9s;
7
}

The x-axis

In terms of markup, each level is a list item placed inside the .chart-levels list. Next, each list item will hold its text within a span element, like this:

1
<ul class="d-flex position-absolute chart-levels">
2
  <li class="flex-grow-1 position-relative">
3
    <span class="position-absolute">Novice</span>
4
  </li>
5
  ...
6
</ul>

Some things to note:

  • We set the list as a flex container. Also, we absolutely position it and give it a left padding equal to the width of the spans of the first list.
  • We give list items flex-grow: 1 to grow and take up all the available space. 
  • The spans are positioned at the very bottom of their parent list item. 
The position of the span elementsThe position of the span elementsThe position of the span elements

The associated styles for that list:

1
/*CUSTOM VARIABLES HERE*/
2
3
.chart-wrapper .chart-levels {
4
  left: 0;
5
  bottom: 0;
6
  width: 100%;
7
  height: 100%;
8
  padding-left: var(--skills-width);
9
}
10
11
.chart-wrapper .chart-levels li {
12
  border-right: 1px solid rgba(211, 211, 211, 0.3);
13
}
14
15
.chart-wrapper .chart-levels li:last-child {
16
  border-right: 0;
17
}
18
19
.chart-wrapper .chart-levels span {
20
  bottom: 0;
21
  transform: translateY(50px) rotate(45deg);
22
  padding: 10px;
23
  width: 100%;
24
}

6. Build the Section #3

The third section of our page includes:

  • A heading
  • A mailto link

Here’s what it looks like:

The layout of the third sectionThe layout of the third sectionThe layout of the third section

Section #3 HTML

1
<section class="d-flex flex-column justify-content-center align-items-center vh-100 position-relative" id="contact">
2
  <h2 class="h1">...</h2>
3
  <a href="mailto:hello@digitalmonsters.com" class="text-gray h2">...</a> 
4
</section>

7. Go Responsive

We’re almost done! As one last thing, let’s ensure that the text has a solid appearance across all screens. We’ll apply a rule targeting narrow screens: 

1
@media screen and (max-width: 600px) {
2
  html {
3
    font-size: 12px;
4
  }
5
}

One important note here is that in our styles we’ve used rem for setting the font sizes. This approach is really useful because the font sizes are relative to the root element (html). If we decrease its font size like in the code above, the rem-related font sizes will be dynamically decreased. Of course, we could have used rems for the other CSS properties as well.

The final state of our project:

Conclusion

In this tutorial we improved our CSS knowledge by learning how to build an attractive static portfolio page. We went one step further and created a responsive bar chart without using any external JavaScript library, SVG, or the canvas element. Just with plain CSS!

Hopefully this tutorial has given you enough inspiration for building your awesome portfolio site. I’d love to see your work–be sure to share it with us!

Next Steps

If you want to further enhance or extend this demo, here are two things you can do:

  • Add smooth scrolling behavior to the menu links without using any external JavaScript library.
  • Use the more effective Intersection Observer API instead of the scroll event for checking whether the chart is visible in the viewport or not. 

As always, thanks for reading!

More Practical Projects to Boost Your Front-end Skills

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.