首页 > 解决方案 > 如何在动态 SVG 中嵌入多个静态 SVG?

问题描述

我有多个通过 Sketch 制作的 SVG。直到现在,我都亲自挑选了一个来签署我在 GitHub 上的项目的自述文件。但我刚刚找到了维基百科的动态 SVG 之一,制作这样的作品集会很有趣。所以我想要一个像这个一样的动态 SVG 默认显示一个静态 SVG 和其他,因为鼠标光标悬停在它上面。

通过对维基百科的逆向工程,我确实成功地让它展示了我的。但是,这会腐蚀它们;它们看起来一点也不好看,可能是因为它们的复杂性以及与文档的其余部分缺乏“隔离”。起初,我怀疑使用 iframe 或 shadow DOM,但我是一名软件工程师,而不是一名 Web 开发人员,而且我的前端并不出色。然而,很明显,维基百科确实使用了影子 DOM。不过,这有关系吗?

我确实在网上搜索,希望是正确的,但徒劳无功。我挖掘的最接近的帮助是这个 StackOverflow 页面

那么你如何实现这一目标呢?

标签: htmlsvgdynamicstatic

解决方案


SVG 的特点是它只是 XML。您可以将 SVG 加载到 XML 解析器中并获取所需的部分,然后将它们添加到画布或以其他方式独立操作它们。

我只是为我自己当前的项目做了这个。该图像是一个简单的国际象棋套装,但我可以将整个国际象棋套装放在一个文件中,包括白色和黑色棋子,然后单独解析它们,这样我就可以调整它们的大小甚至更改它们的颜色。这个 JavaScript 可以使用一些重构,但它应该让您对需要做什么有一个很好的了解。

loadPieces("images/SimpleStaunton.svg");

loadPieces(fileName) // the file name is a relative path 
{
    var request = new XMLHttpRequest();

    request.open("GET", fileName);
    request.setRequestHeader("Content-Type", "image/svg+xml");
    var me = this;
    
    request.addEventListener("load", function(event) {
        var response = event.target.responseText;
        var doc = new DOMParser();
        var xml = doc.parseFromString(response, "image/svg+xml");
        
        me.parseChessSets(xml.children[0]);
    });
    request.send();
}

parseChessSets(xml)
{
    this.baseSet = new ChessSet(xml);
}

为了使下一部分工作,我进入 InkScape 并命名每个单独的对象,这样我就可以确切地知道我从 XML 中提取了 SVG 的哪一部分。(我包括了一个朝左的骑士和一个朝右的骑士,这就是为什么有两个骑士的原因。)因为它们是 SVG,所以你可以随心所欲地给它们上色,所以我只是拉出路径。您可能想做的还不止这些。一旦您将 XML 转换为 .xml 文件object,您将能够更轻松地了解如何做到这一点。

棘手的部分是whitePiece.getAttribute("d"),或者更确切地说xml.children[0].getAttribute("d"),是 SVG 的路径,这一点都不明显。

class ChessSet
{
    white = new ChessPieces();
    black = new ChessPieces("#fff", "#000");
    pieceNames = ["king", "queen", "bishop", "knightl", "knightr", "rook", "pawn"];

    constructor (xml)
    {
        for (var i = 0; i < this.pieceNames.length; i++)
        {
            var whitePiece = xml.children["white" + this.pieceNames[i]];

            if (!whitePiece)
            {
                continue;
            }

            var blackPiece = xml.children["black" + this.pieceNames[i]] || whitePiece;
            
            this.white[this.pieceNames[i]] = new SvgImage(whitePiece.getAttribute("d"), whitePiece.style);
            this.black[this.pieceNames[i]] = new SvgImage(blackPiece.getAttribute("d"), blackPiece.style);
    }
}

class ChessPieces
{
    scalePercent = 100; // percentage to shrink king to 90% height of a square
    strokeColor;
    fillColor;
    
    king;
    queen;
    bishop;
    knightr;
    knightl;
    rook;
    pawn;
    
    constructor(stroke = "#000", fill = "#fff")
    {
        this.strokeColor = stroke;
        this.fillColor = fill;
    }
}
class SvgImage
{
    transform = {left: 0, down: 0};
    pathElement = null;
    name = null;
    scaleFactor = 1;
    strokeWidth = 1;
    
    constructor(path, style)
    {
        this.path = path;
        this.style = style;
    }
}

然后我可以绘制单独的路径:

var newElement = this.drawPath(set[this.baseSet.pieceNames[i]].name, set[this.baseSet.pieceNames[i]].path, 1, set.strokeColor, set.fillColor);
this.context.appendChild(newElement);

drawPath(id, textPath, strokeWidth = 1, strokeColor = "#000", fill = "none", opacity = 1)
{
    var newpath = document.createElementNS(this.svgns, "path");  
    
    newpath.setAttributeNS(null, "id", id);  
    newpath.setAttributeNS(null, "d", textPath);  
    newpath.setAttributeNS(null, "stroke", strokeColor);  
    newpath.setAttributeNS(null, "stroke-width", strokeWidth);  
    newpath.setAttributeNS(null, "opacity", opacity);  
    newpath.setAttributeNS(null, "fill", fill);
    
    return newpath;
}

变量this.context是我正在使用的画布 DOM。

现在我可以使用 CSS 变换和 object/variable 来调整片段的大小piece.scaleFactor。我将piece作为我们之前解析的单个 SVG 发送到SVGImage课程中。我发送到的 X 和 Y 坐标Move是像素。

transformPiece(piece, left, down)
{
    if (piece)
    {
        piece.transform.left = left;
        piece.transform.down = down;
        piece.pathElement.style.transform = "scale(" + piece.scaleFactor + ") translate(" + piece.transform.left + "px, " + piece.transform.down + "px)";
    }
}

movePiece(piece, x, y)
{
    if (piece)
    {
        var left = x / piece.scaleFactor;
        var down = y / piece.scaleFactor;
        
        this.transformPiece(piece, left, down);
    }
}

需要注意的是,您需要适应原始图像中 SVG 的偏移量。我最终做的是重写 SVG 路径本身的起始坐标。部分需要注意的是,您必须先将图像绘制到画布上,然后才能弄清楚偏移量是多少,因此您最终必须删除原始图像并将新图像添加到画布中,然后才能调用movePieceortransformPiece和让它在你期望的地方结束。

fixPath(piece, strokeColor, fillColor)
{
    if (piece)
    {
        var box = piece.pathElement.getBoundingClientRect()
        var fragments = piece.path.split(" ");
        var coords = fragments[1].split(",");

        var x = (coords[0] * 1) - box.x - window.scrollX;
        var y = (coords[1] * 1) - box.y - window.scrollY;

        fragments[1] = x + "," + y;
        piece.path = fragments.join(" ");
        
        this.context.removeChild(piece.pathElement);
        piece.pathElement = this.drawPath(piece.name, piece.path, 1, strokeColor, fillColor);
        
        this.context.appendChild(piece.pathElement);
    }
}

我只花了一两个星期就弄清楚了这一切,以及所有错误的方法。;-)

抱歉,这有点复杂。我把它简化了一点,但我也留下了一堆object你不关心的符号和属性。我相信您可以根据您的需要大大简化它。大多数情况下,我只是从我的项目中复制并粘贴它,这样我就知道代码正在工作。


推荐阅读