Preface
Goal: Bringing responsive pagination, using mobile first.
Source Code
This article use tutor-11 theme. We will create it step by step.
1: Source
Original 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
Altered Source
Coder will feel like home
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.
2: Prepare
This step is required.
Preview: General
This is what we want to achieve in this tutorial.
Layout: Liquid: Blog
Consider use pagination/05-responsive
partial, in blog.html
.
---
layout: columns-single
---
{% include pagination-v1/05-responsive.html %}
{% assign posts = paginator.posts %}
{% include index/blog-list.html %}
SASS: Main
// Import partials from `sass_dir` (defaults to `sass/css`)
@import
// Heeyeun's Open Color
"open-color/_open-color-variables",
// Bootstrap Related
"../bootstrap/functions",
"variables",
"../bootstrap/variables",
"../bootstrap/mixins/breakpoints",
// Tailor Made
"main/layout-page",
"main/layout-content",
"main/logo",
"main/decoration",
"main/sticky-footer",
"main/list",
"main/pagination",
"post/content",
"post/navigation"
;
3: Navigation: HTML Class
HTML: The Final Result
Consider have a look at the image below.
The HTML that we want to achieve is similar as below.
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<!-- Arrow: First Page. -->
<li class="page-item icon-first">
<a class="page-link hoverable" href="/pages" rel="first">
<i data-feather="chevrons-left"></i></a>
</li>
<!-- Arrow: Previous Page. -->
<li class="page-item icon-previous">
<a class="page-link hoverable" href="/pages/blog-4" rel="prev">
<i data-feather="chevron-left"></i></a>
</li>
<!-- First Page. -->
<li class="page-item first">
<a class="page-link hoverable" href="/pages">1</a>
</li>
<!-- Early (More Pages) Indicator. -->
<li class="pages-indicator first disabled">
<span class="page-link">…</span>
</li>
<!-- Page numbers. -->
<li class="page-item pagination--offset-2">
<a class="page-link hoverable" href="/pages/blog-3">3</a>
</li>
<li class="page-item pagination--offset-1">
<a class="page-link hoverable" href="/pages/blog-4">4</a>
</li>
<li class="page-item active pagination--offset-0">
<span class="page-link">5</span>
</li>
<li class="page-item pagination--offset-1">
<a class="page-link hoverable" href="/pages/blog-6">6</a>
</li>
<li class="page-item pagination--offset-2">
<a class="page-link hoverable" href="/pages/blog-7">7</a>
</li>
<!-- Late (More Pages) Indicator. -->
<li class="pages-indicator last disabled">
<span class="page-link">…</span>
</li>
<!-- Last Page. -->
<li class="page-item last">
<a class="page-link hoverable" href="/pages/blog-9">9</a>
</li>
<!-- Arrow: Next Page. -->
<li class="page-item icon-next">
<a class="page-link hoverable" href="/pages/blog-6" rel="next">
<i data-feather="chevron-right"></i></a>
</li>
<!-- Arrow: Last Page. -->
<li class="page-item icon-last">
<a class="page-link hoverable" href="/pages/blog-9" rel="last">
<i data-feather="chevrons-right"></i></a>
</li>
</ul>
</nav>
Middle Pagination
All you need to care is, only these lines.
<!-- Page numbers. -->
<li class="page-item pagination--offset-2">...</li>
<li class="page-item pagination--offset-1">...</li>
<li class="page-item active pagination--offset-0">...</li>
<li class="page-item pagination--offset-1">...</li>
<li class="page-item pagination--offset-2">...</li>
Our short term goal is,
to put the pagination--offset
class.
Partial: Responsive Skeleton
As usual, the skeleton, to show the complexity.
<nav aria-label="Page navigation">
{% if total_pages > 1 %}
<ul class="pagination justify-content-center">
<!-- variable initialization -->
<!-- Arrow: First Page. -->
<!-- Arrow: Previous Page. -->
{% if total_pages > link_max %}
<!-- First Page. -->
<!-- Early (More Pages) Indicator. -->
{% endif %}
<!-- Page numbers. -->
{% for page_cursor in (1..total_pages) %}
<!-- Flag Calculation -->
{% if page_current_flag == true %}
<!-- Calculate Offset Class. -->
{% assign diff_offset = page_cursor | minus: page_current | abs %}
<!-- Show Pager. -->
...
{% endif %}
{% endfor %}
{% if total_pages > link_max %}
<!-- Late (More Pages) Indicator. -->
<!-- Last Page. -->
{% endif %}
<!-- Arrow: Next Page. -->
<!-- Arrow: Last Page. -->
</ul>
{% endif %}
</nav>
Calculate Offset Value
Notice this part:
{% if page_current_flag == true %}
<!-- Calculate Offset Class. -->
{% assign diff_offset = page_cursor | minus: page_current | abs %}
{% endif %}
Notice the new variable called diff_offset
.
Using Offset Class
All we need is just adding the offset class.
{% if page_current_flag == true %}
<!-- Show Pager. -->
<li class="page-item
{% if page_cursor == page_current %}active{% endif %}
pagination--offset-{{ diff_offset }}">
...
</li>
{% endif %}
Combined Code
<!-- Show Pager. -->
{% if page_current_flag == true %}
<li class="page-item
{% if page_cursor == page_current %}active{% endif %}
pagination--offset-{{ diff_offset }}">
{% if page_cursor == page_current %}
<span class="page-link">
{{ page_cursor }}
</span>
{% else %}
<!-- p_link calculation -->
...
<a class="page-link hoverable"
href="{{ p_link }}"
>{{ page_cursor }}</a>
{% endif %}
</li>
{% endif %}
That is all. Now that the HTML part is ready, we should go on, by setting up the responsive breakpoints using SCSS.
4: Responsive: Mobile First
HTML: Render Preview
As written above, we have have this html
structure,
to create arrow icon.
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<!-- Arrow: First Page. -->
<li class="page-item icon-first">
<a class="page-link hoverable" href="/pages" rel="first">
<i data-feather="chevrons-left"></i></a>
</li>
<!-- Arrow: Previous Page. -->
<li class="page-item icon-previous">
<a class="page-link hoverable" href="/pages/blog-4" rel="prev">
<i data-feather="chevron-left"></i></a>
</li>
...
<!-- Arrow: Next Page. -->
<li class="page-item icon-next">
<a class="page-link hoverable" href="/pages/blog-6" rel="next">
<i data-feather="chevron-right"></i></a>
</li>
<!-- Arrow: Last Page. -->
<li class="page-item icon-last">
<a class="page-link hoverable" href="/pages/blog-9" rel="last">
<i data-feather="chevrons-right"></i></a>
</li>
</ul>
</nav>
SASS: Bootstrap Breakpoints
Consider setting, using mobile first:
.icon-first,
.icon-last {
display: none;
}
@include media-breakpoint-up(sm) {
.icon-first,
.icon-last {
display: block;
}
}
Normally, the pagination on mobile screen, would looks like this figure below:
When the page size hits small sm
size,
the pagination would looks like this below:
SASS: Text and Icon
Consider further setting. For wider area, we can apply text instead of icon.
@include media-breakpoint-up(md) {
.icon-previous :after { content: " Previous" }
.icon-next :before { content: "Next " }
.icon-first :after { content: " First" }
.icon-last :before { content: "Last " }
.icon-previous svg,
.icon-next svg,
.icon-first svg,
.icon-last svg {
display: none;
}
}
When the page width hit medium md
size,
the icon will dissapear, and replaced with text,
set in CSS above.
5: Responsive: Custom 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:
Note that you can have different view arrangement as you want. You can see more in examples below.
SASS: Bootstrap Custom Breakpoint Variables.
I’m using custom breakpoint, instead of Bootstrap v4.x breakpoints.
$grid-breakpoints-custom: (
xs: 0,
xs2: 320px,
xs3: 400px,
xs4: 480px,
sm: 576px,
sm2: 600px,
md: 768px,
lg: 992px,
xl: 1200px
) !default;
SASS: Bootstrap Breakpoint Skeleton
With breakpoint above, we can setup css skeleton, with empty css rules.
ul.pagination {
@include media-breakpoint-up(xs, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(xs2, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(xs3, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(xs4, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(sm, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(sm2, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(md, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(lg, $grid-breakpoints-custom) { ... }
@include media-breakpoint-up(xl, $grid-breakpoints-custom) { ... }
}
SASS: Using Bootstrap Breakpoint: Mobile
We can fill initial rule, for mobile screen, as below:
ul.pagination {
li.icon-first,
li.icon-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;
}
}
SASS: Using Custom Breakpoint: Pagination Offset
And then continue with pagination--offset
setting,
for each custom breakpoints.
ul.pagination {
...
@include media-breakpoint-up(xs, $grid-breakpoints-custom) {
}
@include media-breakpoint-up(xs2, $grid-breakpoints-custom) {
li.pages-indicator {
display: inline-block;
}
}
@include media-breakpoint-up(xs3, $grid-breakpoints-custom) {
li.pagination--offset-1 {
display: inline-block;
}
}
@include media-breakpoint-up(xs4, $grid-breakpoints-custom) {
li.pagination--offset-2 {
display: inline-block;
}
}
@include media-breakpoint-up(sm, $grid-breakpoints-custom) {
li.icon-first,
li.icon-last,
li.pagination--offset-3 {
display: inline-block;
}
}
@include media-breakpoint-up(sm2, $grid-breakpoints-custom) {
li.pagination--offset-4 {
display: inline-block;
}
}
@include media-breakpoint-up(md, $grid-breakpoints-custom) {
li.pagination--offset-5,
li.pagination--offset-6 {
display: inline-block;
}
}
@include media-breakpoint-up(lg, $grid-breakpoints-custom) {
li.pagination--offset-7 {
display: inline-block;
}
}
@include media-breakpoint-up(xl, $grid-breakpoints-custom) {
}
}
This setup breakpoint is actually up to you. You may change to suit whatever you need.
SASS: Complete Code
Now you can have the complete code as below:
.icon-first,
.icon-last {
display: none;
}
@include media-breakpoint-up(sm) {
.icon-first,
.icon-last {
display: block;
}
}
@include media-breakpoint-up(md) {
.icon-previous :after { content: " Previous" }
.icon-next :before { content: "Next " }
.icon-first :after { content: " First" }
.icon-last :before { content: "Last " }
.icon-previous svg,
.icon-next svg,
.icon-first svg,
.icon-last svg {
display: none;
}
}
$grid-breakpoints-custom: (
xs: 0,
xs2: 320px,
xs3: 400px,
xs4: 480px,
sm: 576px,
sm2: 600px,
md: 768px,
lg: 992px,
xl: 1200px
) !default;
ul.pagination {
li.icon-first,
li.icon-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;
}
@include media-breakpoint-up(xs, $grid-breakpoints-custom) {
}
@include media-breakpoint-up(xs2, $grid-breakpoints-custom) {
li.pages-indicator {
display: inline-block;
}
}
@include media-breakpoint-up(xs3, $grid-breakpoints-custom) {
li.pagination--offset-1 {
display: inline-block;
}
}
@include media-breakpoint-up(xs4, $grid-breakpoints-custom) {
li.pagination--offset-2 {
display: inline-block;
}
}
@include media-breakpoint-up(sm, $grid-breakpoints-custom) {
li.icon-first,
liicon-last,
li.pagination--offset-3 {
display: inline-block;
}
}
@include media-breakpoint-up(sm2, $grid-breakpoints-custom) {
li.pagination--offset-4 {
display: inline-block;
}
}
@include media-breakpoint-up(md, $grid-breakpoints-custom) {
li.pagination--offset-5,
li.pagination--offset-6 {
display: inline-block;
}
}
@include media-breakpoint-up(lg, $grid-breakpoints-custom) {
li.pagination--offset-7 {
display: inline-block;
}
}
@include media-breakpoint-up(xl, $grid-breakpoints-custom) {
}
}
6: Screenreader
Accessability Class
I just follow Bootstrap guidance:
To hidden content visually,
you simply need to add sr-only
.
<span class="sr-only">Hidden Content</span>
Aria Label
Instead of class, we can also utilize aria-label
.
but I do not bother to implement it in my bootstrap code.
Preview: General
There shoud be nomore preview, because this is screen reader.
This content can be read by screenreader.
You can test using inspect element
.
Our final code will be equipped with screenreader class.
7: Summary
Now the pagination tutorial is done.
Complete Code
You can have a look at our complete code here:
{% capture spaceless %}
{% assign total_pages = paginator.total_pages %}
{% assign color_main = page.color_main | default: layout.color_main %}
{% endcapture %}
<nav aria-label="Page navigation">
{% if total_pages > 1 %}
<ul class="pagination justify-content-center">
{% capture spaceless %}
<!--
Pagination links
* https://glennmccomb.com/articles/how-to-build-custom-hugo-pagination/
-->
<!-- Get paginate_root from page in frontmatter -->
{% assign paginate_root = page.paginate_root %}
{% assign p_first = paginate_root
| prepend: site.baseurl %}
{% assign p_last = site.paginate_path
| relative_url
| replace: ':num', total_pages %}
{% assign page_current = paginator.page %}
{% assign link_offset = 2 %}
{% assign link_max = link_offset | times: 2 | plus: 1 %}
{% assign limit_lower = link_offset | plus: 1 %}
{% assign limit_upper = total_pages | minus: link_offset %}
{% assign min_lower = link_max %}
{% assign max_upper = total_pages | minus: link_max %}
{% assign lower_offset = page_current | minus: link_offset %}
{% assign upper_offset = page_current | plus: link_offset %}
{% assign lower_indicator = 2 %}
{% assign upper_indicator = total_pages | minus: 1 %}
{% endcapture %}
<!-- First Page. -->
{% unless paginator.page == 1 %}
{% assign p_first = paginate_root
| prepend: site.baseurl %}
<li class="page-item icon-first ">
<a class="page-link hoverable" href="{{ p_first }}"
rel="first">
<i data-feather="chevrons-left"></i>
<span class="sr-only">First</span>
</a>
</li>
{% else %}
<li class="page-item icon-first disabled">
<span class="page-link">
<i data-feather="chevrons-left"></i></span>
</li>
{% endunless %}
<!-- Previous Page. -->
{% if paginator.previous_page %}
{% assign p_prev = paginator.previous_page_path
| prepend: site.baseurl %}
<li class="page-item icon-previous">
<a class="page-link hoverable"
href="{{ p_prev }}"
rel="prev">
<i data-feather="chevron-left"></i>
<span class="sr-only">Previous</span>
</a>
</li>
{% else %}
<li class="page-item icon-previous disabled">
<span class="page-link">
<i data-feather="chevron-left"></i></span>
</li>
{% endif %}
{% if total_pages > link_max %}
<!-- First Page. -->
{% if lower_offset > 1 %}
<li class="page-item first">
<a class="page-link hoverable"
href="{{ p_first }}">
<span class="sr-only">Page 1</span>
1</a>
</li>
{% endif %}
<!-- Early (More Pages) Indicator. -->
{% if lower_offset > lower_indicator %}
<li class="pages-indicator first disabled">
<span class="page-link">…</span>
</li>
{% endif %}
{% endif %}
<!-- Page numbers. -->
{% for page_cursor in (1..total_pages) %}
{% capture spaceless %}
<!-- Flag Calculation -->
{% assign page_current_flag = false %}
{% if total_pages > link_max %}
<!-- Complex page_cursor numbers. -->
<!-- Lower limit pages. -->
<!-- If the user is on a page_cursor which is in the lower limit. -->
{% if page_current <= limit_lower %}
<!-- If the current loop page_cursor is less than max_links. -->
{% if page_cursor <= min_lower %}
{% assign page_current_flag = true %}
{% endif %}
<!-- Upper limit pages. -->
<!-- If the user is on a page_cursor which is in the upper limit. -->
{% elsif page_current >= limit_upper %}
<!-- If the current loop page_cursor is greater than total pages minus $max_links -->
{% if page_cursor > max_upper %}
{% assign page_current_flag = true %}
{% endif %}
<!-- Middle pages. -->
{% else %}
{% if (page_cursor >= lower_offset) and (page_cursor <= upper_offset) %}
{% assign page_current_flag = true %}
{% endif %}
{% endif %}
{% else %}
<!-- Simple page_cursor numbers. -->
{% assign page_current_flag = true %}
{% endif %}
{% if page_current_flag == true %}
<!-- Calculate Offset Class. -->
{% assign diff_offset = page_cursor | minus: page_current | abs %}
{% endif %}
{% endcapture %}
<!-- Show Pager. -->
{% if page_current_flag == true %}
<li class="page-item
{% if page_cursor == page_current %}active{% endif %}
pagination--offset-{{ diff_offset }}">
{% if page_cursor == page_current %}
<span class="page-link">
{{ page_cursor }}
</span>
{% else %}
{% capture spaceless %}
{% if page_cursor == 1 %}
{% assign p_link = p_first %}
{% else %}
{% assign p_link = site.paginate_path
| relative_url
| replace: ':num', page_cursor %}
{% assign p_link = p_link
| prepend: site.baseurl %}
{% endif %}
{% endcapture %}
<a class="page-link hoverable"
href="{{ p_link }}"
><span class="sr-only">Page {{ page_cursor }}</span>
{{ page_cursor }}</a>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% if total_pages > link_max %}
<!-- Late (More Pages) Indicator. -->
{% if upper_offset < upper_indicator %}
<li class="pages-indicator last disabled">
<span class="page-link">…</span>
</li>
{% endif %}
<!-- Last Page. -->
{% if upper_offset < total_pages %}
<li class="page-item last">
<a class="page-link hoverable"
href="{{ p_last }}"
><span class="sr-only">Page {{ total_pages }}</span>
{{ total_pages }}</a>
</li>
{% endif %}
{% endif %}
<!-- Next Page. -->
{% if paginator.next_page %}
{% assign p_next = paginator.next_page_path
| prepend: site.baseurl %}
<li class="page-item icon-next">
<a class="page-link hoverable"
href="{{ p_next }}"
rel="next">
<i data-feather="chevron-right"></i>
<span class="sr-only">Next</span>
</a>
</li>
{% else %}
<li class="page-item icon-next disabled">
<span class="page-link">
<i data-feather="chevron-right"></i></span>
</li>
{% endif %}
<!-- Last Page. -->
{% unless paginator.page == total_pages %}
{% assign p_last = site.paginate_path
| relative_url
| replace: ':num', total_pages
| prepend: site.baseurl %}
<li class="page-item icon-last">
<a class="page-link hoverable" href="{{ p_last }}"
rel="last">
<i data-feather="chevrons-right"></i>
<span class="sr-only">Last</span>
</a>
</li>
{% else %}
<li class="page-item icon-last disabled">
<span class="page-link">
<i data-feather="chevrons-right"></i></span>
</li>
{% endunless %}
</ul>
{% endif %}
</nav>
Small Animated
You can have different pagination arrangement as you want.
Or try to mess it up a little bit:
I think this is all for now.
What is Next?
Consider continue reading [ Jekyll Bootstrap - Blog Post ].
Thank you for reading.