├── ie6.html ├── moltenleading.min.js ├── index.html ├── readme.md └── moltenleading.js /ie6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Molten Leading for IE6+7 7 | 8 | 24 | 25 | 26 |
27 |

Mauris vel neque sit amet nunc gravida congue sed sit amet purus vel neque sit amet nunc gravida congue sed sit amet purus

28 |

Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec.

29 |
30 | 31 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /moltenleading.min.js: -------------------------------------------------------------------------------- 1 | /*! Molten Leading, plain JavaScript version, v1.20 */ 2 | (function(f,e,k){function h(a,c){this.body=e.body;this.selector=a;this.ticking=!1;this.testel=null;this.eminpx=this.reminpx=0;this.options=g.extend({},h.defaultOptions,c);"querySelector"in e&&e.querySelector(this.selector)?this.elements=e.querySelectorAll(this.selector):e.getElementsByTagName(this.selector)&&(this.elements=e.getElementsByTagName(this.selector))}f.requestAnimFrame=function(){return f.requestAnimationFrame||f.webkitRequestAnimationFrame||f.mozRequestAnimationFrame||function(a){f.setTimeout(a, 3 | 1E3/60)}}();var g={extend:function(a){var c=arguments;this.forEach(c,function(b){b=c[b];for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d])});return a},bind:function(a,c){return function(){a.apply(c,arguments)}},forEach:function(a,c,b){var d=a.length,f,e;for(e=0;e=b||ed.maxline)e=d.maxline;c.style.lineHeight=e},getRems:function(){this.origFontSize=this.body.style.fontSize;this.body.style.fontSize="100%";this.body.appendChild(this.testel);this.reminpx=parseFloat(this.testel.offsetWidth,10);this.body.contains(this.testel)&&this.body.removeChild(this.testel);this.body.style.fontSize=this.origFontSize},getEms:function(a,c){c.appendChild(this.testel);this.eminpx=parseFloat(this.testel.offsetWidth,10); 7 | this.body.contains(this.testel)&&this.testel.parentNode.removeChild(this.testel)},testStyles:function(a){a=a.style;a.visibility="hidden";a.position="absolute";a.fontSize="1em";a.width="1em";a.padding="0";a.border="0"},requestTick:function(){this.ticking||(requestAnimFrame(g.bind(this.update,this)),this.ticking=!0)}};h.defaultOptions={minline:1.2,maxline:1.8,minwidth:320,maxwidth:768,units:"px"};f.moltenLeading=function(a,c){var b=new h(a,c);b.init();return b}})(window,document); 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Molten Leading · Vanilla JavaScript version 7 | 8 | 40 | 41 | 42 |
43 |

Mauris vel neque sit amet nunc gravida congue sed sit amet purus vel neque sit amet nunc gravida congue sed sit amet purus

44 |

Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec.

45 |
46 |

Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec.

47 |

Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus.

48 |

Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit. Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec.

49 |
50 |
51 |

Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec. Aliquam erat volutpat. Mauris vel neque sit amet nunc gravida congue sed sit amet purus. Quisque lacus quam, egestas ac tincidunt a, lacinia vel velit.

