jekyll自定义分页组件
不采用其他现有的分页插件,自定义分页组件,实现纯前端分页功能。
1. 整体思路
假定所有待分页的元素都位于同一个父元素下,获取所有待分页的元素,根据每页数量pageSize
和当前页currentPage
,计算出当前需要显示的元素的起始索引start
和结束索引end
,然后根据start
和end
显示元素,将其他元素隐藏。
2. 设计目标
能够实现显示总页数、当前页、显示当前页前后页码、跳转到指定页、修改每页显示数量等功能,如下图所示:
3. 实现过程
这里只展示核心逻辑,一些较为简单的逻辑这里就不多赘述。完整代码请参考https://github.com/lxmghct/lxmghct.github.io/blob/master/_includes/pagination.html
3.1 分页核心逻辑
开始索引为pageSize * (currentPage - 1)
,结束索引为pageSize * currentPage
,遍历所有待分页元素,根据索引通过添加或移除hide
类来显示或隐藏元素。
function changeContentShow() {
const showPageStart = paginationData.pageSize * (paginationData.currentPage - 1);
const showPageEnd = paginationData.pageSize * paginationData.currentPage;
for (let i = 0; i < paginationData.total; i++) {
if (i >= showPageStart && i < showPageEnd) {
pageItems[i].classList.remove("hide");
} else {
pageItems[i].classList.add("hide");
}
}
}
3.2 显示当前页前后页码
这里采用的方式是:每次页码更新时,重新生成页码列表,根据当前页码和总页数,生成当前页码前后大约3个页码的页码列表,如果当前页码距离第一页或最后一页小于3,则显示当前页码前后的页码直到第一页或最后一页;否则就显示为省略号...
。
function showPagerList() {
const totalPage = Math.ceil(paginationData.total / paginationData.pageSize);
pagerContainer.innerHTML = "";
const createLi = (i) => {
const li = document.createElement("li");
li.innerText = i;
pagerContainer.appendChild(li);
// add click event
if (i !== "...") {
li.addEventListener("click", () => {
paginationData.currentPage = i;
changePage();
});
}
// set active
if (i === paginationData.currentPage) {
li.classList.add("active");
}
}
const start = 1, end = totalPage;
const pagerNumberList = [];
if (paginationData.currentPage - start > 3) {
pagerNumberList.push(...[start, start + 1, "...", paginationData.currentPage - 1, paginationData.currentPage]);
} else {
for (let i = start; i <= paginationData.currentPage; i++) {
pagerNumberList.push(i);
}
}
if (end - paginationData.currentPage > 3) {
pagerNumberList.push(...[paginationData.currentPage + 1, "...", end - 1, end]);
} else {
for (let i = paginationData.currentPage + 1; i <= end; i++) {
pagerNumberList.push(i);
}
}
pagerNumberList.forEach((item) => {
createLi(item);
});
}
3.3 优化组件的使用
将该分页组件封装为_include
目录下的一个组件,通过组件调用时传参来配置一些参数,尽量能够直接使用。
这里我打算父组件只向分页组件传递待分页元素父组件的选择器,分页组件自动获取该父组件下的所有待分页元素,然后进行分页操作。这样做的好处是:父组件不需要关心待分页元素的数量,也不需要任何其他操作就可以使用分页组件。
考虑到分页组件有时候需要放在分页元素的父组件外,所以还需要传递一个参数parent
,用来指定分页元素的父组件的选择器。这两个选择器可以相同,也可以不同。
组件之间传参的方式为:
父组件使用如下方式调用子组件并传参:
<!-- pagination.html -->
<div class="my-pagination">
<ul class="my-pager">
<li class="active">1</li>
<li>2</li>
</ul>
<span class="my-pagination__sizes">
<span class="my-pagination__sizes__label">每页</span>
<select class="my-pagination__sizes__select" autocomplete="off">
<option>5</option>
<option>10</option>
<option>20</option>
</select>
<span class="my-pagination__sizes__label">条</span>
</span>
<span class="my-pagination__total">共 0 条</span>
<span class="my-pagination__goto">
<span class="my-pagination__goto__label">前往</span>
<input class="my-pagination__goto__input" type="text" />
<span class="my-pagination__goto__label">页</span>
</span>
</div>
<script>
(function () {
const contentSelector = '.home';
const parentSelector = '.home';
const contentContainer = document.querySelector(`${contentSelector}`);
if (!contentContainer || !contentSelector || !parentSelector) {
console.error("pagination.html: content or parent is not defined");
return;
}
const pageItems = []
const pagerContainer = document.querySelector(`${parentSelector} .my-pager`);
const totalContainer = document.querySelector(`${parentSelector} .my-pagination__total`);
const sizesSelect = document.querySelector(`${parentSelector} .my-pagination__sizes__select`);
const gotoInput = document.querySelector(`${parentSelector} .my-pagination__goto__input`);
const paginationData = {
total: 0,
pageSize: 10,
currentPage: 1
}
sizesSelect.value = paginationData.pageSize;
function showPagerList() {
const totalPage = Math.ceil(paginationData.total / paginationData.pageSize);
pagerContainer.innerHTML = "";
const createLi = (i) => {
const li = document.createElement("li");
li.innerText = i;
pagerContainer.appendChild(li);
// add click event
if (i !== "...") {
li.addEventListener("click", () => {
paginationData.currentPage = i;
changePage();
});
}
// set active
if (i === paginationData.currentPage) {
li.classList.add("active");
}
}
const start = 1, end = totalPage;
const pagerNumberList = [];
if (paginationData.currentPage - start > 3) {
pagerNumberList.push(...[start, start + 1, "...", paginationData.currentPage - 1, paginationData.currentPage]);
} else {
for (let i = start; i <= paginationData.currentPage; i++) {
pagerNumberList.push(i);
}
}
if (end - paginationData.currentPage > 3) {
pagerNumberList.push(...[paginationData.currentPage + 1, "...", end - 1, end]);
} else {
for (let i = paginationData.currentPage + 1; i <= end; i++) {
pagerNumberList.push(i);
}
}
pagerNumberList.forEach((item) => {
createLi(item);
});
}
function changeTotal() {
pageItems.length = 0;
for (let i = 0; i < contentContainer.children.length; i++) {
if (contentContainer.children[i].classList.contains("my-pagination") || contentContainer.children[i].tagName === "SCRIPT") {
continue;
}
pageItems.push(contentContainer.children[i]);
}
paginationData.total = pageItems.length;
totalContainer.innerText = `共 ${paginationData.total} 条`;
}
function changeContentShow() {
const showPageStart = paginationData.pageSize * (paginationData.currentPage - 1);
const showPageEnd = paginationData.pageSize * paginationData.currentPage;
for (let i = 0; i < paginationData.total; i++) {
if (i >= showPageStart && i < showPageEnd) {
pageItems[i].classList.remove("hide");
} else {
pageItems[i].classList.add("hide");
}
}
}
function changePage() {
showPagerList();
changeContentShow();
gotoInput.value = paginationData.currentPage;
}
function startPagination() {
changeTotal();
changePage();
}
sizesSelect.addEventListener("change", (e) => {
paginationData.pageSize = e.target.value;
paginationData.currentPage = 1;
changePage();
});
gotoInput.addEventListener("keyup", (e) => {
if (e.keyCode === 13) {
const gotoPage = parseInt(e.target.value);
if (gotoPage > 0 && gotoPage <= Math.ceil(paginationData.total / paginationData.pageSize)) {
paginationData.currentPage = gotoPage;
changePage();
}
}
});
// 监听内容变化
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
changeTotal();
paginationData.currentPage = 1;
changePage();
}
}
});
observer.observe(contentContainer, { childList: true });
startPagination();
})();
</script>
子组件接收参数的方式为:
const contentSelector = '';
const parentSelector = '';
const contentContainer = document.querySelector(`${contentSelector}`);
const pagerContainer = document.querySelector(`${parentSelector} .my-pager`);
3.4 实时更新分页组件
待分页容器内的元素有时会在其他地方被改变,所以最好能够实时更新分页组件的各个分页参数,避免不必要的额外操作,方便分页组件的使用。可以采用MutationObserver
来监听待分页容器内元素的变化,然后实时更新分页组件。
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
changeTotal(); // update total
paginationData.currentPage = 1;
changePage(); // update page
}
}
});
observer.observe(contentContainer, { childList: true });