65.9K
CodeProject 正在变化。 阅读更多。
Home

高级 JavaScript 打字测试系统

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2009年3月16日

CPOL

2分钟阅读

viewsIcon

70069

downloadIcon

3886

轻松计算用户的打字速度和准确性!

Typing Test Systep

引言

这是一个较为高级的 JavaScript 打字测试系统,它允许您在您的网站上放置一个表格和一些代码块(例如,我们在招聘申请系统中使用的那样),并获得关于用户打字能力的详细统计信息,包括:

  • 每分钟字数 (WPM)
  • 准确率百分比
  • 总字数
  • 正确单词/错误单词 (错误数)
  • 完成时间
  • 以及根据需要添加更多信息

要查看系统实际应用,请访问 http://www.itsllc.us/TypingTest_1.asp;您也可以下载上述较低质量的版本,或按照下面的代码进行操作。

背景

这段代码的背景不多,除了它使用 JavaScript 作为基础来执行——是的,这是完全客户端脚本。您也可以在服务器端脚本语言或环境中(例如 ASP)中执行相同的操作;但是,它很可能会效率更低。

使用代码

要实现下面的代码,只需将代码(或复制粘贴——这更快,对吧)写入文档的 <HEAD> 部分——或者,如果您使用包含文件,您可以将它放在任何您想要的地方。

下面显示的是使系统正常工作的 JavaScript 代码。它相当简单明了,并且注释非常多——因此您不应该有任何理解上的问题;但如果您有任何疑问,请随时留下评论或与我联系。

<SCRIPT LANGUAGE="JavaScript">
//Holds whether or not we have already started the first typing test or now
// True = The test has already started
// False = The test hasn't started yet
var hasStarted = false;

//strToTest is an array object that holds various strings to be used as the base typing test
// - If you update the array, be sure to update the intToTestCnt
//   with the number of ACTIVE testing strings
var intToTestCnt = 4;
var strToTest = new Array("Innovative Technical Solutions, LLC (ITS) is a Native" + 
                " American owned business that was established in Paducah, " + 
                "Kentucky in April 2000. ITS is a certified and Small Disadvantaged " + 
                "Business by the U.S. Small Business Administration. Our headquarters " + 
                "are in Paducah, Kentucky and we have" + 
                " satellite offices located in Tennessee, " + 
                "Ohio, and Illinois. ITS is a leading" + 
                " edge Information Technology firm that " + 
                "is comprised of professionals with a broad" + 
                " range of experience in software " + 
                "development, high-speed imaging/scanning (TIFF, PDF, Text, " + 
                "and OCR capabilities), document management, records management," + 
                " relevance management, information security, environmental " + 
                "management, fire services management, fire protection " + 
                "engineering, and protective force expertise.",
                "The ITS Information Technology (IT) Team are experts " + 
                "in the identification, capture, indexing, microfilming, imaging, " + 
                "disposition, turnover, storage, and retrieval of records, " + 
                "and in the administration of records management databases. " + 
                "The types of records we have extensive experience in managing " + 
                "include waste management, hazardous waste, waste shipment, " + 
                "environmental compliance, environmental" + 
                " monitoring, feasibility studies, " + 
                "environmental work plans, cleanup actions, cemetery records, and " + 
                "various Federal laws such as CERCLA, Paper Reduction, " + 
                "Pollution Prevention, and Clean Water and Air.",
                "Collectively, the professional background of key ITS personnel " + 
                "demonstrates Fortune 100 Company experience that includes, " + 
                "but is not limited to, DOE, the Department of Defense, EPA, the " + 
                "Tennessee Valley Authority (TVA), Lockheed Martin Utility Services, " + 
                "Lockheed Martin Energy Systems, British Nuclear Fuels Limited, various " + 
                "state and local agencies, and USEC." + 
                " We consider the depth and magnitude " + 
                "of this experience as a proposition value " + 
                "to both our current and future customers.",
                "With our years of experience, we completely understand document " + 
                "management and technology. We know the importance of deadlines and " + 
                "we know the importance of production without error. We refuse to " + 
                "over-commit to deadlines that can not be met. Dedication to excellence " + 
                "in providing quality products and services through innovative ideas and " + 
                "processes. Steadfast resolve to a positive working environment that " + 
                "allows for the personal and professional development of all employees, " + 
                "while sustaining project service, and customer satisfaction. " + 
                "Commitment to the highest ethical management practices that " + 
                "recognize client satisfaction as a top priority.")
