Ghost theme - Part 4: Finishing the Sidebar

Please Note that this theme is not in use anymore. See this post for more info.

On mobile Layouts < 360px width the Sidebar still overflows vertically. Lets fix it and implement the features decided on in Part 3.

First a small screenshot of the problem:

As you can see the Sidebar flows into the other content.
To fix this lets remove some margins and change the nav to list its items horizontally.

All done with this bit of CSS in the styles.css file since we are changing the default layout.

.sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-wrap: wrap;
  max-height: var(--sidebar-height);

  & .page-title {
      margin: 0px;
  }

  & .page-description {
      margin: 0px;
  }

  & .navigation {
    & .nav {
      display: flex;
      list-style: none;
      padding: 0px;
      margin: 0px;
    }
  }
}
@media screen and (min-width: 961px) {
    .sidebar {
        position: relative;
    }
}

This has actually become quite a lot to keep in the styles.css file.
Lets extract it into components/sidebar.css and import it.

All should look good now. Except when the content doesnt wrap!
Here is a screenshot of what I mean:

Everything is in a row. The fix is quite easy with flexbox. Lets just set the flex-direction to column. That should fix it. Right?

Almost. The flex-wrap: wrap from earlier now messes things up. So lets just get rid of it on the sidebar. It worked great when the direction was row but now it is wrapping at the vertical ends, which is not what we want.

Now the sidebar looks pretty nice and our layout is almost done!

The features

Lets go on to the features:

  1. when scrolling the Title disappears
  2. when scrolling the Description disappears
  3. Only the Navigation is shown and the sidebar should shrink to its size but stay at the top

With position: sticky; the last one is pretty easy to do these days.
But to hide the other two we would need to restructure the HTML of our Sidebar since the position is relative to its parent. Which means it would still go out of the screen together with it.

So the last resort here is JavaScript together with position: fixed;. In general we should not use JavaScript for styling, but for this weird case lets make an exception and add a new class to the sidebar when it scrolls out of view.
Using that class we can then use the correct styles to achieve our goal and maybe add some nice CSS Animation.

Lets code

To use JavaScript create the file assets/js/helpers/styling.js.
The gotede build tools will take care of doing all the hard work of getting it onto the page, such as using babel and concatenating the JavaScript files you create.
Just take note that imports wont work. Since this is a Theme for purely Server-side rendered pages, that is totally fine. The JavaScript should be kept to a minimum here.

It should contain the following code, which will attach a new class to the Sidebar when we scroll down and then remove this class again once we scroll to the top:

function initScrollingListener() {
  const sidebar = document.getElementsByClassName("sidebar")[0];

  function addScrollClass() {
    if (window.pageYOffset >= 0) {
      sidebar.classList.add("scrolled");
      window.removeEventListener("scroll", addScrollClass);
      window.addEventListener("scroll", removeScrollClass);
    }
  }

  function removeScrollClass() {
    if (window.pageYOffset == 0) {
      sidebar.classList.remove("scrolled");
      window.removeEventListener("scroll", removeScrollClass);
      window.addEventListener("scroll", addScrollClass);
    }
  }

  if (window.pageYOffset == 0) {
    window.addEventListener("scroll", addScrollClass);
  } else {
    addScrollClass();
  }
}

initScrollingListener();

You should have some basic understanding of programming to really understand what is happening here but here is a small breakdown:

1.  If pageYOffset is 0 we are at the top, so we add a Listener that will call our addScrollClass function once we scroll. Otherwise we are not at the top and can call that function directly.
2. addScrollClass adds the class to the Sidebar element when we are not at the top of the page and the removes the Listener for calling itself on scrolling. Then it adds the Listener that calls removeScrollClass when we scroll.
3. removeScrollClass does exactly the opposite of addScrollClass.

Here is a list of all the DOM functionalities that are used in this piece of code: window, document, getElementsByClassName, window.pageYOffset, Element.classList, addEventListener, removeEventListener

Maybe you notice that we will call one of these functions every time that we scroll. In this case it is a very small amount of code, but it still has to run on the CPU, which is why we should generally avoid these kind of hacks.

Using the new class

So lets go and implement the things that should happen when we scroll in CSS.

.sidebar {
    /* ... */
    position: fixed;
    /* ... */
    &.scrolled{
        height: calc(var(--sidebar-height) / 1.5);
        width: var(--sidebar-width);

        & .page-title {
           display: none; 
        }

        & .page-description {
           display: none; 
        }
      }

This is all we need for now.
It should be inside the .sidebar block and hides both the description and the title when the scrolled class is added.

While it is not an optimal solution, since we use JavaScript for styling, it works in this case and does not put much load on the CPU since we only add two Listeners to our Window.

An Animation

To add some spiciness to our Sidebar suddenly reducing in size, we can add a small Animation when this happens.
Lets just make a dummy one for now where the Background transitions through colors. We can fine-tune this later when we got to the real colors of the theme.

  &.scrolled {
    /*...*/
    animation: background-transition 3s;
    background-color: yellow;
    z-index: 1;
  }
  /*...*/

  @keyframes background-transition {
    0% {
        background-color: blue;
    }
    100% {
        background-color: yellow;
    }
  }

One more thing is left to do.
While we have a nice Sidebar for a mobile layout these rules will break the Desktop design. Since we would have to overwrite all these rules in a MediaQuery lets make it easy on us and instead wrap these inside one themselves to keep the default on the other layouts.
To do this put all the CSS we just created for the scrolled class inside @media screen and (max-width: 960px) and dont forget to wrap them inside the .sidebar class as well.

The final sidebar.css should look like this:

.sidebar {
  grid-area: Sidebar;
  display: flex;
  flex-direction: column;
  max-height: var(--sidebar-height);

  position: fixed;
  top: 0;

  & .page-title {
      margin: 0px;
  }

  & .page-description {
      margin: 0px;
  }

  & .navigation {
    & .nav {
      display: flex;
      list-style: none;
      padding: 0px;
      margin: 0px;
    }
  }
}

@media screen and (max-width: 960px) {
    .sidebar {
        &.scrolled {
            height: calc(var(--sidebar-height) / 1.5);
            width: var(--sidebar-width);
            animation: background-transition 3s;
            background-color: yellow;
            z-index: 1;

        & .page-title {
            display: none; 
        }

        & .page-description {
            display: none; 
        }
        }
    }
}

@media screen and (min-width: 961px) {
    .sidebar {
        position: relative;
    }
}

@keyframes background-transition {
    0% {
        background-color: blue;
    }
    100% {
        background-color: yellow;
    }
}

The end

Here is a GIF of the new behaviour:

Caught that Image that overflows our page? A Bug, how nice. I will fix it by next time. If you somehow followed along and want to do this as well, check out the class of such and element and try to find a setting that restricts its width ;)

Otherwise it looks like we are done here from a Basic Layouting point of view.
Of course there is still much needed on the visual front, which is what we will start in Part 6, where it is going to be all about Typography and choosing a Font.

Cover: https://unsplash.com/photos/SXihyA4oEJs