Skip to content

Instantly share code, notes, and snippets.

@davidanton1d
Created August 22, 2014 07:04
Show Gist options
  • Save davidanton1d/75b481d01f91bc460a87 to your computer and use it in GitHub Desktop.
Save davidanton1d/75b481d01f91bc460a87 to your computer and use it in GitHub Desktop.
A Pen by David Forsberg.
<link href='http://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>
$("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();
});

Spritz clone v.2

Thought it would be a fun challenge to build something like the Spritz service. Skipped the science part and hacked away, and the result is pretty decent! It measures each character, and then calculates a visual centerpoint (slightly off to the left). Would be nice to rebuild this into a bookmarklet. (Feel free to fork it!)

A Pen by David Forsberg on CodePen.

License.

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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment