使用 d3 围绕自己的中心旋转 svg 路径

Rotate an svg path around its own centre using d3

我有一个用 Inkscape 制作的 SVG 路径(切换图标),我试图以编程方式围绕它自己的中心旋转 90 度。在我的正确网页中,我有许多这些图标都由 ID 引用,因此这需要是我可以应用的通用解决方案。

看起来 this 问题是相同的,但 OP 从未跟进过,他们永远无法提供更多代码或 fiddle。

我有一个 fiddle here,它显示了我的示例的所有内容。

knob = d3.select("#switch1")
knob.attr('transform', 'rotate(0 0 0)')

是我用于旋转的基本代码。我需要知道如何计算 x 和 y 值,以便可以使任何给定的图标指向示例中的 on/off 文本。或者使用 d3

获得相同旋转效果的另一种方法

如果我这样做 knob.attr('transform', 'rotate(90 0 0)') 然后图标从页面上消失 - 我以为 0 0 是围绕其相对中心旋转?

如果我手动执行 knob.attr('transform', 'rotate(90 15 15)') 我可以将其保留在页面上但在错误的位置。

SVG路径由;

组成
       <path
       id="switch1"
       style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.12199998;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 35.741778,27.194664 -1.082299,0 0,-8.512001 1.082299,-9e-6 z m -5.563199,-4.274211 a 5.00005,5.000032 0 0 0 9.999999,2e-5 5.00005,5.000032 0 1 0 -9.999999,-2e-5 z"
       inkscape:connector-curvature="0" />

可以在 fiddle 上找到完整的 SVG 标记。

您可以通过 getBBox():

获取 <path> 的位置
const centre = knob.node().getBBox();

那么,就是计算它的中心了:

knob.attr("transform", "rotate(" + angle + ", " + 
    (centre.x + centre.width / 2) + ", " + (centre.y + centre.height / 2) + ")");

这里angle显然是你想要的角度。

这是一个使用您的 SVG(但更小)的演示,单击 SVG 中的任意位置以旋转路径:

