Kolja Sam Pluemer

Building a Carousel in Jekyll without JS

Tutorial on how to build a card carousel without javascript with the static blog generator Jekyll.

27.08.2021

So, you have some kind of static page builder setup and want to build a carousel. Prerequisites? Knowledge-wise, you are set if you understood the first sentence. Tool-wise, I do all the stuff with Jekyll, but any reasonably powerful page builder will likely do. It would be very sad if you could not do this with Hugo.

Sorry, what are we building?

Carousel Mockup

Visually, we are going for something like the above. I got this idea at the start of building my personal portfolio page, that is why the content is mediocre screenshots of side projects and bad copy. I also vaguely stole Apple’s Neumorphism because I like it. You can of course do this with pictures of used cars and use Bootstrap, if you like.

The picture you are seeing is a mock-up in plain HTML/CSS, and I actually recommend you to set up something similar in your own design. You can copy all that into the page builder stuff later, but it helps to have a satisfying design to look at before slapping in loads of ugly Liquid spaghetti.

If won’t copy my whole HTML/CSS for the mock here, because it is long, dirty and not the point, but here are some pointers if you actually want to go for a similar style:

  • flexbox is practical for both the whole layout and the card layout, but it always is anyways
  • This generator is incredibly good for the neumorphistic cards
  • Use transform: scale() to make the outer cards smaller as a whole.
  • Use @media to just hide the outer cards when the screen becomes too small, and you have the microwave-meal version of a responsive layout

An important note: I am using the “blogging” function of Jekyll for this - the data of each card is from a .md file in the _posts directory, where your blog posts, or in this case, portfolio entries live.

You can probably adapt this to arbitrary data (say, a bunch of images), but other people have already made that. 0 1

The build

The weird thing about Jekyll and family is that they are static page builders. Meaning, after you are finished ranting in markdown and hacking important! into your CSS, it compiles all that into a bunch of files which are then set in stone, so to speak. That means you cannot do certain things that feel very basic, like passing parameters in the URL. However, the templating language Liquid is quite fucking potent.

I will just spare you my attempts of implementing this in stupid ways which were mostly based on temporarily forgetting how the web works and get to it. If you are interested in the whole process of getting here, just imagine some pain.

As mentioned, I found the easiest way to do this thing is to use the blog functionality. Meaning, put the things you want to carousel through in _posts and implement the carousel on the layout page you designated for those (default post.html). You can make more than one of those folders, like say _posts, _stories and _portfolio, that is very easy.

Then, you just use the helpful page variable that pulls the content straight out of your markdown file for the middle card and the sweet .next and .previous objects for the outer cards. That done, the code for the three (or more) cards looks like this:


<div class="card-outer">
  {% if page.previous %}
    {{ page.previous.content }}
  {% else %}
    {{ site.posts.first.content }}
  {% endif %}
</div>

<div id="card-middle">
  {{ page.content }}
</div>

<div class="card-outer">
  {% if page.next %}
    {{ page.next.content }}
  {% else %}
    {{ site.posts.last.content }}
  {% endif %}
</div>

The code above is obviously a minimal example, because I hate it when people barf out their entire overloaded cruft of an HTML file. You can use any property, like date or title and what not for all three cards and of course write much more convoluted code.

You can cycle through these components with any kind of link at each site that has the following href:


<!-- left link -->
href="{% if page.previous %}{{page.previous.url}}{% else %}{{site.portfolio.last.url}}{% endif %}"
<!-- right link -->
href="{% if page.next %}{{page.next.url}}{% else %}{{site.portfolio[0].url}}{% endif %}"

In my case, my link encloses an <svg> that encodes the nice arrow icon, but that is arbitrary.

If you are confused about the if statements; they make the carousel go round. If there is no other post “to the right”, they instead link to the start (and if there is none “to the left”, they link to the end). You can imagine writing your posts below each other on a piece of paper and then taping the top to the bottom.

Sidenote: Jekyll has a lot of fun sorting stuff in weird ways, so depending on whether you have dates in your file titles and stuff like that, you might have to switch all .first with .last and vice versa. The symptoms of that is your carousel doubling entries.

Closure

That is basically it. You can probably do a bit of CSS animation do make it look really nice and care more about mobile users.

A problem you might encounter is that you now have “blocked” the layout for whatever type of post you put into the carousel. What if you want your user to be able to click on a portfolio card and see a detailed view or the whole post? I was able to solve that, but very crudely: I have a symlink on my local system that copy pastes my posts to another sibling directory, which is named differently (in my case: _portfolio to portfolio_long). Now I have the same collection of portfolio posts twice, using the same layout (because layout is defined in the front matter, and files are carbon copies) but with different page.urls. I then use that url to make an if condition within the portfolio template, which then uses the “detailed” HTML layout or the carousel layout. That is not the definition of optimized, but we are not loading any javascript libraries but only raw HTML, so I still sleep at night.

I will spare you pompous words for a simple Jekyll tutorial. Furthermore, the final result looks just like the mockup since we just have added logic, so let me just say: Have fun with this!