惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
F
Fortinet All Blogs
U
Unit 42
F
Full Disclosure
雷峰网
雷峰网
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
罗磊的独立博客
D
DataBreaches.Net
C
Check Point Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
O
OpenAI News
C
CXSECURITY Database RSS Feed - CXSecurity.com
aimingoo的专栏
aimingoo的专栏
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Hacker News
The Hacker News
Webroot Blog
Webroot Blog
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
N
News | PayPal Newsroom
P
Proofpoint News Feed
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
Google Online Security Blog
Google Online Security Blog
H
Help Net Security
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
M
MIT News - Artificial intelligence
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
MyScale Blog
MyScale Blog
腾讯CDC

张鑫旭-鑫空间-鑫生活

1分钟速度了解text-align match-parent声明 « 张鑫旭-鑫空间-鑫生活 光标的形状也能设置了,就是CSS caret-shape属性 « 张鑫旭-鑫空间-鑫生活 pretext与文字四面环绕效果的实现 « 张鑫旭-鑫空间-鑫生活 JSON.rawJSON方法的作用是什么? « 张鑫旭-鑫空间-鑫生活 如何使用CSS判断鼠标从哪个方向进入元素? « 张鑫旭-鑫空间-鑫生活 借助mediabunny纯JS实现视频水印、剪裁、合成等功能 « 张鑫旭-鑫空间-鑫生活 务必谨慎使用JS WeakRef弱引用 « 张鑫旭-鑫空间-鑫生活 CSS六边形头像的实现与蜂巢布局 « 张鑫旭-鑫空间-鑫生活 CSS corner-shape与背景底纹技术 « 张鑫旭-鑫空间-鑫生活 浅学WebTransport API:下一代Web双向通信技术 « 张鑫旭-鑫空间-鑫生活 HTML dialog元素新支持了closedBy属性 « 张鑫旭-鑫空间-鑫生活 HTML interestfor属性与悬停popover交互效果 « 张鑫旭-鑫空间-鑫生活 CSS3模拟window7炫酷界面效果展示 « 张鑫旭-鑫空间-鑫生活 图片旋转+剪裁js插件(兼容各浏览器) « 张鑫旭-鑫空间-鑫生活 纯js无图片无外链CSS图片幻灯片播放插件 « 张鑫旭-鑫空间-鑫生活 CSS border三角、圆角图形生成技术简介 « 张鑫旭-鑫空间-鑫生活 CSS “渐进增强”在web制作中常见应用举例 « 张鑫旭-鑫空间-鑫生活 让IE6/IE7/IE8浏览器支持CSS3属性 « 张鑫旭-鑫空间-鑫生活 jQuery – 高亮动态显示页面HTML代码插件 « 张鑫旭-鑫空间-鑫生活 翻译 – 逐渐消失的Flash网站 « 张鑫旭-鑫空间-鑫生活 CSS reset的重新审视 – 避免样式重置 « 张鑫旭-鑫空间-鑫生活 新浪微博插入话题后部分文字选中的js实现 « 张鑫旭-鑫空间-鑫生活 JavaScript实现http地址自动检测并添加URL链接 « 张鑫旭-鑫空间-鑫生活 CSS实现兼容性的渐变背景(gradient)效果 « 张鑫旭-鑫空间-鑫生活 CSS content内容生成技术以及应用 « 张鑫旭-鑫空间-鑫生活 CSS gradient渐变之webkit核心浏览器下的使用 « 张鑫旭-鑫空间-鑫生活 CSS渐变之CSS3 gradient在Firefox3.6下的使用 « 张鑫旭-鑫空间-鑫生活 区分IE8/IE7/IE6及其他浏览器-CSS “\9″hack « 张鑫旭-鑫空间-鑫生活 CSS实现跨浏览器兼容性的盒阴影效果 « 张鑫旭-鑫空间-鑫生活 CSS3&HTML5各浏览器支持情况一览表 « 张鑫旭-鑫空间-鑫生活 翻译 – CSS Sprites:实用技术还是生厌之物? « 张鑫旭-鑫空间-鑫生活 纯CSS实现易拉罐3D滚动效果 « 张鑫旭-鑫空间-鑫生活 纯CSS实现侧边栏/分栏高度自动相等 « 张鑫旭-鑫空间-鑫生活 CSS+JavaScript实现页面不同布局的切换 « 张鑫旭-鑫空间-鑫生活 JavaScript实现最简单的拖拽效果 « 张鑫旭-鑫空间-鑫生活 自己写的无图片版jQuery zxxbox弹出框插件 « 张鑫旭-鑫空间-鑫生活 中国古代道家思想与网页重构的思考 « 张鑫旭-鑫空间-鑫生活 让所有浏览器支持HTML5 video视频标签 « 张鑫旭-鑫空间-鑫生活 纯CSS实现各类气球泡泡对话框效果 « 张鑫旭-鑫空间-鑫生活 JS HEX十六进制与RGB, HSL颜色的相互转换 « 张鑫旭-鑫空间-鑫生活 CSS实现兼容性的渐变、高光等文字效果 « 张鑫旭-鑫空间-鑫生活 去除冗余 – 精简您的CSS样式代码 « 张鑫旭-鑫空间-鑫生活 对overflow与zoom”清除浮动”的一些认识 « 张鑫旭-鑫空间-鑫生活 jQuery Pagination Ajax分页插件中文详解 « 张鑫旭-鑫空间-鑫生活 absolute绝对定位的非绝对定位用法 « 张鑫旭-鑫空间-鑫生活 网页布局思想浅议-淘宝新版首页为实例 « 张鑫旭-鑫空间-鑫生活 回流与重绘:CSS性能让JavaScript变慢? « 张鑫旭-鑫空间-鑫生活 CSS float浮动的深入研究、详解及拓展(二) « 张鑫旭-鑫空间-鑫生活 CSS float浮动的深入研究、详解及拓展(一) « 张鑫旭-鑫空间-鑫生活 60个极富创造性的震撼的电影海报设计 « 张鑫旭-鑫空间-鑫生活 jQuery Lightbox(balupton版)图片展示插件讲解 « 张鑫旭-鑫空间-鑫生活 博客内嵌flash音乐播放器代码、下载与使用 « 张鑫旭-鑫空间-鑫生活 CSS3 animate实现图片墙3D翻转效果 « 张鑫旭-鑫空间-鑫生活 图片旋转效果的一些研究、jQuery插件及实例 « 张鑫旭-鑫空间-鑫生活 CSS3 border-image详解、应用及jQuery插件 « 张鑫旭-鑫空间-鑫生活 jQuery-两款不同原理的圆角插件讲解 « 张鑫旭-鑫空间-鑫生活 页面可用性之outline轮廓外框的一些研究 « 张鑫旭-鑫空间-鑫生活 CSS3 transition实现超酷图片墙动画效果 « 张鑫旭-鑫空间-鑫生活 HTML CSS列表元素ul,ol,dl的研究与应用 « 张鑫旭-鑫空间-鑫生活 jQuery图片文本滚动切换插件jCarousel中文翻译与详解 « 张鑫旭-鑫空间-鑫生活 酷酷的jQuery鼠标悬停图片放大切换显示效果 « 张鑫旭-鑫空间-鑫生活 IE6下z-index犯癫不起作用bug的初步研究 « 张鑫旭-鑫空间-鑫生活 checkbox复选框的一些深入研究与理解 « 张鑫旭-鑫空间-鑫生活 jQuery之使用slideToggle实现垂直下拉菜单 « 张鑫旭-鑫空间-鑫生活 jQuery-innerfade内部列表自动淡入淡出插件 « 张鑫旭-鑫空间-鑫生活 jQuery-火焰灯效果导航菜单 « 张鑫旭-鑫空间-鑫生活 IE下css bug集合-翻译自haslayout.net « 张鑫旭-鑫空间-鑫生活 翻译-IE7/8@font-face嵌入字体与文字平滑 « 张鑫旭-鑫空间-鑫生活 jQuery-马化腾产品设计与用户体验的一些技术实现 « 张鑫旭-鑫空间-鑫生活 像素的世界及其在web开发制作中的应用 « 张鑫旭-鑫空间-鑫生活 css行高line-height的一些深入理解及应用 « 张鑫旭-鑫空间-鑫生活 CSS实现圆角六色渐变自适应按钮详解 « 张鑫旭-鑫空间-鑫生活 我熟知的三种三栏网页宽度自适应布局方法 « 张鑫旭-鑫空间-鑫生活 jQuery照片图像剪裁插件Jcrop中文翻译详解 « 张鑫旭-鑫空间-鑫生活 Ajax Upload多文件上传插件翻译及中文演示 « 张鑫旭-鑫空间-鑫生活 告别图片—使用字符实现兼容性的圆角尖角效果beta版 « 张鑫旭-鑫空间-鑫生活 大小不固定的图片、多行文字的水平垂直居中 « 张鑫旭-鑫空间-鑫生活 jQuery boxy弹出层对话框插件中文演示及讲解 « 张鑫旭-鑫空间-鑫生活 JavaScript实现新浪微博文字放大显示动画效果 « 张鑫旭-鑫空间-鑫生活 翻译:让网络更快一些——最小化浏览器中的回流(reflow) « 张鑫旭-鑫空间-鑫生活 jQuery之addClasas与removeClass使用实例 « 张鑫旭-鑫空间-鑫生活 关于Google圆角高光高宽自适应按钮及其拓展 « 张鑫旭-鑫空间-鑫生活 JavaScript实现图片幻灯片滚动播放动画效果 « 张鑫旭-鑫空间-鑫生活 jQuery-鼠标经过显示大图并跟随鼠标效果插件 « 张鑫旭-鑫空间-鑫生活 关于gif图片(或png8)杂边锯齿的问题 « 张鑫旭-鑫空间-鑫生活 对html与body的一些研究与理解 « 张鑫旭-鑫空间-鑫生活 鲜为人知的一个解决兼容性问题的利器——小数 « 张鑫旭-鑫空间-鑫生活 搜狐白社会似iphone短信对话框效果的优化 « 张鑫旭-鑫空间-鑫生活 关于文字内容溢出用点点点(…)省略号表示 « 张鑫旭-鑫空间-鑫生活 jquery之append与insertBefore使用实例 « 张鑫旭-鑫空间-鑫生活 jQuery之图片关联伸缩效果 « 张鑫旭-鑫空间-鑫生活 jquery之图片左右切换动画效果 « 张鑫旭-鑫空间-鑫生活 jQuery之replace字符串替换实现不同尺寸图片切换 « 张鑫旭-鑫空间-鑫生活 jQuery-实现图片的放大镜显示效果 « 张鑫旭-鑫空间-鑫生活 jQuery-单击文字或图片内容放大显示效果插件 « 张鑫旭-鑫空间-鑫生活 reflection.js-实现图片投影倒影效果js插件 « 张鑫旭-鑫空间-鑫生活 jQuery-很酷的弹出层效果js插件 « 张鑫旭-鑫空间-鑫生活 复选框单选框与文字对齐问题的研究与解决 « 张鑫旭-鑫空间-鑫生活 css margin的相关属性,问题及应用 « 张鑫旭-鑫空间-鑫生活 IE6下png背景不透明问题的综合拓展 « 张鑫旭-鑫空间-鑫生活
使用jsPDF导出PDF文件实践分享 « 张鑫旭-鑫空间-鑫生活
张鑫旭,zhangxinxu · 2023-06-04 · via 张鑫旭-鑫空间-鑫生活

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10854 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

PDF封面图

一、jsPDF项目简介

最近遇到个需要将网页中特定内容转为PDF的需求,所以有机会试用了下jsPDF,也遇到了一些问题,这里分享给大家了。

首先,项目地址:https://github.com/parallax/jsPDF

目前2.6万的star数,可以说是Github上Top级别的项目了,也是Web导PDF的首选解决方案。

Star数目示意

官方的使用示意也很简单,构造,内容和保持。

import { jsPDF } from "jspdf";

// 默认是 a4 纸张尺寸,纵向,单位是mm
const doc = new jsPDF();

doc.text("Hello world!", 10, 10);
doc.save("a4.pdf");

好,接下来,就是把页面中的DOM内容作为PDF内容就可以了,理论上如此,但实际使用却令人深思。

二、内置html()方法惊为天人

jsPDF内置一个名为html()的方法,可以直接让HTML元素作为PDF的内容。

var doc = new jsPDF();
// element就是需要转变为pdf的DOM元素
doc.html(element, {
   callback: function (doc) {
     doc.save();
   },
   x: 10,
   y: 10
});

结果我试了下,效果惊为天人。

内置html方法的惊人效果

研究一番,发现是要导入完整的中文字体,而一个中文字体,少说5-6M,在Web这种场景下就不太合适。

于是改变策略,决定先用html2canvas将内容转成图片,再以图片的方式,一页一页地插入到PDF中,实现成本会低很多,不足就是文字无法选择。

三、借助html2canvas

html2canvas这个项目之前也有提过,接近3万 Star,也是Github上的顶级开源项目。

项目地址:https://github.com/niklasvh/html2canvas

极简使用示意

这里快速演示下如何使用html2canvas和jsPDF生成PDF文件。

假设页面上有个布局元素,id属性值是element,效果如下图所示:

示意布局图

则下面的这段代码就可以让这段布局内容变成PDF文件下载下来。

<script src="./html2canvas.min.js"></script>
<script src="./jspdf.umd.min.js"></script>
<script>
var pdf = new jspdf.jsPDF();
html2canvas(target).then(function(canvas) {
    pdf.addImage(canvas.toDataURL('image/jpeg'), 10, 10);
    pdf.save('mybook.pdf');
});
</script>

此时的PDF打开效果就是这样的:

PDF效果示意

眼见为实,您可以狠狠地点击这里:html2canvs与jspdf生成PDF简易demo

然而,真实的项目开发要比demo页面麻烦的多。

几乎大多数人一定会遇到的问题,那就是如果html2canvas生成的图片过长,该如何在PDF中分页。

