我正在try 开发一款基于音乐概念的《五人圈》的游戏.为了纠正错误,我将元素旋转了15度,使圆的最顶端位于第一段的中间,而不是第一段的起点(您可能会争辩说它旋转了-15度,但不管怎样).

我现在面临的问题是,我不能按要求在我的圆圈段内旋转文本.每个段是一个<g>元素,其内嵌套子元素<path>和子元素<text>.目前,我只使用rotate属性,这对于单个字符或2个字符来说是可以的,但如果超过这个范围,它看起来就不正确了.rotate属性的作用如描述here所示("旋转每个单独字形的方向.可以单独旋转字形.").我想要旋转整个<text>元素,以便所有字符旋转到相同的Angular AND看起来呈现在同一条线上,垂直于字母的旋转.

我try 使用transform=rotate(15)text-anchor: middle的组合,而不是使用rotate属性,但看起来它似乎也在X和Y坐标中移动元素.我也try 过使用dominant-baseline属性,但我的VSCode声明该属性不存在,而glyph-orientation-horizontal属性,据我所知没有任何作用.

以下是我的代码(我使用的是Reactjs,所以我只是给您提供了可以非常轻松地放入App.js中的组件代码和组件的css代码(有几行D3btw,只是为了计算路径弧)),以及一些屏幕截图,一个图像不使用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;
  }
}

The rendering without using the rotate attribute The rendering using the rotate attribute

推荐答案

一旦您了解(A)如何在SVG中旋转对象和(B)如何在SVG中定位文本,我们很快就会解决这个问题!

让我们从一个简单的正方形开始:

<svg viewBox="0 0 60 60">
  <rect x="15" y="15" width="30" height="30"/>
</svg>

enter image description here

现在,我们将使用transform=rotate(45)进行旋转:

<svg viewBox="0 0 60 60">
  <rect x="15" y="15" width="30" height="30" transform="rotate(45)"/>
</svg>

enter image description here enter image description here

正方形已经旋转,但绕着左上角of the viewbox旋转,左上角of the viewbox是坐标(0,0).默认情况下,SVG中的旋转是围绕origin点完成的.如果要绕任何其他点旋转对象,则需要将这些坐标指定为rotate transform的第二个和第三个参数.我们希望围绕位于点(30,30)的正方形中心旋转,因此我们的变换需要为transform=rotate(45, 30, 30).

<svg viewBox="0 0 60 60">
  <rect x="15" y="15" width="30" height="30" transform="rotate(45, 30, 30)"/>
</svg>

enter image description here

好的,魔术!这就是我们想要的!现在让我们try 一些文本,我们将从我们的视区(30,30)的中心开始.

<svg viewBox="0 0 60 60">
  <text x="30" y="30">fred</text>
</svg>

enter image description here

因此,默认情况下,x坐标指定文本的start,y坐标指定其baseline.这不是我们想要的.我们希望两个坐标都指定文本的centre.我们可以通过为水平对齐设置text-anchor="middle"和为垂直对齐设置dominant-baseline="central"来实现这一点.

<svg viewBox="0 0 60 60">
  <text x="30" y="30" text-anchor="middle" dominant-baseline="central">fred</text>
</svg>

enter image description here

是!现在只需围绕(30,30)旋转即可获得所需的结果.

<svg viewBox="0 0 60 60">
  <text x="30" y="30" text-anchor="middle" dominant-baseline="central" transform="rotate(45, 30, 30)">fred</text>
</svg>

enter image description here enter image description here

body {
  margin: 50px;
  background: #f6f6f6
}
svg {
  width: 60px;
  border: 1px solid #ddd;
  background: white;
  font-family: sans-serif;
}
<svg viewBox="0 0 60 60">
  <text x="30" y="30" text-anchor="middle" dominant-baseline="central" transform="rotate(45, 30, 30)">fred</text>
</svg>

<svg viewBox="0 0 60 60">
  <text x="30" y="30" text-anchor="middle" dominant-baseline="central">
    <animateTransform id="op"
      attributeName="transform"
      type="rotate"
      from="0 30 30"
      to="360 30 30"
      dur="1s"
      repeatCount="indefinite" />
    fred
  </text>
</svg>

因此,现在希望您可以采用这些SVG概念并在您的Reaction代码中实现它们.


注1.SVG视框的原点不一定在左上角.您可以设置一个视框,以便 (0,0)位于中间,例如:

<svg viewBox="-30 -30 60 60">

注2.SVG变换也可以设置动画.有关示例,请参阅上面的代码片段,从MDN开始编写文档是一个不错的 Select .

Javascript相关问答推荐

创建私有JS出口

禁用从vue.js 2中的循环创建的表的最后td的按钮

如何在不分配整个数组的情况下修改包含数组的行为主体?

如何获取转换字节的所有8位?

当在select中 Select 选项时,我必须禁用不匹配的 Select

Cypress -使用commands.js将数据测试id串在一起失败,但在将它们串在一起时不使用命令有效

当作为表达式调用时,如何解析方法decorator 的签名?

阿波罗返回的数据错误,但在网络判断器中是正确的

查找最长的子序列-无法重置数组

XSLT处理器未运行

在执行异步导入之前判断模块是否已导入()

不能将空字符串传递给cy.containes()

用JS从平面文件构建树形 struct 的JSON

如何将innerHTML字符串修剪为其中的特定元素?

如何将数组用作复合函数参数?

如何在不影响隐式类型的情况下将类型分配给对象?

如何在AG-Grid文本字段中创建占位符

顶点图使用组标签更新列之间的条宽

设置复选框根据选中状态输入选中值

如何设置时间选取器的起始值,仅当它获得焦点时?