shakyShane / gulp-svg-sprites

Create SVG sprites or compile to <symbols>
MIT License
334 stars 45 forks source link

duplicate generated linearGradient id in symbol mode #108

Closed donomron closed 6 years ago

donomron commented 7 years ago

Consider two icons with linear gradient and each gradient having its own unique id. for example:

bike.svg: 
<linearGradient id="BIKE_1" ... > ... </linearGradient>
<linearGradient id="BIKE_2" ... > ... </linearGradient>
<linearGradient id="BIKE_3" ... > ... </linearGradient>
...
<path fill="url(#BIKE_1)" ... />
<path fill="url(#BIKE_2)" ... />
<path fill="url(#BIKE_3)" ... />

and second icon:

car.svg: 
<linearGradient id="CAR_1" ... > ... </linearGradient>
<linearGradient id="CAR_2" ... > ... </linearGradient>
<linearGradient id="CAR_3" ... > ... </linearGradient>
...
<path fill="url(#CAR_1)" ... />
<path fill="url(#CAR_2)" ... />
<path fill="url(#CAR_3)" ... />

When sprite.svg is generated using symbol mode, these unique ids are replaced with a,b,c , .... and since this auto generating id names is reset in each file, we might face a sprite.svg file with more than one id name corresponding to a gradient. Is there any way to stop generating these ids automatically and use the ones which are in original svg files?

kayek88 commented 6 years ago

I have the same situation with my svg sprite

soumen-basak commented 6 years ago

I am also having this same issue.

isaacalves commented 6 years ago

I have the same problem.

Plus, different SVG files might actually already have ids with the same name.

Is there a way to generate a file hash based on the original file name or something to be prefixed to each linearGradient id?

For instance, the final SVG would have a bike symbol with something like bike-a, bike-b, etc as linearGradients ids. Or just a-a, a-b, and then b-a, b-b for the next symbol, and so on

kirill-talk commented 6 years ago

I've made some code that fix this problem

...
.pipe(svgSprite({
  mode: "symbols",
  preview: {
    symbols: './preview/symbols.html'
  },
  selector: "%f",
  svg: {
    symbols: 'symbols.svg'
  },
  transformData: function (data, config) {
    data.svg.map(function(item) {
      //change id attribute
      item.data=item.data.replace(/id=\"([^\"]+)\"/gm, 'id="'+item.name+'-$1"');

      //change id in fill attribute
      item.data=item.data.replace(/fill=\"url\(\#([^\"]+)\)\"/gm, 'fill="url(#'+item.name+'-$1)"');

      //change id in mask attribute
      item.data=item.data.replace(/mask=\"url\(\#([^\"]+)\)\"/gm, 'mask="url(#'+item.name+'-$1)"');

      //replace double id for the symbol tag
      item.data=item.data.replace('id="'+item.name+'-'+item.name+'"', 'id="'+item.name+'"');
      return item;
    });
    return data; // modify the data and return it
  }
}))
...
davidwebca commented 6 years ago

I should have looked here first 🤦‍♂️ I just coded the same regexes as Krill in one of my projects because of gradients. But I've also changed the symbols.svg template to have it extract all the "defs" into a single one outside of symbols because of that issue in Firefox https://stackoverflow.com/questions/12867704/svg-linear-gradient-does-not-work-in-firefox. Came to leave it here in case someone needs it :

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0" style="position:absolute">
<defs>
    <% _.forEach(svg, function(svgItem, index) { %>
            <% var defs = svgItem.data.match(/<defs.*?\/defs>/gi) || [] %>
            <%= defs.join('').replace('<defs>', '').replace('</defs>', '') %>
    <% }); %>
        </defs>
    <% _.forEach(svg, function(svgItem) { %>
        <symbol id="<%= svgItem.name %>" viewBox="<%= svgItem.viewBox %>">
            <%= svgItem.data.replace(/<svg.*?>(.*?)<\/svg>/, "$1") %>
        </symbol>
    <% }); %>
</svg>

Btw, this seems to be a svgo problem coming from the options passed here in svg-sprite-data : https://github.com/shakyShane/svg-sprite-data/blob/master/lib/svg-sprite.js (cleanupIDs). The problem would be easily fixable if svgo was run after the combining strings / files since it already smartly manages duplicate and url referencing ids. I think changing the order of the waterfall here would do it : https://github.com/shakyShane/svg-sprite-data/blob/b73293731d3aa8866dcc50081def6629b9c741be/lib/svg-sprite.js#L378

idmistir commented 6 years ago

@kirill-talk 's answer is what worked best for us too. However, it was lacking one more replace for us, for "filter" which also got a url(#a). I'm attaching the entire transformData @kirill-talk posted, modified w/ two more lines for clarity sake.

transformData: function (data) {
                data.svg.map(function(item) {
                    //change id attribute
                    item.data=item.data.replace(/id=\"([^\"]+)\"/gm, 'id="'+item.name+'-$1"');

                    //change id in fill attribute
                    item.data=item.data.replace(/fill=\"url\(\#([^\"]+)\)\"/gm, 'fill="url(#'+item.name+'-$1)"');

                    //change id in filter attribute
                    item.data=item.data.replace(/filter=\"url\(\#([^\"]+)\)\"/gm, 'filter="url(#'+item.name+')"');

                    //change id in mask attribute
                    item.data=item.data.replace(/mask=\"url\(\#([^\"]+)\)\"/gm, 'mask="url(#'+item.name+'-$1)"');

                    //replace double id for the symbol tag
                    item.data=item.data.replace('id="'+item.name+'-'+item.name+'"', 'id="'+item.name+'"');
                    return item;
                });
                return data;
            }
donomron commented 6 years ago

@kirill-talk sorry for my late response. this solves the problem

I'm closing this issue as @kirill-talk and @idmistir solutions worked for me.