以及,图片跨域了,又当如何处理?

四、图片跨域和分页的问题

canvas与图片跨域的问题,我之前专门撰文讲解过,传送门地址:“解决canvas图片getImageData,toDataURL跨域问题

里面的方法虽多,根据我多年的实践,还是服务器设置Access-Control-Allow-Origin运行访问,前端fetch或XMLHttpRequest获取图片数据的策略最好用。

以fetch举例,想要获得一个图片地址是 imgUrl 的图像数据,可以这么处理:

fetch(imgUrl).then(res => res.blob()).then(blob => {
  var reader = new FileReader() ;
  reader.onload = function () {
    // this.result 就是图片的base64地址
  };
  reader.readAsDataURL(blob) ;
})

为何需要转成base64呢?因为根据我的实践,html2canvas内容中的图片地址,需要转换成base64地址,才能真正可用。

分页的问题

分页的问题可以使用代码搞定,设置好每一页PDF的高度,然后canvas的高度一页一页剪掉再分别添加即可。

这里就有一些需要提前知道的,关于尺寸的知识。

当我们构造一个 pdf 实例的时候,如果不设置任何参数,则其尺寸是 A4 纸的尺寸,210毫米×297毫米。

根据我的实践,这个尺寸有些小了,生成的PDF内容模糊,阅读体验极为不佳。