var strToTestType = "";

var checkStatusInt;

//General functions to allow for left and right trimming / selection of a string
function Left(str, n){
 if (n <= 0)
     return "";
 else if (n > String(str).length)
     return str;
 else
     return String(str).substring(0,n);
}
function Right(str, n){
    if (n <= 0)
       return "";
    else if (n > String(str).length)
       return str;
    else {
       var iLen = String(str).length;
       return String(str).substring(iLen, iLen - n);
    }
}

//beginTest Function/Sub initializes the test and starts
//the timers to determine the WPM and Accuracy
function beginTest()
{
 //We're starting the test, so set the variable to true
 hasStarted = true;
 
 //Generate a date value for the current time as a baseline
 day = new Date();
 
 //Count the number of valid words in the testing baseline string
 cnt = strToTestType.split(" ").length;
 
 //Set the total word count to the number of valid words that need to be typed
 word = cnt;
 
 //Set the exact time of day that the testing has started
 startType = day.getTime();
 
 //Disable the printing button (if used, in this download it's not included)
 document.getElementById("printB").disabled = true;
 
 calcStat();
 
 //Initialize the testing objects by setting the values 
 //of the buttons, what to type, and what is typed
 document.JobOp.start.value = "-- Typing Test Started --";
 document.JobOp.start.disabled = true;
 document.JobOp.given.value = strToTestType;
 document.JobOp.typed.value = "";
 
 //Apply focus to the text box the user will type the test into
 document.JobOp.typed.focus();
 document.JobOp.typed.select();
}

//User to deter from Copy and Paste, also acting as a testing protection system
// Is fired when the user attempts to click or apply focus
// to the text box containing what needs to be typed
function deterCPProtect()
{
 document.JobOp.typed.focus();
}

