Skip to content

Instantly share code, notes, and snippets.

@charliepark
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save charliepark/a124b768c4db4ff3404d to your computer and use it in GitHub Desktop.
Save charliepark/a124b768c4db4ff3404d to your computer and use it in GitHub Desktop.
This is one approach; another is here: https://gist.github.com/charliepark/80f4bea797d17adc44e0
var BalancedMeasures = {
minimumScreenSizeToRunOn: 992,
classNameToMeasure: "graf--pullquote",
breakCode: "<br class='bmjs'>",
charactersToTriggerOrphanScreen: /[,\.\?\!]/,
run: function(classToRunOn){
if(this.onSmallerScreen()){return;}
var elements = this.getElementsToMeasure(classToRunOn);
Log.toConsole(elements.length);
for(var i = 0; i < elements.length; i++){
arrayOfSubstrings = this.getArrayOfBalancedSubstrings(elements[i]);
Log.toConsole(arrayOfSubstrings);
arrayOfSubstrings = this.screenForOrphans(arrayOfSubstrings);
elements[i].innerHTML = arrayOfSubstrings.join(this.breakCode);
}
},
// onSmallerScreen
//
// The goal of BalancedMeasures is to create a more square-ish text block for pullquotes.
// The CSS for tablets and phones already handles this well, so we only apply this library
// on larger (desktop) screens.
//
// returns a boolean
onSmallerScreen: function(){
var body = document.getElementsByTagName("body")[0];
var bodyWidth = parseFloat(window.getComputedStyle(body, null).getPropertyValue('width'));
return bodyWidth < this.minimumScreenSizeToRunOn;
},
getElementsToMeasure: function(classToRunOn){
if(typeof(classToRunOn) === 'undefined'){classToRunOn = this.classNameToMeasure}
return document.getElementsByClassName(classToRunOn);
},
// getArrayOfBalancedSubstrings
//
//
//
// Note: this assumes that ">" and "<" in text is properly encoded (&lt;, &gt;)
//
// Example:
//
//
// returns an array of strings
getArrayOfBalancedSubstrings: function(element){
var idealLength = this.getIdealCharactersPerLineCount(element);
Log.toConsole("The idealLength is " + idealLength);
var string = element.innerHTML.split(this.breakCode).join(" ").replace(" "," ");
var isDomContent = false;
var counter = 0; // this only counts actual text (no DOM code)
var substringStart = 0;
var substringLength = 0; // this counts all characters, including DOM code
var substrings = []; // what we'll be adding to and then returning
for(var i = 0; i < string.length; i++){
substringLength++;
// We DON'T want to factor DOM element data into our substring's length.
if(isDomContent && string[i] != ">"){continue;}
if(string[i] === "<"){isDomContent = true; continue;}
if(string[i] === ">"){isDomContent = false;continue;}
// At this point, we're only dealing with actual text from the element.
// Are we looking at the very last character in the whole string? If so, push this substring over to the array.
if(i == string.length - 1){substrings.push( string.substr( substringStart, substringLength ).trim() );continue;}
counter++;
// We haven't yet hit our idealLength, so we can just move on to the next character.
if( counter < idealLength ){continue;}
// At this point, we're only dealing with text that's longer than our desired string length.
// Specifically, we only care about breaking at spaces. Otherwise, the function just moves along.
if(string[i] === " "){
substrings.push( string.substr( substringStart, substringLength ).trim() );
substringStart += substringLength;
substringLength = 0;
counter = 0;
}
}
return substrings;
},
getIdealCharactersPerLineCount: function(element){
var text = element.textContent; Log.toConsole("text: "+text);Log.toConsole("text.length: "+text.length);
var numberOfLines = Math.round(this.getNumberOfLinesInElement(element)); Log.toConsole("numberOfLines: "+numberOfLines);
return text.length / numberOfLines;
},
getNumberOfLinesInElement: function(element){
var height = element.offsetHeight; Log.toConsole("height: "+height);
height = height - parseFloat( window.getComputedStyle(element, null).getPropertyValue('padding-top') );
height = height - parseFloat( window.getComputedStyle(element, null).getPropertyValue('padding-bottom') );
Log.toConsole("height: "+height);
var lineHeight = this.getLineHeightOfElement(element); Log.toConsole("lineHeight: "+lineHeight);
return height / lineHeight;
},
getLineHeightOfElement: function(element){
var lineHeight = window.getComputedStyle(element, null).getPropertyValue('line-height');
if(lineHeight === "normal"){
lineHeight = ( 1.14 * parseFloat( window.getComputedStyle(element, null).getPropertyValue('font-size') ) );
}
Log.toConsole("lineHeight: "+lineHeight);
return parseFloat(lineHeight);
},
screenForOrphans: function(arrayOfSubstrings){
for(var i = 0; i < arrayOfSubstrings.length - 1; i++){
var lineAsArray = arrayOfSubstrings[i].split(" ");
var orphanIsPresent = this.orphanIsPresent(lineAsArray);
if(orphanIsPresent){
lastWord = lineAsArray.pop();
arrayOfSubstrings[i] = lineAsArray.join(" ").trim();
arrayOfSubstrings[i+1] = lastWord+" "+arrayOfSubstrings[i+1];
}
}
return arrayOfSubstrings;
},
orphanIsPresent: function(lineAsArray){
var lastCharacterOfSecondToLastWord = lineAsArray.slice(-2)[0].slice(-1);
return lastCharacterOfSecondToLastWord.match(this.charactersToTriggerOrphanScreen);
}
};
Log = {
toConsole: function(string){
// console.log(string); // uncomment to get sanity checks in the console; remove `Log.toConsole()`s before minifying!
}
};
//// TODO
//// - run on every page load
//// - run on every resize
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment