如何旋转整个 SVG 文本对象而不是单个字形

Mo0*_*rBy 3 javascript css svg reactjs

我正在尝试开发一款基于“五度循环”的游戏,这是一个音乐概念。为了使其正确,我将元素旋转了 15 度,以便圆的最顶部位于第一段的中间,而不是第一段的开头(您可能会说它旋转了 -15 度,但无论如何) 。

我现在面临的问题是我无法根据需要旋转圆段内的文本。每个段都是一个<g>带有子元素<path><text>嵌套在其中的子元素的元素。目前,我只是使用该rotate属性,这对于单个字符或 2 个字符来说是可以的,但除此之外,它看起来不正确。该属性的工作方式如下rotate所述(“旋转每个单独字形的方向。可以单独旋转字形。”)。我想旋转整个元素,以便所有字符都旋转到相同的角度,并且看起来呈现在同一条线上,垂直于字母的旋转。<text>

我尝试使用transform=rotate(15)和的组合text-anchor: middle而不是使用rotate属性,但看起来好像它也在 X 和 Y 坐标中移动元素。我也尝试过使用该dominant-baseline属性,但我的 VSCode 指出该属性不存在glyph-orientation-horizontal,并且该属性据我所知没有执行任何操作。

这是我的代码(我正在使用 Reactjs,所以我只是为您提供可以非常轻松地放入 App.js 中的组件代码以及组件的 CSS 代码(顺便说一句,有几行 D3,只是为了计算路径弧))以及一些屏幕截图,一张图像没有使用该rotate属性,另一张图像使用了该rotate属性。

组件和 CSS 代码:

// My component
import './CircleOfFifths.css';
import { useEffect, useState } from 'react';
import * as d3 from 'd3';
import musicKeys from '../../MusicKeys';

export default function CircleOfFifths({ outerRadius }) {

  const diameter = outerRadius * 2;
  const innerRadius = outerRadius * 0.7;
  const innerRadius2 = outerRadius * 0.4;

  const [musicKeysObject, setMusicKeysObject] = useState(musicKeys);

  useEffect(() => {
    let newMusicKeysObject = musicKeysObject;
    for (let i=0; i<newMusicKeysObject.length; i++) {
      newMusicKeysObject[i].segmentMetadata.majorCircle.isVisible = true;
      newMusicKeysObject[i].segmentMetadata.minorCircle.isVisible = true;
    }
    setMusicKeysObject([...newMusicKeysObject]);
  }, [])

  const majorOnMouseUpHandler = (index) => {
    let newMusicKeysObject = musicKeysObject;
    if (newMusicKeysObject[index].segmentMetadata.majorCircle.isVisible) {
      newMusicKeysObject[index].segmentMetadata.majorCircle.isVisible = false;
    } else {
      newMusicKeysObject[index].segmentMetadata.majorCircle.isVisible = true;
    }
    setMusicKeysObject([...newMusicKeysObject]);
  }

  const minorOnMouseUpHandler = (index) => {
    let newMusicKeysObject = musicKeysObject;
    if (newMusicKeysObject[index].segmentMetadata.minorCircle.isVisible) {
      newMusicKeysObject[index].segmentMetadata.minorCircle.isVisible = false;
    } else {
      newMusicKeysObject[index].segmentMetadata.minorCircle.isVisible = true;
    }
    setMusicKeysObject([...newMusicKeysObject]);
  }

  const calculateArc = (innerRadius, outerRadius, startAngle, endAngle) => {
    return d3.arc()
      .innerRadius(innerRadius)
      .outerRadius(outerRadius)
      .startAngle(startAngle)
      .endAngle(endAngle);
  }

  const renderMajorSegment = (musicKey, index) => {
    let arc = calculateArc(outerRadius, innerRadius, musicKey.segmentMetadata.startAngle, musicKey.segmentMetadata.endAngle);
    let [arcCenterX, arcCenterY] = arc.centroid();

    return <g className={`circle-segment ${musicKey.segmentMetadata.majorCircle.isVisible ? 'isVisible': ''}`} key={index} onMouseUp={() => majorOnMouseUpHandler(index)}>
            <path 
              d={arc.apply()} // apply() is needed to generate the string that goes into the 'd' attribute
            />
            {/* Need to adjust center X position due to increased font-size */}
            <text x={arcCenterX-10} y={arcCenterY} rotate={15}>
              {musicKey.chords[0].replace('Major', '')}
            </text>
          </g>
  }

  const renderMinorSegment = (musicKey, index) => {
    let arc = calculateArc(innerRadius, innerRadius2, musicKey.segmentMetadata.startAngle, musicKey.segmentMetadata.endAngle);
    let [arcCenterX, arcCenterY] = arc.centroid();

    return <g className={`circle-segment ${musicKey.segmentMetadata.minorCircle.isVisible ? 'isVisible': ''}`} key={index} onMouseUp={() => minorOnMouseUpHandler(index)}>
            <path 
              d={arc.apply()} // apply() is needed to generate the string that goes into the 'd' attribute
            />
            {/* Need to adjust center X position due to increased font-size */}
            <text x={arcCenterX-10} y={arcCenterY} rotate={15}>
              {musicKey.chords[5].replace(' minor', 'mgh')}
            </text>
          </g>
  }

  return (
    <div className='circle-of-fifths-container'>
      <svg
        height={diameter*1.1}
        width={diameter*1.1}>
        <g className='circle-container' transform={`translate(${diameter/2 + 25},${diameter/2 + 25}) rotate(-15)`}>
          <circle r={outerRadius} className="base-circle"/>
          <g className='outer-circle-segments-container'>
            {musicKeysObject.map((musicKey, index) => renderMajorSegment(musicKey, index))}
          </g>
          <g className='inner-circle-segments-container'>
            {musicKeysObject.map((musicKey, index) => renderMinorSegment(musicKey, index))}
          </g>
        </g>
      </svg>
    </div>
  )
}

