如何控制 `<use>` 元素的 `transform-box`?
How to control `transform-box` for `<use>` elements?
背景
我喜欢 SVG2 中扩展的 CSS 支持。不必一遍又一遍地重写属性真是太好了。所以我一直在将项目中的一些代码从 SVG 属性转换为 CSS。其中大部分工作正常。
当谈到转换时,如果您对 CSS 转换在 HTML 中的工作方式感到满意,事情可能看起来很棘手。 (对于 rotate()
转换尤其如此,这是本问题的重点。)这是因为 SVG 没有 HTML 具有的“自动流”。
换句话说,当你有一堆HTML个元素时,一个接一个,它们会自动按照盒子模型自行布局。
SVG 中没有这种“自动”或“默认”布局。因此,SVG 转换默认为从原点计算。 (即用户坐标中的 0,0
)。
几乎-完美的解决方案
对于大多数元素,有一个简单的解决方案:很棒的 CSS 属性 transform-box
。在大多数情况下,使用以下 CSS 将允许您以与 HTML 元素几乎相同的方式转换 SVG 元素:
/* whatever elements you want to transform */
rect, polygon {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate90 {
transform: rotate(90deg);
}
现在,您可以做类似...
<rect class="rotate90" x="123" y="789" width="50" height="50" />
并且会围绕CSS中指定的transform-origin
旋转。由于上面的例子使用了 center center
的 transform-origin
,它原地旋转。
这与使用 transform: rotate(…)
的 HTML 个元素的行为相匹配。而且——特别是如果在 SVG 图像中有很多像这样的旋转——它 way 比等效的 SVG 标记更好。
为什么 CSS 比等效的 SVG 标记更好?
因为 SVG’s rotate()
function has a slightly different syntax 没有等同于上面使用的 transform-box: fill-box
CSS,除非您为每个旋转指定 X 和 Y 坐标。
这意味着你必须每次输入旋转点,像这样:
<rect x="123" y="789" width="50" height="50"
transform="rotate(90 123 789)"
></rect>
<!--
In this example, the rotation pivots around the X and Y coordinates of the `rect` element.
If you wanted to rotate around the center, you would have to calculate:
x + width/2
y + width/2
And use those values in the `rotate()` function instead.
-->
问题
我 运行 遇到的问题是 CSS 解决方案不适用于 <use />
元素。
很清楚为什么:<use />
元素将引用的元素克隆到新位置。因此,就 CSS 而言,它正在转换 <use />
元素,而不是克隆的 (Shadow-DOM) 内容。
当涉及将 CSS 应用于 <use />
的其他挑战时——例如设置不同的配色方案——有解决方案(例如 this one from SVG superhero Sara Soueidan)。
但是要解决坐标的问题,我还没有想出解决办法。
示例代码
编辑:为了更明确地说明我要做什么,这里有一些示例代码。
.transform-tl,
.transform-tc,
.transform-tr,
.transform-cl,
.transform-cc,
.transform-cr,
.transform-bl,
.transform-bc,
.transform-br {
transform-box: fill-box;
}
.transform-tl { transform-origin: top left; }
.transform-tc { transform-origin: top center; }
/*
…and so on, for the other combinations of `transform-origin` keyword values…
*/
.rotate90.cw {
transform: rotate(90deg)
}
.rotate90.ccw {
transform: rotate(-90deg)
}
<!--
Using the CSS classes in the following manner works as intended for most SVG elements as intended.
But with the `<use />` element, the rotation does not pivot around the top left corner, as expected... :(
-->
<use
class="transform-tl rotate90 cw"
x ="0"
y ="1052"
href ="#block-A12-2"
></use>
(感谢@PaulLeBeau 推动包含这段代码。)
有人有解决办法吗?
(即使是变通解决方案——只要它涉及的重复次数少于在每个 <use />
上指定 SVG transform
属性——也是受欢迎的!)
所以您想用 <use x="..." y="..."
将元素放在某个地方,然后将其旋转到位?
我找到的最简单的解决方案确实包括 transform
属性,但您不需要指定旋转点。请参阅以下示例,其中绿色矩形符合您的要求。
在CSS中,我们在transform-box
规则中包含了use
个元素。然后我们定位和旋转每个具有transform
属性的元素(替换x
、y
和CSS旋转):
/* whatever elements you want to transform */
rect, polygon, use {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<rect id="rr" x="80" y="60" width="50" height="50" />
<use href="#rr" x="100" y="0" class="rotate45" fill="tomato" />
<use href="#rr" transform="translate(100 0) rotate(45)" fill="lime" />
</svg>
假设与@Sphinxxx 假设的条件相同...
然后您也可以将 <use>
元素包装在一个组 (<g>
) 元素中,然后对其应用旋转 class。
/* whatever elements you want to transform */
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="lime" />
</g>
</svg>
这是怎么回事?为什么这样做?
原因与取消引用 <use>
元素的方式以及发生这种情况时发生的转换有关。如果您阅读 the section about <use>
elements in the SVG spec,它表示:
In the generated content, the ‘use’ will be replaced by ‘g’, where all attributes from the ‘use’ element except for ‘x’, ‘y’, ‘width’, ‘height’ and ‘xlink:href’ are transferred to the generated ‘g’ element. An additional transformation translate(x,y) is appended to the end (i.e., right-side) of the ‘transform’ attribute on the generated ‘g’, where x and y represent the values of the ‘x’ and ‘y’ attributes on the ‘use’ element.
所以,下面的 SVG(我想这会像你正在尝试的那样):
<use href="#rr" x="100" y="0" fill="lime" class="rotate45" />
将被取消引用为等同于:
<g fill="blue" transform="rotate(45) translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
因为你可以认为变换是从右到左应用的,所以 translate(100,0)
将在旋转之前应用,这意味着旋转将移动偏离你想要的位置 100 像素的东西它。这就是 transform-box: fill-box
不适用于 <use>
元素的原因。
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<use href="#rr" x="100" y="0" fill="blue" stroke="blue" class="rotate45" />
<g fill="lime" transform="rotate(45) translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</svg>
但是对于我的解决方案,<g>
+ <use>
设置...
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="lime" />
</g>
将被取消引用为等同于:
<g class="rotate45">
<g fill="lime" transform="translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</g>
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="blue" stroke="blue"/>
</g>
<g class="rotate45">
<g fill="lime" transform="translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</g>
</svg>
在此版本中,两个变换操作中的每一个都应用于不同的元素,因此 transform-box
和 transform-origin
分别应用。并且变换原点对平移没有影响。
这意味着 transform-box
旋转计算(即在 <g>
上)将应用于已翻译的对象。所以它会如你所愿。
在 transform-box
之前的日子里,这两个示例会给出相同的结果。
背景
我喜欢 SVG2 中扩展的 CSS 支持。不必一遍又一遍地重写属性真是太好了。所以我一直在将项目中的一些代码从 SVG 属性转换为 CSS。其中大部分工作正常。
当谈到转换时,如果您对 CSS 转换在 HTML 中的工作方式感到满意,事情可能看起来很棘手。 (对于 rotate()
转换尤其如此,这是本问题的重点。)这是因为 SVG 没有 HTML 具有的“自动流”。
换句话说,当你有一堆HTML个元素时,一个接一个,它们会自动按照盒子模型自行布局。
SVG 中没有这种“自动”或“默认”布局。因此,SVG 转换默认为从原点计算。 (即用户坐标中的 0,0
)。
几乎-完美的解决方案
对于大多数元素,有一个简单的解决方案:很棒的 CSS 属性 transform-box
。在大多数情况下,使用以下 CSS 将允许您以与 HTML 元素几乎相同的方式转换 SVG 元素:
/* whatever elements you want to transform */
rect, polygon {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate90 {
transform: rotate(90deg);
}
现在,您可以做类似...
<rect class="rotate90" x="123" y="789" width="50" height="50" />
并且会围绕CSS中指定的transform-origin
旋转。由于上面的例子使用了 center center
的 transform-origin
,它原地旋转。
这与使用 transform: rotate(…)
的 HTML 个元素的行为相匹配。而且——特别是如果在 SVG 图像中有很多像这样的旋转——它 way 比等效的 SVG 标记更好。
为什么 CSS 比等效的 SVG 标记更好?
因为 SVG’s rotate()
function has a slightly different syntax 没有等同于上面使用的 transform-box: fill-box
CSS,除非您为每个旋转指定 X 和 Y 坐标。
这意味着你必须每次输入旋转点,像这样:
<rect x="123" y="789" width="50" height="50"
transform="rotate(90 123 789)"
></rect>
<!--
In this example, the rotation pivots around the X and Y coordinates of the `rect` element.
If you wanted to rotate around the center, you would have to calculate:
x + width/2
y + width/2
And use those values in the `rotate()` function instead.
-->
问题
我 运行 遇到的问题是 CSS 解决方案不适用于 <use />
元素。
很清楚为什么:<use />
元素将引用的元素克隆到新位置。因此,就 CSS 而言,它正在转换 <use />
元素,而不是克隆的 (Shadow-DOM) 内容。
当涉及将 CSS 应用于 <use />
的其他挑战时——例如设置不同的配色方案——有解决方案(例如 this one from SVG superhero Sara Soueidan)。
但是要解决坐标的问题,我还没有想出解决办法。
示例代码
编辑:为了更明确地说明我要做什么,这里有一些示例代码。
.transform-tl,
.transform-tc,
.transform-tr,
.transform-cl,
.transform-cc,
.transform-cr,
.transform-bl,
.transform-bc,
.transform-br {
transform-box: fill-box;
}
.transform-tl { transform-origin: top left; }
.transform-tc { transform-origin: top center; }
/*
…and so on, for the other combinations of `transform-origin` keyword values…
*/
.rotate90.cw {
transform: rotate(90deg)
}
.rotate90.ccw {
transform: rotate(-90deg)
}
<!--
Using the CSS classes in the following manner works as intended for most SVG elements as intended.
But with the `<use />` element, the rotation does not pivot around the top left corner, as expected... :(
-->
<use
class="transform-tl rotate90 cw"
x ="0"
y ="1052"
href ="#block-A12-2"
></use>
(感谢@PaulLeBeau 推动包含这段代码。)
有人有解决办法吗?
(即使是变通解决方案——只要它涉及的重复次数少于在每个 <use />
上指定 SVG transform
属性——也是受欢迎的!)
所以您想用 <use x="..." y="..."
将元素放在某个地方,然后将其旋转到位?
我找到的最简单的解决方案确实包括 transform
属性,但您不需要指定旋转点。请参阅以下示例,其中绿色矩形符合您的要求。
在CSS中,我们在transform-box
规则中包含了use
个元素。然后我们定位和旋转每个具有transform
属性的元素(替换x
、y
和CSS旋转):
/* whatever elements you want to transform */
rect, polygon, use {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<rect id="rr" x="80" y="60" width="50" height="50" />
<use href="#rr" x="100" y="0" class="rotate45" fill="tomato" />
<use href="#rr" transform="translate(100 0) rotate(45)" fill="lime" />
</svg>
假设与@Sphinxxx 假设的条件相同...
然后您也可以将 <use>
元素包装在一个组 (<g>
) 元素中,然后对其应用旋转 class。
/* whatever elements you want to transform */
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="lime" />
</g>
</svg>
这是怎么回事?为什么这样做?
原因与取消引用 <use>
元素的方式以及发生这种情况时发生的转换有关。如果您阅读 the section about <use>
elements in the SVG spec,它表示:
In the generated content, the ‘use’ will be replaced by ‘g’, where all attributes from the ‘use’ element except for ‘x’, ‘y’, ‘width’, ‘height’ and ‘xlink:href’ are transferred to the generated ‘g’ element. An additional transformation translate(x,y) is appended to the end (i.e., right-side) of the ‘transform’ attribute on the generated ‘g’, where x and y represent the values of the ‘x’ and ‘y’ attributes on the ‘use’ element.
所以,下面的 SVG(我想这会像你正在尝试的那样):
<use href="#rr" x="100" y="0" fill="lime" class="rotate45" />
将被取消引用为等同于:
<g fill="blue" transform="rotate(45) translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
因为你可以认为变换是从右到左应用的,所以 translate(100,0)
将在旋转之前应用,这意味着旋转将移动偏离你想要的位置 100 像素的东西它。这就是 transform-box: fill-box
不适用于 <use>
元素的原因。
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<use href="#rr" x="100" y="0" fill="blue" stroke="blue" class="rotate45" />
<g fill="lime" transform="rotate(45) translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</svg>
但是对于我的解决方案,<g>
+ <use>
设置...
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="lime" />
</g>
将被取消引用为等同于:
<g class="rotate45">
<g fill="lime" transform="translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</g>
rect, polygon, use, g {
transform-box: fill-box;
transform-origin: center center; /* or `top left`, `center right`, etc. */
}
.rotate45 {
transform: rotate(45deg);
}
<svg width="300" height="200">
<defs>
<rect id="rr" x="80" y="60" width="50" height="50" />
</defs>
<use href="#rr" x="100" y="0" fill="tomato" />
<g class="rotate45">
<use href="#rr" x="100" y="0" fill="blue" stroke="blue"/>
</g>
<g class="rotate45">
<g fill="lime" transform="translate(100,0)">
<rect x="80" y="60" width="50" height="50" />
</g>
</g>
</svg>
在此版本中,两个变换操作中的每一个都应用于不同的元素,因此 transform-box
和 transform-origin
分别应用。并且变换原点对平移没有影响。
这意味着 transform-box
旋转计算(即在 <g>
上)将应用于已翻译的对象。所以它会如你所愿。
在 transform-box
之前的日子里,这两个示例会给出相同的结果。