52 |
53 |
54 | 55 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Molten Leading (plain JS version) 2 | 3 | Manually adjusting ```line-height``` for optimum readability across a bunch of media queries is kind of a pain. With Molten Leading you can set a minimum width at which the adjustment starts, a maximum element width where it stops, and a minimum and maximum line height to adjust through. 4 | 5 | All the work here is based on [@Wilto’s Molten-Leading](https://github.com/Wilto/Molten-Leading) jQuery version of the plugin. 6 | 7 | #### Features: 8 | 9 | * Automatically adjust line-height based on element width for optimal readability. 10 | * Works in all major desktop and mobile browsers, including IE 6 and up. 11 | * Uses requestAnimationFrame for the best possible performance. 12 | * Free to use in both commercial and non-commercial projects. 13 | * Doesn’t require external JavaScript libraries. 14 | * Supports "px", "em" and "rem" CSS units. 15 | * Weighs only 1.17Kb minified and Gzip’ed. 16 | * Supports multiple instances. 17 | 18 | 19 | ## Demo 20 | 21 | * There’s a demo here, try resizing your browser window. 22 | 23 | ## Usage instructions 24 | 25 | Following the steps below you should be able to get everything up and running. 26 | 27 | 1. Link files: 28 | ```html 29 | 30 | ``` 31 | 32 | 2. Hook up the plugin: 33 | ```html 34 | 35 | 38 | ``` 39 | 40 | 4. Customizable options: 41 | ```javascript 42 | moltenLeading("h1", { 43 | minline: 1.2, // Integer: Minimum line-height for the element (multiplied by the element's font-size) 44 | maxline: 1.8, // Integer: Maximum line-height for the element (multiplied by the element's font-size) 45 | minwidth: 320, // Integer: Minimum element width where the adjustment starts 46 | maxwidth: 768, // Integer: Maximum element width where the adjustment stops 47 | units: "px" // String: CSS units used for the min & max widths, can be "px", "em" or "rem" 48 | }); 49 | ``` 50 | 51 | ## Public methods 52 | 53 | There’s currently one public method, ```refresh()```. Refresh allows you to manually call Molten Leading’s update methods that calculate and update the line-height of specified element(s). Example of the usage: 54 | 55 | ```javascript 56 | var myLeading = moltenLeading("h1", { 57 | minline: 1.2, 58 | maxline: 1.8 59 | }); 60 | 61 | // Then somewhere later on: 62 | myLeading.refresh(); 63 | ``` 64 | 65 | 66 | ## Notes 67 | 68 | * Tested to be working all the way down to IE6. side note: if you need to support IE6 & 7 you’re gonna have to use simple "tag selectors," since the plugin uses getElementsByTagName as a fallback if querySelector isn’t supported. 69 | * Built progressive enhancement in mind, so the plugin will silently fail when a browser doesn’t support certain selector (only IE6 & 7). 70 | * When using ```em``` units, keep in mind that ems are relative to the currently specified font-size of the parent element, so the width might not always be what you think it is. 71 | * The ```rem``` units are relative to the `````` element’s font-size instead, so they are a bit easier to grasp. 72 | * Full credits go to both Wilto who wrote the orinal plugin and to Tim Brown for the original idea. 73 | 74 | 75 | ## Possible performance bottlenecks 76 | 77 | * Don’t use CSS ```transition: all``` inside Molten Leading enabled containers, will make window.resize performance slower. 78 | 79 | 80 | ## Running on localhost 81 | 82 | 1. Clone this repo by running ```git clone git@github.com:arielsalminen/Molten-Leading.git``` 83 | 2. If you’re using Mac OS X, open the "Molten-Leading" folder and run ```python -m SimpleHTTPServer 8000``` 84 | 3. Done! Now view the project at [http://localhost:8000](http://localhost:8000) 85 | 86 | 87 | ## Tested on the following platforms 88 | 89 | * iOS 4.2.1+ 90 | * Android 2.3+ 91 | * Windows Phone 7.5+ 92 | * Blackberry 7.0+ 93 | * Blackberry Tablet 2.0+ 94 | * Jolla 95 | * Firefox Phone 96 | * Kindle 3.3+ 97 | * Symbian Belle 98 | * Symbian S40 Asha 99 | * webOS 2.0+ 100 | * Windows XP 101 | * Windows 7 102 | * Mac OS X 103 | 104 | 105 | ## Changelog 106 | 107 | `1.20` (2014-07-09) - Adds support for ```px```, ```em``` and ```rem``` units in addition to performance optimization. 108 | 109 | `1.10` (2014-06-26) - Performance improvements. Handles debouncing of events now via requestAnimationFrame, which removes the need for the previous threshold setting. Adds also public methods. 110 | 111 | `1.03` (2014-06-24) - Fixes debouncing of events and optimizes performance (adds also an option to control the debounce timing). 112 | 113 | `1.02` (2014-06-21) - Adds minified version. 114 | 115 | `1.01` (2014-06-21) - Removes unnecessary code. 116 | 117 | `1.00` (2014-06-19) - First release. 118 | -------------------------------------------------------------------------------- /moltenleading.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Molten Leading, plain JavaScript version, v1.20 3 | * https://github.com/arielsalminen/Molten-Leading 4 | */ 5 | (function (window, document, undefined) { 6 | "use strict"; 7 | 8 | /** 9 | * Shim layer for requestAnimationFrame with setTimeout fallback 10 | */ 11 | window.requestAnimFrame = (function () { 12 | return window.requestAnimationFrame || 13 | window.webkitRequestAnimationFrame || 14 | window.mozRequestAnimationFrame || 15 | function (callback) { window.setTimeout(callback, 1000 / 60); }; 16 | })(); 17 | 18 | /** 19 | * Utilities 20 | */ 21 | var util = { 22 | 23 | /** 24 | * Merge object into target 25 | * 26 | * @param {target} the object for other objects to be merged into 27 | * @return {Object} the merged object (a reference to target) 28 | */ 29 | extend : function (target) { 30 | var args = arguments; 31 | 32 | this.forEach(args, function (i) { 33 | var source = args[i]; 34 | for (var property in source) { 35 | if (source.hasOwnProperty(property)) { 36 | target[property] = source[property]; 37 | } 38 | } 39 | }); 40 | 41 | return target; 42 | }, 43 | 44 | /** 45 | * Simple bind method 46 | * 47 | * @param {function} callback function 48 | * @param {object} context 49 | */ 50 | bind : function (fn, context) { 51 | return function () { 52 | fn.apply(context, arguments); 53 | } 54 | }, 55 | 56 | /** 57 | * forEach method 58 | * 59 | * @param {array} array to loop through 60 | * @param {function} callback function 61 | * @param {object} context 62 | */ 63 | forEach : function (array, callback, context) { 64 | var length = array.length; 65 | var cont, i; 66 | 67 | for (i = 0; i < length; i++) { 68 | cont = callback.call(context, i, array[i]); 69 | if (cont === false) { 70 | break; // Allow early exit 71 | } 72 | } 73 | }, 74 | 75 | /** 76 | * Executes a function when window resize ends 77 | * 78 | * @param {function} callback function 79 | * @param {integer} milliseconds to wait 80 | * @param {object} context 81 | */ 82 | onResizeEnd : function (fn, threshold, context) { 83 | var timer; 84 | return function () { 85 | threshold = threshold || 250; 86 | clearTimeout(timer); 87 | timer = setTimeout(function () { 88 | fn.apply(context, arguments); 89 | }, threshold); 90 | } 91 | }, 92 | 93 | /** 94 | * Adds event listeners 95 | * 96 | * @param {element} the element which the even should be added to 97 | * @param {event} the event to be added 98 | * @param {function} the function that will be executed 99 | */ 100 | addListener : function (el, evt, fn, bubble) { 101 | if ("addEventListener" in el) { 102 | el.addEventListener(evt, fn, bubble); 103 | 104 | } else if ("attachEvent" in el) { 105 | 106 | // check if the callback is an object and contains handleEvent 107 | if (typeof fn === "object" && fn.handleEvent) { 108 | el.attachEvent("on" + evt, function () { 109 | 110 | // Bind fn as this 111 | fn.handleEvent.call(fn); 112 | }); 113 | } else { 114 | el.attachEvent("on" + evt, fn); 115 | } 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * The MoltenLeading object 122 | * 123 | * @param {String} CSS selector for target elements 124 | * @param {Object} MoltenLeading options 125 | * @constructor 126 | */ 127 | function MoltenLeading(selector, options) { 128 | this.body = document.body; 129 | this.selector = selector; 130 | this.ticking = false; 131 | this.testel = null; 132 | this.reminpx = 0; 133 | this.eminpx = 0; 134 | 135 | this.options = util.extend({}, MoltenLeading.defaultOptions, options); 136 | 137 | // If querySelector is supported, use that 138 | if ("querySelector" in document && document.querySelector(this.selector)) { 139 | this.elements = document.querySelectorAll(this.selector); 140 | 141 | // If querySelector is not supported, use getElementsByTagName (for IE7 & 6) 142 | } else if (document.getElementsByTagName(this.selector)) { 143 | this.elements = document.getElementsByTagName(this.selector); 144 | } 145 | } 146 | 147 | MoltenLeading.prototype = { 148 | constructor : MoltenLeading, 149 | 150 | /** 151 | * Intializes the instance 152 | * 153 | * @function 154 | */ 155 | init : function () { 156 | if (this.options.units !== "px") { 157 | this.testel = document.createElement("div"); 158 | this.testStyles(this.testel); 159 | this.throttledUpdate = util.onResizeEnd(this.doUnitConversions, 100, this); 160 | util.addListener(window, "resize", this.throttledUpdate, false); 161 | this.doUnitConversions(); 162 | } 163 | 164 | // Update leading on window resize 165 | util.addListener(window, "resize", this, false); 166 | this.update(); 167 | }, 168 | 169 | /** 170 | * Refresh all calculations 171 | * 172 | * @public 173 | */ 174 | refresh : function () { 175 | this.doUnitConversions(); 176 | this.update(); 177 | }, 178 | 179 | /** 180 | * Updates leading when needed 181 | * 182 | * @function 183 | * @private 184 | */ 185 | update : function () { 186 | util.forEach(this.elements, this.calcLeading, this); 187 | this.ticking = false; 188 | }, 189 | 190 | /** 191 | * Attach this as the event listener 192 | * 193 | * @function 194 | * @private 195 | */ 196 | handleEvent : function () { 197 | this.requestTick(); 198 | }, 199 | 200 | /** 201 | * Do unit conversions 202 | * 203 | * @function 204 | * @private 205 | */ 206 | doUnitConversions : function () { 207 | if (this.options.units === "rem") { 208 | this.getRems(); 209 | } else if (this.options.units === "em") { 210 | util.forEach(this.elements, this.getEms, this); 211 | } 212 | 213 | // …aand finally get up to date measures 214 | this.update(); 215 | }, 216 | 217 | /** 218 | * Calculates the appropriate leading and updates DOM 219 | * 220 | * @param {index} the index of the element 221 | * @param {Element} the element to calculate the leading for 222 | * @return {string} the calculated leading for the element 223 | * @private 224 | */ 225 | calcLeading : function (i, el) { 226 | var elwidth = 0; 227 | var o = this.options; 228 | 229 | if (o.units === "rem") { 230 | elwidth = el.offsetWidth / this.reminpx; 231 | } else if (o.units === "em") { 232 | elwidth = el.offsetWidth / this.eminpx; 233 | } else { 234 | elwidth = el.offsetWidth; 235 | } 236 | 237 | var widthperc = parseInt((elwidth - o.minwidth) / (o.maxwidth - o.minwidth) * 100, 10); 238 | var linecalc = o.minline + (o.maxline - o.minline) * widthperc / 100; 239 | 240 | if (widthperc <= 0 || linecalc < o.minline) { 241 | linecalc = o.minline; 242 | } else if (widthperc >= 100 || linecalc > o.maxline) { 243 | linecalc = o.maxline; 244 | } 245 | 246 | el.style.lineHeight = linecalc; 247 | }, 248 | 249 | /** 250 | * Calculates the value of 1rem in pixels 251 | * 252 | * @return {string} the value of 1rem in pixels 253 | * @private 254 | */ 255 | getRems : function () { 256 | this.origFontSize = this.body.style.fontSize; 257 | 258 | // Reset body to ensure the correct value is returned 259 | this.body.style.fontSize = "100%"; 260 | this.body.appendChild(this.testel); 261 | 262 | // Cache the value 263 | this.reminpx = parseFloat(this.testel.offsetWidth, 10); 264 | 265 | if (this.body.contains(this.testel)) { 266 | this.body.removeChild(this.testel); 267 | } 268 | 269 | this.body.style.fontSize = this.origFontSize; 270 | }, 271 | 272 | /** 273 | * Calculates the value of 1em inside certain element in pixels 274 | * 275 | * @param {index} the index of the element 276 | * @param {Element} the element to calculate the em from 277 | * @return {string} the value of 1em in pixels 278 | * @private 279 | */ 280 | getEms : function (i, el) { 281 | el.appendChild(this.testel); 282 | 283 | // Cache the value 284 | this.eminpx = parseFloat(this.testel.offsetWidth, 10); 285 | 286 | if (this.body.contains(this.testel)) { 287 | this.testel.parentNode.removeChild(this.testel); 288 | } 289 | }, 290 | 291 | /** 292 | * Gives styles for the units test elements 293 | * 294 | * @param {element} the element to give the styles to 295 | * @private 296 | */ 297 | testStyles : function (el) { 298 | var css = el.style; 299 | css.visibility = "hidden"; 300 | css.position = "absolute"; 301 | css.fontSize = "1em"; 302 | css.width = "1em"; 303 | css.padding = "0"; 304 | css.border = "0"; 305 | }, 306 | 307 | /** 308 | * Calls rAF if it hasn't been already called, 309 | * ensuring events don't get stacked 310 | * 311 | * @private 312 | */ 313 | requestTick : function () { 314 | if (!this.ticking) { 315 | requestAnimFrame(util.bind(this.update, this)); 316 | this.ticking = true; 317 | } 318 | } 319 | 320 | }; 321 | 322 | /** 323 | * Default options 324 | */ 325 | MoltenLeading.defaultOptions = { 326 | minline: 1.2, // Integer: Minimum line-height for the element (multiplied by the element's font-size) 327 | maxline: 1.8, // Integer: Maximum line-height for the element (multiplied by the element's font-size) 328 | minwidth: 320, // Integer: Minimum element width where the adjustment starts 329 | maxwidth: 768, // Integer: Maximum element width where the adjustment stops 330 | units: "px" // String: CSS units used for the min & max widths, can be "px", "em" or "rem" 331 | }; 332 | 333 | /** 334 | * Expose a public-facing API 335 | * 336 | * @param {String} CSS selector for target elements 337 | * @param {Object} the options 338 | */ 339 | function expose(selector, options) { 340 | var mtl = new MoltenLeading(selector, options); 341 | mtl.init(); 342 | return mtl; 343 | } 344 | window.moltenLeading = expose; 345 | 346 | }(window, document)); 347 | --------------------------------------------------------------------------------