简介
分页是开辟中最罕见的需求之一。
关于分页,我们议论的最多的是后端的数据库分页,这关乎到我们运用程序的机能,也是分页这个需求的中心。
而前端要做的,是把后端返回的数据呈如今页面上,事变被以为是简朴噜苏的。
在单页运用中,我们有许多中分页计划,最罕见的是无穷转动、上一页 & 下一页和页码。
本文将谈谈这三种分页体式格局。
通用
不管运用哪一种分页计划,我们都须要处置惩罚一些通用的需求,如:
剖析 url,提取当前页面的参数
依据返回数据天生自定义 DOM
移除某个 Node 节点中的一切子元素
往某个 Node 节点中插进去元素列表
// 剖析 url 中的查询字符串
// 如 http://host/items?page=5 中提取 page=5 中的 5
function parsePage() {
var searchString = window.location.search.substr(1).split('&').filter(v => v.indexOf('page') !== -1)[0];
var page = Number(searchString.split('=')[1]);
return isNaN(page) ? 1 : page;
}
// 天生自定义 DOM
// generateItemView :: Object -> DOM Node
function generateItemView(object) { /* implementation */ }
// 移除 Node 中一切子节点
function removeItems(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
// 往 Node 中插进去元素列表
function insertItems(node, items) {
items.forEach(item => node.appendChild(generateItemView(item)));
}
下文的示例代码中会直接挪用这些函数,不再反复定义。
无穷转动
不管对从前端照样后端来讲,无穷转动都是我以为最简朴的分页计划。
对后端来讲,根据 page
和 limit
直接查出局限,然后返回一个数组给前端即可,不须要像其他计划那样还要查询总数。
对前端来讲,直接依据后端返回的数据举行拼接即可,当后端返回一个空数组时,能够以为已到末了一页,这时刻就不须要再发要求到后端了。
// 后端返回的数据结构
// GET /items?page=5
{ items: [...] }
// 前端处置惩罚
function getItems(page) {
fetch(`/items?page=${page}`)
.then(res => res.json())
.then(res => {
if (res.items.length > 0) {
insertItems(
document.getElementById('container'),
res.items
);
} else {
alert('No more data');
}
});
}
无穷转动虽然完成起来简朴,用户体验也不错,但有一些致命的瑕玷:
轻易涌现机能题目
轻易丧失阅读进度
现在有一些计划能够处理这些瑕玷:机能题目能够经由过程动态衬着来处理,而丧失阅读进度则能够经由过程简朴的新开窗口来处理。
上一页 & 下一页
这类分页体式格局和无穷转动比起来,会庞杂一点点。
最主要是因为后端须要查询总数,然后依据当前页数来盘算是不是能够查询上一页或下一页。
固然,盘算这部份能够在后端做,也能够在前端做。
后端盘算
如果在后端盘算,那末后端要做的事变就有:
查询总数
盘算
hasPrev
和hasNext
查询元素列表
而前端方面则相对简朴:
依据后端返回的
hasPrev
和hasNext
来推断是不是须要显现上一页/下一页按钮移除容器内的一切元素,再插进去新的元素(即用新元素替代旧元素)
// 后端返回数据结构
// GET /items?page=5
{
// hasPrev 和 hasNext 都须要后端去查询总数,然后盘算出来
hasPrev: true,
hasNext: true,
items: [...]
}
// 前端处置惩罚
function getItems(page) {
fetch(`/items?page=${page}`)
.then(res => res.json())
.then(res => {
res.hasPrev
? document.getElementById('prevButton').style.display = 'block'
: document.getElementById('prevButton').style.display = 'none';
res.hasNext
? document.getElementById('nextButton').style.display = 'block'
: document.getElementById('nextButton').style.display = 'none';
var container = document.getElementById('container');
removeItems(container);
insertItems(container, res.items);
});
}
这个计划完成起来比较简朴,但瑕玷是每次分页都须要查询总页数,浪费资源。
前端盘算
如果是前端盘算的话,那末后端要做的事变就相对简朴,只需再供应一个查询总数的接口即可。
而前端方面,须要做更多的事变,同时要斟酌当前端数据丧失机(如用户革新页面)的处置惩罚计划。
第一次加载页面时须要挪用一次查询总数的接口,同时挪用猎取元素的接口
返回数据后盘算
hasPrev
和hasNext
,用来推断是不是须要显现上一页/下一页按钮移除容器内的一切元素,再插进去新的元素(即用新元素替代旧元素)
// 后端返回数据结构
// GET /itemsCount
{ total: 100 }
// GET /items?page=5
{ items: [...] }
// 前端处置惩罚
var total = 0;
var limit = 10;
window.onload = getItemsCount(getItems);
// 猎取总数
function getItemsCount(callback) {
fetch('/itemsCount')
.then(res => res.json())
.then(res => {
total = res.total;
callback.call(null, parsePage());
});
}
function getItems(page) {
fetch(`/items?page=${page}`)
.then(res => res.json())
.then(res => {
var hasPrev = page != 1;
var hasNext = page != Math.ceil(total / limit);
hasPrev
? document.getElementById('prevButton').style.display = 'block'
: document.getElementById('prevButton').style.display = 'none';
hasNext
? document.getElementById('nextButton').style.display = 'block'
: document.getElementById('nextButton').style.display = 'none';
var container = document.getElementById('container');
removeItems(container);
insertItems(container, res.items);
});
}
这类计划能够让后端甩锅给前端,前端的活又变多拉!
页码
末了我们谈谈页码分页。
这个计划和「上一页 & 下一页」的计划很相似,差别的处所在于这个计划须要依据当前页面和总数来天生页码。
天生页码是这个计划最贫苦的处所。举个简朴的例子,假定我们的数据有 50 页,我们不可能把一切页码都显现出来,须要天生一组不一连的页码。
我们能够采纳下面的情势来显现页面:
// ------------------------------
// 我个人比较喜好用 -1 来示意省略的地区
// 在天生 DOM 的时刻,能够用省略号来展现
// ------------------------------
// 假定当前是第 1 页
[1, 2, 3, -1, 50]
// 假定当前是第 3 页
[1, 2, 3, 4, 5, -1, 50]
// 假定当前是第 25 页
[1, -1, 23, 24, 25, 26, 27, -1, 50]
// 假定当前是第 48 页
[1, -1, 46, 47, 48, 49, 50]
// 假定当前是第 50 页
[1, -1, 48, 49, 50]
天生页码的准绳一般都是:
第一页和末了一页必需展现
其他页面按需展现,一般是当前页面的前后两页(即 x +- 2)
当页数少于 10 页的时刻,直接显现出一切页码(为何是 10 页?其实在满足前两个准绳的情况下,只需 7 页省略号就会一般显现了。但页数较少的情况下显现省略号觉得怪怪的。)
var lastPage = Math.ceil(total / limit);
// 依据当前页天生动态页码
function genPages() {
if (lastPage <= 10) {
return Array(lastPage).fill().map((v, i) => i + 1);
}
// dynamicPages 为除第一页和末了一页以外的页码,-1 示意省略号
var dynamicPages;
if (page === 1) {
dynamicPages = [2, 3, -1];
} else if (page === 2) {
dynamicPages = [2, 3, 4, -1];
} else if (page === 3) {
dynamicPages = [2, 3, 4, 5, -1];
} else if (page === lastPage - 2) {
dynamicPages = [-1, page - 2, page - 1, page, page + 1];
} else if (page === lastPage - 1) {
dynamicPages = [-1, page - 2, page - 1, page];
} else if (page === lastPage) {
dynamicPages = [-1, page - 2, page - 1];
} else {
dynamicPages = [-1, page - 2, page - 1, page, page + 1, page + 2, -1];
}
dynamicPages.unshift(1);
dynamicPages.push(lastPage);
return dynamicPages;
}
天生动态页码这部份的逻辑,不管放在前端照样后端都影响不大,能够根据本身须要去挑选。
至于其他部份的细节,和「上一页 & 下一页」相似,这里就不再反复了。
出处
http://scarletsky.github.io/2…