/* 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 += " "; $("#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(" ", " ") + "</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(" ", " ") + "</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(); });

!