Where to Discuss?

Local Group

Preface

Goal: Bringing responsive pagination, using mobile first.

While writing this CSS code, I can’t claim myself as a developer. Because CSS is not a programming language.

But weird that, responsive design has their own logic. So yeah, I have to code, a little.

Source Code

You can download the source code of this article here.

Extract and run on CLI using $ npm install.


1: Source

I respect copyright. The code below inspired by:

I made a slight modification. But of course the logic remain the same.

Of course, this article is talking about SASS


2: Prepare

This step is required.

Preview: General

This is the complete version.

Hexo Pagination: Responsive Animation

Configuration

As usual in Hexo - Pagination - Intro.

Layout: EJS Index

Consider use pagination/05-responsive layout, in index.ejs

SASS: Main

// dart-sass --watch -I sass sass/css:source/css/

...

@import "panel"
@import "blog"
@import "list"
@import "pagination"

3: Navigation: HTML Class

The Final Result.

Consider have a look at the image below.

Hexo Pagination: Responsive 1

The HTML that we want to achieve is similar as below.

<nav class="pagination is-small is-centered" ...>

    <ul class="pagination-list">
      <!-- Previous Page. -->
      <li class="blog-previous"><a class="pagination-previous" ...>«&nbsp;</a></li>

      <!-- First Page. -->
      <li class="first"><a class="pagination-link" ...>1</a></li>

      <!-- Early (More Pages) Indicator. -->
      <li class="pages-indicator first"><span class="pagination-ellipsis">&hellip;</span></li>

      <li class="pagination--offset-2"><a class="pagination-link" ...>3</a></li>
      <li class="pagination--offset-1"><a class="pagination-link" ...>4</a></li>
      <li class="pagination--offset-0"><a class="pagination-link is-current" ...>5</a></li>
      <li class="pagination--offset-1"><a class="pagination-link" ...>6</a></li>
      <li class="pagination--offset-2"><a class="pagination-link" ...>7</a></li>

      <!-- Late (More Pages) Indicator. -->
      <li class="pages-indicator last"><span class="pagination-ellipsis">&hellip;</span></li>

      <!-- Last Page. -->
      <li class="last"><a class="pagination-link" ...>9</a></li>

      <!-- Next Page. -->
      <li class="blog-next"><a class="pagination-next" ...>&nbsp;»</a></li>
    </ul>

</nav>

Middle Pagination

All you need to care is, only these lines.

      <li class="pagination--offset-2">...</li>
      <li class="pagination--offset-1">...</li>
      <li class="pagination--offset-0">...</li>
      <li class="pagination--offset-1">...</li>
      <li class="pagination--offset-2">...</li>

Our short term goal is, to put the pagination–offset class.

Layout: Pagination Code Skeleton

As usual, the skeleton, to show the complexity.

// Variable Initialization.

<nav role="navigation" aria-label="pagination">
  <% if (page.total > 1) { %>
    <ul class="pagination-list">

    // Previous Page.
    // First Page.
    // Early (More Pages) Indicator.

    <% 
  var cursor; 
  for (cursor = 1; cursor <= page.total; cursor++) { 
    // Variable Initialization

    if (page.total > max_links) {
      // Complex page numbers.

      // Check between these three:
      // * Lower limit pages, or
      // * Upper limit pages, or
      // * Middle pages.

    } else {
      // Simple page numbers.
      ...
    }
    
    // Calculate Offset Class.
    ...

    // Show Pager.
    ...

    <% } %>

    // Late (More Pages) Indicator.
    // Last Page.
    // Next Page.

    </ul>
  <% } %>
</nav>

Calculate Offset Value

Notice this part:

      // Calculate Offset Class.
      var page_offset_class = 'pagination--offset-' 
                            + Math.abs(cursor - current);

Notice the new variable called page_offset_class.

Using Offset Class

All we need is just adding the offset class.

      <li class="<%= page_offset_class %>">
        ...
      </li>

The real code, is also not very simple.

      <li class="<%= page_offset_class %>">
        <% if (current != cursor) { %>
        <a href="<%= pagination_url(cursor) %>"
           class="pagination-link" 
           aria-label="Goto page <%= cursor %>">
          <%= cursor %>
        </a>
        <% } else { %>
        <a class="pagination-link is-current" 
           aria-label="Page <%= current %>">
          <%= current %>
        </a>
        <% } %>
      </li>

Combined Code

    if (show_cursor_flag) { 
      // Calculate Offset Class.
      var page_offset_class = 'pagination--offset-' 
                            + Math.abs(cursor - current);

      // Show Pager.
      %>
      <li class="<%= page_offset_class %>">
        <% if (current != cursor) { %>
        <a href="<%= pagination_url(cursor) %>"
           class="pagination-link" 
           aria-label="Goto page <%= cursor %>">
          <%= cursor %>
        </a>
        <% } else { %>
        <a class="pagination-link is-current" 
           aria-label="Page <%= current %>">
          <%= current %>
        </a>
        <% } %>
      </li>
      <% } /* if */ %>
    <% } /* for */ %>

