HTML SVG повторно использовать группу <g> с <use> и изменять атрибуты внутренних элементов индивидуально для каждого экземпляра

Aug 21 2020

Поэтому я хотел бы повторно использовать сгруппированную фигуру svg и изменить один атрибут одного из элементов внутри группы индивидуально для каждого экземпляра. В следующем упрощенном примере создается второй круг с прямоугольником внутри. Теперь я хочу изменить атрибут «width» прямоугольника «my-rect» индивидуально для каждой формы с помощью javascript. Использование идентификатора my-rect изменит ширину обоих прямоугольников, но я хочу изменить только один.

Моя цель (если мой подход - ерунда): мне нужно нарисовать несколько таких фигур, и единственное, что отличается, - это положение и ширина прямоугольника.

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

Ответы

4 ccprog Aug 20 2020 at 23:43

С небольшими хитростями это возможно. Вы должны воспользоваться наследованием CSS, чтобы получить какое-либо значение свойства внутри теневого элемента. В этом случае для масштабирования и позиционирования прямоугольника будут использоваться пользовательские переменные.

Для этого необходимо немного переписать разметку. Сначала вы пишете свою группу внутри <defs>элемента, делая его шаблоном для повторного использования, но не визуализируемым сам по себе. Во-вторых, прямоугольник помещается внутри вложенного <svg overflow="visible">элемента. Задавая этому элементу координаты x / y и оставив их равными 0 для <rect>элемента, легче отслеживать, где окажется левая сторона прямоугольника после операции преобразования.

Теперь изменение ширины прямоугольника достигается с помощью scaleX()преобразования плюс translate()для позиции. Это должно быть в синтаксисе преобразования CSS . Использование transformатрибута не работает (пока). Следовательно, нам также понадобится transform-originсвойство, установленное слева от охватывающего <svg>элемента.

Вместо того, чтобы записывать конкретное значение для масштабирования, значение выражается как переменная со значением по умолчанию 1 var(--scale, 1):; то же самое для позиционных значений. Значение переменной устанавливается в styleатрибут для каждого <use>элемента в отдельности: style="--scale:2;--posX:20px; --posY:-10px". Обратите внимание на необходимость пишущих pxединиц!

#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

Шон сказал:

Если пользовательские элементы веб-компонентов будут расширены до пространства имен SVG,
будет возможно более сложное повторное использование.

Что верно, вы не можете создавать собственные элементы SVG (пока).

Но вы можете создать настраиваемый элемент, который генерирует 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>

Пользовательские элементы для SVG - современное решение для многих хаков oldskool SVG

Обновлять

Если OP хочет один SVG с кругами, мы можем использовать shadowDOM и тот факт, что элементы внутри lightDOM не отображаются. Мы даже можем использовать неопределенные <rect> элементы (в пространстве имен HTML), чтобы мы могли легко вставить их в строку 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)Связанные ответы StackOverflow: пользовательские элементы и SVG

2 Sean Aug 20 2020 at 22:28

Это невозможно. useЭлемент создает closed shadow root, что означает его содержание является недоступным для JS. Хотя вы можете установить атрибуты экземпляров повторно используемого элемента по отдельности (если они не были установлены в исходном элементе), вы не сможете повлиять на элементы, которые являются дочерними элементами повторно используемого элемента.

Альтернативный подход - повторно использовать элементы прямоугольника и круга напрямую и поместить их в новую группу.

AmirFo Sep 27 2020 at 17:27

Этот getAttributeметод у меня не сработал, но это сработало:

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

Использование :

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

Источник: D