//The final call to end the test -- used when the 
//user has completed their assignment
// This function/sub is responsible for calculating
// the accuracy, and setting post-test variables
function endTest()
{
 //Clear the timer that tracks the progress of the test, since it's complete
 clearTimeout(checkStatusInt);
 
 //Initialize an object with the current date/time
 //so we can calculate the difference 
 eDay = new Date();
 endType = eDay.getTime();
 totalTime = ((endType - startType) / 1000)
 
 //Calculate the typing speed by taking the number of valid words 
 //typed by the total time taken and multiplying it by one minute in seconds (60)
 //***** 1A ********************************************************** 1A *****
 //We also want to disregard if they used a double-space after 
 //a period, if we didn't then it would throw everything after the space off
 //Since we are using the space as the seperator for words; it's the 
 //difference between "Hey. This is me.
 //" versus "Hey. This is me." and
 //Having the last three words reporting as wrong/errors due 
 //to the double space after the first period, see?
 //****************************************************************************
 wpmType = Math.round(((document.JobOp.typed.value.replace(/  /g, 
                  " ").split(" ").length)/totalTime) * 60)
 
 //Set the start test button label and enabled state
 document.JobOp.start.value = ">> Re-Start Typing Test <<";
 document.JobOp.start.disabled = false;
 
 //Flip the starting and stopping buttons around since the test is complete
 document.JobOp.stop.style.display="none";
 document.JobOp.start.style.display="block";
 
 //Declare an array of valid words for what NEEDED to be typed and what WAS typed
 //Again, refer to the above statement on removing the double spaces globally (1A) 
 var typedValues = document.JobOp.typed.value.replace(/  /g, " ");
 var neededValues = Left(document.JobOp.given.value, 
         typedValues.length).replace(/  /g, " ").split(" ");
 typedValues = typedValues.split(" ");
  
 //Disable the area where the user types the test input
 document.JobOp.typed.disabled=true;
 
 //Declare variable references to various statistical layers
 var tErr = document.getElementById("stat_errors");
 var tscore = document.getElementById("stat_score");
 var tStat = document.getElementById("stat_wpm");
 var tTT = document.getElementById("stat_timeleft");
 
 var tArea = document.getElementById("TypeArea");
 var aArea = document.getElementById("AfterAction");
 var eArea = document.getElementById("expectedArea");
  
 //Initialize the counting variables for the good valid words and the bad valid words
 var goodWords = 0;
 var badWords = 0;
 
 //Declare a variable to hold the error words 
 //we found and also a detailed after action report
 var errWords = "";
 var aftReport = "<b>Detailed Summary:</b><br>" + 
                 "<font color=\"DarkGreen\">";
 
 //Enable the printing button
 document.getElementById("printB").disabled = false;
 
 //Loop through the valid words that were possible 
 //(those in the test baseline of needing to be typed)
 var str;
 var i = 0;
 for (var i = 0; i < word; i++)
 {
  //If there is a word the user typed that is 
  //in the spot of the expected word, process it
  if (typedValues.length > i)
  {
   //Declare the word we expect, and the word we recieved
   var neededWord = neededValues[i];
   var typedWord = typedValues[i];
   
   //Determine if the user typed the correct word or incorrect
   if (typedWord != neededWord)
   {
    //They typed it incorrectly, so increment the bad words counter
    badWords = badWords + 1;
    errWords += typedWord + " = " + neededWord + "\n";
    aftReport += "<font color=\"Red\"><u>" + 
                 neededWord + "</u></font> ";
   }
   else
   {
    //They typed it correctly, so increment the good words counter
    goodWords = goodWords + 1;
    aftReport += neededWord + " ";
   }
  }
  else
  {
   //They didn't even type this word, so increment the bad words counter
   //Update: We don't want to apply this penalty because they may have chosen to end the test
   //    and we only want to track what they DID type and score off of it.
   //badWords = badWords + 1;
  } 
 }
 
 //Finalize the after action report variable with the typing summary 
 //at the beginning (now that we have the final good and bad word counts)
 aftReport += "</font>";
 aftReport = "<b>Typing Summary:</b><br>You typed " + 
            (document.JobOp.typed.value.replace(/  /g, " ").split(" ").length) + 
            " words in " + totalTime + " seconds, a speed of about " + 
            wpmType + " words per minute.\n\nYou also had " + badWords + 
            " errors, and " + goodWords + " correct words, giving scoring of " + 
            ((goodWords / (goodWords+badWords)) * 100).toFixed(2) + 
            "%.<br><br>" + aftReport;
 
 //Set the statistical label variables with what 
 //we found (errors, words per minute, time taken, etc) 
 tErr.innerText = badWords + " Errors";
 tStat.innerText= (wpmType-badWords) + " WPM / " + wpmType + " WPM";
 tTT.innerText = totalTime.toFixed(2) + " sec. elapsed";
 
 //Calculate the accuracy score based on good words typed 
 //versus total expected words -- and only show the percentage as ###.##
 tscore.innerText = ((goodWords / (goodWords+badWords)) * 100).toFixed(2) + "%";
 
 //Flip the display of the typing area and the 
 //expected area with the after action display area
 aArea.style.display = "block";
 tArea.style.display = "none";
 eArea.style.display = "none";
 
 //Set the after action details report to the summary as 
 //we found; and in case there are more words found than typed
 //Set the undefined areas of the report to a space,
 //otherwise we may get un-needed word holders
 aArea.innerHTML = aftReport.replace(/undefined/g, " ");
 
 //Notify the user of their testing status via a JavaScript Alert
 //Update: There isn't any need in showing this popup now that 
 //we are hiding the typing area and showing a scoring area
 //alert("You typed " + (document.JobOp.typed.value.split(" ").length) + 
 //        " words in " + totalTime + " seconds, a speed of about " + 
 //        wpmType + " words per minute.\n\nYou also had " + badWords + 
 //        " errors, and " + goodWords + " correct words, giving scoring of " + 
 //       ((goodWords / (goodWords+badWords)) * 100).toFixed(2) + "%.");
}

