前端打印排版避坑指南:用 CSS 做出「能直接打印」的练习题页

火力盒子技术专栏 · 本文所有代码都是实际跑在 8 个在线打印生成器里的经验,链接见文末

最近给家里打印机做了一组"填设置就能打印"的前端生成器——口算题卡、田字格字帖、数独、迷宫、单词默写纸、五线谱纸。做之前以为打印就是把 window.print() 一调的事,真上手才发现 @media print 的水比想象深得多。这篇把踩过的坑按「后果 → 原因 → 修法」列出来。

坑一:屏幕上完美,打印出来一半在纸外

后果:预览排版很好,打印出来最右侧一列被切掉。

原因:打印时页面的 CSS 像素宽度和屏幕渲染不一致,用 px 或 vw 定宽的容器很容易超过 A4 的可打印宽度(默认页边距下约 794px × 剩余边距)。

修法:打印区域不要用固定像素宽,用 % 或 max-width;在 @media print 里显式约束:

@media print {
  @page {
    size: A4 portrait;
    margin: 14mm;
  }
  .sheet {
    width: 100% !important;
    max-width: none !important;
    padding: 0 !important;
  }
}

@page { size: A4 } 这一行是关键——它告诉浏览器按 A4 分页排版,而不是套用屏幕视口。

坑二:浅灰虚线打印成纯黑,省墨设计失效

后果:控笔训练的浅灰虚线轨迹,打印出来是浓浓的实线,孩子没法"描红"。

原因:浏览器的打印默认策略是不打印背景色/背景图(这是给用户省墨的好意),但有些引擎对 background-image 画出来的线条处理又不一致。

修法:凡是"必须出现在纸上"的视觉元素,优先用 SVG stroke 或 CSS border 而不是背景图——这两个默认会被打印。确实需要背景的,加保险:

.trace-line {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

并在打印窗口提示用户勾选"背景图形"。我们的做法是双管齐下:轨迹用 SVG 虚线(stroke-dasharray: 7 6),把"勾选背景图形"写进页面上的打印提示。

坑三:答案页和题目挤在同一页

后果:数独答案和题目连在一起打出来,孩子第一眼就看到答案。

原因:打印流式排版不会主动分页,除非你告诉它。

修法:答案容器加 break-before: page,题目区块加 break-inside: avoid 防止一道题被腰斩:

.ans-page {
  break-before: page;
}
.puzzle {
  break-inside: avoid;
}

注意老写法 page-break-before 是 CSS 2.1 时代产物,现代浏览器(Chrome/Edge/Safari 都已支持 break-*)直接用新属性即可,但两个都写最稳。

坑四:隐藏 UI 用 display: none,结果打印白页

后果:把设置面板 display: none 后打印出来第一页是空白。

原因:被隐藏元素如果仍然占据文档流的分页计算位置(常见于 visibility: hidden 或 transform 布局),会产生空页;另外 position: fixed 的导航条在某些引擎里会每一页都打印一次。

修法:一律用 display: none(不用 visibility),并记得处理 fixed 元素:

@media print {
  .no-print,
  .tb-nav {
    display: none !important;
  }
}

我们踩的另一个相关坑:隐藏「生成设置」面板后,如果结果区在它下面且结果还没生成,打出来就是全白。所以打印按钮在没有生成内容时保持 disabled。

坑五:动态生成的内容,打印出来是旧的

后果:改了设置重新点"生成",打印出来的还是上一次的题。

原因:这不是打印的锅,是状态管理的问题——window.print() 是同步快照,打印瞬间 DOM 里是什么就是什么。

修法:保证"生成"按钮点击后先同步完成 DOM 更新、再允许打印。对于耗时算法(比如 9×9 数独挖 50 个洞的唯一解验证),用分批异步:

function step() {
  // 每批生成 2 道题,让出主线程刷新进度
  if (done < total) {
    setTimeout(step, 0);
  } else {
    printBtn.disabled = false; // 全部生成完才允许打印
  }
}

打印按钮的 disabled 状态绑定"生成完成"事件,从交互上杜绝打印到旧内容。

灰度省墨设计:从源头少用墨

给家庭用户做打印页,省墨是真实需求。三条实践:

  1. 黑白优先:所有题卡默认纯黑白,颜色只出现在屏幕 UI 上(打印区不引用主题变量,硬编码 #111 / #fff)。
  2. 线条分级:外框 2.5px、宫格分隔 1px、辅助线 0.5px + 浅灰——视觉层级靠粗细而不是颜色深浅。
  3. SVG 代替位图:迷宫、五线谱、控笔轨迹全部 SVG 矢量输出,任何缩放都锐利,文件小、渲染快。

最后:测试打印别用真纸

开发时在打印预览(Chrome DevTools → Rendering → Emulate print media,或 Ctrl+P 的预览页)里循环就够了,真纸留给验收。预览里重点检查三件事:分页位置是否腰斩题目、页边距内内容是否溢出、灰度是否分级正确。

以上所有坑的"修复后成品"都在火力盒子的打印工具箱里,8 个生成器全部开源可抄作业:https://www.ahotbox.com/tools/education/print-studio.html

(发布到 CSDN 时请删除上一行的外链,或替换为纯文本域名 ahotbox.com)

💡 关于火力盒子:ahotbox.com 收录 1000+ 款免费纯前端在线工具(开发、文本、时间、生成器、安全等),所有数据在浏览器本地处理,无需注册、不上传数据、支持离线使用。