css - `transform-box`元素如何控制 `<use>`?

标签 css svg css-transforms shadow-dom svg-transforms

背景

我喜欢 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" />

它会围绕transform-origin旋转在 CSS 中指定。由于上面的示例使用了 transform-origincenter center , 它原地旋转。

匹配使用 transform: rotate(…) 的 HTML 元素的行为.而且——特别是如果 SVG 图像中有很多像这样的旋转——它比等效的 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 /> element 将引用的元素克隆到新位置。因此,就 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 插入包含这段代码。)


有人有解决方案吗?

(即使是变通解决方案——只要它涉及的重复次数少于在每个 transform 上指定 SVG <use /> 属性——也是受欢迎的!)

最佳答案

假设与@Sphinxxx 假设的条件相同......

然后你也可以只包装你的 <use>组 ( <g>) 元素中的元素并将旋转类应用于该元素。

/* 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-boxtransform-origin分别申请。并且变换原点对平移没有影响。

那么这意味着 transform-box旋转计算(即在 <g> 上)将应用于已翻译的对象。所以它会按照您的意愿行事。

前几天 transform-box , 这两个例子会给出相同的结果。

关于css - `transform-box`元素如何控制 `<use>`?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67651011/

相关文章:

jquery - 为 AMX 命令按钮应用 CSS

jquery - 基本 CSS 特性在 IE8 中不起作用

jquery - css 查找第一个 child 给出了错误的结果

css3 旋转不会在悬停时返回

html - 如何在左上角或右上角旋转和定位元素?

javascript - 如何获取页面中使用的所有外部或内部CSS?

css - Chrome 中的 SVG 填充神器

svg - 使用 PyQT 中的 QSvgWidget 全屏设置 SVG 图像的尺寸?

javascript - d3js 强制布局为每个节点动态添加图像或圆圈

html - 带有背景的 CSS 旋转文本显示了一些背景问题