HTML SVG reutiliza un grupo <g> con <use> y cambia los atributos de los elementos internos individualmente para cada instancia

Aug 21 2020

Así que me gustaría reutilizar una forma svg agrupada y cambiar un atributo de uno de los elementos dentro del grupo individualmente para cada instancia. El siguiente ejemplo simplificado crea un segundo círculo con un rectángulo dentro. Ahora quiero cambiar el atributo de "ancho" del rectángulo "my-rect" individualmente para cada una de las formas con javascript. Usar la identificación "my-rect" cambiará el ancho de ambos rectángulos, pero quiero cambiar solo uno.

Mi objetivo (si mi enfoque no tiene sentido): tengo que dibujar varias de estas formas y lo único que difiere es la posición y el ancho del rectángulo.

<svg height="1000" width="1000">
  <a transform="translate(110,110)">
    <g id="my-group">
      <g>
        <circle r="100" fill="#0000BF" stroke="black" stroke-width="2" fill-opacity="0.8"></circle>
      </g>
      <g>
        <rect id="my-rect" y="-50" height="100" x="-50" width="50">
        </rect>
      </g>
    </g>
  </a>
  <use xlink:href="#my-group" x="340" y="110"/>
</svg>

Respuestas

4 ccprog Aug 20 2020 at 23:43

Con un poco de engaño, es posible. Debe aprovechar la herencia de CSS para obtener algún valor de propiedad dentro de un elemento de sombra. En este caso, serán las variables personalizadas las que se utilizarán para escalar y colocar el rectángulo.

El marcado tiene que ser reescrito un poco para esto. Primero, escribe su grupo dentro de un <defs>elemento, convirtiéndolo en una plantilla para reutilizar, pero no renderizado por sí mismo. En segundo lugar, el rectángulo se coloca dentro de un <svg overflow="visible">elemento anidado. Darle a este elemento las coordenadas x/y y dejarlas en 0 para el <rect>elemento hace que sea más fácil rastrear dónde terminará el lado izquierdo del rectángulo después de una operación de transformación.

Ahora el cambio de ancho del rect se logra con una scaleX()transformación más a translate()para la posición. Esto debe estar en sintaxis de transformación CSS . Usar un transformatributo no funcionaría (todavía). Por lo tanto, también necesitamos una transform-originpropiedad, establecida en el lado izquierdo del <svg>elemento envolvente.

En lugar de escribir un valor concreto para la escala, el valor se expresa como una variable con el valor predeterminado 1: var(--scale, 1); Lo mismo para los valores posicionales. El valor de la variable se establece en un styleatributo para cada <use>elemento por separado: style="--scale:2;--posX:20px; --posY:-10px". px¡Observe la necesidad de unidades de escritura !

#my-rect {
    transform-origin: left top;
    transform: translate(var(--posX, 0), var(--posY, 0)) scaleX(var(--scale, 1));
}
<svg height="1000" width="1000">
  <defs>
    <g id="my-group">
      <g>
        <circle r="100" fill="#0000BF" stroke="black" stroke-width="2" fill-opacity="0.8"></circle>
      </g>
      <svg x="-50" y="-50" overflow="visible">
        <rect id="my-rect" height="100" width="50">
        </rect>
      </svg>
    </g>
  </defs>
  <use xlink:href="#my-group" x="110" y="110" style="--scale:1"/>
  <use xlink:href="#my-group" x="340" y="110" style="--scale:2;--posX:20px; --posY:-10px"/>
</svg>

5 Danny'365CSI'Engelman Aug 21 2020 at 03:37

Sean dijo:

Si los elementos personalizados de componentes web se expanden al espacio de nombres SVG,
será posible una reutilización más compleja

Lo cual es cierto, no puedes hacer elementos SVG personalizados (todavía).

Pero puedes hacer un elemento personalizado que genere SVG:

customElements.define("rect-in-circle", class extends HTMLElement{
  connectedCallback(){
    const a = x => this.getAttribute(x);
    this.innerHTML=`<svg viewBox="-100 -100 100 100">`+
                   `<g transform="translate(-50 -50)">`+
                     `<circle r="50" fill="#123456AB"/>`+
                     `<rect y="${a("y")}" height="${a("height")}"`+
                           `x="${a("x")}"  width="${a("width" )}"/>`+
                   `</g></svg>`
  }
});
svg{
  width:100px;
  height:100px;
  background:grey;
  fill:green;
}
<rect-in-circle x=-10 y=-10 width=20 height=20></rect-in-circle>
<rect-in-circle x=-40 y=-20 width=10 height=40></rect-in-circle>
<rect-in-circle x= 10 y= 20 width=30 height= 5></rect-in-circle>

Elementos personalizados para SVG son una solución moderna para muchos trucos SVG de la vieja escuela

Actualizar

Si OP quiere un SVG con círculos, podemos usar shadowDOM y el hecho de que los elementos dentro de lightDOM no se muestran. Incluso podemos usar elementos no definidos <rect> (en el espacio de nombres HTML) para que podamos inyectarlos fácilmente en la cadena SVG.

<script>
  customElements.define("rects-in-circles", class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => {
        const rects = [...this.children];
        const width = rects.length * 100;
        this.attachShadow({
          mode: "open"
        }).innerHTML = `<svg viewBox='0 0 ${width} 100'>` + 
          rects.map((rect, idx) => `<g transform='translate(${50+100*idx} 50)'>` +
          `<circle r='50' fill='green'/>` +
          rect.outerHTML +
          `</g>`) + "</svg>";

      })
    }
  });

</script>

<rects-in-circles>
  <rect x=-10 y=-10 width=20 height=20></rect>
  <rect x=-40 y=-20 width=10 height=40></rect>
  <rect x=10 y=20 width=30 height=5></rect>
  <rect x=-40 y=-40 width=50 height=50></rect>
</rects-in-circles>

(my)Respuestas relacionadas de StackOverflow: Elementos personalizados y SVG

2 Sean Aug 20 2020 at 22:28

Esto no es posible. El useelemento crea un closed shadow root, lo que significa que JS no puede acceder a su contenido. Si bien puede establecer atributos de instancias del elemento reutilizado individualmente (si no se han establecido en el elemento original), no podrá afectar los elementos que son hijos del elemento reutilizado.

Un enfoque alternativo sería reutilizar los elementos de rectángulo y círculo directamente y colocarlos en un nuevo grupo.

AmirFo Sep 27 2020 at 17:27

El getAttributemétodo no funcionó para mí, pero esto funcionó:

customElements.define("source-link", class extends HTMLElement {
    static get observedAttributes() {
        return ['href'];
    }

    get href() {
        return this.getAttribute('href');
    }

    set href(val) {
        if (val) {
            this.setAttribute('href', val);
        } else {
            this.removeAttribute('href');
        }
    }
    connectedCallback(){
        this.innerHTML= '<a href="' + this.href + '" target="_blank" title="source"><svg fill="#37f" viewBox="0 0 512 512" width="16"><g><path d="M488.727,0H302.545c-12.853,0-23.273,10.42-23.273,23.273c0,12.853,10.42,23.273,23.273,23.273h129.997L192.999,286.09    c-9.089,9.089-9.089,23.823,0,32.912c4.543,4.544,10.499,6.816,16.455,6.816c5.956,0,11.913-2.271,16.457-6.817L465.455,79.458    v129.997c0,12.853,10.42,23.273,23.273,23.273c12.853,0,23.273-10.42,23.273-23.273V23.273C512,10.42,501.58,0,488.727,0z"/></g><g><path d="M395.636,232.727c-12.853,0-23.273,10.42-23.273,23.273v209.455H46.545V139.636H256c12.853,0,23.273-10.42,23.273-23.273    S268.853,93.091,256,93.091H23.273C10.42,93.091,0,103.511,0,116.364v372.364C0,501.58,10.42,512,23.273,512h372.364    c12.853,0,23.273-10.42,23.273-23.273V256C418.909,243.147,408.489,232.727,395.636,232.727z"/></g></svg></a>';
    }
});

uso :

<source-link href="//google.com"></source-link>

Fuente :D