That is all. Now that the HTML part is ready, we should go on, by setting up the responsive breakpoints using SCSS.

  • .

4: Responsive: Breakpoints

Responsive is easy if you understand the logic.

It is all about breakpoints.

Preview: Each Breakpoint

Consider again, have a look at the animation above, frame by frame. We have at least five breakpoint as six figures below:

Hexo Pagination: Responsive 1

Hexo Pagination: Responsive 2

Hexo Pagination: Responsive 3

Hexo Pagination: Responsive 4

Hexo Pagination: Responsive 5

Hexo Pagination: Responsive 6

SASS: Bulma Custom Breakpoint Variables.

I’m using custom breakpoint, instead of Bulma 7.x breakpoints.

// Breakpoint

$xs1: 0
$xs2: 320px
$xs3: 400px
$xs4: 480px
$sm1: 576px
$sm2: 600px
$md:  768px
$lg:  992px
$xl:  1200px

The name inspired by Bootsrap, but it has nothing do with Bootstrap.

SASS: Bulma Breakpoint Skeleton

With breakpoint above, we can setup css skeleton, with empty css rules.

ul.pagination-list
  +from($xs1)
  +from($xs2)
  +from($xs3)
  +from($xs4)
  +from($sm1)
  +from($sm2)
  +from($md)
  +from($lg)
  +from($xl)

SASS: Using Bulma Breakpoint: Simple

We can fill any rules, as below:

+tablet
  li.blog-previous a:after
    content: " previous"
  li.blog-next a:before
    content: "next "

SASS: Using Custom Breakpoint: Pagination Offset

We can fill any rules, inside pagination class as below:

// Responsiveness

ul.pagination-list
  li.pagination--offset-1,
  li.pagination--offset-2,
  li.pagination--offset-3,
  li.pagination--offset-4,
  li.pagination--offset-5,
  li.pagination--offset-6,
  li.pagination--offset-7
    display: none
  +from($xs3)
    li.pagination--offset-1
      display: inline-block
  +from($xs4)
    li.pagination--offset-2
      display: inline-block
  +from($sm1)
    li.pagination--offset-3
      display: inline-block
  +from($sm2)
    li.pagination--offset-4
      display: inline-block
  +from($md)
    li.pagination--offset-5,
    li.pagination--offset-6
      display: inline-block
  +from($lg)
    li.pagination--offset-7
      display: inline-block

This setup breakpoint is actually up to you. You may change ti suit whatever you need.

SASS: Responsive Indicator

You can also add CSS rules for indicator.

// Responsiveness

ul.pagination-list
  li.first,
  li.last,
  li.pages-indicator
    display: none
  +from($xs2)
    li.pages-indicator
      display: inline-block
  +from($sm1)
    li.first,
    li.last
      display: inline-block

Short and simple.

SASS: Hover Effect

If you desire, a slight enhancement without breaking the original looks, hover is a good idea.

// hover color

ul.pagination-list li 
  a:hover
    box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15)
    background-color: $yellow
    color: #000
  a.is-current:hover
    background-color: $gray
    color: #fff

SASS: Complete Code

Now you can have the complete code as below:

+tablet
  li.blog-previous a:after
    content: " previous"
  li.blog-next a:before
    content: "next "

// Breakpoint

$xs1: 0
$xs2: 320px
$xs3: 400px
$xs4: 480px
$sm1: 576px
$sm2: 600px
$md:  768px
$lg:  992px
$xl:  1200px

// Responsiveness

ul.pagination-list
  li.first,
  li.last,
  li.pages-indicator
    display: none
  li.pagination--offset-1,
  li.pagination--offset-2,
  li.pagination--offset-3,
  li.pagination--offset-4,
  li.pagination--offset-5,
  li.pagination--offset-6,
  li.pagination--offset-7
    display: none
  +from($xs1)
  +from($xs2)
    li.pages-indicator
      display: inline-block
  +from($xs3)
    li.pagination--offset-1
      display: inline-block
  +from($xs4)
    li.pagination--offset-2
      display: inline-block
  +from($sm1)
    li.first,
    li.last,
    li.pagination--offset-3
      display: inline-block
  +from($sm2)
    li.pagination--offset-4
      display: inline-block
  +from($md)
    li.pagination--offset-5,
    li.pagination--offset-6
      display: inline-block
  +from($lg)
    li.pagination--offset-7
      display: inline-block
  +from($xl)

// hover color

ul.pagination-list li 
  a:hover
    box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15)
    background-color: $yellow
    color: #000
  a.is-current:hover
    background-color: $gray
    color: #fff

5: Summary

You can have a look at our complete code here:

<%
/*
* Helper function
*/

function pagination_url(number) {
  var path;

  // default for index
  var path = config.index_generator.path;
  
  // dirty quick fix, avoid double (//)
  if (path=='/') { path = '' }
  
  if (is_archive()){
    path = '/' + config.archive_dir;

    if (is_month()){
      // trailing zero
      var month = ( page.month < 10 ? '0' + page.month : page.month );
      path += '/' + page.year + '/' + month;
    } else if (is_year()){
      path += '/' + page.year;
    }
  } else if (is_category()){
    path = '/' + config.category_dir + '/' + page.category;
  } else if (is_tag()){
    path = '/' + config.tag_dir + '/' + page.tag;
  }

  if (number>1) {
      path = path + '/' + config.pagination_dir + '/' + number;
  }

  return url_for(path);
}

