如何在循环绑定事件时让每个Add按钮仅对应指定exercise元素?
Workout Builder:Add按钮与对应训练项关联问题解决方案
问题描述
我正在开发一个Workout Builder,左侧列是包含描述、示例视频和Add按钮的exercise元素列表,点击Add按钮需将对应exercise移至右侧exercise-builder列。希望通过for循环为每个按钮绑定事件,而非给每个按钮硬编码ID,但目前无法实现addButton[i]与exercise[i]的对应关联,现有代码中moveToBuilder函数固定操作exercise[0],求优雅解决方案。
现有代码
HTML代码
<body> <div class="header"> <h1 class="header-title">WORKOUT BUILDER</h1> <img id="header-img" src="img/workoutheader.jpg"> </div> <!-- This is the big list of exercises. --> <div class="list-of-exercises"> <!-- This is an individual exercise. --> <div class="exercise" id="power-clean"> <div class="exercise-name"> <h2 class="name" id="power-clean-name">Power Clean</h2> </div> <!-- Here's the add button for this specific exercise. As you can see I have a specific ID here where I could hardcore each button, but I'm looking for a more elegant solution. --> <button class="add-button" id="add-button-power-clean">Add</button> <!-- This is the rest of the exercise content, like a sample GIF, sets and reps, etc. --> <div class="exercise-contents" id="power-clean-contents"> <iframe src="https://giphy.com/embed/c10FJ0dpZ5CEF03JTO" width="480" height="270" frameBorder="0" class="giphy-embed" allowFullScreen></iframe><p><a href="https://giphy.com/gifs/crossfit-power-clean-lift-c10FJ0dpZ5CEF03JTO"></a></p> <div class="sets-and-reps-form" id="sets-and-reps-form-power-clean"> <label for="sets">Sets</label> <input type="number" class="sets" id="sets-power-clean" fname="sets"> <br> <label for="reps">Reps</label> <input type="number" class="reps" id="reps-power-clean" fname="reps"> <input class="sets-and-reps-submit" id="sets-and-reps-submit-power-clean" type="submit" value="Submit"> </div> </div> </div> <!-- Here's the start of the next exercise. --> <div class="exercise" id="back-squat"> <div class="exercise-name"> <h2 class="name" id="back-squat-name">Back Squat</h2> </div> <button class="add-button" id="add-button-back-squat">Add</button> <div class="exercise-contents" id="back-squat-contents"> <div class="tenor-gif-embed" data-postid="24035556" data-share-method="host" data-aspect-ratio="1.49533" data-width="100%"><a href="https://tenor.com/view/workouts-squats-gif-24035556">Workouts Squats GIF</a>from <a href="https://tenor.com/search/workouts-gifs">Workouts GIFs</a></div> <script type="text/javascript" async src="https://tenor.com/embed.js"></script> <div class="sets-and-reps-form" id="sets-and-reps-form-back-squat"> <label for="sets">Sets</label> <input type="number" class="sets" id="sets-back-squat" fname="sets"> <br> <label for="reps">Reps</label> <input type="number" class="reps" id="reps-back-squat" fname="reps"> <input class="sets-and-reps-submit" id="sets-and-reps-submit-back-squat" type="submit" value="Submit"> </div> </div> </div> </div> <!-- This is the div where I want people to be able to "add" the different exercises. --> <div class="exercise-builder"> <div class="final-readout"> <p id="intensity-score">Intensity Score: 0</p> <p id="ETA">Estimated Time To Complete Workout: 0</p> </div> </div> <script src="script.js" async defer></script> </body>
原JavaScript代码
let moveToBuilder = function(){ let exerciseBuilder = document.querySelector(".exercise-builder"); let exercise = document.querySelectorAll(".exercise"); // So here I'm just using 0 as the array test value to get it working. But like how could I turn the 0 into something that pairs with the various Submit Button array values? So when I hit the "Power Clean" add button, it only adds Power Clean, etc. exerciseBuilder.insertBefore(exercise[0], exerciseBuilder.firstChild); } for (var i = 0; i < addButton.length; i++) { addButton[i].addEventListener('click', moveToBuilder, false); }
优雅解决方案
方法1:DOM遍历定位对应训练项(推荐)
无需维护索引,直接通过点击按钮的DOM层级找到所属的exercise容器,逻辑清晰且兼容动态新增的训练项:
// 提前获取目标容器,避免重复查询DOM const exerciseBuilder = document.querySelector(".exercise-builder"); function moveToBuilder(event) { // 从点击的按钮向上查找最近的.exercise父元素 const targetExercise = event.target.closest(".exercise"); // 将训练项插入到builder区域的最前方 exerciseBuilder.insertBefore(targetExercise, exerciseBuilder.firstChild); } // 给所有add按钮绑定事件 document.querySelectorAll(".add-button").forEach(button => { button.addEventListener('click', moveToBuilder); });
优点:
- 不需要依赖按钮和训练项的索引顺序,代码鲁棒性强
- 新增训练项时无需修改JS代码,按钮自动生效
方法2:闭包解决循环绑定的索引问题
如果坚持使用for循环,利用let的块级作用域特性(或立即执行函数)保存每个循环的正确索引:
const exerciseBuilder = document.querySelector(".exercise-builder"); const exercises = document.querySelectorAll(".exercise"); const addButtons = document.querySelectorAll(".add-button"); // 使用let声明i,确保每个循环迭代的i值独立 for (let i = 0; i < addButtons.length; i++) { addButtons[i].addEventListener('click', function() { exerciseBuilder.insertBefore(exercises[i], exerciseBuilder.firstChild); }); }
注意:原代码中用var声明i会导致所有事件处理函数共享同一个i值(循环结束后i为按钮总数),使用let可以解决这个问题。
方法3:事件委托(最优动态场景方案)
如果后续会通过JS动态添加训练项,事件委托是最高效的方案——仅给父容器绑定一次事件,所有子按钮的点击事件都会冒泡到父容器处理:
const exerciseBuilder = document.querySelector(".exercise-builder"); const exercisesContainer = document.querySelector(".list-of-exercises"); // 给训练项列表容器绑定点击事件 exercisesContainer.addEventListener('click', function(event) { // 判断点击的元素是add按钮 if (event.target.matches(".add-button")) { const targetExercise = event.target.closest(".exercise"); exerciseBuilder.insertBefore(targetExercise, exerciseBuilder.firstChild); } });
优点:
- 仅需绑定一次事件,性能更优
- 动态新增的训练项按钮自动具备点击功能,无需重新绑定事件
内容的提问来源于stack exchange,提问作者BrandonLearnsToCode
相关产品推荐
相关产品推荐

