JSFiddle - React, Tailwind, and code Playground
by Maksim Kachurin
HTML
<div class="container">
<div class="pagination" data-visible="5">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<div class="buttons">
<button data-direction="start">Start</button>
<button data-direction="prev">Prev</button>
<button data-direction="next">Next</button>
<button data-direction="end">End</button>
</div>
</div>
SCSS
.container {
display: flex;
justify-content: center;
flex-flow: row wrap;
}
.pagination {
width: 100%;
}
ul {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
}
li {
display: block;
text-align: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: #1b1c1c;
margin: 0 4px;
cursor: default;
pointer-events: all;
transition: all .2s;
font-size: 0;
opacity: 0;
}
li[data-state=normal] {
cursor: pointer;
pointer-events: all;
font-size: 9px;
opacity: .6;
}
li[data-state=active] {
opacity: 1;
}
li[data-state=more] {
width: 8px;
height: 8px;
opacity: .3;
}
li[data-state=next] {
width: 12px;
height: 12px;
opacity: .3;
pointer-events: all;
}
li[data-state=hidden] {
width: 0;
height: 0;
margin: 0;
opacity: 0;
}
/* Compensate empty space*/
li[data-state=normal]:first-child,
li[data-state=active]:first-child {
margin-left: 40px;
}
li[data-state=next]:first-child {
margin-left: 20px;
}
li[data-state=normal]:last-child,
li[data-state=active]:last-child {
margin-right: 40px;
}
li[data-state=next]:last-child {
margin-right: 20px;
}
JavaScript
class BulletsPagination {
constructor(options) {
this.options = Object.assign({
pagination: '.pagination',
targets: 'li',
visible: 3,
active: 0
}, options);
this.active = this.options.active;
this.pagination = this.options.pagination;
// Pagination El not valid Element
if (typeof this.pagination !== 'object' || !this.pagination.querySelectorAll) {
return false;
}
if (Array.isArray(this.options.targets) || (this.options.targets instanceof NodeList) || (this.options.targets instanceof HTMLCollection)) {
this.targets = [...this.options.targets];
}
else {
this.targets = [...this.pagination.querySelectorAll(this.options.targets)];
}
this.setPagination(this.active, true);
}
static isNormal(state) {
return ['normal', 'active'].includes(state);
}
static getMove({ from, to, map, move = 0 }) {
// let move = (to - from);
const normal = this.isNormal(map[to - move].state);
if (normal) {
return move;
}
// move forward
if (from < to) {
return this.getMove({ from, to, map, move: (move + 1) });
}
// move backward
else if (to < from) {
return this.getMove({ from, to, map, move: (move - 1) });
}
}
setPagination(index, force) {
let { targets, options, constructor, newMap, oldMap, active } = this;
// Normalize new active index
index = Math.max(Math.min(index, targets.length - 1), 0);
let first;
let last;
// Fill previuos state
oldMap = (oldMap && oldMap.length) ? oldMap : targets.map((target) => {
const state = target.getAttribute('data-state') || 'hidden';
return {
target,
state: state
};
});
...