├── .gitignore ├── History.md ├── component.json ├── examples └── example1.html ├── clamp.min.js ├── README.markdown └── clamp.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.0.1 / 2014-11-30 3 | ================== 4 | 5 | * Support for component. 6 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clampjs", 3 | "repo": "DemocracyOS/Clamp.js", 4 | "description": "Clamp.js cuts off a long text and replace by elipsis (...)", 5 | "keywords": [ 6 | "clamp", 7 | "text" 8 | ], 9 | "version": "0.0.1", 10 | "scripts": [ 11 | "clamp.js" 12 | ], 13 | "main": "clamp.js", 14 | "license": "WTFPL" 15 | } 16 | -------------------------------------------------------------------------------- /examples/example1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Clamp.js Example 1 6 | 7 | 8 | 13 |

Curabitur magna lectus, cursus at euismod sed, aliquet a lectus. Vivamus ac magna purus, in eleifend eros.

14 |
15 |

Quisque pellentesque dui sit amet nisl sollicitudin faucibus. Ut est mauris, vestibulum ullamcorper feugiat consectetur, ullamcorper eu lorem. Mauris congue convallis felis sit amet scelerisque. Etiam sed sodales orci. Ut porta massa commodo turpis dictum suscipit. Quisque sit amet metus eget arcu lacinia pellentesque. Aliquam aliquam tortor nec odio tempus non pharetra ipsum ultricies. Aenean pretium tristique orci vitae tempus. Sed vitae quam ut felis aliquam blandit nec et odio. Mauris at magna odio, at mattis risus. Phasellus eu enim mi, sit amet hendrerit est. Integer egestas consectetur blandit. Etiam odio nibh, fermentum non venenatis nec, dictum vel ligula. Quisque rutrum imperdiet arcu at ultricies. Integer in diam ut elit euismod posuere et id sapien. Cras ligula leo, hendrerit vitae sagittis nec, commodo sed lacus. In aliquam pretium mauris sed ullamcorper. Phasellus fermentum iaculis massa ac condimentum. Ut nisl turpis, vulputate in rhoncus sed,

