SVG: 将 svg 导出成图片

svg 导出成图片会有很多限制, 外链的图片, 样式均会丢失,推荐使用 saveSvgAsPng.js 进行导出

以下是借鉴库写的导出函数:

function reEncode(data) {
    return decodeURIComponent(
        encodeURIComponent(data).replace(/%([0-9A-F]{2})/g, (match, p1) => {
            const c = String.fromCharCode(`0x${p1}`);
            return c === '%' ? '%25' : c;
        })
    )
}


function export2Base64Img(svgDom, MIMEType, option) {
    var serializer = new XMLSerializer();
    var source = serializer.serializeToString(svgDom);
    // 方式一: unescape(encodeURIComponent(txt))
    // var path = "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(source)));
    // 方式二: decodeURIComponent(encodeURIComponent(txt))
    var path = "data:image/svg+xml;base64," + window.btoa(reEncode(source));
    var canvas = document.createElement("canvas"),
        context = canvas.getContext("2d"),
        img = new Image(),
        pixelRatio = window.devicePixelRatio || 1,
        _exportPath, handler
    option = option || {};

    canvas.width = parseFloat(svgDom.getAttribute('width')); //  * pixelRatio
    canvas.height = parseFloat(svgDom.getAttribute('height')); //  * pixelRatio 
    img.src = path;
    img.onload = function () {
        // 增加底色
        if (option.background) {
            context.beginPath();
            context.rect(0, 0, canvas.width, canvas.height);
            context.fillStyle = option.background;
            context.fill();
            context.closePath();
        }
        //
        context.drawImage(img, 0, 0);

        var marker = option.watermark || "";

        if (marker) {
            context.font = "18px 微软雅黑";
            context.fillStyle = "rgba(12, 0, 70, 0.5)";

            var textWidth = context.measureText(marker).width,
                textHegith = 50,
                pk = 1.2,
                rotate = (option.rotation || -45) * Math.PI / 180,
                sinReg = Math.sin(rotate),
                cosReg = Math.cos(rotate),
                width = Math.abs(canvas.width * cosReg) + Math.abs(canvas.height * sinReg),
                height = Math.abs(canvas.height * cosReg) + Math.abs(canvas.width * sinReg);

                var xf = Math.ceil(width / textWidth * pk);
                var yf = Math.ceil(height / textHegith);

                context.rotate(rotate);

                for (var i = 0; i < yf; i++) {
                    for (var k = 0; k < xf; k++) {
                        context.fillText(marker, textWidth * k * pk - canvas.height * cosReg, textHegith * i)
                    }
                }
            }


        document.body.appendChild(canvas);
        _exportPath = canvas.toDataURL(MIMEType || 'image/png', 1)
        typeof handler === 'function' && handler(_exportPath)
        document.body.removeChild(canvas)
    }

    return new Promise(function (resolve, reject) {
        handler = resolve
    })
}
View Code

一个小例子;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        * {
            /* margin: 0; */
            padding: 0;
        }

        body {
            background: #f3f3f3;
        }

        svg .pinkCircle {
            fill: rgba(145, 92, 101, 0.671);
            stroke: #f60;
            stroke-width: 5;
        }

        svg .blackRect {
            fill: black;
            stroke: #ccc;
            stroke-width: 2;
        }
    </style>
</head>

<body>
    <p>Svg Start</p>
    <svg width="800" height="600" id="svg">
        <!-- 圆形: cx, cy, r -->
        <circle cx="80" cy="40" r="15" class="pinkCircle"></circle>
        <circle cx="180" cy="40" r="15" class="pinkCircle"></circle>
        <circle cx="100" cy="100" r="25" class="pinkCircle"></circle>
        <!-- fill="pink" stroke="#f60" stroke-width="5" 也可以直接设置属性的方式 -->

        <!-- 矩形: x, y, rx, ry -->
        <rect x="80" y="50" class="blackRect" width="60" height="42" rx="8" ry="20"></rect>
        <!-- 通过 class 定义的样式只能定义填充和边框, 无法定义 svg内几何图形的大小或位置 -->
        <!-- rect 标签的 坐标位置用 x, y来表示, 而且额外可以定义 rx, ry; 表示圆角在 水平/垂直方向上的曲率半径 -->

        <!-- 椭圆 -->
        <ellipse cx="120" cy="120" rx="60" ry="20"></ellipse>
        <!-- cx: 椭圆中心点的水平位置
             cy: 椭圆中心点的垂直位置
             rx: 椭圆水平方向上的半径
             ry: 椭圆垂直方向上的半径
            ???? 椭圆什么时候会有中心点? 而且椭圆是有两个中心点
            椭圆的绘制方法:
            https://jingyan.baidu.com/article/4ae03de30f5bfc3efe9e6b7f.html -->

        <!-- 多边形 -->
        <polygon points="300,300 350,350 420,300"></polygon>
        <!-- points 一系列的 x 和 y 坐标点, 描述了多边形各个角的位置
            多边形是一个封闭的形状, 如果制定了几个点, 那么这几个点自动会与第一个点相连 -->

        <!-- 线 -->
        <line x1="0" y1="0" x2="700" y2="700" stroke="black" stoke-width="2"></line>
        <!-- x1: 起点的水平位置
            y1: 起点的垂直位置
            x2: 终点的水平位置
            y2: 终点的垂直位置 -->

        <!-- 路径 -->
        <!-- d: 告知浏览器如何绘图 -->
        <!-- M: moveto 的缩写, 目标是一个假想的点 
            q 告诉光标要绘制一条贝塞尔曲线, 什么是贝塞尔曲线:? 可以从B站中去搜索查看, 一段几十分钟的讲解, 很复杂, 到处都在用的贝塞尔曲线 -->
        <path d="M 100 500 L 300 500 L 200 300 z" fill="orange" stroke="black" stroke-width="3" />

        <!-- 文本 -->
        <text x="120" y="520" font-size="30" fill="blue" stroke="#ccc" stroke-width="2" font-family="Comic Sans MS"
            text-anchor="start" opacity="0.5">hello Svg

            <a xlink:href="http://www.baidu.com" target="_blank">这是一个链接</a>
        </text>
        <!-- svg 的基本文本属性与 HTML 同名, font-size, font-family, font-weight, font-style
        除了颜色除外, 这些都是。 svg文本和图形一样, 颜色都叫"填充", 同样, 也可以在文本周围添加 "笔触" -->
        <!-- x 和 y 属性指定了文本锚点的位置, 默认情况下, 锚点标识了文本的开始并与文本基线垂直对齐
            设置 text-anchor 可以改变锚点的位置, 值有 start, center, end
            svg 没有办法简单绘制一个文本块并让它们自动换行
        -->

        <!--  svg 绘图中, 还可以使用 opacity 设置透明度等;  -->
    </svg>

    <script>
        function reEncode(data) {
            return decodeURIComponent(
                encodeURIComponent(data).replace(/%([0-9A-F]{2})/g, (match, p1) => {
                    const c = String.fromCharCode(`0x${p1}`);
                    return c === '%' ? '%25' : c;
                })
            )
        }

        function export2Base64Img(svgDom, MIMEType, option) {
            var serializer = new XMLSerializer();
            var source = serializer.serializeToString(svgDom);
            // 方式一: unescape(encodeURIComponent(txt))
            // var path = "data:image/svg+xml;base64," + window.btoa(unescape(encodeURIComponent(source)));
            // 方式二: decodeURIComponent(encodeURIComponent(txt))
            var path = "data:image/svg+xml;base64," + window.btoa(reEncode(source));
            var canvas = document.createElement("canvas"),
                context = canvas.getContext("2d"),
                img = new Image(),
                pixelRatio = window.devicePixelRatio || 1,
                _exportPath, handler
            option = option || {};

            canvas.width = parseFloat(svgDom.getAttribute('width')); //  * pixelRatio
            canvas.height = parseFloat(svgDom.getAttribute('height')); //  * pixelRatio 
            img.src = path;
            img.onload = function () {
                // 增加底色
                if (option.background) {
                    context.beginPath();
                    context.rect(0, 0, canvas.width, canvas.height);
                    context.fillStyle = option.background;
                    context.fill();
                    context.closePath();
                }
                //
                context.drawImage(img, 0, 0);

                var marker = option.watermark || "";

                if (marker) {
                    context.font = "18px 微软雅黑";
                    context.fillStyle = "rgba(12, 0, 70, 0.5)";

                    var textWidth = context.measureText(marker).width,
                        textHegith = 50,
                        pk = 1.2,
                        rotate = (option.rotation || -45) * Math.PI / 180,
                        sinReg = Math.sin(rotate),
                        cosReg = Math.cos(rotate),
                        width = Math.abs(canvas.width * cosReg) + Math.abs(canvas.height * sinReg),
                        height = Math.abs(canvas.height * cosReg) + Math.abs(canvas.width * sinReg);

                    var xf = Math.ceil(width / textWidth * pk);
                    var yf = Math.ceil(height / textHegith);

                    context.rotate(rotate);

                    for (var i = 0; i < yf; i++) {
                        for (var k = 0; k < xf; k++) {
                            context.fillText(marker, textWidth * k * pk - canvas.height * cosReg, textHegith * i)
                        }
                    }
                }


                document.body.appendChild(canvas);
                _exportPath = canvas.toDataURL(MIMEType || 'image/png', 1)
                typeof handler === 'function' && handler(_exportPath)
                document.body.removeChild(canvas)
            }

            return new Promise(function (resolve, reject) {
                handler = resolve
            })
        }


        var svg = document.getElementById("svg");
        svg.onclick = function () {
            var img = export2Base64Img(svg, null, {
                watermark: 'copyright reserved 2020 版权所有',
                background: '#fff'
            });
            img.then(function (base64src) {
                // console.log('路径:::', base64src)
                downLoad(base64src, '图.png')
            })
        }
        function downLoad(url, fileName) {
            var oA = document.createElement("a");
            oA.download = fileName || '';
            oA.style.display = 'none'
            oA.href = url;
            document.body.appendChild(oA);
            oA.click();
            oA.remove();
        }

    </script>
</body>

</html>
View Code
原文地址:https://www.cnblogs.com/liuyingde/p/14133116.html