//calcStat is a function called as the user types 
//to dynamically update the statistical information
function calcStat()
{
//If something goes wrong, we don't want to cancel the test -- so fallback 
//error proection (in a way, just standard error handling)
try {
 //Reset the timer to fire the statistical update function again in 250ms
 //We do this here so that if the test has ended (below) we can cancel and stop it
 checkStatusInt=setTimeout('calcStat();',250);
 
 //Declare reference variables to the statistical information labels
 var tStat = document.getElementById("stat_wpm");
 var tTT = document.getElementById("stat_timeleft");
 
 var tProg = document.getElementById("stProg");
 var tProgt = document.getElementById("thisProg");
 
 var tArea = document.getElementById("TypeArea");
 var aArea = document.getElementById("AfterAction");
 var eArea = document.getElementById("expectedArea");
   
 //Refer to 1A (above) for details on why we are removing the double space
 var thisTyped = document.JobOp.typed.value.replace(/  /g, " ");
 
 //Create a temp variable with the current time of day to calculate the WPM
 eDay = new Date();
 endType = eDay.getTime();
 totalTime = ((endType - startType) / 1000)

 //Calculate the typing speed by taking the number of valid words 
 //typed by the total time taken and multiplying it by one minute in seconds (60)
 wpmType = Math.round(((thisTyped.split(" ").length)/totalTime) * 60)

 //Set the words per minute variable on the statistical information block
 tStat.innerText=wpmType + " WPM";
 
 //The test has started apparantly, so disable the stop button
 document.JobOp.stop.disabled = false;
  
 //Flip the stop and start button display status
 document.JobOp.stop.style.display="block";
 document.JobOp.start.style.display="none";
 
 //Calculate and show the time taken to reach this point 
 //of the test and also the remaining time left in the test
 //Colorize it based on the time left (red if less 
 //than 5 seconds, orange if less than 15)
 if (Number(60-totalTime) < 5)
 {
  tTT.innerHTML="<font color=\"Red\">" + String(totalTime.toFixed(2)) + 
                " sec. / " + String(Number(60-totalTime).toFixed(2)) + 
                " sec.</font>";
 }
 else
 {
  if (Number(60-totalTime) < 15)
  {
   tTT.innerHTML="<font color=\"Orange\">" + 
                 String(totalTime.toFixed(2)) + " sec. / " + 
                 String(Number(60-totalTime).toFixed(2)) + " sec.</font>";
  }
  else
  {
   tTT.innerHTML=String(totalTime.toFixed(2)) + " sec. / " + 
                 String(Number(60-totalTime).toFixed(2)) + " sec.";
  }
 }
  
 //Determine if the user has typed all of the words expected
 if ((((thisTyped.split(" ").length)/word)*100).toFixed(2) >= 100)
 {
  tProg.width="100%";
  
  tProgt.innerText = "100%";
 }
 else
 {
  //Set the progress bar with the exact percentage of the test completed
  tProg.width=String((((thisTyped.split(" ").length)/word)*100).toFixed(2))+"%";
  
  tProgt.innerText = tProg.width;
 }
 
 //Determine if the test is complete based on them
 //having typed everything exactly as expected
 if (thisTyped.value == document.JobOp.given.value)
 {
  endTest();
 }
 
 //Determine if the test is complete based on whether or not they have 
 //typed exactly or exceeded the number of valid words (determined by a space)
 if (word <= (thisTyped.split(" ").length))
 {
  endTest();
 }
 
 //Check the timer; stop the test if we are at or exceeded 60 seconds
 if (totalTime >= 60)
 {
  endTest();
 }
 
//Our handy error handling
} catch(e){};
}

