Skip to content

Instantly share code, notes, and snippets.

@devshades-au
Created September 22, 2023 13:09
Show Gist options
  • Save devshades-au/bcb0fdf3cc35a54bafa6eaf27ab8ed91 to your computer and use it in GitHub Desktop.
Save devshades-au/bcb0fdf3cc35a54bafa6eaf27ab8ed91 to your computer and use it in GitHub Desktop.
Scrollable pagination, neon theme
<p class="infos">
You can switch pages using your mouse wheel !<br />
The amount of rows per pages depends on the height of your window when loading the page.
</p>
<div class="table-container" style="--data-limit: 9">
<div class="table" id="table-0" data-limit="9">
<div class="table-row table-heading">
<div class="table-col">
#
</div>
<div class="table-col">
First name
</div>
<div class="table-col">
Last name
</div>
<div class="table-col">
Title
</div>
<div class="table-col">
Completed
</div>
</div>
<div class="table-row" data-copy="103">
<div class="table-col">
<span class="auto-increment">0</span>
</div>
<div class="table-col">
<span class="auto-firstname"></span>
</div>
<div class="table-col">
<span class="auto-lastname"></span>
</div>
<div class="table-col">
<span class="auto-title"></span>
</div>
<div class="table-col">
<span class="auto-integer" min="0" max="250"></span>
</div>
</div>
</div>
<div class="pagination-container">
<div class="d-flex row flex-fill align-items-end justify-content-center">
<span class="pagination-info"></span>
<button class="pagination-extreme pagination-left">
<i class="fa-solid fa-angles-left"></i>
</button>
<button class="pagination-extreme pagination-left-one">
<i class="fa-solid fa-angle-left"></i>
</button>
<div class="d-flex flex-fill justify-content-center">
<div class="pagination-wrapper">
<ul class="pagination" data-table="table-0"></ul>
</div>
</div>
<button class="pagination-extreme pagination-right-one">
<i class="fa-solid fa-angle-right"></i>
</button>
<button class="pagination-extreme pagination-right">
<i class="fa-solid fa-angles-right"></i>
</button>
</div>
</div>
<div class="progress-bar">
<div class="progress-point"></div>
</div>
</div>
const FIRSTNAMES = [
'John', 'Steve', 'Marc', 'Franklin', 'Isaac', 'Vincent', 'Edwin',
'Ashlyn', 'Anthony', 'Alia', 'Abby', 'Francesca'
];
const LASTNAMES = [
'Shafer', 'Whitetaker', 'Glenn', 'Stephens', 'Sherman', 'Howard',
'Kent', 'Clay', 'Beck', 'Simmons', 'Briggs', 'Lee', 'Maxwell'
];
const TITLES = [
'CEO', 'President', 'Vice President', 'Director', 'Manager',
'Developer'
];
const BTN_WIDTH = 4;
document.addEventListener('DOMContentLoaded', () => {
initCopies();
initAutoLimits();
initAutoIncrements();
initAutoFirstnames();
initAutoLastnames();
initAutoTitles();
initAutoIntegers();
initPaginations();
});
function initCopies()
{
let targets = [...document.querySelectorAll('*[data-copy]')];
targets.reverse().forEach((original) => {
let amount = parseInt(original.getAttribute('data-copy'));
for (let i = 0; i < amount; i++) {
let copy = original.cloneNode(true);
original.parentNode.insertBefore(copy, original.nextSibling);
}
});
}
function initAutoIncrements()
{
let autos = document.querySelectorAll('.auto-increment');
autos.forEach((auto, i) => {
auto.innerHTML = i + 1;
});
}
const rand = (a, b) => Math.floor((Math.random() * b) + a);
function initAutoFirstnames()
{
let autos = document.querySelectorAll('.auto-firstname');
autos.forEach((auto) => {
auto.innerHTML = FIRSTNAMES[rand(0, FIRSTNAMES.length)];
});
}
function initAutoLastnames()
{
let autos = document.querySelectorAll('.auto-lastname');
autos.forEach((auto) => {
auto.innerHTML = LASTNAMES[rand(0, LASTNAMES.length)];
});
}
function initAutoTitles()
{
let autos = document.querySelectorAll('.auto-title');
autos.forEach((auto) => {
auto.innerHTML = TITLES[rand(0, TITLES.length)];
});
}
function initAutoIntegers()
{
let autos = document.querySelectorAll('.auto-integer');
autos.forEach((auto) => {
let min = parseInt(auto.getAttribute('min'));
let max = parseInt(auto.getAttribute('max'));
auto.innerHTML = rand(min, max);
});
}
function initPaginations()
{
let paginations = document.querySelectorAll('.pagination');
paginations.forEach((pagination) => {
let table = document.getElementById(pagination.getAttribute('data-table'));
pagination.setAttribute('data-x', '0');
if (table !== null && table !== undefined) {
createPagination(pagination, table);
pagination.closest('.table-container').addEventListener('wheel', (event) => {
event.preventDefault();
scrollPages(
event.wheelDelta
? event.wheelDelta > 0
: event.deltaY < 0, pagination,
table
);
});
}
});
}
function createPagination(pagination, table)
{
let limit = parseInt(table.getAttribute("data-limit"));
let rows = table.querySelectorAll('.table-row:not(.table-heading)');
let page_count = Math.ceil(rows.length / limit);
if (isNaN(limit))
limit = 10;
rows.forEach((row, index) => {
if (index >= limit)
row.style.display = 'none';
});
for (let i = 0; i < page_count; i++) {
let new_button = document.createElement('li');
new_button.innerHTML = "<span>" + (i + 1) + "</span>";
if (i === 0)
new_button.classList.add('active');
pagination.appendChild(new_button);
new_button.addEventListener('click', () => {
switchPage(pagination, table, i);
});
}
updatePaginationInfos(pagination, table, page_count, 0);
initPaginationExtremes(pagination, table, page_count);
}
function scrollPages(direction, pagination, table)
{
let last_active = pagination.querySelector('li.active');
let buttons = pagination.querySelectorAll('li');
let last_index = Array.from(buttons).indexOf(last_active);
let scroll_index = null;
if (direction && last_index > 0) {
scroll_index = last_index - 1;
} else if (!direction && last_index < buttons.length - 1) {
scroll_index = last_index + 1;
}
if (scroll_index !== null)
switchPage(pagination, table, scroll_index);
}
function switchPage(pagination, table, index, bypass = -1)
{
let limit = parseInt(table.getAttribute("data-limit"));
let rows = table.querySelectorAll('.table-row:not(.table-heading)');
let buttons = pagination.querySelectorAll('li');
let last_active = pagination.querySelector('li.active');
let x_pos = parseInt(pagination.getAttribute('data-x'));
let x_shift = 0;
let target_pos = (-index + 2) * BTN_WIDTH;
let page_count = Math.ceil(rows.length / limit);
if (bypass !== -1) {
x_shift = (-bypass + 2) * BTN_WIDTH;
} else {
if (index > 1 && index < buttons.length - 2) {
x_shift = target_pos;
} else if (index === 1) {
x_shift = 0;
} else if (index === page_count - 2) {
x_shift = (-page_count + 5) * BTN_WIDTH;
} else {
x_shift = x_pos;
}
}
rows.forEach((row, row_index) => {
if (row_index < index * limit || row_index >= (index * limit) + limit) {
row.style.display = 'none';
} else {
row.style.display = 'flex';
row.style.opacity = '0';
setTimeout(() => {
row.style.opacity = '1';
}, 50);
}
});
last_active.classList.remove('active');
buttons[index].classList.add('active');
pagination.style.transform = 'translateX(' + x_shift + 'rem)';
pagination.setAttribute('data-x', x_shift);
updatePaginationInfos(pagination, table, page_count, index);
updatePaginationProgress(pagination, index, page_count - 1);
}
function updatePaginationInfos(pagination, table, page_count, index)
{
let info = pagination.closest('.pagination-container').querySelector('.pagination-info');
if (info === null || info === undefined)
return;
let from = 0, to = 0;
let rows = table.querySelectorAll('.table-row:not(.table-heading)');
let started = false;
for (let i = 1; i < rows.length; i++) {
let display = rows[i - 1].style.display;
if (display !== 'none' && !started) {
started = true;
from = i;
} else if ((display === 'none' && started) || i == rows.length - 1) {
to = i == rows.length - 1 ? rows.length : i - 1;
break;
}
}
info.innerHTML = 'Displaying ' + from + '-' + to + ' on page ' + (index + 1) + '/' + page_count;
}
function initPaginationExtremes(pagination, table, max)
{
let container = pagination.closest('.pagination-container');
let left = container.querySelector('.pagination-left');
let right = container.querySelector('.pagination-right');
if (left !== null && left !== undefined) {
left.addEventListener('click', () => {
switchPage(pagination, table, 0, Math.min(2, max - 1));
});
}
if (right !== null && right !== undefined) {
right.addEventListener('click', () => {
switchPage(pagination, table, max - 1, Math.max(0, max - 3));
});
}
initPaginationSteppedExtremes(pagination, table, container, max);
}
function initPaginationSteppedExtremes(pagination, table, container, max)
{
let left = container.querySelector('.pagination-left-one');
let right = container.querySelector('.pagination-right-one');
if (left !== null && left !== undefined) {
left.addEventListener('click', () => {
let last_active = pagination.querySelector('li.active');
let buttons = pagination.querySelectorAll('li');
let last_index = Array.from(buttons).indexOf(last_active);
if (last_index > 0)
switchPage(pagination, table, last_index - 1);
});
}
if (right !== null && right !== undefined) {
right.addEventListener('click', () => {
let last_active = pagination.querySelector('li.active');
let buttons = pagination.querySelectorAll('li');
let last_index = Array.from(buttons).indexOf(last_active);
if (last_index < max - 1)
switchPage(pagination, table, last_index + 1);
});
}
}
function updatePaginationProgress(pagination, index, total)
{
let dot = pagination.closest('.table-container').querySelector('.progress-point');
dot.style.left = ((index / total) * 100) + '%';
}
function initAutoLimits()
{
let table_containers = document.querySelectorAll('.table-container');
let limit = window.innerHeight / 70;
table_containers.forEach((container) => {
let table = container.querySelector('.table');
container.setAttribute('style', '--data-limit: ' + limit);
table.setAttribute('data-limit', limit);
});
}

Scrollable pagination, neon theme

I tried to make this pagination system as easy to use as possible and to make it intuitive.

You can click on the page button, on the left and right button to get to the left-most or right-most page and you can also scroll with your mousewheel inside the container.

All that with a neon theme as bonus with some animations :)

A Pen by roco on CodePen.

License.

@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@200;300;400;500;600;700;800;900;1000&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
$primary: #00121B;
$secondary: #80FF80;
$complementary: #7FFFD4;
$accent: #eb3461;
$dark: #000;
$font: 'Cairo', sans-serif;
$font-scale: 0.390625vw;
$button-width: 4rem;
$row-height: 2rem;
@function getFont($unit) {
@return calc(#{$font-scale} + #{$unit});
}
@mixin r-flex {
position: relative;
display: flex;
}
@mixin a-flex {
position: absolute;
display: flex;
}
body {
@include r-flex;
justify-content: center;
align-items: center;
height: 100%;
min-height: 100vh;
margin: 0;
padding: 0;
background: linear-gradient($primary, darken($primary, 5%));
overflow: hidden;
font-family: $font;
}
.d-flex { display: flex; }
.row { flex-direction: row; }
.flex-fill { flex: 1 1 auto; width: 100%; }
.justify-content-center { justify-content: center; }
.justify-content-start { justify-content: flex-start; }
.justify-content-end { justify-content: flex-end; }
.align-items-center { align-items: center; }
.align-items-start { align-items: flex-start; }
.align-items-end { align-items: flex-end; }
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0turn;
}
@keyframes conic-rotate {
to {
--angle: 1turn;
}
}
.table-container {
@include r-flex;
width: 85vw;
height: calc(((var(--data-limit) + 1) * 2rem) + 10rem);
transform-style: preserve-3d;
box-shadow: inset 0 -.5rem 1rem rgba($primary, .5), inset 0 0 1rem rgba($complementary, .3);
border: 2px solid rgba($complementary, .5);
border-radius: 1rem;
flex-direction: column;
&::before {
@include a-flex;
content: '';
width: 100%;
height: 100%;
top: 0;
left: 0;
background: conic-gradient(from var(--angle), $secondary, rgba($complementary, .5), $secondary, royalblue, $secondary);
filter: blur(.5rem);
transform: translatez(-2px);
z-index: -2;
opacity: 0.99;
animation: conic-rotate 12s infinite linear;
}
&::after {
@include a-flex;
content: '';
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: rgba($primary, .95);
transform: translatez(-1px);
z-index: -1;
border-radius: inherit;
}
}
.table {
@include r-flex;
width: 100%;
padding: 1rem 0 0 0;
flex-direction: column;
align-items: center;
justify-content: center;
background-image: linear-gradient(90deg, transparent, rgba($secondary, .15), transparent);
-webkit-background-image: linear-gradient(90deg, transparent, rgba($secondary, .15), transparent);
overflow: hidden;
box-shadow: 0 .5rem 1rem rgba($dark, .1);
.table-row {
@include r-flex;
flex-direction: row;
gap: inherit;
width: calc(100% - 4rem);
padding: 0 2rem;
transition: opacity .5s;
}
.table-row:nth-child(2n) {
background-color: rgba($dark, .1);
}
.table-col {
@include r-flex;
flex-direction: column;
gap: inherit;
flex: 1 1 0;
height: $row-height;
justify-content: center;
align-items: flex-start;
color: $complementary;
text-shadow: 0 0 .5rem rgba($complementary, .6);
font-size: getFont(12px);
padding: 0 1rem;
cursor: pointer;
white-space: nowrap;
span {
@include r-flex;
cursor: pointer;
&::before, &::after {
@include a-flex;
content: '';
height: 70%;
width: .3rem;
border-top: 2px solid $complementary;
border-bottom: 2px solid $complementary;
opacity: 0;
pointer-events: none;
transition: opacity .3s, transform .3s;
}
&::before {
border-left: 2px solid $complementary;
left: 0;
top: 50%;
transform: translate(-.5rem, -50%) scale(0.6) rotate(20deg);
}
&::after {
border-right: 2px solid $complementary;
right: 0;
top: 50%;
transform: translate(.5rem, -50%) scale(0.6) rotate(20deg);
}
&:hover::before {
opacity: 1;
transform: translate(-1rem, -50%) scale(0.8) rotate(0deg);
}
&:hover::after {
opacity: 1;
transform: translate(1rem, -50%) scale(0.8) rotate(0deg);
}
}
}
.table-heading .table-col {
color: $secondary !important;
text-shadow: 0 0 .5rem rgba($secondary, .6);
font-weight: 600;
text-transform: uppercase;
}
}
.pagination-container {
@include r-flex;
flex: 1 1 auto;
width: 100%;
justify-content: flex-end;
align-items: center;
padding-bottom: 1.5rem;
padding-top: 1rem;
flex-direction: column;
}
.pagination-wrapper {
@include r-flex;
height: fit-content;
overflow: hidden;
justify-content: flex-start;
width: $button-width * 5;
}
.pagination {
@include r-flex;
flex-direction: row;
list-style-type: none;
margin: 0;
padding: 0;
align-items: center;
justify-content: flex-start;
transition: transform .5s;
& > li {
@include r-flex;
width: $button-width;
max-width: $button-width;
flex: 0 0 auto;
height: 3rem;
justify-content: center;
align-items: center;
color: $secondary;
font-size: getFont(18px);
z-index: 5;
cursor: pointer;
user-select: none;
text-shadow: 0 0 .4rem rgba($secondary, .6);
span {
pointer-events: none;
}
&.active span {
@include r-flex;
height: 2.5rem;
padding: 0 1rem;
font-weight: bolder;
align-items: center;
justify-content: center;
background-image: linear-gradient(45deg, rgba($secondary, .1), rgba($primary, .4));
border: 1px solid rgba($secondary, .3);
color: lighten($complementary, 10%);
border-radius: 5px;
text-shadow: 0 0 .4rem rgba(lighten($complementary, 10%), .6);
}
}
}
.pagination-info {
@include a-flex;
font-size: getFont(10px);
color: desaturate($secondary, 70%);
bottom: 4.5rem;
text-shadow: 0 0 .5rem rgba(desaturate($secondary, 70%), .5);
}
.pagination-extreme {
@include r-flex;
height: 3rem;
width: 10rem;
border: none;
background-color: transparent;
border-radius: 5px;
justify-content: center;
align-items: center;
color: $secondary;
font-size: getFont(15px);
cursor: pointer;
transition: transform .3s;
&:hover {
transform: scale(1.4);
}
}
.progress-bar {
@include r-flex;
width: calc(100% - 6rem);
height: 2px;
background-color: rgba($complementary, .1);
border-radius: 2px;
margin: 0 3rem 2rem 3rem;
.progress-point {
@include a-flex;
height: .5rem;
width: .5rem;
background-color: darken($complementary, 60%);
border-radius: 50%;
top: 50%;
left: 0;
transform: translateY(-50%);
transition: left .5s;
}
}
.infos {
@include a-flex;
top: 0;
left: 50%;
transform: translateX(-50%);
font-family: $font;
margin: 0;
padding: 0;
width: 100%;
justify-content: center;
align-items: center;
height: 6rem;
font-size: getFont(12px);
line-height: 24px;
font-weight: 600;
background-color: $secondary;
background-image: linear-gradient(45deg, royalblue, $secondary, $complementary);
-webkit-background-image: linear-gradient(45deg, $secondary, royalblue, $complementary);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
text-shadow: 0 0 .4rem rgba($complementary, .4);
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment