导出微信公众号文章为PDF

在微信公众号文章页面中添加按钮,点击后导出文章为PDF格式,并显示标题、作者和时间等元信息。

  1. // ==UserScript==
  2. // @name 导出微信公众号文章为PDF
  3. // @namespace https://github.com/dlzmoe/scripts
  4. // @version 0.5
  5. // @author dlzmoe
  6. // @description 在微信公众号文章页面中添加按钮,点击后导出文章为PDF格式,并显示标题、作者和时间等元信息。
  7. // @match https://mp.weixin.qq.com/s/*
  8. // @grant none
  9. // @license MIT
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // 创建一个按钮
  17. var button = document.createElement('button');
  18. button.innerHTML = '导出为PDF';
  19. button.style.position = 'fixed';
  20. button.style.top = '10px';
  21. button.style.right = '10px';
  22. button.style.zIndex = '9999';
  23. button.style.backgroundColor = '#4CAF50';
  24. button.style.color = 'white';
  25. button.style.border = 'none';
  26. button.style.padding = '10px 20px';
  27. button.style.fontSize = '16px';
  28. button.style.cursor = 'pointer';
  29. button.style.transition = 'background-color 0.3s ease'; // 添加过渡效果
  30. document.body.appendChild(button);
  31.  
  32. // 标志位:防止连续多次点击
  33. let isExporting = false;
  34.  
  35. // 添加加载动画
  36. function startLoading() {
  37. button.disabled = true; // 禁用按钮
  38. button.style.backgroundColor = '#888'; // 变成灰色表示加载中
  39. button.innerHTML = '正在导出...'; // 更改按钮文本为加载状态
  40. }
  41.  
  42. // 停止加载动画
  43. function stopLoading() {
  44. button.disabled = false; // 启用按钮
  45. button.style.backgroundColor = '#4CAF50'; // 恢复原始颜色
  46. button.innerHTML = '导出为PDF'; // 恢复按钮文本
  47. }
  48.  
  49. // 点击按钮时执行导出PDF的操作
  50. button.addEventListener('click', function () {
  51. if (isExporting) {
  52. return; // 如果已经在导出过程中,则不允许再次点击
  53. }
  54.  
  55. isExporting = true; // 设置为正在导出
  56. startLoading(); // 启动加载动画
  57.  
  58. // 获取文章内容和标题、作者、时间等元信息
  59. var article = document.querySelector('.rich_media_content');
  60. var title = document.querySelector('.rich_media_title');
  61. var author = document.querySelector('.weui-wa-hotarea'); // 文章作者
  62. var publishTime = document.querySelector('#publish_time'); // 文章时间
  63.  
  64. if (article) {
  65. // 创建一个容器用于添加元信息
  66. var metaInfoDiv = document.createElement('div');
  67. metaInfoDiv.style.marginBottom = '20px';
  68. metaInfoDiv.style.borderBottom = '1px solid #eee';
  69. metaInfoDiv.style.paddingBottom = '15px';
  70.  
  71. // 标题
  72. var titleElement = document.createElement('h1');
  73. titleElement.innerText = title ? title.innerText.trim() : '未命名文章';
  74. titleElement.style.fontSize = '24px';
  75. titleElement.style.marginBottom = '10px';
  76. metaInfoDiv.appendChild(titleElement);
  77.  
  78. // 作者
  79. if (author) {
  80. var authorElement = document.createElement('p');
  81. authorElement.innerText = '作者: ' + author.innerText.trim();
  82. authorElement.style.fontSize = '14px';
  83. authorElement.style.margin = '5px 0';
  84. metaInfoDiv.appendChild(authorElement);
  85. }
  86.  
  87. // 时间
  88. if (publishTime) {
  89. var timeElement = document.createElement('p');
  90. timeElement.innerText = '发布时间: ' + publishTime.innerText.trim();
  91. timeElement.style.fontSize = '14px';
  92. timeElement.style.margin = '5px 0';
  93. metaInfoDiv.appendChild(timeElement);
  94. }
  95.  
  96. // 将元信息插入到文章内容的顶部
  97. article.insertBefore(metaInfoDiv, article.firstChild);
  98.  
  99. // 添加防止图片分页的CSS样式
  100. var style = document.createElement('style');
  101. style.innerHTML = `
  102. .rich_media_content img {
  103. page-break-inside: avoid;
  104. break-inside: avoid;
  105. max-width: 100%;
  106. height: auto;
  107. }
  108. .rich_media_content p, .rich_media_content div {
  109. page-break-inside: avoid;
  110. break-inside: avoid;
  111. }
  112. `;
  113. document.head.appendChild(style);
  114.  
  115. // 确保所有图片加载完成
  116. let images = article.querySelectorAll('img');
  117. let imagePromises = [];
  118.  
  119. images.forEach(function (img) {
  120. // 处理懒加载的图片,确保图片的真实 URL 被加载
  121. if (img.dataset && img.dataset.src) {
  122. img.src = img.dataset.src;
  123. }
  124.  
  125. // 通过跨域获取图片,并将图片转换为 base64 格式
  126. imagePromises.push(
  127. new Promise(function (resolve) {
  128. var imgElement = new Image();
  129. imgElement.crossOrigin = 'Anonymous';
  130. imgElement.src = img.src;
  131. imgElement.onload = function () {
  132. var canvas = document.createElement('canvas');
  133. canvas.width = imgElement.width;
  134. canvas.height = imgElement.height;
  135. var ctx = canvas.getContext('2d');
  136. ctx.drawImage(imgElement, 0, 0);
  137. img.src = canvas.toDataURL('image/jpeg'); // 使用JPEG格式并压缩质量到70%
  138. resolve();
  139. };
  140. imgElement.onerror = resolve; // 即使图片加载失败,继续处理
  141. })
  142. );
  143. });
  144.  
  145. // 确保图片加载完成后再导出PDF
  146. Promise.all(imagePromises).then(function () {
  147. // 使用文章标题作为文件名
  148. var fileName = title ? title.innerText.trim() + '.pdf' : 'WeChat_Article.pdf';
  149.  
  150. var opt = {
  151. margin: 0.5,
  152. filename: fileName,
  153. image: {
  154. type: 'jpeg',
  155. quality: 1 // 降低图片质量以减小PDF体积
  156. },
  157. html2canvas: {
  158. scale: 1.5, // 降低渲染比例以减小PDF体积
  159. useCORS: true, // 允许跨域图片
  160. logging: false, // 关闭日志
  161. // 可以根据需要添加其他html2canvas选项
  162. },
  163. jsPDF: {
  164. unit: 'in',
  165. format: 'a4', // 使用A4格式,比letter更常用且体积可能更小
  166. orientation: 'portrait'
  167. },
  168. pagebreak: {
  169. mode: ['avoid-all', 'css', 'legacy']
  170. } // 遵循CSS中的page-break规则
  171. };
  172.  
  173. // 使用 html2pdf 将文章内容导出为 PDF
  174. html2pdf().from(article).set(opt).save().then(function () {
  175. // 导出完成后,恢复按钮状态
  176. stopLoading();
  177. isExporting = false; // 重置导出状态
  178. }).catch(function (error) {
  179. alert('导出过程中出现问题: ' + error.message);
  180. stopLoading(); // 即使出现错误也恢复按钮状态
  181. isExporting = false; // 重置导出状态
  182. });
  183. }).catch(function (error) {
  184. alert('处理图片时出现问题: ' + error.message);
  185. stopLoading(); // 即使出现错误也恢复按钮状态
  186. isExporting = false; // 重置导出状态
  187. });
  188. } else {
  189. alert('未找到文章内容');
  190. stopLoading(); // 如果未找到文章内容,恢复按钮状态
  191. isExporting = false; // 重置导出状态
  192. }
  193. });
  194. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址