如何旋转整个SVG文本对象而非单个字形?
五度圈游戏开发中的文本旋转问题
我正在开发一款基于音乐概念“五度圈(Circle of fifths)”的游戏。为保证显示正确,已将元素旋转15度,使圆形顶部位于第一个分段的中间而非起始位置(也可认为是旋转-15度)。
目前遇到的问题是无法按需求旋转圆形分段内的文本。每个分段是包含子元素<path>和<text>的<g>元素。当前使用rotate属性,该属性仅适合单个或两个字符的文本,字符数更多时显示异常。根据MDN文档,rotate属性会“单独旋转每个字形的方向”。我希望旋转整个<text>元素,使所有字符保持同一旋转角度,且显示在同一直线上,与文字旋转方向垂直。
尝试过结合使用transform="rotate(15)"和text-anchor: middle替代rotate属性,但元素的X、Y坐标会发生偏移。还尝试过dominant-baseline属性(VSCode提示该属性不存在)以及glyph-orientation-horizontal属性(未产生任何效果)。
以下是我的代码(基于React开发,包含D3代码用于计算路径弧线,可直接放入App.js):
// 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> ) }
对应的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; } }
渲染效果截图
未使用rotate属性的渲染效果:

使用rotate属性的渲染效果:

内容的提问来源于stack exchange,提问作者Mo0rBy
相关产品推荐
相关产品推荐

