HTML SVG riutilizza un gruppo <g> con <use> e modifica gli attributi degli elementi interni individualmente per ogni istanza

Aug 21 2020

Quindi mi piacerebbe riutilizzare una forma svg raggruppata e modificare un attributo di uno degli elementi all'interno del gruppo individualmente per ogni istanza. L'esempio semplificato seguente crea un secondo cerchio con un rettangolo all'interno. Ora voglio cambiare l'attributo "width" del rettangolo "my-rect" individualmente per ciascuna delle forme con javascript. L'uso dell'id "my-rect" cambierà la larghezza di entrambi i rettangoli, ma voglio cambiarne solo uno.

Il mio obiettivo (se il mio approccio non ha senso): devo disegnare più di queste forme e l'unica cosa che differisce è la posizione e la larghezza del rettangolo.

<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>

Risposte

4 ccprog Aug 20 2020 at 23:43

Con un po' di trucco, è possibile. Devi sfruttare l'ereditarietà CSS per ottenere un valore di proprietà all'interno di un elemento shadow. In questo caso, saranno le variabili personalizzate che verranno utilizzate per ridimensionare e posizionare il rettangolo.

Il markup deve essere riscritto un po' per questo. Innanzitutto, scrivi il tuo gruppo all'interno di un <defs>elemento, rendendolo un modello per il riutilizzo, ma non reso da solo. In secondo luogo, il rettangolo viene posizionato all'interno di un <svg overflow="visible">elemento nidificato. Dare a questo elemento le coordinate x/y e lasciarle a 0 per l' <rect>elemento rende più facile tracciare dove finirà il lato sinistro del rettangolo dopo un'operazione di trasformazione.

Ora il cambio di larghezza del rect si ottiene con una scaleX()trasformazione più a translate()per la posizione. Questo deve essere nella sintassi di trasformazione CSS . L'uso di un transformattributo non funzionerebbe (ancora). Pertanto, abbiamo anche bisogno di una transform-originproprietà, impostata sul lato sinistro <svg>dell'elemento contenitore.

Invece di scrivere un valore concreto per il ridimensionamento, il valore viene espresso come una variabile con il valore predefinito 1: var(--scale, 1); lo stesso per i valori posizionali. Il valore per la variabile è impostato in un styleattributo per ciascun <use>elemento separatamente: style="--scale:2;--posX:20px; --posY:-10px". Si noti la necessità di scrivere pxunità!

#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 ha detto:

Se gli elementi personalizzati di Web Components vengono espansi nello spazio dei nomi SVG,
sarà possibile un riutilizzo più complesso

Il che è vero, non puoi (ancora) creare elementi SVG personalizzati .

Ma puoi creare un elemento personalizzato che genera 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>

Gli elementi personalizzati per SVG sono una soluzione moderna a molti hack SVG oldskool

Aggiornare

Se OP vuole un SVG con cerchi, possiamo usare shadowDOM e il fatto che gli elementi all'interno di lightDOM non vengono visualizzati. Possiamo persino utilizzare elementi non definiti <rect> (nello spazio dei nomi HTML) in modo da poterli facilmente inserire nella stringa 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)Risposte StackOverflow correlate: elementi personalizzati e SVG

2 Sean Aug 20 2020 at 22:28

Questo non è possibile. L' useelemento crea un closed shadow root, il che significa che i suoi contenuti sono inaccessibili a JS. Mentre puoi impostare gli attributi delle istanze dell'elemento riutilizzato individualmente (se non sono stati impostati sull'elemento originale) non sarai in grado di influenzare gli elementi che sono figli dell'elemento riutilizzato.

Un approccio alternativo consisterebbe nel riutilizzare direttamente gli elementi rettangolo e cerchio e inserirli in un nuovo gruppo.

AmirFo Sep 27 2020 at 17:27

Il getAttributemetodo non ha funzionato per me, ma questo ha funzionato:

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>';
    }
});

Utilizzo :

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

Fonte:D