所以,有必要对PDF尺寸进行自定义,也就是设置得大一些,虽然大尺寸让PDF文件占据空间也大了,但现在是大屏高清时代,流量不值钱,此不足不值一提。

具体而言,我是这么处理的。

首先确定好页面中容器元素的宽度,假设是700px,则PDF的尺寸可以设置为2倍,也就是1400px,而竖版PDF的高宽比是根号二,也就是1.414,所以PDF的高度就是1400*1.414=1979.6像素。

此时,再配合简单的数学计算,我们就可以将canvas图像分隔成一页一页的,分别塞在PDF中。

具体代码参见下一节封装的代码示意。

五、封装后的的方法

为了方便遇到类似需求的同学可以快速完成对应的开发。

我将上面的图像处理和分页封装成了可复用的方法,代码如下所示(支持jspdf和html2canvas src直连和 npm install 安装两种使用形式):

// 导出 pdf 封装方法
// by zhangxinxu(.com)
// 访问 https://www.zhangxinxu.com/wordpress/?p=10854 了解更新信息
export async function exportPdf (element, filename = '未命名', callback = () => {}) {
  if (!element) {
    callback();
    return;
  }

  // 尺寸的确定
  const originWidth = element.offsetWidth || 700;

  // 创建一个容器,用于克隆元素
  const container = document.createElement('div');
  // 16px是为了生成的PDF有安全边距
  container.style.cssText = `position:fixed;left: ${-2 * originWidth}px; top:0;padding:16px;width:${originWidth}px;box-sizing:content-box;`;
  // 插入到body中
  document.body.appendChild(container);
  // 克隆元素
  container.appendChild(element.cloneNode(true));

  // 依赖的库
  var jsPDF;

  if (typeof html2canvas == 'undefined') {
    html2canvas = await import('html2canvas').then(module => module.default);
  }

  if (typeof jspdf == 'undefined') {
    jsPDF = await import('jspdf').then(module => module.jsPDF);
  } else {
    jsPDF = jspdf.jsPDF;
  }

  // 为了保证显示质量,2倍PDF尺寸
  const scale = 2;
  const width = originWidth + 32;

  const PDF_WIDTH = width * scale;
  const PDF_HEIGHT = width * 1.414 * scale;

  // 渲染方法
  const render = function () {
    // 渲染为图片并下载
    html2canvas(container, {
      scale: scale
    }).then(function(canvas) {
      const contentWidth = canvas.width;
      const contentHeight = canvas.height;

      // 一页pdf显示html页面生成的canvas高度
      const pageHeight = contentWidth / PDF_WIDTH * PDF_HEIGHT;

      // canvas图像在画布上的尺寸
      const imgWidth = PDF_WIDTH;
      const imgHeight = PDF_WIDTH / contentWidth * contentHeight;

      let leftHeight = contentHeight;
      let position = 0;

      const doc = new jsPDF('p', 'px', [PDF_WIDTH, PDF_HEIGHT]);

      // 不足一页
      if (leftHeight < pageHeight) {
        doc.addImage(canvas, 'PNG', 0, 0, imgWidth, imgHeight);
      } else {
        // 多页
        while (leftHeight > 0) {
          doc.addImage(canvas, 'PNG', 0, position, imgWidth, imgHeight)
          leftHeight -= pageHeight;
          position -= PDF_HEIGHT;
          //避免添加空白页
          if (leftHeight > 0) {
            doc.addPage();
          }
        }
      }

      doc.save(filename + '.pdf');

      // 移除创建的元素
      container.remove();

      // 隐藏全局loading提示
      callback();
    });
  }

  // 图像地址替换成base64地址
  const eleImgs = container.querySelectorAll('img');
  const length = eleImgs.length;
  let start = 0;
  container.querySelectorAll('img').forEach(ele => {
    let src = ele.src;

    if (!src) {
      return;
    }

    // 事件处理,必须成功或失败
    ele.onload = function () {
      if (!/^http/.test(ele.src)) {
        start++;
        if (start == length) {
          render();
        }
      }
    };

    // 请求图片并转为base64地址
    fetch(src).then(res => res.blob()).then(blob => {
      var reader = new FileReader() ;
      reader.onload = function () {
        ele.src = this.result;
      };
      reader.readAsDataURL(blob) ;
    }).catch(() => {
      // 请求异常处理
      start++;
      if (start == length) {
        render();
      }
    });
  });
}

