123

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <link href='https://fonts.googleapis.com/css?family=Roboto:400,500' rel='stylesheet' type='text/css'>
<textarea id="src">Spritz has been working for nearly 3 years in “Stealth Mode” to perfect our reading methodology. We’ve learned a lot in that time and developed our findings into the core technologies that you see here today. As an introduction to how and why Spritz works let’s start off with a few basics about reading.

</textarea>
<label>Words per minute: </label>
<input type="text" id="wpm" value="500" />
<button id="goBtn">Go!</button>
<div id="target"></div>
<div id="letters"></div>
<div id="playerWrap">
  <div class='line topline'></div>
  <div class='line'></div>
  <div id="player"></div>
</div>
            
          
!
            
              body {
    font-family: Roboto, Helvetica, Arial, sans-serif;
  color: #333;
}
div{
  display: inline;
  font-family: Roboto, Helvetica, Arial, sans-serif;
  font-size: 30px;
  color: #333;
}
textarea {
  width: 100%;
}
#letters div{
  display: none;
}
.red {
  color: #e00;
}
#target {
  display: none;
}
#playerWrap{
  position: relative;
  clear: both;
  padding: 15px 0;
  border: 2px solid #555;
  width: 300px;
  height: 35px;
  display: block;
  
}
#playerWrap .line {
  height: 7px;
  width: 2px;
  position: absolute;
  bottom: 0px;
  background: red;
  margin-left: -1px;
  left: 100px;
}
#playerWrap .line.topline {
  top: 0px;

}
            
          
!
            
              /*
Licenced under Creative Commons Attribution (CC-BY)
License: http://creativecommons.org/licenses/by/4.0/

Original work by David Forsberg, 2014.
Portfolio: https://codepen.io/davidanton1d/
Website: http://davidanton.se/
Contact: @davidanton1d

Feel free to contact me for work, offers, questions or feedback. 
It would also be fun to see what you've built with this, ping me.

Disclaimer: "Spritz", "ORP" and the color "red" might be owned by Spritz Inc.
*/