//Simply does a check on focus to determine if the test has started
function doCheck()
{
 if (hasStarted == false)
 {
  //The test has not started, but the user is typing already -- maybe we should start?
  beginTest(); //Yes, we should -- consider it done!
 }
}
</SCRIPT>

将 JavaScript 插入页面后,您只需要组件的代码(这很快就完成了,而且没有使用 CSS——虽然有点混乱,但您应该能理解,并且可以根据自己的意愿重写或组织代码)。

<div align="center">
 <table border="0" cellpadding="0" cellspacing="0" width="100%">
  <tbody>
  <tr>
   <td style="border-bottom: 2px solid #354562; padding: 4px" class="titlec">
   <input disabled="" id="printB" onclick="window.print();" 
       type="button" value="Print Results" name="printB" 
       style="float: right; font-size: 8pt; font-family: Arial" />
   <input onclick="document.getElementById('AfterAction').style.display='none';
              document.getElementById('expectedArea').style.display='block';
              document.getElementById('typeArea').style.display='block';
              document.JobOp.typed.value='';document.JobOp.typed.disabled=false;
              randNum = Math.floor((Math.random() * 10)) % intToTestCnt;
              strToTestType = strToTest[randNum];
              document.JobOp.given.value = strToTestType;" 
         type="button" value="New Test" 
         name="newtest" 
         style="float: right; font-size: 8pt; font-family: Arial" />ITS 
   Typing Test System</td>
  </tr>
 </tbody>
 </table>