可以自动将容器元素中的图片Base64,同时内容分布在每一页的PDF上并下载。

实践出真知

为了验证封装的方法的效果,我特意做了个演示页面。

//zxx: 演示页面采用的是直联调用

您可以狠狠地点击这里:exportPdf封装方法与跨域图片PDF导出demo

打击可以点击下图所示的“PDF生成”按钮,菊花转几圈之后,就可以看到PDF下载的提示了(看你浏览器设置,也可能是直接保持到本地)。

导出PDF示意

下图是生成的PDF文件的缩略图,可以看到图片和排版都是完全符合预期的。
导出PDF示意图

调用这块的JS代码参考:

<script src="./html2canvas.min.js"></script>
<script src="./jspdf.umd.min.js"></script>
<script type="module">
import { exportPdf } from './exportPdf.js';
// 点击按钮执行PDF导出
button.addEventListener('click', () => {
    const article = document.querySelector('article');
    // 显示loading
    button.loading = true;
    // 由于导出PDF是异步的,所以需要在导出完成后隐藏loading
    exportPdf(article, '最终章 极北大迷宫', () => {
        button.loading = false;
    });
});
</script>

少了很多处理细节,是不是实现起来简单多了。

六、跨行内联背景色的渲染问题

实际使用中,还遇到了比较棘手的问题。

就是内联元素,如果有背景色,且这个背景色换行了,则生成的PDF的这部分色块会覆盖部分内容,导致异常。

例如页面渲染是这样的:
原始布局效果示意

PDF效果却是这样的:

内联色块bug

我查了下html2canvas的issues,有多个类似反馈,但是都没有进行处理。

按照我对html2canvas底层实现方式的理解,这个问题确实不太好处理。

但是,并不表示没有方法。

可以将整块的内联元素,分隔成一个一个独立的内联元素,这样就可以正常渲染了。

也就是将这个结构:

<span class="bgcolor">CSS新世界</span>

转换成这样子的(实际开发不能换行,这里是为了方便大家阅读刻意处理的):

<span>
    <span class="bgcolor">C</span>
    <span class="bgcolor">S</span>
    <span class="bgcolor">S</span>
    <span class="bgcolor">新</span>
    <span class="bgcolor">世</span>
    <span class="bgcolor">届</span>
</span>

来看下最终的效果。

您可以狠狠地点击这里:解决html2canvas span inline background色块问题demo

点击下面这个按钮,JS会对原来的DOM结构进行处理(实际开发可以克隆该元素再处理,以避免DOM结构变化会带来潜在风险):

修复按钮点击示意

此时,生成的PDF效果就和原始布局样式效果保持一致了:

生成效果保持一致了

七、其他点点点

本文示意页面所使用的JS文件都是script直连。

实际开发,多是走前端框架。

由于这两个JS都是体积比较大的JS,因此,可以使用动态加载的方式来实现。

import('html2canvas').then(module => module.default).then(...)
import('jspdf').then(module => module.jsPDF).then(...)

盼星星盼月亮

《CSS选择器世界 第2版》的签字版也已经可以购买啦,打开手机淘宝,扫下图左下角的码就可以了。

书籍购买码

//zxx: 包邮,另外,我这里还有三张极客时间14天畅学卡,先购买的优先赠送之。

OK,其他就没什么好说的,希望本文的内容可以帮到遇到类似需求的小伙伴。

❤️ ? ? ? ? ?

本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=10854

(本篇完)