jpkleemans / vite-svg-loader

Vite plugin to load SVG files as Vue components
MIT License
554 stars 58 forks source link

Is it possible to change defined ids for every time an svg is rendered? #98

Closed jd1378 closed 1 year ago

jd1378 commented 1 year ago

right now when loading a svg, the first time its loaded the gradients defined in it works fine but when you load the same svg for the second time, it no longer renders correctly, as ids clash I was wondering if we could prefix an incremental number to the svg ids used inside it every time its rendered

gkatsanos commented 1 year ago

@jd1378 we'll need a minimal reproduction link on https://stackblitz.com/ to help you out!

gkatsanos commented 1 year ago

@jd1378 solution to your problem: https://github.com/jpkleemans/vite-svg-loader/issues/94#issuecomment-1518917057 more info: https://github.com/svg/svgo/issues/674#issuecomment-1353701782

jd1378 commented 1 year ago

@gkatsanos That, definitely is not the solution It just solves the issue between different svgs Not between the same svg being repeated multiple times

Anyway i solved this by extracting and injecting my defs to top of body when my svg is being loaded and skiped svgo for it

I don't think this is solvable easily (at the level of this library)

DirectorRen-TV commented 1 year ago

I also encountered this problem when using nuxt-svg-loader (one hell of a problem, it's the same). There's one suggestion to solve this within a Vue instance: find and dynamically link all the id's within a component, add an increment to them, and store the global value in $root. But this solution leads to the next step of the problem - the possibility of having more than one application instance on the same page: they will have independent incrementers, potentially causing overlap.

jd1378 commented 1 year ago

well you can do what I did without any problem:

  1. optimize your svg manually, using the svgo or any website like vecta nano
  2. manually rename all ids to something unique
  3. remove defs from svg file (or anything that has id="" attribute)
  4. when your component is mounting (in vue.js onBeforeMount for example), check if any of ids you had in the defs exist in the dom (using document.getElementById), and if it doesn't inject it to the top of body (by creating an svg element and setting it's innerHTML to the defs). you have to inject it to the top or otherwise it won't work. this way you only inject it once. and only inject it when needed.
  5. apply position absolute, width 0 and height 0 for not affecting your page flow

here is an example:

onBeforeMount(() => {
  if (!document.getElementById('one-of-your-defs-ids')) {
    const fillDef = document.createElementNS(
      'http://www.w3.org/2000/svg',
      'svg',
    );
    fillDef.style.position = 'absolute';
    fillDef.style.width = '0';
    fillDef.style.height = '0';
    fillDef.innerHTML =
      '<defs><linearGradient id="one-of-your-defs-ids" x1="11.4857" y1="18.2663" x2="9.07356" y2="5.37942" gradientUnits="userSpaceOnUse"><stop stop-color="#f5953d"/><stop offset=".236" stop-color="#eb493a"/><stop offset="1" stop-color="#ffe82f"/></linearGradient></defs>';
    document.body.prepend(fillDef);
  }
});

I hope this does make it clear for anyone who finds this problem later :)