前言
最近想在博客上开发一个文章海报生成功能,所以研究html生成图片,网上有很多现成的项目,发现很多博主都用html2canvas来html生成图片。我也git项目来试试,开始的测试时候,一切都正常,生成的图片都不错,但准备使用html2canvas来实现博客文章海报时,遇到了一堆问题,比如:生成的图片文字不显示,找了很久才了解到1.3.3以上版有点问题,降级到旧版本后又出现文字向下偏移的现象等问题。在github Issues看了很多,没找到最终的解决办法,同时发现html2canvas项目也很久没维护了。最后发现html-to-image这个项目可以替换html2canvas,通过使用发现非常的不错,解决我遇到的问题,同时使用和html2canvas差不多。
html-to-image或者html2canvas是什么?怎么说呢,就是将HTML/CSS渲染成Canvas,然后导出为图片。类似将html指定部分的代码进行截图,当然不是真正的浏览器截图,而是手动模拟渲染,因此部分复杂CSS效果可能不支持。
食用方法
项目或文档
安装
# npm安装依赖
npm install --save html-to-image
# 或CDN引入
<script src="https://cdn.bootcdn.net/ajax/libs/html-to-image/1.11.13/html-to-image.min.js"></script>
基础用法
// 安装依赖
npm install html-to-image
// 生成PNG图片
import * as htmlToImage from 'html-to-image';
htmlToImage.toPng(document.getElementById('poster'), {
backgroundColor: null, // 透明背景
pixelRatio: 2, // 双倍像素密度
filter: (node) => node.classList.contains('no-render') // 过滤特定元素
}).then((dataUrl) => {
const img = new Image();
img.src = dataUrl;
document.body.appendChild(img);
});
结合jspdf生成PDF
// 安装html-to-image
npm install html-to-image
// 安装jspdf
npm install jspdf
// 转换为Canvas后生成PDF
htmlToImage.toCanvas(document.getElementById('poster'))
.then(canvas => {
const pdf = new jsPDF('p', 'px', [canvas.width, canvas.height]);
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0);
pdf.save('poster.pdf');
});
复杂场景处理
// 处理transform导致的元素偏移
const node = document.getElementById('poster');
const originalTransform = node.style.transform;
node.style.transform = ''; // 临时清除transform
htmlToImage.toPng(node).then((dataUrl) => {
node.style.transform = originalTransform; // 恢复样式
});
// 背景图转img元素
const backgroundImage = node.style.backgroundImage;
if (backgroundImage) {
const imgNode = document.createElement('img');
imgNode.src = backgroundImage.replace(/^url\((['"])?(.*?)\1\)$/, '$2');
imgNode.style.width = '100%';
imgNode.style.height = '100%';
node.appendChild(imgNode);
node.style.backgroundImage = ''; // 临时清除背景图
}
简单示例
我使用比较简单,测试博客文章海报时的测试案例,哈哈哈~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试示例</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.2/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<style>
.poster-post-box {
background: linear-gradient(0deg, #fff, #d5d5d5);
width: 400px;
}
.poster-cover {
height: 200px;
border-radius: 10px;
overflow: hidden;
}
.poster-cover img {
height: 100%;
width: 100%;
}
.poster-date {
height: 100%;
}
.poster-date-hr {
height: 20px;
width: 1px;
background: #818181;
}
/* 标题 */
.poster-post-title {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: normal;
}
.poster-footer {
border: 1px solid #dddddd;
}
#cs img {
-o-object-fit: contain;
object-fit: contain;
max-width: 360px;
height: auto;
width: 100%;
}
</style>
</head>
<body>
<div id="posterCapture" class="poster-post-box p-3">
<!-- 封面 -->
<div class="poster-cover">
<img src="https://www.biibii.cn/usr/uploads/2025/02/171593145.webp" />
</div>
<!-- 线 -->
<hr class="poster-line" />
<!-- 内容 -->
<div class="poster-content d-flex flex-row align-items-center" style="gap: 1.5rem;">
<!-- 日期 -->
<div class="poster-date d-flex flex-column justify-content-center align-items-center flex-shrink-0">
<div class="poster-date-moon" style="line-height: 1;"><span class="date-moon mr-2"
style="font-size: 1.75rem;">05</span><span style="color:#7b7b7b">月</span></div>
<div class="poster-date-hr my-1" style="transform: rotate(45deg);"></div>
<div class="poster-date-day" style="line-height: 1;"><span class="date-day mr-2"
style="font-size: 1.75rem;font-weight: 700;">20</span><span style="color:#7b7b7b">日</span>
</div>
</div>
<!-- 标题 -->
<div class="poster-post-info">
<h5 class="poster-post-title" style="font-weight: 700;">解决Google Chrome浏览器地址栏/书签栏下方黑线问题</h5>
<p class="poster-post-text m-0" style="color:#7b7b7b">作者:星语社长 | 分类:软件分享</p>
</div>
</div>
<!-- 底部 -->
<div class="poster-footer d-flex flex-row justify-content-between align-items-center p-2 mt-4"
style="color:#7b7b7b">
<div class="poster-footer-left">
<div>
<img style="height: 20px;" src="https://www.biibii.cn/usr/themes/HarmonyHues/assets/images/logo.webp"> | 文章海报
</div>
<div>右侧扫码识别前往BIIBII查看更多内容👉</div>
</div>
<div class="poster-footer-right flex-shrink-0">
<img style="height: 50px;"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAABqJJREFUeF7tnet2o0AMg8n7P3T3JGdpBtbmk8yQpln3L8zNsmzZEHpbluVrmfD39fWc5na7yTOO49ZB2fjo3nEhWjfao7PvbH1aVzbGsix3yzUgfy1Ghn0pIOR9GcrrITJPIw+k69G6yhg6D+2bvJrYRuMzZn8zhA7QgGwt0IAM9vjvGOLE0oj6GR1XQ47zEzOje53xDrNJIFAOURxlXSO7NwxZDciyRDZoQO4ycJDQFYY1QxIj3g3jhLeI2pFxnZplJvOj8Pa2ISuL8Q3IE8aX5hBKumevVzU+SVVi0Lju2bkakHtLImjvOGGmAdkZ0WFG1Bf7tYA4B3daEE7NcbQHkp/Z2Fnr3+cncCs2fAiftblIm3Xko6N8qBijXpYzns44M4c0IDu57QAZhTcy6OUMoQ3QdafbS/c6svnsvWfHj/UV2Ui5fvnzEPIkpxKnfPXTaykGp3tuXxRYaQa4Tj2hCiDKltQ2i8MQZd2z9zQgQa9sDEMX++s/+DUgvw0Qtf/k1AbVpK4We/s6QVVR0RkcCU+yWekKIEMakPgNGge81SEakF2EdjyYWPXjgIxncxIdGcEp4Giuyr6cUEs2cLrYaeeDZC/JVpJ5ZMQGZGsBOYeQd6SIG28xRrF2nJfA/QiGqM1FKqCIKdl1MiKBMNNRKOlSeKuqx/GMcre3Adm23CPmTgFkzSGOwR2vrqiRV7BJlaJkZCWkUhtnw5AG5Pit/QbkIBk5zFRzmpM3qrLWYsisnyNUk6tCeVJfR0XczEScnZG6GU7LZ9rzkAYkT/oNyOAdv5Yh1eTlxEdSNpQjKC9QzeLkC1qLQm20Fq1/nxPffifZ2oDwm/L06HkDrtrLqlI/yi3VZwzkHGf7YlWGqsxUaj25l9WA5EGsARlsoxpjb86zXWzKIRQZsnyCDCHJFvkNFVCZr5GRSO/TugSeE/Io6Tt72YBDOaQBqf2MvwE5+OXWUSVPnj4j0ZMY2TCEWie0IScM0Fxq+CPvc0JitcNA51ZqjvC8DcjTLI7DXAYItd+J7lQYVtQIhREyRub1VWaRDdQOhFWH0GapmKPxj7YAPF9XPZTmaUCEhNqAxL+539ul1H53EhZ5PXk7KZQsvKnr0n1KyFX3qNitARl+mVvNXQ2I8K4XeT5V/STBSS6rXYd/WjoV2RslcEVBkCK7KnyRCoquE1sIMApPabO2AanVIQ1I8hEBYpUigd+SIRnFrojF1NpQ1zwKK+ocFFoqoWs/Rk36DwkcvdurFHn76pUMoBxMTbQKK2g/0VrKvMo5pgMSaW+HQQQoCQBai8YrYYoAI8OTiqIck+6R3n4nTyI6OtfVtRqQ5GMrd5Qdg+/D3H58A7Is349wyetS3fy3SHOSoxMunNBAoZLCEF13GqxRSCIbP5I6td8p0ZJXR4dsQOLPOzUgRInd9ZcwRJW91XBAUtIJdVTAnV0r2gspvodXQ9h28qxchzQg/DxDAQ+FDb0GREmVcowZFR63U/JzchCtT45Ge5mhNDd1XwPy/HSGFVqSj9YQA6LrDchggbdliKMgHE+K2jBZGFFDEe2VwtQ+LB5JcxIKY8hS7o2EyTgurEOcokalqGMEMmgDIrxVQtRvQLZuliky/JIDaeyZlXglZJHUJDYpjhSdkcZR+GpAdlZdDUKGzcInjSsDcraXRTmksjHy+kwoEMMiI9L+onyahV/qOtD+HvM2ILV/gqkWzBmgKfMakDcDRH0NiKhNz0sU6iuU3ocLdcwMlUc2ILlOIe2xxwbkaUYCtwFJXI4UjqKMKnL9JYBQc5FoSBqdDl5JjtXwd9agToJ2nCZsnVQMP8MTG5Dt4wb5d+oOYOSJlNzo+kczhJK6CoSisqhLvK5FhSG1QxTm0l6iSp4cobrvTaHbgDzN4VTylefvFDk2spckH3mdM54S3tnrTmtj5r43ng7/wze1J71KSiEr6garY+73qaGDDrtfUxULDciEZyvEoI9jCMU6ip/kzZSUM4M7bIzuJSBVVlEEyK4rbMT3stTCzlFZDUgOaQMy2OZjGUIMcBSbI0VVNjstkCz8RnPQuSkVpLKXBlIOoY01IG8Sss56MDmKM78qt8861yjtSaxcxhCF5us91Lei66R4qJ1xVhES2xuQnYU+AhDyusirHNlLDCK9roQRCm8Ushwb0H6dufBFOZqMirWzxVi0fgNygEoDwv+mgpx6EzGubr9TeKNi7GqGKGxb90BsJ4FAoXqjshwUSV6S8iDlRDFZMaKaQ5S5GhD4yptixN8KyB+jmz5hyZGLAwAAAABJRU5ErkJggg==">
</div>
</div>
</div>
<button id="download">下载图片</button>
<div id="cs" style="border: 1px solid #000;margin-top: 20px;"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/html-to-image/1.11.13/html-to-image.min.js"></script>
</script>
<script>
$(document).ready(function () {
// 核心代码
htmlToImage.toPng(document.querySelector("#posterCapture")).then(function (dataUrl) {
// 将生成Base64添加到页面
var img = new Image();
img.src = dataUrl;
$("#cs").append(img);
// 点击下载按钮,下载png图片
$("#download").click(function () {
var link = document.createElement('a');
link.download = '海报图片.png';
link.href = dataUrl;
link.click();
});
});
});
</script>
</body>
</html>
效果图
总结
从挣扎于html2canvas的诡异Bug,到用html-to-image优雅实现需求,这次替换让我深刻体会到:技术选型没有绝对最优,但持续维护+稳定表现始终是首要考量。当然无论html2canvas还是html-to-image,适合自己才是最完美的替代。如果发现在使用html2canvas过程中,出现很多问题,不妨用html-to-image替代html2canvas,除非项目必须兼容 IE 浏览器。
暂无评论