/* 
* Pagination links 
* https://glennmccomb.com/articles/how-to-build-custom-hugo-pagination/
* Adjacent: Number of links either side of the current page
*/

var current        = page.current;
var adjacent_links = 2;
var max_links      = (adjacent_links * 2) + 1;
var lower_limit    = 1 + adjacent_links;
var upper_limit    = page.total - adjacent_links;
%>

<nav class="pagination is-small is-centered" 
     role="navigation" aria-label="pagination">

  <% if (page.total > 1) { %>
    <ul class="pagination-list">

      <!-- Previous Page. -->
      <li class="blog-previous">
      <% if (page.prev_link) { %>
        <a class="pagination-previous"
           href="<%= url_for(page.prev_link) %>" 
           rel="prev">&laquo;&nbsp;</a>
      <% } else { %>
        <a class="pagination-previous"
           title="This is the first page"
           disabled>&laquo;&nbsp;</a>
      <% } %>
      </li>

    <% if (page.total > max_links) { %>
      <% if (current - adjacent_links > 1) { %>
      <!-- First Page. -->
      <li class="first">
        <a href="<%= pagination_url(1) %>"
           class="pagination-link" 
           aria-label="Goto page 1"
          >1</a>
      </li>
      <% } %>

      <% if (current - adjacent_links > 2) { %>
      <!-- Early (More Pages) Indicator. -->
      <li class="pages-indicator first">
        <span class="pagination-ellipsis">&hellip;</span>
      </li>
      <% } %>
    <% } %>

    <% 
  var cursor; 
  for (cursor = 1; cursor <= page.total; cursor++) { 
    var show_cursor_flag = false;


    if (page.total > max_links) {
      // Complex page numbers.

      if (current <= lower_limit) {
        // Lower limit pages.
        // If the user is on a page which is in the lower limit.
        if (cursor <= max_links) {
          // If the current loop page is less than max_links.
          show_cursor_flag = true;
        }
      } else if (current >= upper_limit) {
        // Upper limit pages.
        // If the user is on a page which is in the upper limit.
        if (cursor > (page.total - max_links)) {
          // If the current loop page is less than max_links.
          show_cursor_flag = true;
        }
      } else {
        // Middle pages.
        if ( (cursor >= current - adjacent_links) 
        &&   (cursor <= current + adjacent_links) ) {
          show_cursor_flag = true;
        }
      }
    } else {
      // Simple page numbers.
      show_cursor_flag = true;
    }

    if (show_cursor_flag) { 
      // Calculate Offset Class.
      var page_offset_class = 'pagination--offset-' 
                            + Math.abs(cursor - current);

      // Show Pager.
      %>
      <li class="<%= page_offset_class %>">
        <% if (current != cursor) { %>
        <a href="<%= pagination_url(cursor) %>"
           class="pagination-link" 
           aria-label="Goto page <%= cursor %>">
          <%= cursor %>
        </a>
        <% } else { %>
        <a class="pagination-link is-current" 
           aria-label="Page <%= current %>">
          <%= current %>
        </a>
        <% } %>
      </li>
      <% } /* if */ %>
    <% } /* for */ %>

    <% if (page.total > max_links) { %>
      <% if (current + adjacent_links < page.total - 1) { %>
      <!-- Late (More Pages) Indicator. -->
      <li class="pages-indicator last">
        <span class="pagination-ellipsis">&hellip;</span>
      </li>
      <% } %>

      <% if (current + adjacent_links < page.total) { %>
      <!-- Last Page. -->
      <li class="last">
        <a href="<%= pagination_url(page.total) %>"
           class="pagination-link" 
           aria-label="Goto page <%= page.total %>"
          ><%= page.total %></a>
      </li>
      <% } %>
    <% } %>

      <!-- Next Page. -->
      <li class="blog-next">
      <% if (page.next_link) { %>
        <a class="pagination-next"
           href="<%= url_for(page.next_link) %>" 
           rel="next">&nbsp;&raquo;</a>
      <% } else { %>
        <a class="pagination-next"
           title="This is the last page"
           disabled>&nbsp;&raquo;</a>
      <% } %>
      </li>
    </ul>
  <% } %>
</nav>

We will have to complete the code later, in the next article. This has been a long article. We still need a few changes, for screen reader.

  • .

Browser: Pagination Preview

Finally the animated version.

Hexo Pagination: Responsive Animation


What is Next ?

Looks good right? This kitten, is still with me. Just like the kitten, do not get rest yet! Our pagination tutorial still have some materials to go.

adorable kitten

Consider continue reading [ Hexo - Pagination - Screen Reader ]. There are, some interesting topic about Pagination in Hexo.

Thank you for reading.