使用phantomJS将html转为图片

  • 问题描述

近期写稿项目碰到一个问题,由于文章会发布到不同设备和平台上,在前端展示的时候可能会与平台本身的样式发生覆盖,导致表格样式显示不正常。短时间内想要做出一个适应所有环境的前端样式不太现实。因为使用本地模板生成的表格不存在样式问题,所以考虑将本地html模板中的<table></table>标签内容转换为图片并在原位置替换。

  • 解决思路

使用phantomJs模拟浏览器访问html模板,用选择器截取dom节点,获取<table>标签并把表格放入新建的canvas画布

  • 实施步骤

1.安装phantomjs并配置环境变量

2.使用cmd命令执行phantomjs脚本,使用phantom访问浏览器的脚本

import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * html dom转图片
 *
 * @author Hongyi Zheng
 * @date 2018/7/31
 */
@Component("dom2ImageService")
public class Dom2ImageService {

    @Value("${server.port}")
    private String port;

    @Value("${server.context-path}")
    private String contextPath;

    private static final Logger logger = LoggerFactory.getLogger(Dom2ImageService.class);

    private final ArticleBean articleBean;

    @Autowired
    FileUploadService fileUploadService;

    @Autowired
    PageContentService pageContentService;

    @Autowired
    ArticleTraceService articleTraceService;

    @Autowired
    public Dom2ImageService(ArticleBean articleBean) {
        this.articleBean = articleBean;
    }

