几周前,我勉强决定我的Chord项目需要基于Web的前端。 在权衡各种选择之后,我决定将前端的核心实现为基于React的ASCII和弦图表渲染器。
经过一些初步的代码草绘,我有了一个可以正常工作的原型,后来进行了一些修订,我发现自己对最终的代码感到满意。 让我们深入研究吧!
在开始研究代码之前,让我们看一下要构建的内容。
我们的和弦后端将和弦视为代表在特定琴弦上弹奏的琴弦的可选数字列表,或代表代表所弹奏的琴弦和用于弹奏该琴弦的手指的可选两元组数字列表。 例如,在后端,我们将使用以下列表代表经典的开放C大调和弦:
[nil,3,2,0,1,nil]
并以普通的指法:
[nil,{3,3},{2,2},{0,nil},{1,1},nil]
不幸的是,Javascript没有“元组”类型,因此我们不得不将和弦表示为一维或二维数字数组。 在我们的前端,这些相同的和弦将像这样表示:
[null,3,2,0,1,null] [null,[3,3],[2,2],[0,null],[1,1],null]
我们的目标是将该表示形式转换为以下和弦图:

让我们开始吧!
我们将从创建一个新的React组件开始,以渲染通过给定chord
道具传递的chord
,并渲染一个样式化的pre
元素,以保存我们即将成为的和弦图表:
const Chart = styled.pre`
font-family:“源代码专业版”;
文本对齐:居中;
`;
导出默认值({和弦})=> {
返回(
);
};
在渲染和弦之前,我们需要计算一些将在整个过程中使用的基本指标,并列出我们的攻击计划:
导出默认值({和弦,名称})=> {
令{min,max} = getMinAndMax(chord)
返回(
{_。链()
.thru(buildFretRange)
.thru(buildFretRows)
.thru(intersperseFretWire)
.thru(appendFingering)
.thru(attachLeftGutter)
.thru(joinRows)
。值()}
);
};
getMinAndMax
帮助器是在模块内部全局定义的,它仅过滤掉未播放的品格并返回一个对象,该对象由和弦中使用的最小品味( min
)和和弦中使用的最大品味( max
)组成:
const getMinAndMax =和弦=>
_.chain(和弦)
.map(string =>(_.isArray(string)?string [0]:string))
.reject(_。isNull)
.thru(frets =>({
min:_.min(frets),
最大值:_。max(品格)
}))
。值();
一旦有了这些指标,我们就可以看到我们的游戏计划是建立我们的buildFretRange
范围( buildFretRange
),建立我们的buildFretRows
行( buildFretRows
),将buildFretRows
线散布在这些buildFretRows
行之间( intersperseFretWire
),附加chord
( appendFingering
)传递的所有指法指示,附加左装订线( attachLeftGutter
),并将所有内容连接在一起( joinRows
)。
现在我们需要构建每个组成部分。
使用范围的min
和max
,我们可以轻松地构建一个辅助函数来构建品格范围:
const buildFretRange =()=> _.range(min,Math.max(max + 1,min + 5));
请注意,我们在和弦图表上设置了最小高度。 如果和弦的范围小于五个品格,我们将在图表底部渲染足够的空品格以填充剩余空间。
我们得到的范围是一个数字范围,和弦范围内使用的每个品格一个。
一旦拥有和弦的范围,就可以将该范围内的每个品格转换为品格行的可渲染表示形式:
const buildFretRows =烦恼=>
_.map(品格,品格=>
_.chain(_。range(chord.length))
。地图(
字符串=>
(_.isArray(chord [string])?chord [string] [0]:chord [string])==
烦恼? (
{fret == 0? “○”:“●”}
):(
{fret == 0? “┬”:“│”}
)
)
。值()
);
我们首先映射每个fret
中的frets
。 对于每个fret
,我们在和弦( _.range(chord.length)
)中映射每个弦。 接下来,我们检查当前弦中是否正在演奏每个string
和fret
组合。 如果是的话,则当手指处于手指状态时,我们将渲染一个●
符号,如果正在播放一个开放的弦时,则将其呈现为○
符号。
如果我们不演奏string
/ fret
组合,我们将使用┬
符号代表吉他的螺母或│
符号代表未打磨的琴弦来渲染品格线。
Finger
和Wire
都是简单样式化的span
元素:
const Finger = styled.span`
font-weight:粗体;
`;
const Wire = styled.span``;
至此,我们的和弦图表开始成形,但是由于没有任何水平品格或品格标记,因此查看时会有些迷惑:

让我们通过在每行品格之间散布品格线来使事情更加清晰:
const intersperseFretWire =行=>
_.flatMap(行,行=> [
行,
{`├$ {_。repeat(“┼”,chord.length-2)}┤`{:。language-javascript}}
]);
我们使用Lodash的flatMap
在每个品格行之后附加一个Wire
组件。 这给我们留下了一系列交替的品格行和品格线。
一些和弦带有指法建议。 我们将这些建议放在和弦图下方:
const appendFingering =行=> [
...行
{_.chain(chord)
.map(fret =>(_.isArray(fret)?fret [1]:“”))
。值()}
];
请注意, Fingering
组件只是一个(未设置样式的) span
:
const Fingering = styled.span'';
我们快完成了。 有些和弦比其他的和弦演奏得更远。 如果不指出吉他的螺母在哪里,演奏者就无法定向自己。
让我们通过在左侧装订线中标记图表的最低品格,为图表的读者提供一些基础:
const attachLeftGutter =行=>
_.map(rows,(row,i)=>(
{i == 0 && min!= 0? _.pad(min,2):“”}
{行}
));
React的新Fragment
语法为我们提供了一种很好的方式来组合多个呈现的组件,而不会引入额外的DOM冗余。
请注意,我们没有为开放的和弦渲染品格标签。 因为我们使用特殊符号( ┬
)渲染螺母,所以我们不需要指出和弦从零号开始。
这里的所有都是它的。 我们可以使用我们的新组件来渲染各种和弦:
当使用光荣的ASCII呈现时,所有这些看起来都很漂亮!

确保检查了Github上的整个项目,并在检查时检查了Giorgio Torres完成的原始解决方案的重构。 Giorgio在我抱怨我的第一个迭代是我编写过的最丑陋的React并贡献了他高度抛光的解决方案后突然大呼出手。 谢谢乔治!
最初于 2018年10月8日 发布在 www.petecorey.com 上。