聊一聊浏览器打印 - window.print

news2025/2/24 21:54:26

前言

一般信息填写类的需求页面,都会增设「预览」和「打印」功能。我们会通过编写 DOM 及样式来绘制出预览视图,而打印则是基于预览来生成 PDF 文件。

浏览器原生 API window.print() 可以用于打印当前窗口(window.document)视图内容。调用此方法会产生一个打印预览弹框,用户可以根据具体设置来得到打印结果。

接下来将从 code 层面带领大家熟悉「打印」的使用。

一、打印样式

默认情况下,基于页面上的内容,会将元素,布局和样式都进行打印;

如果仅想在打印上设置特殊样式,可以通过以下方式:

1.使用打印样式表:

<link href="print.css" media="print" rel="stylesheet" /> 

2.使用媒介查询:

@media print {p{color: lavender;background: #ccc;}h1{color: lightblue;background: #ccc;}
} 

3.使用内联 media 属性

<style media="print"> p{color: lavender;background: #ccc;}h1{color: lightblue;background: #ccc;} </style> 

默认情况下,元素的背景色不会被打印,可通过设置属性来支持:

div{// Chrome、Safari 等 webkit 浏览器内核-webkit-print-color-adjust: exact;// 火狐print-color-adjust: exact;color-adjust: exact;
} 

二、打印指定区域内容

默认情况下,调用 window.print() 会对整个 document.body 进行打印,当需要打印指定容器内容时,可以通过以下几种方式:

1. 对容器进行打印

<body><div id="container"><p>这是一个段落</p><h1>这是一个标题</h1></div><input type="button" value="打印此页面" onclick="printpage()" /><script> const printpage = () => {let newstr = document.getElementById("container").innerHTML;let oldstr = document.body.innerHTML;document.body.innerHTML = newstr;window.print();document.body.innerHTML = oldstr;} </script>
</body> 

2. 对容器内的部分内容进行打印

当只需要打印容器内某一部分内容时,可以通过注释标识进行截取。

<body><div id="container"><!--startprint--><p>这是一个段落</p><!--endprint--><h1>这是一个标题</h1></div><input type="button" value="打印此页面" onclick="printpage()" /><script> const printpage = () => {let oldStr = window.document.body.innerHTML; // 获取body的内容let start = "<!--startprint-->"; // 开始打印标识, 17个字符let end = "<!--endprint-->"; // 结束打印标识let newStr = oldStr.substr(oldStr.indexOf(start) + 17); // 截取开始打印标识之后的内容newStr = newStr.substring(0, newStr.indexOf(end)); // 截取开始打印标识和结束打印标识之间的内容window.document.body.innerHTML = newStr; // 把需要打印的指定内容赋给bodywindow.print(); // 调用浏览器的打印功能打印指定区域window.document.body.innerHTML = oldStr; // body替换为原来的内容} </script>
</body> 

3. 监听打印前后事件

通过监听打印前后事件,对不需要进行打印的元素进行隐藏和放开隐藏。

<body><div id="container"><p>这是一个段落</p><h1 id="title">这是一个标题</h1></div><input type="button" value="打印此页面" onclick="printpage()" /><script> const printpage = () => {window.print();}window.onbeforeprint = function() {// 将一些不需要被打印的元素隐藏document.getElementById('title').style.display = 'none';}window.onafterprint = function() {// 放开隐藏的元素document.getElementById('title').style.display = 'block';} </script>
</body> 

4. iframe

上面几种方式都在当前窗口进行打印,并且都需要更改 document.body 内容,这会出现视图切换,带来的体验不是太好。

下面我们借助 iframe 来实现打印,并且不影响当前视窗的内容展示。

<body><div id="container"><p>这是一个段落</p><h1 id="title">这是一个标题</h1></div><input type="button" value="打印此页面" onclick="printpage()" /><script> const printpage = () => {const printContent = document.querySelector('#container').innerHTML;const iframe = document.createElement('iframe');iframe.setAttribute('style', 'position: absolute; width: 0; height: 0;');document.body.appendChild(iframe);const iframeDoc = iframe.contentWindow.document;// 设置打印展示方式 - 横向展示iframeDoc.write('<style media="print">@page {size: landscape;}</style>');// 向 iframe 中注入 printContent 样式iframeDoc.write(`<link href="./print.css" media="print" rel="stylesheet" />`);// 写入内容iframeDoc.write('<div>' + printContent + '</div>');setTimeout(function(){iframe.contentWindow?.print();document.body.removeChild(iframe);}, 50);} </script>
</body> 

值得注意的是,iframe 是一个新的 window 窗口,不会复用当前窗口的样式,需要为 iframe 注入打印内容所需的样式。

三、强行插入分页

当需要自定义打印分页时机时,可通过如下方式将指定 DOM 设为分割点。

1.在指定元素前添加分页符

@media print {h1 {page-break-before: always;}
} 

2.在指定元素后添加分页符

@media print {h1 {page-break-after: always;}
} 

四、打印设置

1.设置打印布局

@media print {@page {/* 纵向展示(高度展示内容更多) *//* size: portrait;*//* 横向(宽度展示内容更大) */size: landscape;/* 打印的边距 上右下左 */margin: 1cm 2cm 1cm 2cm;}
} 

注意,一旦设置为 size: landscape,在打印时将不能切换展示模式,包括纸张类的设置。

五、最佳实践(React)

1. 背景:

有一个信息填写页面,支持进行预览和打印,预览是一个 Dialog 弹框,打印取自于预览的内容。因此,在打印前,需要将预览内容呈现在 DOM 树上。

2. 思路:

  • 点击打印,将预览 Dialog open state 设置为 true,Dialog 渲染到 DOM 树上;
  • 执行 setTimeout 延迟任务,在 Dialog 渲染在 DOM 树上后对其隐藏(disabled: none),目的是实现视图上不展示 Dialog;
  • 创建 iframe,并将 Dialog 内容及其样式,写入 iframe.document 中;
  • 执行 iframe.contentWindow.print() 进行打印;
  • 打印完成后做一些重置处理:移除 iframe、将 Dialog 隐藏逻辑去掉、将 Dialog open state 置为 false;
  • 这样,在不影响现有页面内容的展示,同时实现了打印 Dialog 内容。

3. 实现:

const printFocus = () => { // 打印事件// 1. 挂载要打印的内容setPreviewOpen(true);setTimeout(() => { // 延迟,等待 Dialog 渲染在 DOM 树上// 2. 隐藏要打印的内容const container = document.querySelector('.preview-wrapper');container.setAttribute('style', 'display: none;');// 3. 创建 iframeconst iframe = document.createElement('iframe');const printContent = container.innerHTML;iframe.setAttribute('style', 'position: absolute; width: 0; height: 0;');document.body.appendChild(iframe);const doc = iframe.contentWindow.document;// 4. 写入内容// doc.write('<style media="print">@page {size: landscape;}</style>');doc.write(`<link href="./preview-focus.css" media="print" rel="stylesheet" />`);doc.write('<div>' + printContent + '</div>');const link = doc.getElementsByTagName('link')[0];link.onload = () => { // 样式文件加载完毕后打印// 5. 执行打印iframe.contentWindow?.print();// 6. 重置工作document.body.removeChild(iframe);setPreviewOpen(false);container?.removeAttribute('style');}}, 0);
} 

六、如果干预打印分页

通常我们会遇到这种情况:在打印内容多于一页时会自动进行分页,若分页的分割点恰巧是一行文字,就会出现文字被切割分别显示在上下两页。

尽管我们可以通过 CSS 属性 page-break-before: always; 来干预分页,但页面内容并非固定的,如何将这个属性恰巧应用在分割点的 DOM 元素之上呢?

下面有一个思路可以参考一下:

1.为可能会被分割的元素设置自定义属性,用于查找访问;
2.根据打印视窗的每页高度,粗估一个高度值,作为页面分割的参考;
3.遍历可分割元素,判断它们是否处于页面分割位置(top < pageHeight && botton > pageHeight);
4.若处于页面分割位置,为此 DOM 设置分割属性 page-break-before: always;

代码实现:

<!-- 自定义属性标识 -->
<div key={index} data-ident="page-break" className="module-paragraph">

<script> // 1、获取可能会被分页符分割的元素const pageBreakEles = container?.querySelectorAll("[data-ident = 'page-break']") || [];// 2、定义打印页面的高度,假设粗估后为 877pxconst printPageHeight = 877;// 3、匹配元素,是否处于页面分割线位置Array.from(pageBreakEles).forEach(ele => {const { top, bottom } = ele.getBoundingClientRect();// 根据高度计算元素处于第几页const currentPage = Math.floor(top / printPageHeight);// 处于分页符位置的元素,设置分割属性if ((top - currentPage * printPageHeight) < printPageHeight && (bottom - currentPage * printPageHeight) > printPageHeight) {(ele as HTMLElement).style.setProperty('page-break-before', 'always');}}); </script> 

最后

为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/405693.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS中animation动画-详解

1、animation有什么组成&#xff1f; Animations由两部分组成&#xff1a;css动画的配置&#xff0c;以及一系列的keyframes&#xff08;用来描述动画的开始、过程、结束状态&#xff09;。不需要了解任何Js技术即可完成动画的制作 2、关键帧应该怎么表示&#xff1f; 0%表示…

CSS的hover属性

1.hover的定义 :hover在鼠标移到链接上时添加的特殊样式 :hover适用于任何元素 2.hover的使用方法 用法1:控制自身的样式 鼠标悬浮在元素上改变元素样式,改变本身的背景颜色,例如: 用法2&#xff1a;通过hover控制其他块的样式 &#xff08;1&#xff09;控制子元素的样式…

【React Router 6 快速上手二】useParams / useSearchParams / useLocation / 编程式路由导航useNavigate等API

前言 博主主页&#x1f449;&#x1f3fb;蜡笔雏田学代码 专栏链接&#x1f449;&#x1f3fb;React专栏 之前学习了react-router-dom5版本的相关内容 参考文章&#x1f449;&#x1f3fb;React路由组件传参的三种方式和 【React路由】编程式路由导航 回顾上篇文章&#x1f449…

nvm管理node版本

nvm 使用 1. nvm介绍 nvm 全英文也叫 node.js version management&#xff0c;是一个 nodejs 的版本管理工具 nvm 和 npm 都是 node.js 版本管理工具&#xff0c;为了解决 node.js 各种版本存在不兼容现象可以通过它可以安装和切换不同版本的 node.js 2. 安装与配置 2-1 nv…

CSS绝对定位(absolute)、相对定位(relative)方法(详解)

CSS中几种常用的定位方法&#xff0c;直接上干货&#xff01; 什么是定位&#xff1f; 元素可以使用的顶部&#xff0c;底部&#xff0c;左侧和右侧属性定位。然而&#xff0c;这些属性无法工作&#xff0c;除非是先设定 position属性。他们也有不同的工作方式&#xff0c;这…

Echarts直角坐标系x轴y轴属性设置大全

1、Echarts版本 "echarts": "^5.3.3", 2、最简单的直角坐标系&#xff0c;以柱状图为例。 常见的直角坐标系&#xff0c;x轴设置type: category&#xff0c;为类目轴&#xff0c;适用于离散的类目数据&#xff1b;y轴设置type: value&#xff0c;为数值轴…

前端编程中你是如何进行调试代码的?这篇文章让你学会更多调试方法——前端新手进阶

要我讲就是&#xff1a;对自己代码自信的人&#xff0c;从来不需要调试&#xff0c;只是log一下值是否符合预期(doge)。 哈哈&#xff0c;这当然是一句玩笑话&#xff0c;不管你是刚刚起步的新手&#xff0c;还是从业多年的老手&#xff0c;编程中或多或少都会遇到一些瓶颈&…

Vue3中的watch监听

目录 一、监听基础ref类型 1、监听单个ref数据 2、 监听多个ref数据 二、监听reactive类型 1、监听对象中单个属性 2、监听对象中多个属性 3、同时监听ref基本类型数据和reactive对象中的属性 4、监听整个对象 5、监听对象中值为对象的属性 三、watchEffect 一、监听基础…

JS数组方法中哪些会改变原数组,哪些不会?

前言 作为一名前端开发人员&#xff0c;我们每天都会与数组打交道。JS 也提供了很多操作数组的原生 API 供我们调用。在这些方法里面&#xff0c;有的方法会改变原数组&#xff0c;有些不会改变原数组。别看这一点小小的区别&#xff0c;往往会造成巨大的影响&#xff0c;特别…

HTML常用标签超详细整理

HTML概述 1.1 什么是HTML HTML是做网站的、Web开发、互联网生态开发(PC端移动端微应用) 目前我们使用的都是HTML5,支持传统的PC端开发&#xff0c;还支持移动端开发还支持微应用开发,从而替换了部分传统的移动端开发技术 1.2 HTML概念 HTML:Hyper Text Markup Language&#…

小满Vue3第三十六章(Vue如何开发移动端)

视频教程Vue3 vite Ts pinia 实战 源码 全栈_哔哩哔哩_bilibili 如果使用npm init vuelatest 报错 error when starting dev server: Error: Cannot find module node:path nodejs 升级为16版本就好了 开发移动端最主要的就是适配各种手机&#xff0c;为此我研究了一套…

让div居中的方式的几种方法

让div水平居中的方式的几种方法。 文章目录 一、margin二、绝对定位三、子元素绝对定位父元素相对定位四、flex布局总结 一、margin 第一种方式我们可以利用外边距属性来使div水平垂直居中 先来看一段有问题的代码 <!DOCTYPE html> <html lang"en"> &…

React框架创建项目详细流程-项目的基本配置-项目的代码规范

文章目录React创建项目流程与规范项目规范项目配置目录结构样式重置Router配置Redux状态管理axios配置React创建项目流程与规范 项目规范 项目规范: 在项目中都会有一些开发规范和代码风格, 下面介绍一下我采用的规范与风格 文件夹、文件名称统一小写、多个单词以连接符(-)连…

vue实战--vue+elementUI实现多文件上传+预览(word/PDF/图片/docx/doc/xlxs/txt)

需求 最近在做vue2.0element UI的项目中遇到了一个需求&#xff1a;需求是多个文件上传的同时实现文件的在线预览功能。需求图如下&#xff1a; 看到这个需求的时候&#xff0c;小栗脑袋一炸。并不知道该如何下手&#xff0c;之前的实践项目中也并没有遇到相似的功能。因此也…

webpack配置

webpack 前端工程化 实际的前端开发 模块化&#xff08;js 的模块化、css 的模块化、资源的模块化&#xff09; 组件化&#xff08;复用已有的 UI 结构、样式、行为&#xff09; 规范化&#xff08;目录结构的划分、编码规范化、接口规范化、文档规范化、Git 分支管理&…

【Web前端】CSS-盒子模型

文章目录一、盒子模型1、网页布局的本质2、盒子模型&#xff08;Box Model&#xff09;组成3、边框&#xff08;border&#xff09;3.1、边框的使用3.2、表格的细线边框3.3、边框会影响盒子实际大小4、内边距&#xff08;padding&#xff09;4.1、内边距的使用方式4.2、内边距会…

CSS响应式布局(自适应布局)

CSS 响应式布局也称自适应布局&#xff0c;是 Ethan Marcotte 在 2010 年 5 月份提出的一个概念&#xff0c;简单来讲就是一个网站能够兼容多个不同的终端&#xff08;设备&#xff09;&#xff0c;而不是为每个终端做一个特定的版本。这个概念是为解决移动端浏览网页而诞生的。…

一文弄懂Vue与Servlet的交互:让你的大学课设变得容易起来

文章目录1 写作动机2 准备工作3 前端请求3.1 get请求3.1.1 仿照网站栏撰写3.1.2 按照Parms格式传参3.2 Post请求3.2.1 按照Data格式传参3.2.2 按照Params格式传参3.3 一个特殊的点4 后端接收请求4.1 提要4.2 代码4.2.1 架子4.2.2 模块一&#xff1a;获取参数4.2.3 模块二&#…

【微信小程序】选择器组件picker

文章目录【微信小程序】选择器组件pickerpicker组件的定义picker组件的类型picker属性共同的属性时间选择器time参考【微信小程序】选择器组件picker picker组件的定义 picker组件是一种从底部向上弹起的滚动选择器。 picker组件的类型 在官方文档中&#xff0c;有提供五种…

【uni-app系列】uni-app之nvue使用

目录一、介绍二、新建 nvue 页面三、开发四、nvue 开发与 vue 开发的常见区别一、介绍 uni-app App 端内置了一个基于 weex 改进的原生渲染引擎&#xff0c;提供了原生渲染能力。 在 App 端&#xff0c;如果使用 vue 页面&#xff0c;则使用 webview 渲染&#xff1b;如果使用…