首页 > 解决方案 > 如何为`控制`transform-b​​ox``元素?

问题描述

背景

我喜欢 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-originCSS 中指定的旋转。由于上面的示例使用 a transform-originof center center,因此它在原地旋转。

这与使用transform: rotate(…). 而且——特别是如果 SVG 图像中有很多这样的旋转——它等效的 SVG 标记要好得多。

为什么 CSS 比等效的 SVG 标记更好?

因为SVG 的rotate()函数语法略有不同,没有与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 />——例如设置不同的配色方案——有一些解决方案(比如来自 SVG 超级英雄 S​​ara 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 />——也会受到欢迎!)

标签: csssvgcss-transformsshadow-domsvg-transforms

解决方案


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

然后你也可以将你的<use>元素包装在一个 group ( <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>取消引用元素的方式以及发生这种情况时转换会发生什么有关。如果您阅读SVG 规范中关于元素的部分<use>,它会说:

在生成的内容中,“use”将被“g”替换,其中“use”元素中除“x”、“y”、“width”、“height”和“xlink:href”之外的所有属性都是转移到生成的'g'元素。附加转换 translate(x,y) 附加到生成的 'g' 上的 'transform' 属性的末尾(即右侧),其中 x 和 y 表示 'x' 和 'y' 的值'use' 元素的属性。

所以,下面的 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,这两个例子会给出相同的结果。


推荐阅读