上一章节中,我们为wordpress网站的首页添加了全站文章列表。一个wordpress网站不可能只有几篇文章,可能会有成千上万的文章,作为一个wordpress博客主题模板,一般情况下,不可能让所有的文章在一个页面一下子全部显示出来,如果真这样,网页的负载会非常大。正确的处理方式,是先显示最前面的十几篇或二十几篇文章,然后,“点击一个分页按钮”或者“鼠标向下滚动触发一个事件”后,再显示后面的十几篇或二十几篇文章,以此类推。本节,我们来介绍“如何给wordpress网站的文章列表添加分页效果”,一起来看看吧。
在wordpress网站模板的functions.php文件中添加如下代码:
//分页函数function pages($query_string){
global $posts_per_page, $paged; //全局变量
$my_query=new WP_Query($query_string ."&posts_per_page=-1"); //创建查询对象
$total_posts=$my_query->post_count; //通过查询对象获取文章总数
if(empty($paged)) $paged=1; //当前页码数,如是$paged为空,就让
$paged=1$prev=$paged - 1; //上一页变量
$next=$paged + 1; //下一页变量
$range=4; //
$showitems=($range * 2)+1; //显示多少个分页页码按钮
$pages=ceil($total_posts/$posts_per_page); //获取总页数
if(1 !=$pages){
echo "<div class='pagination'>";
echo ($paged > 2 && $paged+$range+1 > $pages && $showitems < $pages)? "<a href='".get_pagenum_link(1)."'>最前</a>":"";
echo ($paged > 1 && $showitems < $pages)? "<a href='".get_pagenum_link($prev)."'>上一页</a>":"";
for ($i=1; $i <=$pages; $i++){
if (1 !=$pages &&( !($i >=$paged+$range+1 || $i <=$paged-$range-1) || $pages <=$showitems )){
echo ($paged==$i)? "<span class='current'>".$i."</span>":"<a href='".get_pagenum_link($i)."' class='inactive' >".$i."</a>";
}
}
echo ($paged < $pages && $showitems < $pages) ? "<a href='".get_pagenum_link($next)."'>下一页</a>" :"";
echo ($paged < $pages-1 && $paged+$range-1 < $pages && $showitems < $pages) ? "<a href='".get_pagenum_link($pages)."'>最后</a>":"";echo "</div>\n";
}
}
然后,在wordpress网站前台模板的文章列表的代码下方调用这个分页函数,代码如下:
<?php pages($query_string); //列表分页 ?>
然后,我们就可以在wordpress网站的文章列表下方看到分页按钮。如果想要样式好看一点,可以对分页按钮添加CSS样式,来进行美化一下。
为了更方便wordpress网站模板开发者开发模板主题,wordpress从4.0版本开始,就提供了一个分页函数the_posts_pagination(),通过这个函数,我们同样可能实现wordpress网站的文章列表的分页效果。代码如下:
the_posts_pagination( array('mid_size'=> 3, //当前页码数的 两边 显示几个页码。'prev_text'=>'<', //上一页'next_text'=>'>', //下一南) );
这个函数跟wordpress其wp_list_pages()等函数类似,有一个参数,这个参数可是一个数组类型(如上面的代码,参数就是数组类型),也可以是一个字符串类型,上面的的代码的参数修改成字符串类型后,如下:
the_posts_pagination("mid_size=3&prev_text=<&next_text=>");
是使用字符串类型,还是使用数组类型,主要根据开发者的个人爱好,我个人还是比较喜欢用数组类型,看起来比较清楚。
在wordpress网站模板开发中,使用上面任何一种方式来添加文章列表的分页效果都可以,不过,我个人还是建议使用wordpress自带的函数the_posts_pagination(),对开发更省事,效率更高,而且是wordpress自己的函数,后期都不需要维护。如果想让分页按钮布局更加个性化,可以考第一种方式。
如果还有什么不太明白的,或者你想了解什么,可以在下面评论中给我留言。
最近在业务上遇到了一个问题是要将页面打印输出成pdf文件,通过点击一个按钮,就能够将页面写在一个pdf上,并下载下来,需要保证pdf的内容具有很好的可读性。
经评估要实现这个需求,一种可行的方案是将HTML页面转为PDF,并实现下载。通过技术调研,最终的方案确定为通过html2canvas + jspdf这两个库来实现,通过使用html2canvas提供的方法,将页面元素转为base64图片流,然后将其插入jspdf插件中,实现保存并下载pdf。
html2canvas + jspdf方案是前端实现页面打印的一种常用方案,但是在实践过程中,遇到的最大问题就是分页截断的问题:当页面元素超过一页A4纸的时候,连续的页面就会因为分页而导致内容被截断,进而影响了pdf的可读性。
由于网上关于分页截断的解决思路比较少,所以特意将此次的解决方案记录下来。
首先,我们开始使用 JSPDF 和 html2canvas 生成一个简单的 PDF文件。
创建一个 JSPDF 实例,设置页面的大小、方向和其他参数。参考官网可以写一个很简单的实例
var doc=new jsPDF({
orientation: 'landscape',
unit: 'in',
format: [4, 2]
}
doc.text('Hello world!', 1, 1)
doc.save('two-by-four.pdf')
生成一个pdf文件,并且在文件中写入一定内容,其实JSPDF这个库就能做到。
但是很多业务场景下,我们的目标内容会更复杂,而且还要考虑样式,所以最好的方式是引入html2canvas这个库,将页面元素转换成base64数据,然后贴在pdf中(使用addImage方法),这样就能保证页面的内容。
引入了html2canvas库后,我们更多关注是利用现成组件库、框架或者原生html和css实现更复杂的页面内容。
使用 html2canvas 捕捉 HTML 内容或特定的 HTML 元素,并将其转换为 Canvas。其中,html2canvas 函数的主要用法是:
html2canvas(element, options);
以下是一些常见的配置选项:
下面是一个简单的demo,可以看到html2canvas能够将dom元素转化为一张base64图片,将鼠标选中元素,可以感受到图片和文字的不同。
<div id="capture" style="padding: 10px; background: #f5da55">
<h4 style="color: #000; ">Hello world!</h4>
</div>
html2canvas(document.querySelector("#capture")).then(canvas=> {
document.body.appendChild(canvas)
});
Untitled.png
这一步我们需要使用JSPDF 的addImage方法,其语法如下:
addImage(imageData, format, x, y, width, height, alias, compression)
下面是一串示例代码:
import jsPDF from 'jspdf';
export default function addImageUsage() {
const doc=new jsPDF();
const imageData=【替换成base64数据流】;
doc.addImage(imageData, 'png', 0, 0, 10, 10);
doc.addImage(imageData, 'png', 100, 100, 10, 10);
doc.addImage(imageData, 'png', 200, 200, 10, 10);
drawNet(doc);
doc.save('output.pdf');
}
const drawNet=(doc)=> {
const gap=10;
const start=[0, 0];
const end=[595.28, 841.89];
// 所有横线
for (let i=start[0]; i < end[0]; i=i + gap) {
doc.line(i, 0, i, end[0]);
}
// 所有纵线
for (let j=start[1]; j < end[1]; j=j + gap) {
doc.line(0, j, end[1], j);
}
};
此示例将在 PDF 文档(默认是A4纸大小,宽高为[595.28, 841.89]像素)的 (10, 10) 、(100, 100) 、(200, 200) 坐标处,添加一张png 图像。图像的宽度和高度将分别为 10 和 10 像素,为了了解pdf中的坐标系统,此示例还在pdf文档中生成了间距为10px的网格系统。
了解了上面的三个关键点,接下来我们将这三个步骤串联起来,实现一个基本的html→pdf的方案。大致步骤如下:
基于这5个步骤,可以实现基本的页面打印。
import html2canvas from 'html2canvas';
import jsPDF, { RGBAData } from 'jspdf';
// 将元素转化为canvas元素
// 通过 放大 提高清晰度
// width为内容宽度
async function toCanvas(element: HTMLElement) {
if (!element) return { width: 0, height: 0 };
// canvas元素
const canvas=await html2canvas(element, {
scale: window.devicePixelRatio * 2, // 增加清晰度
useCORS: true // 允许跨域
});
// 获取canvas转化后的宽高
const { width: canvasWidth, height: canvasHeight }=canvas;
// 转化成图片Data
const canvasData=canvas.toDataURL('image/jpeg', 1.0);
return { width: canvasWidth, height: canvasHeight, data: canvasData };
}
/**
* 生成pdf(A4多页pdf截断问题, 包括页眉、页脚 和 上下左右留空的护理)
*/
export async function generatePDF({
/** pdf内容的dom元素 */
element,
/** pdf文件名 */
filename
}) {
if (!(element instanceof HTMLElement)) {
return;
}
const pdf=new jsPDF();
// 一页的高度, 转换宽度为一页元素的宽度
const {
width: imageWidth,
height: imageHeight,
data
}=await toCanvas(element);
// 添加图片
function addImage(
_x: number,
_y: number,
pdfInstance: jsPDF,
base_data:
| string
| HTMLImageElement
| HTMLCanvasElement
| Uint8Array
| RGBAData,
_width: number,
_height: number
) {
pdfInstance.addImage(base_data, 'JPEG', _x, _y, _width, _height);
}
addImage(0, 0, pdf, data!, imageWidth, imageHeight);
return pdf.save(filename);
}
通常,在我们的实践中,会发现2个问题:
这两个问题的解决方案是等比例缩放+循环移位:
通过比例缩放,实现页面内容等比例展示在pdf文档中
令页面元素的宽高为x, y(转化成canvas图片的宽高),pdf文档的宽高为w, h。因为高度可以通过加页延伸,所以可以按照宽度进行缩放,缩放后的图片高度可以通过下列公式计算
如果页面的高度超出了pdf文档的高度,即y > h,使用addPage方法添加一页即可。但是在新的一页中,我们的图片内容的高度需要调整。
假设y=2 * h,这意味我们需要两页才能完整得展示页面内容。在一页pdf中,图片在起始位置插入即可,即
PDF.addImage(pageData, 'JPEG', 0, 0, x, y)// 注意x,y 是缩放后的大小
在第二页pdf中,图片的纵向位置需要调整一页pdf的高度,即
PDF.addImage(pageData, 'JPEG', 0, -h, x, y)// 注意x,y 是缩放后的大小
通过循环计算剩余高度,然后不停调整纵向位置移动base64的图片位置,可以解决多页的问题。
尽管 JSPDF 和 html2canvas 是功能强大的工具,但是他们也有很多槽点,比如得手动分页,手动处理分页截断的问题。等你实践到这一步,就开始面临分页截断的问题,类似的问题也有网友在Github上提出,但是底下依然没有很好的解决思路。
好在掘金上有人分享了一个不错的方法:
jsPDF + html2canvas A4分页截断 完美解决方案(含代码 + 案例) - 掘金
概括一下,其处理分页截断的原理就是在使用addImage之前,将html进行分页,通过维护一个高度位置数据,来记录每次循环迭代addImage的位置。
从高到低遍历维护一个分页数组pages,该数组记录每一页的起始位置,如:pages[0] 对应 第一页起始位置,pages[1] 对应第二页起始位置
Untitled2.png
接下来我们重点讨论如何将页面进行切割,然后生成pages这个数组。
假设页面的高度是1500,pdf宽高是[500, 900],如果不用处理分页截断的问题,我们可以想到第一页(0-900)是用来承载页面从高度为0到900的信息;
第二页(900-1800)是用来承载页面从高度900到1500的,所以pages数组为[0, 900]。
如果要处理分页截断呢,这时候就需要计算页面元素的距离pdf文档起始位置的高度h1,以及该元素的内部高度h2,通过这两个高度来判断这个元素要不要放在下一页,防止截断,示意图如下:
Untitled4.png
如果h1 + h2 > 页面高度, 这时候说明这个元素不处理的就会被分页截断,所以应该要把这个元素放到第二页去渲染,这就意味着pages记录的数据要变化,示意图如下,可以看到pages[1]我们往上调整了,比第二页pdf的起始位置更高。
Untitled5.png
说明渲染第二页pdf的时候,要从h1开始渲染,pages数组为[0, h1],解释为第一页pdf渲染页面高度区域为0-900, 第二页pdf渲染html高度区域为h1-1500。注意到第一页渲染的时候到尾部的时候,会有部分内容和第二页头部内容重合。因为h1到900这部分的内容肯定会渲染,这部分内容一直都是页面元素,我们改变pages[1]的值的原因只是创建一个副本,让页面看起来内容没有被截断。
为了解决这个问题(为了美观),我们用填充一块白色区域遮掉它!此处使用jspdf的rect和setFillColor方法,把重合的区域遮白处理。
pdf.setFillColor(255, 255, 255);
pdf.rect(x, y, Math.ceil(_width), Math.ceil(_height), 'F');
上面我们谈到了h1和h2,其中h1是元素盒子的上边距到打印区域的高度(比例缩放后的高度),h2是元素盒子的内部高度。
计算h1: getBoundingClientRect方法
const rect=contentElement.getBoundingClientRect() || {};
const topDistance=rect.top;
return topDistance;
Untitled6.png
计算h2:offsetHeight方法
Untitled7.png
值得注意的是,因为打印区域的html元素不一定是从窗口顶部开始,所以为了计算实际的h1(元素到打印区域的顶部距离),可以采用这样的方法:
// 对pages进行一个值的修正,因为pages生成是根据根元素来的,根元素并不是我们实际要打印的元素,而是element,
// 所以要把它修正,让其值是以真实的打印元素顶部节点为准
const newPages=pages.map((item)=> item - pages[0]);
上述即是在实现前端页面生成pdf的过程中遇到的问题,以及解决思路。
为了更直观得感受效果,本文也给出了不同场景(单页、多页、多页截断、自定义页眉页脚、横向)下的pdf生成效果,可以通过此链接体验:https://pdf-demo-phi.vercel.app/
此demo的源代码如下:pdf-demo
与现有文章不同的是,本仓库的代码特点在于:
作者:燕平
来源:微信公众号:Goodme前端团队
出处:https://mp.weixin.qq.com/s/-1nA-VI6kmgqHRcYs_NZFA
前端都会面临的一个问题就是分页,如果是纯js分页也是可以的,只是可能代码量比较大,所以今天写一个关于用bootstrap框架分页的例子,希望以后可以帮助到一些对这方面比较头疼的码农。
首先需要明确的一点是,哪些数据是需要分页的,单从数据显示上其实是没有必要分页的,因为页面是可以显示的出来的,但是作为一个相对比较合格的前端,你首先要考虑的不仅仅是这个功能是不是可以实现,而是要考虑用户体验是不是好的,在既有功能上如果可以更多的考虑用户体验的问题,那么才可以算是一个相对比较合格的前端工程师。
先看渲染图:
这个是一个项目中的例子,今天就做以这个为例子,做一下
首先我们将需要用的数据准备好(这个一般是ajax请求到的数据,现在我们直接放到一个js里面,加载js的时候直接取出数据)
var testboke={
"code":200,
"message":null,
"data":{
"total":17,//总条数
"size":10,//分页大小-默认为0
"pages":2,//总页数
"current":1,//当前页数
"records":[//author-riverLethe-double-slash-note数据部分
{
"id":17,//项目id
"userName":"Night夜",//发起人名称
"companyName":"康佰裕",//发起人公司名称
"ptypeName":"13",//发起项目类别
"pask":"13",
"pname":"13",
"pdesc":"13"
},
{
"id":16,
"userName":"Night夜",
"companyName":"康佰裕",
"ptypeName":"12",
"pask":"12",
"pname":"12",
"pdesc":"12"
},
{
"id":15,
"userName":"BB机",
"companyName":"北京电影",
"ptypeName":"11",
"pask":"11",
"pname":"11",
"pdesc":"11"
},
{
"id":14,
"userName":"BB机",
"companyName":"北京电影",
"ptypeName":"9",
"pask":"9",
"pname":"9",
"pdesc":"9"
},
{
"id":13,
"userName":"BB机",
"companyName":"北京电影",
"ptypeName":"7",
"pask":"7",
"pname":"7",
"pdesc":"7"
},
{
"id":12,
"userName":"Night夜",
"companyName":"康佰裕",
"ptypeName":"6",
"pask":"6",
"pname":"6",
"pdesc":"6"
},
{
"id":11,
"userName":"BB机",
"companyName":"北京电影",
"ptypeName":"5",
"pask":"5",
"pname":"5",
"pdesc":"5"
},
{
"id":10,
"userName":"Night夜",
"companyName":"康佰裕",
"ptypeName":"4",
"pask":"4",
"pname":"4",
"pdesc":"4"
},
{
"id":9,
"userName":"BB机",
"companyName":"北京电影",
"ptypeName":"3",
"pask":"3",
"pname":"3",
"pdesc":"3"
},
{
"id":8,
"userName":"Night夜",
"companyName":"康佰裕",
"ptypeName":"2",
"pask":"2",
"pname":"2",
"pdesc":"2"
}
]
}
}
接下来画页面的表格:
<body> <div class="templatemo-content col-1 light-gray-bg"> <div class="templatemo-top-nav-container"> <div class="row"> <nav class="templatemo-top-nav col-lg-12 col-md-12"> <li> <a href="">发起项目列表管理</a> </li> </nav> </div> </div> <!--正文部分--> <div style="background: #E8E8E8;height: 60rem;"> <div class="templatemo-content-container"> <div class="templatemo-content-widget no-padding"> <!--表头--> <div class="panel-heading templatemo-position-relative"> <h2 class="text-uppercase">发起项目列表</h2></div> <div class="panel panel-default table-responsive" id="mainContent"> </div> </div> </div> </div> <div class="pagination-wrap" id="callBackPager"> </div> <footer class="text-right"> <p>Copyright ? 2018 Company Name | Designed by <a href="http://www.csdn.com" target="_parent">csdn</a> </p> </footer> </body>
这个时候也页面上是没有任何的元素的,因为我们需要的是将页面上的表格用js动态的画出来,这样才可以实现取出来的数据是可以分页的,但是画表格的前提是你要可以拿到数据对不对,所以接下来应该是拿数据,而不是急着画表格,因为没有数据的时候你的表格即使是画出来了,也是显示不出来的,那么我们开始拿数据:
我们写一个函数取数据:
/*将数据取出来*/
function data(curr, limit) {
//console.log("tot:"+totalCount)
/*拿到总数据*/
totalCount=testboke.data.total; //取出来的是数据总量
dataLIst=testboke.data.records; // 将数据放到一个数组里面(dataLIst 还未声明,莫着急)
createTable(curr, limit, totalCount);
console.log("tot:"+totalCount)
}
拿到数据以后怎么做,分页,是的,不是急着将数据放到表格里面,先分页,ok我们加载分页的js(bootstrap的分页js)
<link href="../../css/font-awesome.min.css" rel="stylesheet" /> <link href="../../css/bootstrap.min.css" rel="stylesheet" /> <link href="../../css/templatemo-style.css" rel="stylesheet" /> <link href="../../css/layui/css/layui.css" rel="stylesheet" /> <script src="../../js/bootstrap.min.js" type="text/javascript"></script> <script src="../../js/jquery-1.8.3-min.js" type="text/javascript"></script> <script src="../../js/jquery.min.js" type="text/javascript"></script> <script src="../../js/extendPagination.js" type="text/javascript"></script> <script src="../../js/layui/lay/dest/layui.all.js" type="text/javascript"></script> <!--引如测试数据的js--> <script src="../../js/testcode.js" type="text/javascript"></script>
上面的这些js,css都可以去cdn上面找到,除了testcode,在最上面,就是我们加载数据的js。
下面就是将分页的代码写上:
var currPage=1;
var totalCount;
var dataLIst=[];
window.onload=function() {
/*取到总条数*/
/*每页显示多少条 10条*/
var limit=10;
data(currPage, limit)
//console.log(totalCount)
createTable(1, limit, totalCount);
$('#callBackPager').extendPagination({
totalCount: totalCount,
limit: limit,
callback: function(curr, limit, totalCount) {
data(curr, limit)
}
});
}
加载以后的效果是这样的:
这个时候就是已经基本将数据处理好了,只是没有将数据放进去,最后我们将数据放进去就可以了,(我的写法不建议借鉴,很多现成的循环画表格的方法,我是原生的拼接字符串写的,不嫌麻烦的可以自己拼一下,毕竟不管是什么框架,最底层的还是这样的实现原理)
*请认真填写需求信息,我们会在24小时内与您取得联系。