let toggle = 0;
let svg = d3.select("svg")
const knob = d3.select("#switch1")
const centre = knob.node().getBBox();
svg.on("click", function() {
  const angle = (toggle = 1 - toggle) ? 90 : 0;
  knob.attr("transform", "rotate(" + angle + ", " + (centre.x + centre.width / 2) + ", " + (centre.y + centre.height / 2) + ")");
})
svg {
 border: 1px solid gray;
 background-color: lavender;
 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="200" height="100" viewBox="0 0 50 50" id="svg2" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="bar.svg">
  <defs
     id="defs4" />
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="11.2"
     inkscape:cx="34.921875"
     inkscape:cy="1047.7595"
     inkscape:document-units="px"
     inkscape:current-layer="layer1"
     showgrid="true"
     inkscape:window-width="1920"
     inkscape:window-height="1033"
     inkscape:window-x="-4"
     inkscape:window-y="-4"
     inkscape:window-maximized="1">
    <inkscape:grid
       type="xygrid"
       id="grid4157" />
  </sodipodi:namedview>
  <metadata
     id="metadata7">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <path
       id="switch1"
       style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.12199998;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 35.741778,27.194664 -1.082299,0 0,-8.512001 1.082299,-9e-6 z m -5.563199,-4.274211 a 5.00005,5.000032 0 0 0 9.999999,2e-5 5.00005,5.000032 0 1 0 -9.999999,-2e-5 z"
       inkscape:connector-curvature="0" />
    <text
       xml:space="preserve"
       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       x="25.714285"
       y="13.612206"
       id="text4135"
       sodipodi:linespacing="125%"><tspan
         sodipodi:role="line"
         id="tspan4137"
         x="25.714285"
         y="13.612206"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">OFF</tspan></text>
    <text
       xml:space="preserve"
       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       x="44.566826"
       y="26.086744"
       id="text4135-2"
       sodipodi:linespacing="125%"><tspan
         sodipodi:role="line"
         id="tspan4137-2"
         x="44.566826"
         y="26.086744"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">ON</tspan></text>
  </g>
</svg>

您还可以添加过渡:

let toggle = 0;
let svg = d3.select("svg")
const knob = d3.select("#switch1")
const centre = knob.node().getBBox();
const centreX = centre.x + centre.width / 2;
const centreY = centre.y + centre.height / 2;
svg.on("click", function() {
  const angle = (toggle = 1 - toggle) ? 90 : 0;
  knob.transition()
    .ease(d3.easeLinear)
    .attrTween("transform", function() {
      return d3.interpolateString("rotate(" + (90 - angle) + ", " + centreX + ", " + centreY + ")", "rotate(" + angle + ", " + centreX + ", " + centreY + ")")
    })
})
svg {
  border: 1px solid gray;
  background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
  xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="200" height="100" viewBox="0 0 50 50" id="svg2" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="bar.svg">
  <defs
     id="defs4" />
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="11.2"
     inkscape:cx="34.921875"
     inkscape:cy="1047.7595"
     inkscape:document-units="px"
     inkscape:current-layer="layer1"
     showgrid="true"
     inkscape:window-width="1920"
     inkscape:window-height="1033"
     inkscape:window-x="-4"
     inkscape:window-y="-4"
     inkscape:window-maximized="1">
    <inkscape:grid
       type="xygrid"
       id="grid4157" />
  </sodipodi:namedview>
  <metadata
     id="metadata7">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title />
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <path
       id="switch1"
       style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.12199998;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
       d="m 35.741778,27.194664 -1.082299,0 0,-8.512001 1.082299,-9e-6 z m -5.563199,-4.274211 a 5.00005,5.000032 0 0 0 9.999999,2e-5 5.00005,5.000032 0 1 0 -9.999999,-2e-5 z"
       inkscape:connector-curvature="0" />
    <text
       xml:space="preserve"
       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       x="25.714285"
       y="13.612206"
       id="text4135"
       sodipodi:linespacing="125%"><tspan
         sodipodi:role="line"
         id="tspan4137"
         x="25.714285"
         y="13.612206"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">OFF</tspan></text>
    <text
       xml:space="preserve"
       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       x="44.566826"
       y="26.086744"
       id="text4135-2"
       sodipodi:linespacing="125%"><tspan
         sodipodi:role="line"
         id="tspan4137-2"
         x="44.566826"
         y="26.086744"
         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">ON</tspan></text>
  </g>
</svg>

虽然 Gerardo 的 展示了显而易见且应用最广泛的解决方案,但还有其他方法可以实现。这个答案有一些实验性的接触(Gerardo 报告它在 Safari 上被破坏)因为它列出了两个不是 off-the-shelf 的方法。将其视为探索可能性并添加到知识库中,而不是呈现惯用的方式。


您可以使用 transform-origin CSS property to control the origin for an element's transformations. Because Firefox seems to handle this differently you should also set transform-box:fill-box 而不是获取边界框并进行一些无可否认的简单数学运算。将旋转的原点设置为 50% 50% 将按照您最初预期的那样围绕其中心旋转元素。旁注:围绕中心旋转是 HTML 的默认行为,而 SVG 默认情况下围绕坐标原点旋转。

这是 Gerardo 演示的更简化版本:

let toggle = 0;
const svg = d3.select("svg")
const knob = d3.select("#switch1")
svg.on("click", function() {
  const angle = (toggle = 1 - toggle) ? 90 : 0;
  knob.attr("transform", "rotate(" + angle + ")");
})
path {
  fill: none;
  stroke: #000000;
  stroke-width: 1.12199998;
  transform-box: fill-box;
  transform-origin: 50% 50%
}

text {
  font-size: 10px;
  font-family: Sans;
  fill: #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

  <svg width="200" height="100" viewBox="0 0 50 50">
    <g>
      <path id="switch1"
         d="m 35,27 -1,0 0,-9 1,0 z m -5.7,-4 a 5,5 0 0 0 10,0 5,5 0 1 0 -10,0 z" />
      <text x="26" y="14">OFF</text>
      <text x="45" y="26">ON</text>
    </g>
  </svg>

您甚至可以使用 hidden-checkbox-label 技巧挑战极限,完全不用 JavaScript!

path {
  fill: none;
  stroke: #000000;
  stroke-width: 1.12199998;
  transform-box: fill-box;
  transform-origin: 50% 50%
}

text {
  font-size: 10px;
  font-family: Sans;
  fill: #000000;
}

#dummy {
  display: none;
}

#dummy:checked+label path {
  transform: rotate(90deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<input type="checkbox" id="dummy">

<label for="dummy">
  <svg width="200" height="100" viewBox="0 0 50 50">
    <g>
      <path id="switch1"
         d="m 35,27 -1,0 0,-9 1,0 z m -5.7,-4 a 5,5 0 0 0 10,0 5,5 0 1 0 -10,0 z" />
      <text x="26" y="14">OFF</text>
      <text x="45" y="26">ON</text>
    </g>
  </svg>
</label>