JavaScript純前端方式實(shí)現(xiàn)頁(yè)面信息優(yōu)雅下載與打印
前端經(jīng)常會(huì)遇到一些下載需求,常規(guī)的像打印、圖片、word、PDF等等,以下是我對(duì)純前端下載的一些整理
打印
打印是最簡(jiǎn)單的需求,也是最容易實(shí)現(xiàn)的
window.print()
可以直接調(diào)用 window.print() 方法,所有瀏覽器都支持 print():
但是這個(gè)方法會(huì)打印所有頁(yè)面內(nèi)容,無(wú)法指定打印那些內(nèi)容,此時(shí)需要做一些額外工作
添加 css
function windowPrint(style) {
const styleElement = document.createElement("style");
const head = document.querySelector("head");
styleElement.onload = function () {
window.print();
setTimeout(() => {
head.removeChild(styleElement);
}, 1000);
};
styleElement.innerHTML = `@media print {
${style}
}`;
head.appendChild(styleElement);
}
如果需要分頁(yè)處理可以增加樣式
@media print .page-break-auto {
page-break-before: auto!important;
page-break-after: auto!important;
page-break-inside: avoid!important;
}
iframe.contentWindow.print()
將打印內(nèi)容放入iframe,打印完成后移除
function iframePrint() {
const iframe = document.createElement("iframe");
let str = ''
const styles = document.querySelectorAll('style,link')
for (let i = 0; i < styles.length; i++) {
str += styles[i].outerHTML
}
const f = document.body.appendChild(iframe)
// 將iframe不可見
iframe.style = 'position:absolute;width:0;height:0;top:-10px;left:-10px;'
const frameWindow = f.contentWindow || f.contentDocument
const doc = f.contentDocument || f.contentWindow.document
doc.open()
doc.write(str + dom.outerHTML)
doc.close()
iframe.onload = function() {
frameWindow.focus()
frameWindow.print()
}
}
下載圖片
下載圖片需要借助第三方插件 html2canvas
function createCanvns(element: HTMLElement) {
const canvas = await html2canvas(element, {
useCORS: true,
imageTimeout: 0,
scale: 2,
onclone: (doc) => {},
ignoreElements: (element) => {
},
});
return canvas;
}
下載 PDF
下載 PDF 需要借助第三方插件 jspdf
import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";
interface Blank {
height: number;
width: number;
x: number;
y: number;
}
class PdfBlank implements Blank {
height: number;
width: number;
x: number;
y: number;
constructor(blank: Blank) {
this.height = blank.height;
this.width = blank.width;
this.x = blank.x;
this.y = blank.y;
}
}
interface PdfPage {
height: number;
position: number;
blank?: Blank;
}
interface CanvasNode {
node: HTMLElement;
image: string;
offsetHeight: number;
offsetWidth: number;
imageHeight: number;
pages: PdfPage[];
position: number;
}
interface PDFExportOptions {
filename: string;
onProgress?: (progress: number) => void;
}
export class PDFExporter {
scale = 2;
width = 595.28;
height = 841.89;
async exportToPDF(element: HTMLElement, options: PDFExportOptions) {
this.updateProgress(0, options.onProgress);
console.time("exportToPDF");
const nodes = await this.getPageNode(element, options);
await new Promise((resolve) => setTimeout(resolve, 10));
await this.getNodeCanvas(nodes, options);
await this.createPdf(nodes, options);
console.timeEnd("exportToPDF");
this.updateProgress(100, options.onProgress);
}
async createCanvns(element: HTMLElement) {
const canvas = await html2canvas(element, {
useCORS: true,
imageTimeout: 0,
scale: this.scale,
onclone: (doc) => {},
ignoreElements: (element) => {
return (
element.classList.contains("canvas-ignore-this")
);
},
});
return canvas;
}
async getPageNode(element: HTMLElement, options: PDFExportOptions) {
const stack: { node: Node }[] = [{ node: element }];
const results: CanvasNode[] = [];
let position = 0;
// 更新進(jìn)度
let pageProgress = 0;
while (stack.length > 0) {
if (pageProgress++ < 20) {
this.updateProgress(pageProgress++, options.onProgress);
}
const { node } = stack.pop()!;
if (node instanceof HTMLElement) {
const { createCanvasGroup, createCanvas } = node.dataset;
if (createCanvas != null) {
// 獲取最小截圖元素
const rect = node.getBoundingClientRect();
const offsetHeight = rect.height; // 包含小數(shù)部分的精確高度
const offsetWidth = rect.width; // 包含小數(shù)部分的精確高度
const imageHeight = (this.width / offsetWidth) * offsetHeight;
const canvasNode: CanvasNode = {
node,
image: "",
offsetHeight,
offsetWidth,
imageHeight,
position,
pages: [],
};
if (position + imageHeight < this.height) {
canvasNode.pages.push({
height: imageHeight,
position,
});
position += imageHeight;
} else {
// 獲取分頁(yè)
this.getNodePage(canvasNode);
position = canvasNode.position;
}
results.push(canvasNode);
} else if (createCanvasGroup != null) {
const children = Array.from(node.childNodes);
const childrenLength = children.length;
for (let i = childrenLength - 1; i >= 0; i--) {
stack.push({ node: children[i] });
}
}
}
}
return results;
}
async getNodeCanvas(canvasNodes: CanvasNode[], options: PDFExportOptions) {
// 更新進(jìn)度 - 60
const canvasNodesLength = canvasNodes.length;
for (let i = 0; i < canvasNodesLength; i++) {
const pageProgress = 20 + (40 * i) / canvasNodesLength;
this.updateProgress(pageProgress, options.onProgress);
const canvasNode = canvasNodes[i];
const canvas = await this.createCanvns(canvasNode.node);
const image = canvas.toDataURL("image/jpeg", 1);
canvasNode.image = image;
}
}
getNodePage(canvasNode: CanvasNode) {
const { node, image } = canvasNode;
const stack: { node: Node }[] = [];
const results: PdfPage[] = [];
// 獲取上一張圖的position
let firstPosition = canvasNode.position;
const children = Array.from(node.childNodes);
const childrenLength = children.length;
for (let i = childrenLength - 1; i >= 0; i--) {
stack.push({ node: children[i] });
}
let imageHeight = 0;
let position = firstPosition;
while (stack.length > 0) {
const { node } = stack.pop()!;
if (node instanceof HTMLElement) {
const rect = node.getBoundingClientRect();
const offsetHeight = rect.height; // 包含小數(shù)部分的精確高度
const offsetWidth = rect.width; // 包含小數(shù)部分的精確高度
const height = (this.width / offsetWidth) * offsetHeight;
// 如果高度大于pdf高度,則需要遞歸子元素
if (height > this.height) {
const children = Array.from(node.childNodes);
if (children.length > 0) {
const childrenLength = children.length;
for (let i = childrenLength - 1; i >= 0; i--) {
stack.push({ node: children[i] });
}
continue;
}
}
if (firstPosition + imageHeight + height > this.height) {
const blankHeight = this.height - firstPosition - imageHeight;
const blank = new PdfBlank({
height: blankHeight,
width: this.width,
x: 0,
y: firstPosition + imageHeight,
});
results.push({ height: imageHeight, position, blank });
if (firstPosition) {
position -= firstPosition;
}
position -= imageHeight;
firstPosition = 0;
imageHeight = 0;
}
imageHeight += height;
}
}
if (imageHeight > 0) {
results.push({ height: imageHeight, position });
}
// 最后一頁(yè)的圖片高度
canvasNode.position = imageHeight;
canvasNode.pages = results;
}
async createPdf(nodes: CanvasNode[], options: PDFExportOptions) {
const pdf = new jsPDF("p", "pt", [this.width, this.height]);
const nodeLength = nodes.length;
const jProgress = 40 / nodeLength;
for (let i = 0; i < nodeLength; i++) {
const node = nodes[i];
const { image, pages, imageHeight } = node;
const pagesLength = pages.length;
for (let j = 0; j < pagesLength; j++) {
const pageProgress = 60 + (jProgress * j) / pagesLength;
this.updateProgress(pageProgress, options.onProgress);
const page = pages[j];
const { height, position, blank } = page;
if (height > 0) {
pdf.addImage(image, "JPEG", 0, position, this.width, imageHeight);
if (blank) {
this.addBlank(pdf, blank);
if (i !== pagesLength - 1 && j !== pagesLength - 1) {
pdf.addPage();
}
}
}
}
}
pdf.save("test.pdf");
}
addBlank(pdf: jsPDF, blank: PdfBlank) {
pdf.setFillColor(255, 255, 255);
pdf.rect(
blank.x,
blank.y,
Math.ceil(blank.width),
Math.ceil(blank.height),
"F"
);
}
// 更新進(jìn)度
updateProgress(
progress: number,
onProgress?: (progress: number) => void
): void {
if (onProgress && typeof onProgress === "function") {
try {
onProgress(Math.min(100, Math.max(0, Math.round(progress))));
} catch (e) {
console.warn("進(jìn)度回調(diào)錯(cuò)誤:", e);
}
}
}
}
下載 word
下載 word 需要借助 docx 插件
import {
Document,
Paragraph,
TextRun,
Packer,
Table,
TableRow,
TableCell,
ImageRun,
type FileChild,
type ParagraphChild,
type IRunOptions,
type IParagraphOptions,
type ITableCellOptions,
type ITableOptions,
} from "docx";
import { saveAs } from "file-saver";
interface FileChildNode {
getNode(): FileChild;
}
interface ParagraphChildNode {
getNode(): ParagraphChild | null;
}
interface TableCellNode {
getNode(): TableCell;
}
class TextRunNode implements ParagraphChildNode {
options: IRunOptions;
constructor(options: IRunOptions) {
this.options = options;
}
getNode() {
return new TextRun(this.options);
}
}
class ImageRunNode implements ParagraphChildNode {
type: string;
data: Buffer | ArrayBuffer;
width: number = 100;
height: number = 100;
constructor() {}
getNode() {
if (!this.data) {
return null;
}
return new ImageRun({
data: this.data,
type: this.type as any,
transformation: { width: this.width, height: this.height },
});
}
getImageType(url: string) {
const noQueryUrl = url.split("?")[0];
const type = noQueryUrl.split(".").pop() || ("png" as any);
return type;
}
async getImageBuffer(localImage: string, width: number, height: number) {
const response = await fetch(localImage);
this.data = await response.arrayBuffer();
this.type = this.getImageType(localImage);
this.width = width;
this.height = height;
}
async getImageBufferFromUrl(url: string) {
try {
const response = await fetch(url);
this.data = await response.arrayBuffer();
this.type = this.getImageType(url);
const domElements = document.querySelectorAll(`[src="${url}"]`);
for (const domElement of Array.from(domElements)) {
const { width, height } = domElement.getBoundingClientRect();
if (width > 0 && height > 0) {
this.width = width;
this.height = height;
break;
}
}
const maxWidth = 595;
// 限制最大寬度
if (this.width > maxWidth) {
this.height = this.height * (maxWidth / this.width);
this.width = maxWidth;
}
} catch (error) {
console.error(error);
}
}
}
class ParagraphNode implements FileChildNode {
options: IParagraphOptions;
children: ParagraphChildNode[];
constructor(options: IParagraphOptions, children: ParagraphChildNode[] = []) {
this.options = options;
this.children = children;
}
push(child: ParagraphChildNode | ParagraphChildNode[]) {
if (Array.isArray(child)) {
this.children.push(...child);
} else {
this.children.push(child);
}
}
getNode() {
const children = this.children
.map((child) => child.getNode())
.filter(Boolean) as ParagraphChild[];
return new Paragraph({
...this.options,
children,
});
}
}
class TableNode implements FileChildNode {
children: TableCellNode[];
options: Partial<ITableOptions>;
constructor(options: Partial<ITableOptions>, children: TableCellNode[] = []) {
this.children = children;
this.options = options;
}
getNode() {
return new Table({
...this.options,
rows: [
new TableRow({
children: this.children.map((child) => child.getNode()),
}),
],
});
}
}
class TableCellNode implements TableCellNode {
options: Partial<ITableCellOptions>;
children: ParagraphNode[];
constructor(
options: Partial<ITableCellOptions>,
children: ParagraphNode[] = []
) {
this.options = options;
this.children = children;
}
push(child: ParagraphNode | ParagraphNode[]) {
if (Array.isArray(child)) {
this.children.push(...child);
} else {
this.children.push(child);
}
}
getNode() {
return new TableCell({
...this.options,
children: this.children.map((child) => child.getNode()),
});
}
}
class DocxDocument {
children: FileChildNode[] = [];
constructor() {}
push(child: FileChildNode | FileChildNode[]) {
if (Array.isArray(child)) {
this.children.push(...child);
} else {
this.children.push(child);
}
}
getChildren() {
return this.children.map((child) => child.getNode());
}
async downloadDocx(filename: string) {
const children = this.getChildren();
const doc = new Document({
sections: [
{
children,
},
],
});
const blob = await Packer.toBlob(doc);
saveAs(blob, filename);
}
/**
* 像素(px) 轉(zhuǎn) docx 的 size(半磅單位)
* @param {number} px - 像素值
* @returns {number} docx 的 size 值
*/
pxToSize(px: number | string) {
px = Number(px);
if (isNaN(px)) {
px = 14;
}
return Math.round(px * 1.5); // px × 1.5 并四舍五入
}
}
class HtmlNodes {
html: string;
color: string = "#262626";
size: number = 14;
constructor(html: string, size?: number, color?: string) {
this.html = html;
this.size = size || this.size;
this.color = color || this.color;
}
// 判斷塊級(jí)元素
isBlockElement(tagName: string) {
return (
tagName === "p" ||
tagName === "div" ||
tagName === "li" ||
tagName === "h1" ||
tagName === "h2" ||
tagName === "h3" ||
tagName === "h4" ||
tagName === "h5" ||
tagName === "h6" ||
tagName === "br"
);
}
rgbToHex(rgb: string): string {
const result = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(rgb);
if (!result) {
return "000000";
}
const r = parseInt(result[1], 10);
const g = parseInt(result[2], 10);
const b = parseInt(result[3], 10);
return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
/**
* 像素(px) 轉(zhuǎn) docx 的 size(半磅單位)
* @param {number} px - 像素值
* @returns {number} docx 的 size 值
*/
pxToSize(px: number | string) {
px = Number(px);
if (isNaN(px)) {
px = 14;
}
return Math.round(px * 1.5); // px × 1.5 并四舍五入
}
checkPrecedingContent(node: HTMLElement) {
const previousSibling = node.previousSibling;
if (previousSibling && previousSibling.nodeType !== Node.ELEMENT_NODE) {
return false;
}
const previousElementSibling = node.previousElementSibling;
const isBlockElement =
previousElementSibling &&
this.isBlockElement(previousElementSibling?.tagName.toLowerCase());
if (isBlockElement) {
return true;
}
return false;
}
hasInlineUnderline(style: CSSStyleDeclaration) {
return (
style.textDecoration.includes("underline") ||
style.textDecorationLine.includes("underline")
);
}
hasInlineStrike(style: CSSStyleDeclaration) {
return (
style.textDecoration.includes("line-through") ||
style.textDecorationLine.includes("line-through")
);
}
isTextItalic(style: CSSStyleDeclaration) {
// 檢查 font-style
if (style.fontStyle === "italic") return true;
return false;
}
getTextRunOptions(node: HTMLElement): IRunOptions {
const data: any = {};
const style = node.style;
// 換行
let breakValue;
const tagName = node.tagName.toLowerCase();
switch (tagName) {
case "p":
case "div":
case "li":
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
case "br":
breakValue = 1;
break;
case "b":
case "strong":
data.bold = true;
break;
case "i":
case "em":
data.italics = true;
break;
case "u":
data.underline = { type: "single" };
break;
case "a":
data.underline = { type: "single" };
data.color = "#00AD63";
break;
case "sub":
data.subscript = true;
break;
case "sup":
data.superscript = true;
break;
default:
if (this.checkPrecedingContent(node)) {
breakValue = 1;
}
break;
}
data.break = breakValue;
// 字體需要進(jìn)行轉(zhuǎn)換
const sizeMap = {
"xx-small": 8,
"x-small": 9,
small: 10,
medium: 12,
normal: 14,
large: 16,
"x-large": 18,
"xx-large": 24,
"xxx-large": 32,
"xxxx-large": 40,
"xxxxx-large": 48,
"xxxxxx-large": 64,
"xxxxxxx-large": 80,
};
const fontSize = style.fontSize || this.size || 14;
const sizeValue = sizeMap[fontSize as keyof typeof sizeMap] || fontSize;
const size = this.pxToSize(sizeValue);
data.size = size;
// 顏色 - rgb需要進(jìn)行轉(zhuǎn)換
let color = style.color || data.color || this.color || "#262626";
if (color.startsWith("rgb")) {
color = this.rgbToHex(color);
}
if (!color.startsWith("#")) {
color = `#${color}`;
}
data.color = color;
// 背景顏色
let backgroundColor = style.backgroundColor;
if (backgroundColor) {
if (backgroundColor.startsWith("rgb")) {
backgroundColor = this.rgbToHex(backgroundColor);
}
// 去掉前面的#
if (backgroundColor.startsWith("#")) {
backgroundColor = backgroundColor.slice(1);
}
data.shading = {
fill: backgroundColor,
};
console.log(style.backgroundColor, data.shading);
}
// 粗體
data.bold = style.fontWeight === "bold" || Number(style.fontWeight) >= 500;
// 下劃線
if (this.hasInlineUnderline(style)) {
data.underline = { type: "single" };
}
// 斜體
if (this.isTextItalic(style)) {
data.italics = true;
}
// 刪除線
if (this.hasInlineStrike(style)) {
data.strike = true;
}
// 檢測(cè)DOM元素的上標(biāo)/下標(biāo)狀態(tài)
if (style.verticalAlign === "sub") {
data.subscript = true;
}
if (style.verticalAlign === "super") {
data.superscript = true;
}
return data;
}
async getNodes() {
const parser = new DOMParser();
const htmlDom = parser.parseFromString(this.html, "text/html");
const rootNode = htmlDom.body;
const stack: { node: Node; options: IRunOptions }[] = [
{ node: rootNode, options: {} },
];
const results: (TextRunNode | ImageRunNode)[] = [];
while (stack.length > 0) {
const { node, options } = stack.pop()!;
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent?.trim();
if (text) {
results.push(new TextRunNode({ text, ...options }));
}
continue;
}
if (node instanceof HTMLElement) {
// 圖片處理
if (node.tagName === "IMG") {
const src = node.getAttribute("src");
if (src) {
const imageRun = new ImageRunNode();
await imageRun.getImageBufferFromUrl(src);
results.push(imageRun);
}
continue;
}
const children = Array.from(node.childNodes);
const options = this.getTextRunOptions(node);
for (let i = children.length - 1; i >= 0; i--) {
stack.push({ node: children[i], options });
}
}
}
return results;
}
}
export {
TextRunNode,
ImageRunNode,
HtmlNodes,
ParagraphNode,
TableNode,
TableCellNode,
DocxDocument,
type ParagraphChildNode,
};以上就是JavaScript純前端方式實(shí)現(xiàn)頁(yè)面信息優(yōu)雅下載與打印的詳細(xì)內(nèi)容,更多關(guān)于JavaScript頁(yè)面信息下載與打印的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript設(shè)計(jì)模式之模板方法模式原理與用法示例
這篇文章主要介紹了JavaScript設(shè)計(jì)模式之模板方法模式原理與用法,結(jié)合實(shí)例形式分析了JavaScript模板方法模式的概念、組成、定義、使用等相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-08-08
WEB前端開發(fā)框架Bootstrap3 VS Foundation5
WEB前端開發(fā)框架Bootstrap3 VS Foundation5,這篇文章主要介紹了Bootstrap3與Foundation5的五大區(qū)別,感興趣的小伙伴們可以參考一下2016-05-05
layui問題之自動(dòng)滾動(dòng)二級(jí)iframe頁(yè)面到指定位置的方法
今天小編就為大家分享一篇layui問題之自動(dòng)滾動(dòng)二級(jí)iframe頁(yè)面到指定位置的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2019-09-09
隨鼠標(biāo)移動(dòng)的時(shí)鐘非常漂亮遺憾的是只支持IE
這篇文章主要介紹了隨鼠標(biāo)移動(dòng)的時(shí)鐘非常漂亮遺憾的是只支持IE,需要的朋友可以參考下2014-08-08
dedecms頁(yè)面如何獲取會(huì)員狀態(tài)的實(shí)例代碼
下面小編就為大家?guī)?lái)一篇dedecms頁(yè)面如何獲取會(huì)員狀態(tài)的實(shí)例代碼。一起跟隨小編過來(lái)看看吧,希望對(duì)大家有所幫助。2016-03-03

