1

I am working on an interface containing many SVG artworks, created in Adobe Illustrator. The colors for these SVGs are to be customizable by the user, and all the artworks originally use the same set of colors. The idea is that once the user changes the each color, it is reflected in all the SVG artworks.

Right now, I have the functionality for this working fine. I first use LESS to generate all the shades of colors in my CSS and assign them to classes.

Like this

@color3-base: #FF0000;
@color3-shade1: mix(@color3-base, @color2-base, 90%);
@color3-shade2: mix(@color3-base, @color2-base, 80%);
@color3-shade3: mix(@color3-base, @color2-base, 70%);
@color3-shade4: mix(@color3-base, @color2-base, 60%);
@color3-shade5: mix(@color3-base, @color2-base, 50%);
@color3-shade6: mix(@color3-base, @color2-base, 40%);
@color3-shade7: mix(@color3-base, @color2-base, 30%);
@color3-shade8: mix(@color3-base, @color2-base, 20%);

and this

.color3-base-fill {fill: @color3-base;}
.color3-shade1-fill {fill: @color3-shade1;}
.color3-shade2-fill {fill: @color3-shade2;}
.color3-shade3-fill {fill: @color3-shade3;}
.color3-shade4-fill {fill: @color3-shade4;}
.color3-shade5-fill {fill: @color3-shade5;}
.color3-shade6-fill {fill: @color3-shade6;}
.color3-shade7-fill {fill: @color3-shade7;}
.color3-shade8-fill {fill: @color3-shade8;}

I use the less.js method less.modifyVars to update the value of my base colors, like the color @color3-base. Any change to each color results in all the different shades of those colors used throughout the SVG artworks. All colors are generated perfectly and there is no problem there.

I use the solution provided here to load all my SVGs inline, so that they are the part of my DOM and can take affect from all the color shade changes taking place in my LESS CSS.

Now here, in these loaded SVGs, I have manually gone in and replaced the fill statements with class statements. Like fill="#FF0000" with a class="color3-base-fill" (or the class belonging to the particular fill shade I am replacing) so that it retrieves the fill value from the class, and can always take affect from the color changes happening through LESS. Because all our artworks use the same set of colors, and now have their fill statements manually replaced with classes that correspond with those colors, this works great. A change to each color using less.modifyVars reflects cleanly on all the colors on all the artworks.

The problem is that we have a large number of SVG artwork files and I have to replace every single fill statement in the SVG manually with its corresponding shade class. This opens margin for a lot of human error, and this appears to be something that should be automated instead.

What I would ideally like is to be able to make these replacements automatically, when the SVGs are loaded. This would make any artwork we make in Adobe Illustrator instantly customizable upon loading. I tried different things to convert my SVGs to strings first so that I can replace the fill declarations to class declarations, but I haven't been successful. It seems like SVG is neither XML, nor HTML and it is hard to serialize it. My knowledge is limited at this point so any help in solving the above problem will be great!

Thank you!

Community
  • 1
  • 1
alik
  • 3,820
  • 9
  • 41
  • 55

2 Answers2

1

One solution would be to use a XSLT stylesheet. You could run it to pre-process your files before serving, configure your system to run it automatically on the server-side, or even run it on the client (depending on browser support).

SVG is 100% XML, so you can process it with XSLT.

Suppose you have a SVG like this one:

<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
    <circle cx="300" cy="300" r="200" fill="#FF0000">
        <desc>Text</desc>
    </circle>
    <rect rx="10" ry="10" height="150" width="200" x="10" y="10" fill="#0000FF" />
</svg>

If you load it into a XSL processor with the XSLT 1.0 stylesheet below, it will look for all the fill attributes in all elements, compare its contents (I assumed #FF0000 as color3-base-fill and #0000FF as color4-base-fill), remove the fill attribute and replace it with a corresponding class.

<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node() | @*" />
    </xsl:copy>
</xsl:template>

<xsl:template match="*[@fill]">
    <xsl:choose>
        <xsl:when test=".[@fill='#FF0000']">
            <xsl:copy>
                <xsl:attribute name="class">
                    <xsl:text>color3-base-fill</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="node()|@*[not(name() = 'fill')]"/>
            </xsl:copy>
        </xsl:when>
        <xsl:when test=".[@fill='#0000FF']">
            <xsl:copy>
                <xsl:attribute name="class">
                    <xsl:text>color4-base-fill</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="node()|@*[not(name() = 'fill')]"/>
            </xsl:copy>
        </xsl:when>
        <xsl:otherwise>
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" />
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>

</xsl:template>

It could be improved, of course. You could have your table of color/class relationships in a separate file, and load it so you don't have to hardwire the results like I did above.

It's much more efficient if you compile the stylesheet and do the transformation before serving or caching in server-side. That way you can also use XSLT 2.0 which is more powerful. But if you want to load it in a browser, you can include this processing instruction in the SVG, and when you load the SVG it will be transformed on the fly:

<?xml-stylesheet type="text/xsl" href="the-stylesheet.xsl"?>

This is the SVG transformed by the stylesheet above:

<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="100%" height="100%" version="1.1">
    <circle class="color3-base-fill" cx="300" cy="300" r="200">
        <desc>Text</desc>
    </circle>
    <rect class="color4-base-fill" rx="10" ry="10" height="150" width="200" x="10" y="10"/>
</svg>
helderdarocha
  • 23,209
  • 4
  • 50
  • 65
  • This appears to be the right solution but I wasn't able to make it work properly. Since the tool I am working on is currently just all client end, the XSLT transformation was not working for me. I am sure once a server is involved, this could work nicely. – alik Mar 11 '14 at 00:15
1

I managed to solve this problem by first loading all my SVGs in to my DOM, and simply using jQuery to remove the fill attributes and replace them with the right corresponding class attributes.

function applyClassesOverSVGColors(svg, classGroupName, colors){
        for (var i in colors) {
            //Customize Fills
            $("#" + svg.attr("id") + ' [fill="#'+ colors[i] +'"]').each(function(){$(this).removeAttr('fill').addClass($(this).attr("class")+" "+classGroupName+"-fill")});
            //Customize Strokes
            $("#" + svg.attr("id") + ' [stroke="#'+ colors[i] +'"]').each(function(){$(this).removeAttr('stroke').addClass($(this).attr("class")+" "+classGroupName+"-stroke")});  
        };
    }

Above, colors contains all the colors that need to be represented using a class instead of their in-line fill or stroke declarations.

applyClassesOverSVGColors(svg,"color1-base",["50B380","4FB280","4DB180"]);
alik
  • 3,820
  • 9
  • 41
  • 55