JavaScript和弦移调功能异常:重复点击按钮多次移调问题
问题描述
我有7个和弦按钮(C、D、E、F、G、A、B)、一个文本域(textarea)以及原调选择器。将带和弦的《Fly me to the moon》歌词复制到文本域后,点击E按钮一次,和弦能正常升高一个调;但再次点击同一按钮时,和弦会继续升高,出现重复转调的异常。以下是基于Tonal库的实现代码:
问题原因
核心问题在于转调逻辑始终基于初始原调计算区间,而非当前文本的实际调性:
- 每次点击转调按钮时,代码用
window.originalKey(初始或选择的原调)和目标按钮的调性计算转调区间,然后直接修改文本域内容。 - 第一次点击后,文本域的和弦已经是转调后的结果,但
window.originalKey并没有更新,第二次点击时,依然用原调计算相同的区间,导致已经转调过的和弦被再次转调,出现持续升高的异常。 - 代码没有跟踪当前文本域的实际调性,每次操作都默认从原始调性开始转调。
解决方案
需要新增变量跟踪当前文本的实际调性,转调时基于当前调性计算区间,而非固定的原调:
- 保存原始歌词内容,避免原调切换时无法恢复初始状态。
- 新增
currentKey变量记录当前文本的调性,初始值与原调一致。 - 点击转调按钮时,计算
currentKey到目标调的区间,转调后更新currentKey为目标调。 - 切换原调选择器时,重置
currentKey为新的原调,并将文本域恢复为原始歌词。
修改后的完整代码
HTML
<script src="https://cdn.jsdelivr.net/npm/tonal/browser/tonal.min.js"></script> <!-- 原调选择器 --> <div> <label for="originalKey_id">原调:</label> <select id="originalKey_id"> <option value="C">C</option> <option value="D">D</option> <option value="E">E</option> <option value="F">F</option> <option value="G">G</option> <option value="A">A</option> <option value="B">B</option> </select> </div> <!-- 和弦按钮 --> <div class="pads_container"> <button class="pads" data-padd="C">C</button> <button class="pads" data-padd="D">D</button> <button class="pads" data-padd="E">E</button> <button class="pads" data-padd="F">F</button> <button class="pads" data-padd="G">G</button> <button class="pads" data-padd="A">A</button> <button class="pads" data-padd="B">B</button> </div> <textarea value="" id="textarea" rows="5" cols="25"> D G A D Fly me to the moon, let me play among the stars </textarea>
CSS
body { font-family: Arial, sans-serif; background-color: #333; color: #ccc; text-align: center; } .pads_container { position:relative; top:2vmin; display:flex; width:95%; left:1vmin; right:1vmin; } button.pads { padding: 1em; font-size: 3vmin; border: 1px solid #ccc; background: green; color: #fff; cursor: pointer; } button.pads.blue { background-color: #007bff; color: #fff; } button.pads:hover { background-color: #00a3d9; } #textarea { top:3em; position:relative; width:100%; background: #222; color: #ddd; }
JavaScript
const { transpose, Interval } = Tonal; // 保存原始歌词和当前调性 let originalLyrics = document.getElementById("textarea").value; let currentKey = 'C'; // 初始当前调性与原调一致 // 转调函数:基于当前调性计算区间 function transposeChords(text, interval) { return text.replace(/\b([A-G][#b]?)(m|min|maj|dim|dim7|aug|sus|add)?([0-9]*)\b/g, (match, root, type, number) => { let chordType = type; // 处理扩展和弦 if (number) { chordType = chordType || ''; if (number.match(/\d+/)) { chordType += number; } } try { const transposedRoot = transpose(root, interval); const transposedChord = transposedRoot + (chordType || ''); return transposedChord || match; } catch (e) { console.error('转调出错', match, e); return match; } }); } // 处理转调按钮点击 window.transpose = function(padd) { // 计算当前调性到目标调的区间 let interval = Interval.distance(currentKey, padd); let lyrics = document.getElementById("textarea").value; document.getElementById('textarea').value = transposeChords(lyrics, interval); // 更新当前调性为目标调 currentKey = padd; colorPads(padd); }; // 高亮当前选中的按钮 function colorPads(padd) { const buttons = document.querySelectorAll('.pads'); buttons.forEach(button => { if (button.dataset.padd === padd) { button.classList.add('blue'); } else { button.classList.remove('blue'); } }); } // 切换原调时重置状态 function originalKey_function() { const select_OrigKey = document.getElementById('originalKey_id'); window.originalKey = select_OrigKey.value; // 重置当前调性为新的原调 currentKey = window.originalKey; // 恢复文本域为原始歌词 document.getElementById("textarea").value = originalLyrics; colorPads(window.originalKey); } // 按钮点击事件监听 document.querySelectorAll('.pads').forEach(button => { button.addEventListener('click', () => { const padd = button.dataset.padd; window.transpose(padd); }); }); // 原调选择器变更事件监听 document.getElementById('originalKey_id').addEventListener('change', originalKey_function); // 初始化 window.originalKey = 'C'; colorPads(window.originalKey);
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

