HTML SVG 重用组 <g> 和 <use> 并为每个实例单独更改内部元素的属性
HTML SVG reuse a group <g> with <use> and change attributes of inner elements individually for each instance
所以我想重复使用分组的 svg 形状,并为每个实例单独更改组内元素之一的一个属性。
下面的简化示例创建了第二个圆,里面有一个矩形。我现在想为每个具有 javascript 的形状分别更改“my-rect”矩形的“宽度”属性。使用 id "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>
这是不可能的。 use
元素创建一个 closed shadow root
,这意味着它的内容是 JS 无法访问的。虽然您可以单独设置重用元素实例的属性(如果它们尚未在原始元素上设置),您将无法影响作为重用元素的子元素的元素。
另一种方法是直接重用矩形和圆形元素,并将它们放在一个新组中。
通过一些技巧,这是可能的。您必须利用 CSS 继承来在影子元素中获取一些 属性 值。在这种情况下,将使用自定义变量来缩放和定位矩形。
为此必须稍微重写标记。首先,您将组写入 <defs>
元素内,使其成为可重用的模板,但不会自行呈现。其次,矩形放置在嵌套的 <svg overflow="visible">
元素内。为该元素提供 x/y 坐标并将 <rect>
元素的坐标保留为 0,这样可以更轻松地跟踪变换操作后矩形左侧的最终位置。
现在矩形的宽度变化是通过 scaleX()
变换加上位置的 translate()
实现的。此 必须采用 CSS 转换语法 。使用 transform
属性是行不通的(目前)。因此,我们还需要一个 transform-origin
属性,设置在封闭 <svg>
元素的左侧。
不写具体的缩放比例值,而是将值表示为默认值为1的变量:var(--scale, 1)
;位置值相同。变量的值分别在每个 <use>
元素的 style
属性中设置: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>
肖恩说:
If the Web Components Custom Elements gets expanded to the SVG namespace,
more complex reuse will be possible
没错,您还不能制作 自定义 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 中的元素这一事实。我们甚至可以使用 undefined <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)
相关 Whosebug 答案:Custom Elements and SVG
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>
所以我想重复使用分组的 svg 形状,并为每个实例单独更改组内元素之一的一个属性。 下面的简化示例创建了第二个圆,里面有一个矩形。我现在想为每个具有 javascript 的形状分别更改“my-rect”矩形的“宽度”属性。使用 id "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>
这是不可能的。 use
元素创建一个 closed shadow root
,这意味着它的内容是 JS 无法访问的。虽然您可以单独设置重用元素实例的属性(如果它们尚未在原始元素上设置),您将无法影响作为重用元素的子元素的元素。
另一种方法是直接重用矩形和圆形元素,并将它们放在一个新组中。
通过一些技巧,这是可能的。您必须利用 CSS 继承来在影子元素中获取一些 属性 值。在这种情况下,将使用自定义变量来缩放和定位矩形。
为此必须稍微重写标记。首先,您将组写入 <defs>
元素内,使其成为可重用的模板,但不会自行呈现。其次,矩形放置在嵌套的 <svg overflow="visible">
元素内。为该元素提供 x/y 坐标并将 <rect>
元素的坐标保留为 0,这样可以更轻松地跟踪变换操作后矩形左侧的最终位置。
现在矩形的宽度变化是通过 scaleX()
变换加上位置的 translate()
实现的。此 必须采用 CSS 转换语法 。使用 transform
属性是行不通的(目前)。因此,我们还需要一个 transform-origin
属性,设置在封闭 <svg>
元素的左侧。
不写具体的缩放比例值,而是将值表示为默认值为1的变量:var(--scale, 1)
;位置值相同。变量的值分别在每个 <use>
元素的 style
属性中设置: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>
肖恩说:
If the Web Components Custom Elements gets expanded to the SVG namespace,
more complex reuse will be possible
没错,您还不能制作 自定义 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 中的元素这一事实。我们甚至可以使用 undefined <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)
相关 Whosebug 答案:Custom Elements and SVG
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>