</div>
<table border="0" cellpadding="0" 
           cellspacing="0" width="100%">
 <tbody>
 <tr>
  <td style="border-bottom: 1px dotted #860E36; padding: 4px" 
     class="titlea" background="Images/Lt_Red_Back.gif" width="460">
  Accurately and precisely evaluate your typing speed and accuracy.</td>
  <td style="border-bottom: 1px dotted #860E36; padding: 4px" 
    class="titlea" background="Images/Lt_Red_Back.gif" width="190">
  <p align="right">v1.0</p>
  </td>
 </tr>
 <tr>
  <td style="padding: 4px" class="bodya" colspan="2">
  <form name="JobOp">
   <table border="0" cellpadding="5" width="100%">
    <tbody>
    <tr>
     <td>
     <table border="0" cellpadding="5" width="100%">
      <tbody>
      <tr>
       <td align="center" 
         style="border-left: 1px solid #344270; border-right: 2px solid #344270; 
                border-top: 1px solid #344270; border-bottom: 2px solid #344270; 
                padding: 5px; background-color: #CED3E8" 
         background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Net / 
       Gross WPM</font></b></td>
       <td align="center" 
          style="border-left: 1px solid #344270; 
                 border-right: 2px solid #344270; border-top: 1px solid #344270; 
                 border-bottom: 2px solid #344270; padding: 5px; 
                 background-color: #CED3E8" 
          background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Entry 
       Errors</font></b></td>
       <td align="center" 
           style="border-left: 1px solid #344270; border-right: 2px solid #344270; 
                  border-top: 1px solid #344270; border-bottom: 2px solid #344270; 
                  padding: 5px; background-color: #CED3E8" 
           background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Accuracy</font></b></td>
       <td align="center" 
          style="border-left: 1px solid #344270; border-right: 2px solid #344270; 
                 border-top: 1px solid #344270; border-bottom: 2px solid #344270; 
                 padding: 5px; background-color: #CED3E8" 
          background="Images/Blue_Back.gif">
       <b><font face="Arial" size="2" color="#FFFFFF">Elapsed 
       / Remaining</font></b></td>
      </tr>
      <tr>
       <td align="center"><font size="2" face="Arial">
       <div id="stat_wpm">
        Not Started</div>
       </font></td>
       <td style="border-left: 1px dotted #8794C7; border-right: 1px dotted #8794C7; 
                  border-top-width: 1px; border-bottom-width: 1px" align="center">
       <font size="2" face="Arial">
       <div id="stat_errors">
        Waiting...</div>
       </font></td>
       <td style="border-left-width: 1px; border-right: 1px dotted #8794C7; 
                  border-top-width: 1px; border-bottom-width: 1px" align="center">
       <font size="2" face="Arial">
       <div id="stat_score">
        Waiting...</div>
       </font></td>
       <td align="center"><font size="2" face="Arial">
       <div id="stat_timeleft">
        0:00</div>
       </font></td>
      </tr>
     </tbody>
     </table>
     </td>
    </tr>
    <tr>
     <td style="border-left-width: 1px; border-right-width: 1px; 
                border-top: 1px solid #344270; border-bottom-width: 1px">
     <div id="expectedArea" style="display:block">
      <p style="margin-top: 0; margin-bottom: 0">
      <font color="#7A88C0" face="Arial" size="1">
      <textarea name="given" cols="53" rows="10" 
        wrap="on" onfocus="deterCPProtect();" 
        style="width: 100%; border: 1px solid #344270; padding: 2px; 
               font-family:Arial; font-size:9pt">
        Click on the button below to start the typing test. 
        What you will be expected to type will appear here.
      </textarea></font>
      </p>
     </div>
     </td>
    </tr>
    <tr>
     <td>
     <p align="center" style="margin-top: 0; margin-bottom: 2px">
     <input type="button" 
        value="&gt;&gt; Start Typing Test &lt;&lt;" name="start" 
        onclick="beginTest()" 
        style="display:block; border-left:1px solid #293358; border-right:2px 
           solid #293358; border-top:1px solid #293358; border-bottom:2px solid #293358; 
           width: 100%; background-color: #9BB892; color:#FFFFFF; 
           background-image:url('Images/Green_Back.gif')" /></p>
     <p align="center" style="margin-top: 0; margin-bottom: 0">
     <input disabled="" type="button" 
       value="&gt;&gt; End Typing Test &lt;&lt;" 
       name="stop" onclick="endTest()" 
       style="display:none; border-left:1px solid #293358; border-right:2px 
                solid #293358; border-top:1px solid #293358; border-bottom:2px 
                solid #293358; width: 100%; background-color: #F05959; 
                color:#FFFFFF; background-image:url('Images/Red_Back.gif')" /></p>
     </td>
    </tr>
    <tr>
     <td style="font-family: Arial; font-size: 9pt">
     <div id="typeArea" style="display:block">
      <table border="0" width="100%" cellspacing="1">
       <tbody>
       <tr>
        <td style="border: 1px solid #9CA8D1; background-color: #EAECF4">
        <div align="left">
         <table id="stProg" border="0" 
                     width="0%" cellspacing="1">
          <tbody>
          <tr>
           <td style="border: 1px solid #344270; background-color: #8F9BCB; 
                       font-family:Arial; font-size:8pt; color:#FFFFFF" 
                       align="right" background="Images/Blue_Back.gif">
           <div id="thisProg">
            0%</div>
           </td>
          </tr>
         </tbody>
         </table>
        </div>
        </td>
       </tr>
      </tbody>
      </table>
      <p style="margin-top: 0; margin-bottom: 0">
      <font color="#7A88C0" face="Arial" size="1">
      <textarea onkeypress="doCheck();" onkeydown="//calcStat()" 
         name="typed" cols="53" rows="10" wrap="on" 
         style="width: 100%; border: 1px solid #344270; padding: 2px; font-family:Arial; 
                 font-size:9pt"></textarea></font>
      </p>
     </div>
     <div id="afterAction" style="display:none">
     </div>
     </td>
    </tr>
    <script>
 randNum = Math.floor((Math.random() * 10)) % intToTestCnt;
 strToTestType = strToTest[randNum];
 
 document.JobOp.given.value = strToTestType;
 document.JobOp.typed.focus();
 </script>
   </tbody>
   </table>
  </form>
  </td>
 </tr>
</tbody>
</table>

历史

  • 2009/03/16 - 初始发布。
  • 2009/03/17 - 修改系统以提高准确性;添加了计时器;并添加了详细的战后报告。
© . All rights reserved.