    public void convert2Img(String traceId,String tblName,String tblContent) throws IOException {
        logger.info("[" + traceId + "]表格{}转换图片中...", tblName);

        //拼接phantom命令行/参数
        StringBuilder phantomCmd = new StringBuilder();
        String osName = System.getProperties().getProperty("os.name").toLowerCase();
        String tmpPath = articleBean.getD2ImgTemplatePath();
        String htmlName = traceId + tblName + ".html";
        String imgName = traceId + tblName + ".png";
        List<ArticleTrace> list = articleTraceService.selectByTraceId(traceId);
        String tmpName = "";
        if (null != list && list.size() > 0) {
            tmpName = list.get(0).getTempName();
        }
        String date = DateUtils.format(new Date(), DateUtils.STYLE_yyyyMMdd);
        String lxPath = String.format("/opt/app/applications/xxxxx/temp/%s/%s/%s/%s", date, tmpName, "tmp", htmlName);
        String winPath = String.format("%s%s\\%s\\%s\\%s", articleBean.getArticleLocalPath(), date, tmpName, "tmp", htmlName);
        String target;
        String dest;
        if (osName.contains("linux")) {
            target = lxPath;
            dest = String.format("opt/app/applications/xxxxx/temp/%s/%s/%s", date, tmpName, imgName);
            phantomCmd.append(articleBean.getPhantomjsPath())
                    .append(" ")
                    .append(articleBean.getD2ImgJsPath())
                    .append(" http://localhost:").append(port).append(contextPath).append(tmpPath)
                    .append(" ")
                    .append(" \"")
                    .append(TableUtils.strTrans(tblContent, true))
                    .append("\" ")
                    .append(target);
        }else {
            target = winPath;
            dest = String.format("%s%s\\%s\\%s\\%s", articleBean.getArticleLocalPath(), date, tmpName, "tmp", imgName);
            phantomCmd.append(articleBean.getPhantomjsPath())
                    .append(" ")
                    .append(articleBean.getD2ImgJsPath())
                    .append(" http://localhost:").append(port).append(contextPath).append(tmpPath)
                    .append(" \"")
                    .append(TableUtils.strTrans(tblContent, false))
                    .append("\" ")
                    .append(target);
        }

        Process process = TableUtils.executeTbl(phantomCmd.toString());
        String info = ExecuteUtils.getInputInfo(process);
        logger.info("phantomjs log = {}",info);
        if (null != process) {
            try {
                process.waitFor();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //wkhtml将html转成image
        StringBuilder wkCmd = new StringBuilder();
        if (osName.contains("linux")) {
         
            wkCmd.append("wkhtmltoimage --encoding utf8 ").append(target).append(" ").append(dest);
        }else {
            wkCmd.append("wkhtmltoimage ").append(target).append(" ").append(dest);
        }
        Process p = TableUtils.executeTbl(wkCmd.toString());
        if (null != p) {
            try {
                p.waitFor();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        logger.info("[" + traceId + "]" + "{}渲染完毕", tblName);
        PageContent tbl = new pageContent();
        tbl.setTraceId(traceId);
        tbl.setContentKey(tblName);
        String img = ImageUtils.toBase64Str(dest);
        //替换base64头
        if (img.contains(Constants.BASE64_HEADER_JPG)) {
            img = img.substring(img.indexOf(Constants.BASE64_HEADER_JPG) + Constants.BASE64_HEADER_JPG.length());
        } else if (img.contains(Constants.BASE64_HEADER_PNG)) {
            img = img.substring(img.indexOf(Constants.BASE64_HEADER_PNG) + Constants.BASE64_HEADER_PNG.length());
        }
        //压缩图片
        ImageUtils.compress(dest);
        
        //img 标签图片src
        String contentValue = "<img src = \"" + src + "\" alt = \"\"/>";
        List<PageContent> pageContents = pageContentService.selectByTraceAndKey(tbl);
        if (null != pageContents && pageContents.size() > 0) {
            tbl = pageContents.get(0);
            tbl.setIsDel(Constants.IS_DEL_NORMAL);
            tbl.setOutime(new Date());
            tbl.setContentValue(contentValue);   
            //落库
            pageContentService.updateSelective(tbl);
        } else {
            tbl.setContentValue(contentValue);
            tbl.setContentType(Constants.TYPE_TBL); 
            pageContentService.insert(tbl);
        }


    }

}



import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;


/**
 * @author Hongyi Zheng
 * @date 2018/7/30
 */
public class TableUtils {

    private static final Logger logger = LoggerFactory.getLogger(TableUtils.class);

    public static Process executeTbl(String cmd){

        logger.info("命令行执行:"+cmd);

        Runtime rt = Runtime.getRuntime();
        try {
            return rt.exec(cmd);
        } catch (IOException e) {
            logger.error("phantomjs IO异常,cmd = {}", cmd);
            return null;
        }


    }

    /**
     * 命令行字符串转义
     * @param str 初始字符串
     * @return
     */
    public static String strTrans(String str,boolean isLinux){
        if (isLinux) {
            return str.replaceAll("<","\\<");
        }else {
            return str.replace("\"", "\\\"");
        }
    }

}


import net.coobird.thumbnailator.Thumbnails;
import sun.misc.BASE64Encoder;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

public class ImageUtils {

    //压缩图片
    public static void compress(String path){
        try {
            Thumbnails.of(path).scale(1).outputQuality(1).toFile(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String toBase64Str(String imgPath){
        InputStream in = null;
        byte[] data = null;
        // 读取图片字节数组
        try {
            in = new FileInputStream(imgPath);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 对字节数组Base64编码
        BASE64Encoder encoder = new BASE64Encoder();
        // 返回Base64编码过的字节数组字符串
        return encoder.encode(data);
    }

}



phantomJS脚本如下:

//phantomJS脚本
var page = require('webpage').create(),
    system = require('system'),
    url,
    tblContent,
    dest;

if (system.args.length === 0) {
    console.log('phantom : 参数错误!');
    phantom.exit();
} else if (system.args.length === 1) {
    console.log('phantom : url未指定!');
    phantom.exit();
} else {
    start = Date.now();
    url = system.args[1];
    tblContent = system.args[2];
    dest = system.args[3];
    var fs = require("fs");

    //输出访问webpage页面的log
    page.onConsoleMessage = function (msg) {
        console.log(msg);
    };

    //viewportSize being the actual size of the headless browser
    page.viewportSize = {width: 1024, height: 768};

    page.open(url, function (status) {
        if (status === 'success') {
            //页面打开成功则调用appendDiv()函数
            var tbl = page.evaluate(function(tblContent){
                appendDiv(tblContent);
                return document.getElementById('table').getBoundingClientRect();
            },tblContent);
            console.log('phantom : 页面加载成功,用时:' + (Date.now() - start) + 'ms');

            try {
                fs.write(dest, page.content, 'w');
                console.log('phantom : 本地文件写入成功' + dest);
            } catch (e) {
                console.error(e);
            }

        } else {
            console.log('phantom : 页面加载失败,status:' + status);
        }
        //exit phantomJs in 1 secs
        setTimeout(function () {
            phantom.exit();
        }, 1000);
    });
}

3.phantomjs访问html模板

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script type="text/javascript" src="../../js/jquery-1.12.0.min.js"></script>
    <script type="text/javascript" src="../../js/promise-6.1.0.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0
        }

        #table {
            width: 50%;
            margin: 0 auto;
        }

        table {
            border-collapse: collapse;
            border-spacing: 0;
            text-align: center;
            width: 100%;
            font-size: 14px;
        }

        table tr th {
            background-color: #ffc599;
        }

        table tr th, table tr td {
            border: 1px solid #ddd;
            padding: 5px;
            text-align: center;
            -webkit-text-size-adjust: none;
            word-break: break-all;
        }

        /*table tr td:first-child {
            font-weight: 500;
            background-color: #eef5fc;
        }*/
    </style>
</head>
<body>
<div id="table"></div>

<script type="text/javascript">
    function appendDiv(tblContent) {
        var ele = document.getElementById('table');
        ele.innerHTML = tblContent;
        console.log('div填充完毕');
    }
</script>
</body>
</html>

4.主要使用的工具:

phantomjs:http://phantomjs.org/

基于QtWebKit内核的无头浏览器,可以完成模拟浏览器行为,后台操作页面。常用于dom操作,CSS选择器,web测试,爬虫 

可选的图片渲染工具wkhtml2image/html2canvas

5.在html页面使用html2canvas对表格渲染成图片后,需要使用ajax回调后端接口,注意可能导致跨域问题

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script type="text/javascript" src="../../js/jquery-1.12.0.min.js"></script>
    <script type="text/javascript" src="../../js/html2canvas.js"></script>
    <script type="text/javascript" src="../../js/canvas2image.js"></script>
    <script type="text/javascript" src="../../js/promise-6.1.0.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0
        }

        #table {
            width: 780px;
            margin: 0 auto;
        }

        table {
            border-collapse: collapse;
            border-spacing: 0;
            text-align: center;
            width: 100%;
            font-size: 14px;
        }

        table tr th {
            background-color: #ffc599;
        }

        table tr th, table tr td {
            border: 1px solid #ddd;
            padding: 5px;
            text-align: center;
            -webkit-text-size-adjust: none;
            word-break: break-all;
        }

        table tr td:first-child {
            font-weight: 500;
            background-color: #eef5fc;
        }
    </style>
</head>
<body>
<div id="table"></div>

<script type="text/javascript">
    function convert2img(tblContent, traceId, tblName) {

        var cntElem = document.getElementById('table');
        cntElem.innerHTML = tblContent;
        setTimeout(function () {

            //需要截图的包裹的(原生的)DOM 对象
            var shareContent = cntElem;
            //获取dom 宽度
            var width = shareContent.offsetWidth;
            //获取dom 高度
            var height = shareContent.offsetHeight;
            //创建一个canvas节点
            var canvas = document.createElement("canvas");
            //定义任意放大倍数 支持小数
            var scale = 8;
            //定义canvas 宽度 * 缩放
            canvas.style.width = width + 'px';
            canvas.width = width * scale;
            //定义canvas高度 *缩放
            canvas.style.height = height + 'px';
            canvas.height = height * scale;
            //获取context,设置scale
            canvas.getContext("2d").scale(scale, scale);
            var opts = {
                // 添加的scale 参数
                scale: scale,
                //自定义 canvas
                canvas: canvas,
                //日志开关,便于查看html2canvas的内部执行流程
                // logging: true,
                //dom 原始宽度
                width: width,
                height: height,
                // 使用cross-Domain开启跨域配置
                useCORS: true
            };

            html2canvas(shareContent, opts).then(function (canvas) {

                var context = canvas.getContext('2d');
                //禁用图片平滑处理
                context.mozImageSmoothingEnabled = false;
                context.webkitImageSmoothingEnabled = false;
                context.msImageSmoothingEnabled = false;
                context.imageSmoothingEnabled = false;

                //将图片转为JPEG格式并设置canvas画布的宽高(可选BMP/PNG等格式)
                var img = Canvas2Image.convertToJPEG(canvas, canvas.width, canvas.height);

                cntElem.appendChild(img);

                /*$(img).css({
                    "width": canvas.width / 4 + "px",
                    "height": canvas.height / 4 + "px"
                }).addClass('f-full');*/

                var base64img = $('img').attr('src');

                $.ajax({
                    async: false,
                    type: 'POST',
                    url: '/xxxxx/canvas/tbl2img',
                    data: {
                        img: base64img, traceId: traceId, tblId: tblName
                    },
                    success: function () {
                        console.log("回调成功!")
                    },
                    error: function () {
                        console.log('回调失败!');
                    }
                });

            });

        }, 1000);
    }
</script>
</body>
</html>

好了,这样就完成了把html表格标签转为图片保存。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JaxonHowie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值