如何根据表格复选框数量计算百分比并修复环形进度条变量未定义问题?
问题:任务管理器环形进度条与复选框关联时变量未定义错误
我正在制作一款任务管理器,需实现与复选框关联的环形进度条。目前添加行按钮功能正常,但关联进度条时频繁出现变量未定义错误。
原代码
HTML
<body> <h1>Task Manager</h1> <!--Table for Weekly Habits--> <h2>Weekly Habits</h2> <!--Circular Progress Tracker for Daily habits--> <div class="container"> <div class="circular-progress"> <span class="progress-value" onchange="valCheckCount(this)">0%</span> </div> <span class="text">Daily Habits</span> </div> <div id="root"> </div> <!--Creating table format for Dialy Tasks--> <div id="dailyTable"> <table id="table1" class="dailyT"> <!--first table header for title and days--> <tr class="unit-table"> <td> <label for="firstRow">Daily Habits</label> </td> <td> <label for="firstRow">1</label> </td> <td> <label for="firstRow">2</label> </td> <td> <label for="firstRow">3</label> </td> <td> <label for="firstRow">4</label> </td> <td> <label for="firstRow">5</label> </td> <td> <label for="firstRow">6</label> </td> <td> <label for="firstRow">7</label> </td> <td> <label for="firstRow">8</label> </td> <td> <label for="firstRow">9</label> </td> <td> <label for="firstRow">10</label> </td> <td> <label for="firstRow">11</label> </td> <td> <label for="firstRow">12</label> </td> <td> <label for="firstRow">13</label> </td> <td> <label for="firstRow">14</label> </td> <td> <label for="firstRow">15</label> </td> <td> <label for="firstRow">16</label> </td> <td> <label for="firstRow">17</label> </td> <td> <label for="firstRow">18</label> </td> <td> <label for="firstRow">19</label> </td> <td> <label for="firstRow">20</label> </td> <td> <label for="firstRow">21</label> </td> <td> <label for="firstRow">22</label> </td> <td> <label for="firstRow">23</label> </td> <td> <label for="firstRow">24</label> </td> <td> <label for="firstRow">25</label> </td> <td> <label for="firstRow">26</label> </td> <td> <label for="firstRow">27</label> </td> <td> <label for="firstRow">28</label> </td> <td> <label for="firstRow">29</label> </td> <td> <label for="firstRow">30</label> </td> <td> <label for="firstRow">31</label> </td> </tr> </table> <br> <button type="button" id="addRowButton" onClick="addRowFunction()">+</button> </div> </body>
JavaScript
//CHECKING IF THE CHECKBOXES HAVE BEEN CHECKED OR UNCHECKED AND THEN RETURNING THE NUMBER //AS VALCHECKCOUNT var valCheckCount = 0; function update() { let checkboxCounter = document.querySelectorAll('input[name="dailyCheck"]:checked'); valCheckCount = checkboxCounter.length; return valCheckCount; }; let checkboxCounter = document.querySelectorAll('input[name="dailyCheck"]'); checkboxCounter.forEach(function(checkboxD) { checkboxD.addEventListener("change", function(e) { update(); }); }); function addRowFunction() { var progressLength = document.querySelectorAll('input[name="dailyCheck"]'); var parentTable = document.getElementById("table1"); var myTd, myInputCheck, myInputText; var myTr = document.createElement('tr'); myTr.setAttribute('class', 'unit-table'); for (var j = 0; j < 1; j++) { myTd = document.createElement('td'); myInputText = document.createElement('input'); myInputText.setAttribute('type', 'text'); myTd.appendChild(myInputText); myTr.appendChild(myTd); } for (var i = 0; i < 31; i++) { myTd = document.createElement('td'); myInputCheck = document.createElement('input'); myInputCheck.setAttribute('type', 'checkbox'); myInputCheck.setAttribute('name', 'dailyCheck'); myInputCheck.addEventListener('change', function(e) { update(); }); myTd.appendChild(myInputCheck); myTr.appendChild(myTd); } parentTable.appendChild(myTr); return progressLength.length; } //ALL OF THE CODE USED TO CREATE THE CIRCULAR PROGRESS BAR AND TAKES THE INPUT FROM //VALCHECKCOUNT AND THE FUNCTION ABOVE. let circularProgress = document.querySelector(".circular-progress"), progressValue = document.querySelector(".progress-value"); let progressStartValue = 0, progressEndValue = valCheckCount, speed = 20; let progressLength = document.querySelectorAll('input[name="dailyCheck"]').length; let progress = setInterval(() => { progressStartValue++; let percentageAnswer = Math.round((valCheckCount / progressLength) * 100); progressValue.textContent = `${percentageAnswer}%` circularProgress.style.background = `conic-gradient(#7d2ae8 ${percentageAnswer * 3.6}deg, #ededed 0deg)` if (progressStartValue == percentageAnswer) { clearInterval(progress); } }, speed);
错误原因分析
- 进度条仅初始化一次:原代码的
setInterval只在页面加载时执行一次,后续复选框状态变化或添加新行后,进度条不会自动更新。 - 总复选框数量未动态更新:
progressLength仅在初始化时获取,添加新行后不会重新计算总复选框数量,导致百分比计算错误。 - 无效的
onchange事件:HTML中progress-value的onchange事件无效,<span>元素没有change事件,且调用的是变量而非函数。 - 变量作用域问题:定时器变量
progress的作用域导致后续更新时无法正确清除旧定时器。
修复后的代码
HTML(仅移除无效事件)
<body> <h1>Task Manager</h1> <!--Table for Weekly Habits--> <h2>Weekly Habits</h2> <!--Circular Progress Tracker for Daily habits--> <div class="container"> <div class="circular-progress"> <span class="progress-value">0%</span> </div> <span class="text">Daily Habits</span> </div> <div id="root"></div> <!--Creating table format for Daily Tasks--> <div id="dailyTable"> <table id="table1" class="dailyT"> <!--first table header for title and days--> <tr class="unit-table"> <td><label for="firstRow">Daily Habits</label></td> <td><label for="firstRow">1</label></td> <td><label for="firstRow">2</label></td> <td><label for="firstRow">3</label></td> <td><label for="firstRow">4</label></td> <td><label for="firstRow">5</label></td> <td><label for="firstRow">6</label></td> <td><label for="firstRow">7</label></td> <td><label for="firstRow">8</label></td> <td><label for="firstRow">9</label></td> <td><label for="firstRow">10</label></td> <td><label for="firstRow">11</label></td> <td><label for="firstRow">12</label></td> <td><label for="firstRow">13</label></td> <td><label for="firstRow">14</label></td> <td><label for="firstRow">15</label></td> <td><label for="firstRow">16</label></td> <td><label for="firstRow">17</label></td> <td><label for="firstRow">18</label></td> <td><label for="firstRow">19</label></td> <td><label for="firstRow">20</label></td> <td><label for="firstRow">21</label></td> <td><label for="firstRow">22</label></td> <td><label for="firstRow">23</label></td> <td><label for="firstRow">24</label></td> <td><label for="firstRow">25</label></td> <td><label for="firstRow">26</label></td> <td><label for="firstRow">27</label></td> <td><label for="firstRow">28</label></td> <td><label for="firstRow">29</label></td> <td><label for="firstRow">30</label></td> <td><label for="firstRow">31</label></td> </tr> </table> <br> <button type="button" id="addRowButton" onClick="addRowFunction()">+</button> </div> </body>
JavaScript
// 全局变量存储定时器实例,方便后续清除 let progressInterval = null; const circularProgress = document.querySelector(".circular-progress"); const progressValue = document.querySelector(".progress-value"); // 获取当前选中的复选框数量 function getCheckedCount() { return document.querySelectorAll('input[name="dailyCheck"]:checked').length; } // 获取总复选框数量 function getTotalCheckboxes() { return document.querySelectorAll('input[name="dailyCheck"]').length; } // 更新环形进度条 function updateProgressBar() { // 先清除旧的定时器 if (progressInterval) { clearInterval(progressInterval); } const checkedCount = getCheckedCount(); const totalCount = getTotalCheckboxes(); // 避免除以0的情况 const percentage = totalCount === 0 ? 0 : Math.round((checkedCount / totalCount) * 100); let currentProgress = 0; progressInterval = setInterval(() => { if (currentProgress >= percentage) { clearInterval(progressInterval); progressInterval = null; } else { currentProgress++; progressValue.textContent = `${currentProgress}%`; circularProgress.style.background = `conic-gradient(#7d2ae8 ${currentProgress * 3.6}deg, #ededed 0deg)`; } }, 20); } // 给现有复选框绑定事件 function bindCheckboxEvents() { document.querySelectorAll('input[name="dailyCheck"]').forEach(checkbox => { checkbox.addEventListener("change", updateProgressBar); }); } // 添加新行函数 function addRowFunction() { const parentTable = document.getElementById("table1"); const myTr = document.createElement('tr'); myTr.setAttribute('class', 'unit-table'); // 添加文本输入列 const textTd = document.createElement('td'); const textInput = document.createElement('input'); textInput.setAttribute('type', 'text'); textTd.appendChild(textInput); myTr.appendChild(textTd); // 添加31个复选框列 for (let i = 0; i < 31; i++) { const checkTd = document.createElement('td'); const checkInput = document.createElement('input'); checkInput.setAttribute('type', 'checkbox'); checkInput.setAttribute('name', 'dailyCheck'); checkInput.addEventListener('change', updateProgressBar); checkTd.appendChild(checkInput); myTr.appendChild(checkTd); } parentTable.appendChild(myTr); // 添加新行后更新进度条 updateProgressBar(); } // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', () => { bindCheckboxEvents(); updateProgressBar(); });
改动说明
- 拆分功能函数:将获取选中数量、总数量、更新进度条等功能拆分为独立函数,提升代码可读性和可维护性。
- 动态更新进度条:每次复选框变化或添加新行时,重新计算百分比并启动新的定时器,确保进度条实时更新。
- 清除旧定时器:在更新进度条前清除旧的定时器,避免多个定时器同时运行导致进度数值混乱。
- 移除无效事件:删除HTML中
<span>的无效onchange事件。 - 初始化时机:使用
DOMContentLoaded确保页面元素加载完成后再绑定事件和初始化进度条。
内容的提问来源于stack exchange,提问作者LevelUp
相关产品推荐
相关产品推荐