$("document").ready(function(){
  var SpritzItClass = function(){
    var spritzIt = {};





    spritzIt.spritzIt = function(){
      //Get text as an array
      var words = $("#src").val().split(" ");
      var processedlist = [];
      //Clear earlier output, not used anymore
      this.clearOut();
      //Analyze each word to get their ORP - optimal recognition point
      for( var word in words ){
        processedlist.push( this.analyze(words[word]) );
      }
      //Show the text to the user in a timely fashion!
      this.render(processedlist, parseInt( $("#wpm").val() ) );
    };





    spritzIt.clearOut = function(){
      $("#target").html("");
    };





    spritzIt.out = function(a1, a2, red){
      if (red === undefined){
        red = null;
      }

      var indexOfWeightedMedian = this.findMiddle(a2.slice());

      var html = "";
      for (var i = 0; i < a1.length; i++){
        html += "<span";
        if( i == indexOfWeightedMedian){
          html += " style='color:red'";
        }
        html += ">" + a1[i] + "</span>";
      }
      html += "&nbsp;";
      $("#target").append(html);
    };





    spritzIt.analyze = function(val){
      var obj = {};
      
      var a1 = val.split("");
      var a2 = [];
      var alphabet = "ABCDEFGHIJKLMNOPQRSTUVXYZÅÄÖabcdefghijklmnopqrstuvxyzåäö 0123456789".split("");
      //console.log(alphabet);
      for (var i = 0; i < a1.length; i++){
        //a2.push(alphabet.indexOf(a1[i]) + 1);
        if(alphabet.indexOf(a1[i]) >= 0){
          a2.push(this.measurements[alphabet.indexOf(a1[i])]);
        }else{
          a2.push(this.getMeasurementFor(a1[i]));
        }
      }
      
      obj.word = val;
      obj.wordArr = a1.slice();
      obj.measurements = a2.slice();
      obj.middle = this.findMiddle(a2.slice());
      
     this. out(a1, a2);
      return obj;
    };





    spritzIt.measureLetters = function(){
      var alphabet = "ABCDEFGHIJKLMNOPQRSTUVXYZÅÄÖabcdefghijklmnopqrstuvxyzåäö 0123456789".split("");
      var measurements = [];
      for (var i = 0; i < alphabet.length; i++){
        $("#letters").append(
          "<div id=\"alpha-" + alphabet[i].replace(" ", "nbsp") + "\">" + 
          alphabet[i].replace(" ", "&nbsp;") + 
          "</div>");
        measurements.push( $("#alpha-" + alphabet[i].replace(" ", "nbsp")).width() );
      }
      return measurements;  





    };
    spritzIt.getMeasurementFor = function(char){
      var id = Math.round(Math.random()*100000);
      $("#letters").append(
        "<div id=\"alpha-" + id + "\">" + 
        char.replace(" ", "&nbsp;") + 
        "</div>");
      return $("#alpha-" + id).width();
    };





    spritzIt.findMiddle = function(arr){

      /*
        After analyzing a word, we get an array like this:
        
        N, a,t,i,o,n,a,l,e,n <- letter
        12,7,4,4,8,8,7,4,7,8 <- character width in px

        Starting with 0, we're gonna add from the left until it's >0,
        and then we're gonna subtract from the right until it's <0. 
        Like this:

        N, a,t,i,o,n,a,l,e,n
        12,7,4,4,8,8,7,4,7,8
        +12 Result: 12

        When we've added the first "12", we remove it from the array.
        Our result is now 12, so we'll subtract the number to the far right; 8:

        ,7,4,4,8,8,7,4,7,8
        -8  Result: 4

        And so it continues until the balanced middle is found.

        ,7,4,4,8,8,7,4,7,
        -7  Result: -3

        ,7,4,4,8,8,7,4,,
        +7  Result: 4

        ,,4,4,8,8,7,4,,
        -4  Result: 0

        ,,4,4,8,8,7,,,
        +4  Result: 4

        ,,,4,8,8,7,,,   <-- due to a bug, my calculations stopped at this step. At first I just fixed it, 
        -7  Result: -34     but then I changed it back since it made it look more like the Spritz original. Also, it felt better to read.

        ,,,4,8,8,,,,
        +4  Result: 1

        ,,,,8,8,,,,
        -8  Result: -7

        ,,,,8,,,,,
        +8  Result: 1

        Verifying that this i actually the center, meaning the sum of each of the two sides are as close as they can be:
        12,7,4,4,*8*,8,7,4,7,8
        12+7+4+4 = 27
        8+7+4+7+8 = 34
        diff 7

        Comparing to the result if center is shifted one step to the left...
        12+7+4 = 23
        8+8+7+4+7+8 = 42
        diff 19

        ...and to the right, the diff is minimal in the calculated position.
        12+7+4+4+8 = 45
        7+4+7+8 = 26
        diff 19
      */

      var res = 0;
      var pos = 0;
      
      if (arr.length == 1) {
        return 0;
      }
      
      //The bug referred to in the explanation above, is that arr.length is updated after each splice. 
      //Thus, as "i" is growing bigger, the limit is shrinking, making the loop run only half as many times as first intended. 
      for ( var i = 0; i < arr.length; i++ ){
        if(res <= 0){
          //If result <= 0, add from the left
          res += parseInt(arr[0]);
          arr.splice(0,1);
          pos += 1;
        }else{
          //If result > 0, subtract from the right
          res -= parseInt(arr[arr.length-1]);
          arr.splice(arr.length-1,1);    
        }
      }
      //Return position of the "optimal reading point".
      return pos;
    };




    //Recursive function, showing the next word in the array
    spritzIt.nextWord = function(obj, index, wordsPerMinute, pause){
      
      /*
      Positioning the ORP
      */
      //calculate offset
      var margin = 100;
      for (var i = 0; i <= obj[index].middle; i++){
        margin -= obj[index].measurements[i];
        
        //center the ORP letter
        if( i == obj[index].middle){
          margin += parseInt(obj[index].measurements[i])/2;
        }
      }

      //Print word
      var word = "";
      for (var char in obj[index].wordArr){
        if(char == obj[index].middle){
          word += "<span class=\"red\">" + obj[index].wordArr[char] + "</span>";    
        }else{
          word += obj[index].wordArr[char];
        }
      }

      //If pausing (blanking the window after a punctuation), clear the output  
      if(pause){
        $("#player").html("");
      }else{
        $("#player").html("<div style='margin-left:"+margin+"px;'>" + word + "</div>" );
      }
      
      //set timeout for next word
      //check word for pausing characters (:;-,.)
      var longPauseChars = ".;";
      var shortPauseChars = ",:-";
      var timeout = Math.round(60000 / wordsPerMinute);
      var timeoutUnit = Math.round(60000 / wordsPerMinute);
      
      //Slow down on the first two words
      if(index === 0){
        timeout = timeout * 3;
      }  
      if(index == 1){
        timeout = timeout * 2;
      }
      
      //Idea: set a word-length-multiplier about here. 
      //Hard to find good settings, though.
      timeout = timeout * 0.5 + (timeout / 3 * (obj[index].word.length + 2) / 4);
      
      /*
      Punctuations
      */
      //In spritz, a punctuation is followed by an "empty" word. Let's do that.
      var makeAPause = false;
      
      //long punctuation
      if( obj[index].word.match(/.*[.;!?].*/g) ){
        timeout = timeout + timeoutUnit * 1.5;
        //In spritz, a punctuation is followed by an "empty" word. Let's do that.
        makeAPause = true;
      }
      //short punctuation
      if( obj[index].word.match(/.*[\(\),:-].*/g) ){
        timeout = timeout + timeoutUnit * 1;
      }
      //Did we just make a pause? Don't pause again, and back the index up a notch. Also, make sure to take that pause.
      if(pause){
        makeAPause = false;
        index = index - 1;
        timeout = timeout + timeoutUnit * 2;
      }

      totalTime += timeout;
      
      //Show next word
      var that = this;
      if(index < obj.length-1){
        //Regular loop
        setTimeout(function(){
          that.nextWord(obj, index + 1, wordsPerMinute, makeAPause);
        }, timeout);
      }else{
        //Last word, clear out
        setTimeout(function(){
          //Empty player
          $("#player").html("");
          console.log("Total time: " + totalTime + "ms for " + obj.length + "words. That's " + 60000 / (totalTime / obj.length) + "wpm.");
        }, timeout * 2);
      }
    };




    //Starts the word animation
    spritzIt.render = function(obj, wordsPerMinute){
      if(wordsPerMinute === undefined){
        wordsPerMinute = 300;
      }
      this.nextWord(obj, 0, wordsPerMinute);
    };

    var totalTime = 0;
    return spritzIt;
  };

  var mySpritz = new SpritzItClass();

  //Button event handler
  $("#goBtn").click(function(){
    mySpritz.spritzIt();
  });

  //Analyze the font used, by putting letters in divs and measuring them
  mySpritz.measurements = mySpritz.measureLetters();
  
  //Start player on page load
  mySpritz.spritzIt();

});

            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console