// It's CSS
.circle-of-fifths-container {
  margin: 50px;
  text-align: center;
}

.circle-container {
  text-align: center;
}

.base-circle {
  fill: grey;
  padding: 5px;
  border: 5px;
  margin: 5px;
  stroke: grey;
  stroke-width: 5px;
}

.circle-segment {
  visibility: none;
}

.circle-segment.isVisible {
  visibility: visible;
  > path {
    fill: #D7DCDF;
    stroke: black;
    stroke-width: 5px;
  }
  > text {
    font-size: 30px;
    text-anchor: initial;
  }
}
Run Code Online (Sandbox Code Playgroud)

不使用rotate属性的渲染 使用rotate属性进行渲染

Bre*_*ald 5

让\xe2\x80\x99s 回顾一下 (a) SVG 中对象如何旋转以及 (b) SVG 中文本如何定位。我们从一个简单的正方形开始:

\n
<svg viewBox="0 0 60 60">\n  <rect x="15" y="15" width="30" height="30"/>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

现在我们\xe2\x80\x99将使用以下命令旋转它transform=rotate(45)

\n
<svg viewBox="0 0 60 60">\n  <rect x="15" y="15" width="30" height="30" transform="rotate(45)"/>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述\n在此输入图像描述

\n

正方形已旋转,但围绕原点\xe2\x80\x93 坐标 (0,0) \xe2\x80\x93 旋转,在本例中为视图框的左上角。默认情况下,SVG 中的旋转是围绕原点执行的。如果我们想要围绕任何其他点旋转对象,我们需要将这些坐标指定为旋转变换的第二个和第三个参数。我们想要围绕正方形的中心(位于点 (30,30))旋转,因此我们的变换需要为transform=rotate(45, 30, 30)

\n
<svg viewBox="0 0 60 60">\n  <rect x="15" y="15" width="30" height="30" transform="rotate(45, 30, 30)"/>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

好吧,魔法!这就是我们想要的\xe2\x80\x99!现在让\xe2\x80\x99s 尝试一些文本,我们\xe2\x80\x99 将其放置在视口的中心(30,30)。

\n
<svg viewBox="0 0 60 60">\n  <text x="30" y="30">fred</text>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

因此,默认情况下,x 坐标指定文本的开头,y 坐标指定其基线。那\xe2\x80\x99不是我们想要的。我们希望两个坐标都指定文本的中心。我们可以通过设置text-anchor="middle"水平对齐和dominant-baseline="central"垂直对齐来做到这一点。

\n
<svg viewBox="0 0 60 60">\n  <text x="30" y="30" text-anchor="middle" dominant-baseline="central">fred</text>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述

\n

是的!现在\xe2\x80\x99 绕(30,30) 旋转就可以得到想要的结果了。

\n
<svg viewBox="0 0 60 60">\n  <text x="30" y="30" text-anchor="middle" dominant-baseline="central" transform="rotate(45, 30, 30)">fred</text>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\n

在此输入图像描述\n在此输入图像描述

\n

\r\n
\r\n
<svg viewBox="0 0 60 60">\n  <rect x="15" y="15" width="30" height="30"/>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\r\n
<svg viewBox="0 0 60 60">\n  <rect x="15" y="15" width="30" height="30" transform="rotate(45)"/>\n</svg>\n
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

所以现在,希望您可以采用这些 SVG 概念并在您的 React 代码中实现它们。

\n
\n

注意 1. SVG 视图框\xe2\x80\x99 不一定具有左上角的原点。您可以设置一个视图框,使\n(0,0) 位于中心,例如:

\n
<svg viewBox="-30 -30 60 60">\n
Run Code Online (Sandbox Code Playgroud)\n
\n

注 2:SVG 变换也可以设置动画。请参阅上面的代码片段作为示例,MDN是一个不错的文档起点。

\n