16 | 17 |
18 | 19 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /clamp.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Clamp.js 0.5.1 3 | * 4 | * Copyright 2011-2013, Joseph Schmitt http://joe.sh 5 | * Released under the WTFPL license 6 | * http://sam.zoy.org/wtfpl/ 7 | */ 8 | (function(){window.$clamp=function(c,d){function s(a,b){n.getComputedStyle||(n.getComputedStyle=function(a,b){this.el=a;this.getPropertyValue=function(b){var c=/(\-([a-z]){1})/g;"float"==b&&(b="styleFloat");c.test(b)&&(b=b.replace(c,function(a,b,c){return c.toUpperCase()}));return a.currentStyle&&a.currentStyle[b]?a.currentStyle[b]:null};return this});return n.getComputedStyle(a,null).getPropertyValue(b)}function t(a){a=a||c.clientHeight;var b=u(c);return Math.max(Math.floor(a/b),0)}function x(a){return u(c)* 9 | a}function u(a){var b=s(a,"line-height");"normal"==b&&(b=1.2*parseInt(s(a,"font-size")));return parseInt(b)}function l(a){if(a.lastChild.children&&0 8 | //Single line 9 | $clamp(myHeader, {clamp: 1}); 10 | 11 | //Multi-line 12 | $clamp(myHeader, {clamp: 3}); 13 | 14 | //Auto-clamp based on available height 15 | $clamp(myParagraph, {clamp: 'auto'}); 16 | 17 | //Auto-clamp based on a fixed element height 18 | $clamp(myParagraph, {clamp: '35px'}); 19 | 20 | 21 | The $clamp method is the primary way of interacting with Clamp.js, and it takes two 22 | arguments. The first is the element which should be clamped, and the second is an 23 | Object with options in JSON notation. 24 | 25 | 26 | # Options 27 | 28 | **clamp** _(Number | String | 'auto')_. This controls where and when to clamp the 29 | text of an element. Submitting a number controls the number of lines that should 30 | be displayed. Second, you can submit a CSS value (in px or em) that controls the 31 | height of the element as a String. Finally, you can submit the word 'auto' as a string. 32 | Auto will try to fill up the available space with the content and then automatically 33 | clamp once content no longer fits. This last option should only be set if a static 34 | height is being set on the element elsewhere (such as through CSS) otherwise no 35 | clamping will be done. 36 | 37 | **useNativeClamp** _(Boolean)_. Enables or disables using the native -webkit-line-clamp 38 | in a supported browser (ie. Webkit). It defaults to true if you're using Webkit, 39 | but it can behave wonky sometimes so you can set it to false to use the JavaScript- 40 | based solution. 41 | 42 | **truncationChar** _(String)_. The character to insert at the end of the HTML element 43 | after truncation is performed. This defaults to an ellipsis (…). 44 | 45 | **truncationHTML** _(String)_. A string of HTML to insert before the truncation character. 46 | This is useful if you'd like to add a "Read more" link or some such thing at the end of 47 | your clamped node. 48 | 49 | **splitOnChars** _(Array)_. Determines what characters to use to chunk an element into 50 | smaller pieces. Version 0.1 of Clamp.js would always remove each individual character 51 | to check for fit. With v0.2, you now have an option to pass a list of characters it 52 | can use. For example, it you pass an array of ['.', ',', ' '] then it will first remove 53 | sentences, then remove comma-phrases, and remove words, and finally remove individual 54 | characters to try and find the correct height. This will lead to increased performance 55 | and less looping when removing larger pieces of text (such as in paragraphs). The default 56 | is set to remove sentences (periods), hypens, en-dashes, em-dashes, and finally words 57 | (spaces). Removing by character is always enabled as the last attempt no matter what 58 | is submitted in the array. 59 | 60 | **animate** _(Boolean)_. Silly little easter-egg that, when set to true, will animate 61 | removing individual characters from the end of the element until the content fits. 62 | Defaults to false. 63 | -------------------------------------------------------------------------------- /clamp.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Clamp.js 0.5.1 3 | * 4 | * Copyright 2011-2013, Joseph Schmitt http://joe.sh 5 | * Released under the WTFPL license 6 | * http://sam.zoy.org/wtfpl/ 7 | */ 8 | 9 | /** 10 | * Clamps a text node. 11 | * @param {HTMLElement} element. Element containing the text node to clamp. 12 | * @param {Object} options. Options to pass to the clamper. 13 | */ 14 | function clamp(element, options) { 15 | options = options || {}; 16 | 17 | var win = window, 18 | opt = { 19 | clamp: options.clamp || 2, 20 | useNativeClamp: typeof(options.useNativeClamp) != 'undefined' ? options.useNativeClamp : true, 21 | splitOnChars: options.splitOnChars || ['.', '-', '–', '—', ' '], //Split on sentences (periods), hypens, en-dashes, em-dashes, and words (spaces). 22 | animate: options.animate || false, 23 | truncationChar: options.truncationChar || '…', 24 | truncationHTML: options.truncationHTML 25 | }, 26 | 27 | sty = element.style, 28 | originalText = element.innerHTML, 29 | 30 | supportsNativeClamp = typeof(element.style.webkitLineClamp) != 'undefined', 31 | clampValue = opt.clamp, 32 | isCSSValue = clampValue.indexOf && (clampValue.indexOf('px') > -1 || clampValue.indexOf('em') > -1), 33 | truncationHTMLContainer; 34 | 35 | if (opt.truncationHTML) { 36 | truncationHTMLContainer = document.createElement('span'); 37 | truncationHTMLContainer.innerHTML = opt.truncationHTML; 38 | } 39 | 40 | 41 | // UTILITY FUNCTIONS __________________________________________________________ 42 | 43 | /** 44 | * Return the current style for an element. 45 | * @param {HTMLElement} elem The element to compute. 46 | * @param {string} prop The style property. 47 | * @returns {number} 48 | */ 49 | function computeStyle(elem, prop) { 50 | if (!win.getComputedStyle) { 51 | win.getComputedStyle = function(el, pseudo) { 52 | this.el = el; 53 | this.getPropertyValue = function(prop) { 54 | var re = /(\-([a-z]){1})/g; 55 | if (prop == 'float') prop = 'styleFloat'; 56 | if (re.test(prop)) { 57 | prop = prop.replace(re, function () { 58 | return arguments[2].toUpperCase(); 59 | }); 60 | } 61 | return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null; 62 | } 63 | return this; 64 | } 65 | } 66 | 67 | return win.getComputedStyle(elem, null).getPropertyValue(prop); 68 | } 69 | 70 | /** 71 | * Returns the maximum number of lines of text that should be rendered based 72 | * on the current height of the element and the line-height of the text. 73 | */ 74 | function getMaxLines(height) { 75 | var availHeight = height || element.clientHeight, 76 | lineHeight = getLineHeight(element); 77 | 78 | return Math.max(Math.floor(availHeight/lineHeight), 0); 79 | } 80 | 81 | /** 82 | * Returns the maximum height a given element should have based on the line- 83 | * height of the text and the given clamp value. 84 | */ 85 | function getMaxHeight(clmp) { 86 | var lineHeight = getLineHeight(element); 87 | return lineHeight * clmp; 88 | } 89 | 90 | /** 91 | * Returns the line-height of an element as an integer. 92 | */ 93 | function getLineHeight(elem) { 94 | var lh = computeStyle(elem, 'line-height'); 95 | if (lh == 'normal') { 96 | // Normal line heights vary from browser to browser. The spec recommends 97 | // a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff. 98 | lh = parseInt(computeStyle(elem, 'font-size')) * 1.2; 99 | } 100 | return parseInt(lh); 101 | } 102 | 103 | 104 | // MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________ 105 | var splitOnChars = opt.splitOnChars.slice(0), 106 | splitChar = splitOnChars[0], 107 | chunks, 108 | lastChunk; 109 | 110 | /** 111 | * Gets an element's last child. That may be another node or a node's contents. 112 | */ 113 | function getLastChild(elem) { 114 | //Current element has children, need to go deeper and get last child as a text node 115 | if (elem.lastChild.children && elem.lastChild.children.length > 0) { 116 | return getLastChild(Array.prototype.slice.call(elem.children).pop()); 117 | } 118 | //This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying 119 | else if (!elem.lastChild || !elem.lastChild.nodeValue || elem.lastChild.nodeValue == '' || elem.lastChild.nodeValue == opt.truncationChar) { 120 | elem.lastChild.parentNode.removeChild(elem.lastChild); 121 | return getLastChild(element); 122 | } 123 | //This is the last child we want, return it 124 | else { 125 | return elem.lastChild; 126 | } 127 | } 128 | 129 | /** 130 | * Removes one character at a time from the text until its width or 131 | * height is beneath the passed-in max param. 132 | */ 133 | function truncate(target, maxHeight) { 134 | if (!maxHeight) {return;} 135 | 136 | /** 137 | * Resets global variables. 138 | */ 139 | function reset() { 140 | splitOnChars = opt.splitOnChars.slice(0); 141 | splitChar = splitOnChars[0]; 142 | chunks = null; 143 | lastChunk = null; 144 | } 145 | 146 | var nodeValue = target.nodeValue.replace(opt.truncationChar, ''); 147 | 148 | //Grab the next chunks 149 | if (!chunks) { 150 | //If there are more characters to try, grab the next one 151 | if (splitOnChars.length > 0) { 152 | splitChar = splitOnChars.shift(); 153 | } 154 | //No characters to chunk by. Go character-by-character 155 | else { 156 | splitChar = ''; 157 | } 158 | 159 | chunks = nodeValue.split(splitChar); 160 | } 161 | 162 | //If there are chunks left to remove, remove the last one and see if 163 | // the nodeValue fits. 164 | if (chunks.length > 1) { 165 | // console.log('chunks', chunks); 166 | lastChunk = chunks.pop(); 167 | // console.log('lastChunk', lastChunk); 168 | applyEllipsis(target, chunks.join(splitChar)); 169 | } 170 | //No more chunks can be removed using this character 171 | else { 172 | chunks = null; 173 | } 174 | 175 | //Insert the custom HTML before the truncation character 176 | if (truncationHTMLContainer) { 177 | target.nodeValue = target.nodeValue.replace(opt.truncationChar, ''); 178 | element.innerHTML = target.nodeValue + ' ' + truncationHTMLContainer.innerHTML + opt.truncationChar; 179 | } 180 | 181 | //Search produced valid chunks 182 | if (chunks) { 183 | //It fits 184 | if (element.clientHeight <= maxHeight) { 185 | //There's still more characters to try splitting on, not quite done yet 186 | if (splitOnChars.length >= 0 && splitChar != '') { 187 | applyEllipsis(target, chunks.join(splitChar) + splitChar + lastChunk); 188 | chunks = null; 189 | } 190 | //Finished! 191 | else { 192 | return element.innerHTML; 193 | } 194 | } 195 | } 196 | //No valid chunks produced 197 | else { 198 | //No valid chunks even when splitting by letter, time to move 199 | //on to the next node 200 | if (splitChar == '') { 201 | applyEllipsis(target, ''); 202 | target = getLastChild(element); 203 | 204 | reset(); 205 | } 206 | } 207 | 208 | //If you get here it means still too big, let's keep truncating 209 | if (opt.animate) { 210 | setTimeout(function() { 211 | truncate(target, maxHeight); 212 | }, opt.animate === true ? 10 : opt.animate); 213 | } 214 | else { 215 | return truncate(target, maxHeight); 216 | } 217 | } 218 | 219 | function applyEllipsis(elem, str) { 220 | elem.nodeValue = str + opt.truncationChar; 221 | } 222 | 223 | 224 | // CONSTRUCTOR ________________________________________________________________ 225 | 226 | if (clampValue == 'auto') { 227 | clampValue = getMaxLines(); 228 | } 229 | else if (isCSSValue) { 230 | clampValue = getMaxLines(parseInt(clampValue)); 231 | } 232 | 233 | var clampedText; 234 | if (supportsNativeClamp && opt.useNativeClamp) { 235 | sty.overflow = 'hidden'; 236 | sty.textOverflow = 'ellipsis'; 237 | sty.webkitBoxOrient = 'vertical'; 238 | sty.display = '-webkit-box'; 239 | sty.webkitLineClamp = clampValue; 240 | 241 | if (isCSSValue) { 242 | sty.height = opt.clamp + 'px'; 243 | } 244 | } 245 | else { 246 | var height = getMaxHeight(clampValue); 247 | if (height <= element.clientHeight) { 248 | clampedText = truncate(getLastChild(element), height); 249 | } 250 | } 251 | 252 | return { 253 | 'original': originalText, 254 | 'clamped': clampedText 255 | } 256 | } 257 | 258 | /** Expose clamp**/ 259 | module.exports = clamp; --------------------------------------------------------------------------------