├── .npmignore ├── .gitignore ├── .github └── CODEOWNERS ├── .travis.yml ├── bower.json ├── test ├── unset-test.js ├── buster.js ├── style.css ├── helpers.js ├── set-test.js ├── test.html └── calc-test.js ├── GruntFile.js ├── LICENSE-MIT ├── package.json ├── examples ├── 1 │ ├── style.css │ └── index.html └── single-line │ └── index.html ├── README.md └── lib └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Guessed from commit history 2 | * @Financial-Times/apps 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: 2 | - "npm test" 3 | 4 | language: node_js 5 | 6 | node_js: 7 | - "6" 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ftellipsis", 3 | "main": "lib/index.js", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/ftlabs/ftellipsis.git" 7 | }, 8 | "ignore": [ 9 | "examples/", 10 | "test/" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/unset-test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | buster.testCase("Ellipsis#unset()", { 4 | setUp: function(done) { 5 | helpers.injectElement(this, done); 6 | }, 7 | 8 | "Should restore the ellipsis element and it's contents back to how it was before .set()": function() { 9 | var ellip = new Ellipsis(this.el); 10 | var clamped = this.el.children[2]; 11 | var naturalHeight = clamped.clientHeight; 12 | 13 | ellip 14 | .calc() 15 | .set(); 16 | 17 | assert.equals(clamped.clientHeight, 20); 18 | 19 | ellip.unset(); 20 | 21 | assert.equals(clamped.clientHeight, naturalHeight); 22 | }, 23 | 24 | tearDown: function(done) { 25 | helpers.destroyElement(this.el, done); 26 | } 27 | }); -------------------------------------------------------------------------------- /test/buster.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module Dependencies 4 | */ 5 | 6 | var fs = require('fs'); 7 | var config = module.exports; 8 | 9 | config["My tests"] = { 10 | env: "browser", // or "node" 11 | rootPath: "../", 12 | autoRun: false, 13 | sources: [ 14 | "node_modules/superagent/superagent.js", 15 | "test/helpers.js", 16 | "lib/*.js" // Glob patterns supported 17 | ], 18 | tests: [ 19 | "test/*-test.js" 20 | ], 21 | resources: [ 22 | { 23 | path: 'test.html', 24 | content: fs.readFileSync('test/test.html') 25 | }, 26 | { 27 | path: 'test/style.css', 28 | file: 'test/style.css' 29 | } 30 | ] 31 | }; -------------------------------------------------------------------------------- /test/style.css: -------------------------------------------------------------------------------- 1 | 2 | .container { 3 | position: relative; 4 | width: 400px; 5 | height: 190px; 6 | font-size: 13px; 7 | line-height: 20px; 8 | font-family: arial; 9 | } 10 | 11 | .container p { 12 | margin: 0; 13 | text-indent: 1em; 14 | font-size: 13px; 15 | line-height: 20px; 16 | } 17 | 18 | .container-1 { 19 | -webkit-column-count: 1; 20 | -moz-column-count: 1; 21 | -ms-column-count: 1; 22 | column-count: 1; 23 | } 24 | 25 | .container-2 { 26 | -webkit-column-count: 2; 27 | -moz-column-count: 2; 28 | -ms-column-count: 2; 29 | column-count: 2; 30 | } 31 | 32 | .container-3 { 33 | -webkit-column-count: 3; 34 | -moz-column-count: 3; 35 | -ms-column-count: 3; 36 | column-count: 3; 37 | } -------------------------------------------------------------------------------- /GruntFile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | buster: { 8 | test: { 9 | config: 'test/buster.js', 10 | }, 11 | }, 12 | 13 | readme: { 14 | build: { 15 | code: [ 16 | { path: 'lib/index.js' } 17 | ], 18 | output: { 19 | 'docs/readme.hogan': 'README.md' 20 | } 21 | } 22 | }, 23 | uglify: { 24 | build: { 25 | src: 'lib/index.js', 26 | dest: 'build/<%= pkg.name %>.min.js' 27 | } 28 | } 29 | }); 30 | 31 | grunt.loadNpmTasks('grunt-buster'); 32 | grunt.loadNpmTasks('grunt-contrib-uglify'); 33 | grunt.loadNpmTasks('grunt-readme'); 34 | 35 | // Default task. 36 | grunt.registerTask('default', ['uglify', 'readme']); 37 | }; 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Financial Times Ltd. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ftellipsis", 3 | "title": "FTEllipsis", 4 | "description": "Multi-line ellipsis made possible", 5 | "version": "0.2.3", 6 | "homepage": "https://github.com/ftlabs/ftellipsis", 7 | "main": "lib/index.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com:ftlabs/ftellipsis.git" 11 | }, 12 | "author": { 13 | "name": "Wilson Page", 14 | "email": "wilsonpage@me.com", 15 | "github": "wilsonpage", 16 | "organization": "The Finanacial Times Limited" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/ftlabs/ftellipsis/blob/master/LICENSE-MIT" 22 | } 23 | ], 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "grunt": "~0.4.5", 27 | "grunt-readme": "git://github.com/wilsonpage/grunt-readme.git", 28 | "grunt-contrib-uglify": "~0.2.0", 29 | "grunt-contrib-watch": "~0.5.1", 30 | "buster": "~0.7", 31 | "buster-assertions": "~0.10.4", 32 | "superagent": "0.14.4", 33 | "grunt-buster": "~0.3.1", 34 | "grunt-cli": "~0.1.8", 35 | "phantomjs": "^2" 36 | }, 37 | "keywords": [ 38 | "ellipsis", 39 | "css", 40 | "layout" 41 | ], 42 | "scripts": { 43 | "test": "./node_modules/.bin/grunt buster -v" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var helpers = {}; 2 | var request = superagent; 3 | 4 | var assert = buster.referee.assert; 5 | var refute = buster.referee.refute; 6 | 7 | helpers.fixture = function(name, callback) { 8 | request.get(name, function(res) { 9 | callback(res.text); 10 | }); 11 | }; 12 | 13 | helpers.element = function(name, callback) { 14 | helpers.fixture(name, function(text) { 15 | var parent = document.createElement('div'); 16 | parent.innerHTML = text; 17 | callback(parent.removeChild(parent.firstElementChild)); 18 | }); 19 | }; 20 | 21 | helpers.injectElement = function(test, callback) { 22 | helpers.element('test.html', function(el) { 23 | test.el = el; 24 | document.body.insertBefore(el, document.body.firstElementChild); 25 | var forceRender = el.offsetTop; 26 | callback(); 27 | }); 28 | }; 29 | 30 | helpers.destroyElement = function(el, callback) { 31 | el.parentNode.removeChild(el); 32 | callback(); 33 | }; 34 | 35 | function loadStylesheet(url, callback) { 36 | var head = document.getElementsByTagName("head")[0]; 37 | var body = document.body; 38 | var css = document.createElement("link"); 39 | var img = document.createElement("img"); 40 | 41 | css.href = url; 42 | css.rel = "stylesheet"; 43 | head.appendChild(css); 44 | 45 | img.onerror = function() { 46 | body.removeChild(img); 47 | callback(); 48 | }; 49 | 50 | body.appendChild(img); 51 | img.src = url; 52 | } 53 | 54 | loadStylesheet('test/style.css', buster.run); 55 | -------------------------------------------------------------------------------- /examples/1/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | box-sizing: border-box; 4 | } 5 | 6 | html, 7 | body { 8 | height: 100%; 9 | } 10 | 11 | h1 { 12 | font-size: inherit; 13 | } 14 | 15 | p { 16 | break-before: avoid; 17 | } 18 | 19 | 20 | 21 | .ellipsis-overflowing-child { 22 | margin-bottom: 9em; 23 | padding: 0 !important; 24 | position: relative; 25 | } 26 | 27 | .ellipsis-overflowing-child .ellipsis-helper { 28 | width: 8em; 29 | background: -moz-linear-gradient(left, rgba(221,221,221,0) 0%, rgba(221,221,221,1) 100%); /* FF3.6+ */ 30 | background: -o-linear-gradient(left, rgba(221,221,221,0) 0%,rgba(221,221,221,1) 100%); /* Opera 11.10+ */ 31 | background: -ms-linear-gradient(left, rgba(221,221,221,0) 0%,rgba(221,221,221,1) 100%); /* IE10+ */ 32 | background: linear-gradient(to right, rgba(221,221,221,0) 0%,rgba(221,221,221,1) 100%); /* W3C */ 33 | background: red; 34 | } 35 | 36 | .ellipsis-overflowing-child .ellipsis-helper:after { 37 | content: "..."; 38 | float: right; 39 | padding-right: 0.4em; 40 | } 41 | 42 | .container { 43 | height: 33.3333%; 44 | position: relative; 45 | font-size: 13px; 46 | line-height: 20px; 47 | background-color: #DDD; 48 | overflow: hidden; 49 | } 50 | 51 | .container-1 { 52 | -webkit-column-count: 1; 53 | -moz-column-count: 1; 54 | -ms-column-count: 1; 55 | column-count: 1; 56 | } 57 | 58 | .container-2 { 59 | -webkit-column-count: 2; 60 | -moz-column-count: 2; 61 | -ms-column-count: 2; 62 | column-count: 2; 63 | } 64 | 65 | .container-3 { 66 | -webkit-column-count: 3; 67 | -moz-column-count: 4; 68 | -ms-column-count: 3; 69 | column-count: 3; 70 | } 71 | 72 | /*.overflowing-child:after { 73 | content: "..."; 74 | }*/ -------------------------------------------------------------------------------- /test/set-test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | buster.testCase("Ellipsis#set()", { 4 | setUp: function(done) { 5 | helpers.injectElement(this, done); 6 | }, 7 | 8 | "The overflowing child must have its height set": function() { 9 | var ellip = new Ellipsis(this.el); 10 | 11 | this.el.className += ' container-1'; 12 | 13 | ellip 14 | .calc() 15 | .set(); 16 | 17 | assert.equals(ellip.child.el.style.height, ellip.child.clampedHeight + 'px'); 18 | }, 19 | 20 | "All elements after the overflowing child should be hidden": function() { 21 | var ellip = new Ellipsis(this.el); 22 | 23 | this.el.className += ' container-1'; 24 | 25 | ellip 26 | .calc() 27 | .set(); 28 | 29 | assert.equals(this.el.children[3].style.display, 'none'); 30 | assert.equals(this.el.children[4].style.display, 'none'); 31 | }, 32 | 33 | "If a container is specified it should be marked after ellipsis has been set": function() { 34 | this.ellip = new Ellipsis(this.el, { container: document.body }); 35 | 36 | this.el.className += ' container-1'; 37 | 38 | this.ellip 39 | .calc() 40 | .set(); 41 | 42 | assert(document.body.classList.contains('ellipsis-set')); 43 | }, 44 | 45 | 46 | "Should appear completely collapsed if there is not enough room for one line": function() { 47 | this.ellip = new Ellipsis(this.el); 48 | 49 | this.el.className += ' container-3'; 50 | this.el.style.height = '10px'; 51 | 52 | this.ellip 53 | .calc() 54 | .set(); 55 | 56 | assert.equals(this.ellip.child.el.style.overflow, 'hidden'); 57 | assert.equals(this.ellip.child.el.clientHeight, 0); 58 | }, 59 | 60 | "Should not not clamp any child element if there is enough room for all the content": function() { 61 | this.ellip = new Ellipsis(this.el); 62 | 63 | this.el.className += ' container-2'; 64 | this.el.style.width = '820px'; 65 | this.el.style.height = '600px'; 66 | 67 | this.ellip.calc().set(); 68 | 69 | var clampedEl = this.el.querySelector('.ellipsis-overflowing-child'); 70 | 71 | assert.equals(clampedEl, null); 72 | }, 73 | 74 | tearDown: function(done) { 75 | if (this.ellip) this.ellip.unset().destroy(); 76 | helpers.destroyElement(this.el, done); 77 | } 78 | }); -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 |
2 |

One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections.

3 |

The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. "What's happened to me? " he thought. It wasn't a dream.

4 |

His room, a proper human room although a little too small, lay peacefully between its four familiar walls. A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops of rain could be heard hitting the pane, which made him feel quite sad. "How about if I sleep a little bit longer and forget all this nonsense", he thought, but that was something he was unable to do because he was used to sleeping on his right, and in his present state couldn't get into that position. However hard he threw himself onto his right, he always rolled back to where he was.

5 |

He must have tried it a hundred times, shut his eyes so that he wouldn't have to look at the floundering legs, and only stopped when he began to feel a mild, dull pain there that he had never felt before. "Oh, God", he thought, "what a strenuous career it is that I've chosen! Travelling day in and day out. Doing business like this takes much more effort than doing your own business at home, and on top of that there's the curse of travelling, worries about making train connections, bad and irregular food, contact with different people all the time so that you can never get to know anyone or become friendly with them. It can all go to Hell!"

6 |

He felt a slight itch up on his belly; pushed himself slowly up on his back towards the headboard so that he could lift his head better; found where the itch was, and saw that it was covered with lots of little white spots which he didn't know what to make of; and when he tried to feel the place with one of his legs he drew it quickly back because as soon as he touched it he was overcome by a cold shudder. He slid back into his former position. "Getting up early all the time", he thought, "it makes you stupid. You've got to get enough sleep. Other travelling salesmen live a life of luxury. For instance, whenever I go back to the guest house during the morning to copy out the contract, these gentlemen are always still sitting there eating their breakfasts. I ought to just try that with my boss; I'd get kicked out on the spot. But who knows, maybe that would be the best thing for me."

7 |
-------------------------------------------------------------------------------- /test/calc-test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | buster.testCase("Ellipsis#calc()", { 4 | setUp: function(done) { 5 | helpers.injectElement(this, done); 6 | }, 7 | 8 | "Should correctly identify the overflowing child (single column)": function() { 9 | var ellip = new Ellipsis(this.el); 10 | 11 | this.el.className += ' container-1'; 12 | this.el.style.height = '190px'; 13 | this.el.style.width = '400px'; 14 | 15 | ellip.calc(); 16 | 17 | assert.equals(ellip.child.el, this.el.children[2]); 18 | }, 19 | 20 | "Should correctly identify the overflowing child (double column)": function() { 21 | var ellip = new Ellipsis(this.el); 22 | 23 | this.el.className += ' container-2'; 24 | this.el.style.height = '200px'; 25 | this.el.style.width = '400px'; 26 | 27 | ellip.calc(); 28 | 29 | assert.equals(ellip.child.el, this.el.children[2]); 30 | }, 31 | 32 | "Should correctly identify the overflowing child (triple column)": function() { 33 | var ellip = new Ellipsis(this.el); 34 | 35 | this.el.className += ' container-3'; 36 | this.el.style.height = '200px'; 37 | this.el.style.width = '400px'; 38 | 39 | ellip.calc(); 40 | 41 | assert.equals(ellip.child.el, this.el.children[2]); 42 | }, 43 | 44 | "Should set zero height (collapsed) if there is not enought room for one line.": function() { 45 | var ellip = new Ellipsis(this.el); 46 | 47 | this.el.className += ' container-3'; 48 | this.el.style.height = '10px'; 49 | 50 | ellip.calc(); 51 | 52 | assert.equals(ellip.child.clampedHeight, 0); 53 | }, 54 | 55 | "Should not have set an overflowing child if there is enough room for all the content": function() { 56 | var ellip = new Ellipsis(this.el); 57 | 58 | this.el.className += ' container-2'; 59 | this.el.style.width = '820px'; 60 | this.el.style.height = '600px'; 61 | 62 | ellip.calc().set(); 63 | 64 | refute.defined(ellip.child.el); 65 | }, 66 | 67 | "Should calculate correct height (check 1)": function() { 68 | var ellip = new Ellipsis(this.el); 69 | 70 | this.el.className += ' container-3'; 71 | this.el.style.height = '105px'; 72 | 73 | ellip.calc(); 74 | 75 | assert.equals(ellip.child.clampedHeight, 310); 76 | }, 77 | 78 | 79 | "Should calculate correct height (check 2)": function() { 80 | var ellip = new Ellipsis(this.el); 81 | 82 | this.el.className += ' container-2'; 83 | this.el.style.width = '820px'; 84 | this.el.style.height = '100px'; 85 | 86 | ellip.calc(); 87 | 88 | assert.equals(ellip.child.clampedHeight, 40); 89 | }, 90 | 91 | "Should calculate correct height (check 3)": function() { 92 | var ellip = new Ellipsis(this.el); 93 | 94 | this.el.className += ' container-2'; 95 | this.el.style.width = '820px'; 96 | this.el.style.height = '20px'; 97 | 98 | ellip.calc(); 99 | 100 | assert.equals(ellip.child.clampedHeight, 40); 101 | }, 102 | 103 | tearDown: function(done) { 104 | helpers.destroyElement(this.el, done); 105 | } 106 | }); 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FTEllipsis [![Build Status](https://travis-ci.org/ftlabs/ftellipsis.png?branch=master)](https://travis-ci.org/ftlabs/ftellipsis) 2 | 3 | Solves the problem of applying ellipsis (…) on a multi-line block of text at the point it overflows its container. Ellipsis will work in conjuction with CSS [column-count](https://developer.mozilla.org/en-US/docs/CSS/column-count) if you wish. 4 | 5 | Results are best in webkit browsers due to the availability of [webkit-line-clamp](http://dropshado.ws/post/1015351370/webkit-line-clamp). For non-webkit browsers FTEllipsis falls back to clamping text and positioning an element over the end of the overflowing line, allowing the developer to style this however they wish. 6 | 7 | ## Getting Started 8 | 9 | ##### NPM 10 | 11 | ``` 12 | $ npm install ftellipsis 13 | ``` 14 | 15 | ##### Bower 16 | 17 | ``` 18 | $ bower install ftellipsis 19 | ``` 20 | 21 | or download the [production version][min] or the [development version][max]. 22 | 23 | [min]: https://raw.github.com/ftlabs/ftellipsis/master/build/ftellipsis.min.js 24 | [max]: https://raw.github.com/ftlabs/ftellipsis/master/lib/index.js 25 | 26 | ## Examples 27 | 28 | - [Primary](http://ftlabs.github.io/ftellipsis/examples/1/) 29 | - [Single Line](http://ftlabs.github.io/ftellipsis/examples/single-line/) 30 | 31 | ## Usage 32 | 33 | ```js 34 | var element = document.getElementById('my-element'); 35 | var ellipsis = new Ellipsis(element); 36 | 37 | ellipsis.calc(); 38 | ellipsis.set(); 39 | ``` 40 | 41 | Requirements: 42 | 43 | - The element must have a fixed height so that content overflows. 44 | - The element must have child elements (eg. `

`s). 45 | 46 | ### Unsetting 47 | 48 | Unsetting an ellipsis instance removes any styling. 49 | 50 | ```js 51 | ellipsis.unset(); 52 | ``` 53 | 54 | ### Destroying 55 | 56 | Destroying an ellipsis instance resets the instance back to it's original state, unsetting internal variables and state. 57 | 58 | ```js 59 | ellipsis.destroy(); 60 | ``` 61 | 62 | ## Tests 63 | 64 | ``` 65 | $ npm install 66 | $ npm test 67 | ``` 68 | 69 | ## API 70 | 71 | ### Ellipsis(); 72 | 73 | Initialize a new Ellipsis 74 | instance with the given element. 75 | 76 | Options: 77 | 78 | - `container` A parent container element 79 | - `reRender` Forces a redraw after ellipsis applied 80 | 81 | ### Ellipsis#calc(); 82 | 83 | Measures the element and 84 | finds the overflowing child. 85 | 86 | 87 | 88 | ### Ellipsis#set(); 89 | 90 | Clamps the overflowing child using 91 | the information acquired from #calc(). 92 | 93 | 94 | 95 | ### Ellipsis#unset(); 96 | 97 | Unclamps the overflowing child. 98 | 99 | 100 | 101 | ### Ellipsis#destroy(); 102 | 103 | Clears any references 104 | 105 | 106 | 107 | 108 | ## Credits and collaboration 109 | 110 | The lead developer of FTEllipsis is [Wilson Page](http://github.com/wilsonpage) at FT Labs. All open source code released by FT Labs is licenced under the MIT licence. We welcome comments, feedback and suggestions. Please feel free to raise an issue or pull request. Enjoy... -------------------------------------------------------------------------------- /examples/single-line/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ellipsis Test 1 6 | 22 | 23 | 24 | 25 |

26 |

One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections.

27 |

The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. "What's happened to me? " he thought. It wasn't a dream.

28 |

His room, a proper human room although a little too small, lay peacefully between its four familiar walls. A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops of rain could be heard hitting the pane, which made him feel quite sad. "How about if I sleep a little bit longer and forget all this nonsense", he thought, but that was something he was unable to do because he was used to sleeping on his right, and in his present state couldn't get into that position. However hard he threw himself onto his right, he always rolled back to where he was.

29 |

He must have tried it a hundred times, shut his eyes so that he wouldn't have to look at the floundering legs, and only stopped when he began to feel a mild, dull pain there that he had never felt before. "Oh, God", he thought, "what a strenuous career it is that I've chosen! Travelling day in and day out. Doing business like this takes much more effort than doing your own business at home, and on top of that there's the curse of travelling, worries about making train connections, bad and irregular food, contact with different people all the time so that you can never get to know anyone or become friendly with them. It can all go to Hell!"

30 |

He felt a slight itch up on his belly; pushed himself slowly up on his back towards the headboard so that he could lift his head better; found where the itch was, and saw that it was covered with lots of little white spots which he didn't know what to make of; and when he tried to feel the place with one of his legs he drew it quickly back because as soon as he touched it he was overcome by a cold shudder. He slid back into his former position. "Getting up early all the time", he thought, "it makes you stupid. You've got to get enough sleep. Other travelling salesmen live a life of luxury. For instance, whenever I go back to the guest house during the morning to copy out the contract, these gentlemen are always still sitting there eating their breakfasts. I ought to just try that with my boss; I'd get kicked out on the spot. But who knows, maybe that would be the best thing for me."

31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ellipsis Test 1 6 | 7 | 8 | 9 | 10 |
11 |

Byline

12 |

One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections.

13 |

The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. "What's happened to me? " he thought. It wasn't a dream.

14 |

His room, a proper human room although a little too small, lay peacefully between its four familiar walls. A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops of rain could be heard hitting the pane, which made him feel quite sad. "How about if I sleep a little bit longer and forget all this nonsense", he thought, but that was something he was unable to do because he was used to sleeping on his right, and in his present state couldn't get into that position. However hard he threw himself onto his right, he always rolled back to where he was.

15 |

He must have tried it a hundred times, shut his eyes so that he wouldn't have to look at the floundering legs, and only stopped when he began to feel a mild, dull pain there that he had never felt before. "Oh, God", he thought, "what a strenuous career it is that I've chosen! Travelling day in and day out. Doing business like this takes much more effort than doing your own business at home, and on top of that there's the curse of travelling, worries about making train connections, bad and irregular food, contact with different people all the time so that you can never get to know anyone or become friendly with them. It can all go to Hell!"

16 |

He felt a slight itch up on his belly; pushed himself slowly up on his back towards the headboard so that he could lift his head better; found where the itch was, and saw that it was covered with lots of little white spots which he didn't know what to make of; and when he tried to feel the place with one of his legs he drew it quickly back because as soon as he touched it he was overcome by a cold shudder. He slid back into his former position. "Getting up early all the time", he thought, "it makes you stupid. You've got to get enough sleep. Other travelling salesmen live a life of luxury. For instance, whenever I go back to the guest house during the morning to copy out the contract, these gentlemen are always still sitting there eating their breakfasts. I ought to just try that with my boss; I'd get kicked out on the spot. But who knows, maybe that would be the best thing for me."

17 |
18 | 19 |
20 |

Byline

21 |

One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections.

22 |

The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. "What's happened to me? " he thought. It wasn't a dream.

23 |

His room, a proper human room although a little too small, lay peacefully between its four familiar walls. A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops of rain could be heard hitting the pane, which made him feel quite sad. "How about if I sleep a little bit longer and forget all this nonsense", he thought, but that was something he was unable to do because he was used to sleeping on his right, and in his present state couldn't get into that position. However hard he threw himself onto his right, he always rolled back to where he was.

24 |

He must have tried it a hundred times, shut his eyes so that he wouldn't have to look at the floundering legs, and only stopped when he began to feel a mild, dull pain there that he had never felt before. "Oh, God", he thought, "what a strenuous career it is that I've chosen! Travelling day in and day out. Doing business like this takes much more effort than doing your own business at home, and on top of that there's the curse of travelling, worries about making train connections, bad and irregular food, contact with different people all the time so that you can never get to know anyone or become friendly with them. It can all go to Hell!"

25 |

He felt a slight itch up on his belly; pushed himself slowly up on his back towards the headboard so that he could lift his head better; found where the itch was, and saw that it was covered with lots of little white spots which he didn't know what to make of; and when he tried to feel the place with one of his legs he drew it quickly back because as soon as he touched it he was overcome by a cold shudder. He slid back into his former position. "Getting up early all the time", he thought, "it makes you stupid. You've got to get enough sleep. Other travelling salesmen live a life of luxury. For instance, whenever I go back to the guest house during the morning to copy out the contract, these gentlemen are always still sitting there eating their breakfasts. I ought to just try that with my boss; I'd get kicked out on the spot. But who knows, maybe that would be the best thing for me."

26 |
27 | 28 |
29 |

Byline

30 |

One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections.

31 |

The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. "What's happened to me? " he thought. It wasn't a dream.

32 |

His room, a proper human room although a little too small, lay peacefully between its four familiar walls. A collection of textile samples lay spread out on the table - Samsa was a travelling salesman - and above it there hung a picture that he had recently cut out of an illustrated magazine and housed in a nice, gilded frame. It showed a lady fitted out with a fur hat and fur boa who sat upright, raising a heavy fur muff that covered the whole of her lower arm towards the viewer. Gregor then turned to look out the window at the dull weather. Drops of rain could be heard hitting the pane, which made him feel quite sad. "How about if I sleep a little bit longer and forget all this nonsense", he thought, but that was something he was unable to do because he was used to sleeping on his right, and in his present state couldn't get into that position. However hard he threw himself onto his right, he always rolled back to where he was.

33 |

He must have tried it a hundred times, shut his eyes so that he wouldn't have to look at the floundering legs, and only stopped when he began to feel a mild, dull pain there that he had never felt before. "Oh, God", he thought, "what a strenuous career it is that I've chosen! Travelling day in and day out. Doing business like this takes much more effort than doing your own business at home, and on top of that there's the curse of travelling, worries about making train connections, bad and irregular food, contact with different people all the time so that you can never get to know anyone or become friendly with them. It can all go to Hell!"

34 |

He felt a slight itch up on his belly; pushed himself slowly up on his back towards the headboard so that he could lift his head better; found where the itch was, and saw that it was covered with lots of little white spots which he didn't know what to make of; and when he tried to feel the place with one of his legs he drew it quickly back because as soon as he touched it he was overcome by a cold shudder. He slid back into his former position. "Getting up early all the time", he thought, "it makes you stupid. You've got to get enough sleep. Other travelling salesmen live a life of luxury. For instance, whenever I go back to the guest house during the morning to copy out the contract, these gentlemen are always still sitting there eating their breakfasts. I ought to just try that with my boss; I'd get kicked out on the spot. But who knows, maybe that would be the best thing for me."

35 |
36 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | ;(function(){ 3 | 4 | 'use strict'; 5 | 6 | /** 7 | * Aliases 8 | */ 9 | 10 | var indexOf = Array.prototype.indexOf; 11 | var getStyle = window.getComputedStyle; 12 | 13 | /** 14 | * CSS Classes 15 | */ 16 | 17 | var overflowingChildClass = 'ellipsis-overflowing-child'; 18 | var containerClass = 'ellipsis-set'; 19 | 20 | /** 21 | * Vendor Info 22 | */ 23 | 24 | var vendor = getVendorData(); 25 | 26 | /** 27 | * Initialize a new Ellipsis 28 | * instance with the given element. 29 | * 30 | * Options: 31 | * 32 | * - `container` A parent container element 33 | * - `reRender` Forces a redraw after ellipsis applied 34 | * 35 | * @constructor 36 | * @param {Element} el 37 | * @param {Object} options 38 | * @api public 39 | */ 40 | function Ellipsis(el, options) { 41 | if (!el) return; 42 | this.el = el; 43 | this.container = options && options.container; 44 | this.reRender = options && options.reRender; 45 | } 46 | 47 | /** 48 | * Measures the element and 49 | * finds the overflowing child. 50 | * 51 | * @return {Ellipsis} 52 | * @api public 53 | */ 54 | Ellipsis.prototype.calc = function() { 55 | if (!this.el) return this; 56 | var style = getStyle(this.el); 57 | var size = getSize(this.el); 58 | 59 | this.columnHeight = size[1]; 60 | this.columnCount = getColumnCount(style); 61 | this.columnGap = getColumnGap(style); 62 | this.columnWidth = size[0] / this.columnCount; 63 | this.lineHeight = getLineHeight(this.el, style); 64 | this.deltaHeight = size[1] % this.lineHeight; 65 | this.linesPerColumn = Math.floor(this.columnHeight / this.lineHeight); 66 | this.totalLines = this.linesPerColumn * this.columnCount; 67 | 68 | // COMPLEX: 69 | // We set the height on the container 70 | // explicitly to work around problem 71 | // with columned containers not fitting 72 | // all lines when the height is exactly 73 | // divisible by the line height. 74 | if (!this.deltaHeight && this.columnCount > 1) { 75 | this.el.style.height = this.columnHeight + 'px'; 76 | } 77 | 78 | this.child = this.getOverflowingChild(); 79 | 80 | return this; 81 | }; 82 | 83 | /** 84 | * Clamps the overflowing child using 85 | * the information acquired from #calc(). 86 | * 87 | * @return {Ellipsis} 88 | * @api public 89 | */ 90 | Ellipsis.prototype.set = function() { 91 | if (!this.el || !this.child) return this; 92 | 93 | this.clampChild(); 94 | siblingsAfter(this.child.el, { display: 'none' }); 95 | this.markContainer(); 96 | 97 | return this; 98 | }; 99 | 100 | /** 101 | * Unclamps the overflowing child. 102 | * 103 | * @return {Ellipsis} 104 | * @api public 105 | */ 106 | 107 | Ellipsis.prototype.unset = function() { 108 | if (!this.el || !this.child) return this; 109 | 110 | this.el.style.height = ''; 111 | this.unclampChild(this.child); 112 | siblingsAfter(this.child.el, { display: '' }); 113 | this.unmarkContainer(); 114 | this.child = null; 115 | 116 | return this; 117 | }; 118 | 119 | /** 120 | * Clears any references 121 | * 122 | * @return {Ellipsis} 123 | * @api public 124 | */ 125 | 126 | Ellipsis.prototype.destroy = function() { 127 | 128 | // It's super important that we clear references 129 | // to any DOM nodes here so that we don't end up 130 | // with any 'detached nodes' lingering in memory 131 | this.el = this.child = this.container = null; 132 | 133 | return this; 134 | }; 135 | 136 | /** 137 | * Returns the overflowing child with some 138 | * extra data required for clamping. 139 | * 140 | * @param {Ellipsis} instance 141 | * @return {Object} 142 | * @api private 143 | */ 144 | Ellipsis.prototype.getOverflowingChild = function() { 145 | var self = this; 146 | var child = {}; 147 | var lineCounter = 0; 148 | 149 | // Loop over each child element 150 | each(this.el.children, function(el) { 151 | var lineCount, overflow, underflow; 152 | var startColumnIndex = Math.floor(lineCounter / self.linesPerColumn) || 0; 153 | 154 | // Get the line count of the 155 | // child and increment the counter 156 | lineCounter += lineCount = self.getLineCount(el); 157 | 158 | // If this is the overflowing child 159 | if (lineCounter >= self.totalLines) { 160 | overflow = lineCounter - self.totalLines; 161 | underflow = lineCount - overflow; 162 | 163 | child.el = el; 164 | child.clampedLines = underflow; 165 | child.clampedHeight = child.clampedLines * self.lineHeight; 166 | child.visibleColumnSpan = self.columnCount - startColumnIndex; 167 | child.gutterSpan = child.visibleColumnSpan - 1; 168 | child.applyTopMargin = self.shouldApplyTopMargin(child); 169 | 170 | // COMPLEX: 171 | // In order to get the overflowing 172 | // child height correct we have to 173 | // add the delta for each gutter the 174 | // overflowing child crosses. This is 175 | // just how webkit columns work. 176 | if (vendor.webkit && child.clampedLines > 1) { 177 | child.clampedHeight += child.gutterSpan * self.deltaHeight; 178 | } 179 | 180 | return child; 181 | } 182 | }); 183 | 184 | return child; 185 | }; 186 | 187 | /** 188 | * Returns the number 189 | * of lines an element has. 190 | * 191 | * If the element is larger than 192 | * the column width we make the 193 | * assumption that this is FireFox 194 | * and the element is broken across 195 | * a column boundary. In this case 196 | * we have to get the height using 197 | * `getClientRects()`. 198 | * 199 | * @param {Element} el 200 | * @return {Number} 201 | * @api private 202 | */ 203 | 204 | Ellipsis.prototype.getLineCount = function(el) { 205 | return (el.offsetWidth > this.columnWidth) 206 | ? getLinesFromRects(el, this.lineHeight) 207 | : lineCount(el.clientHeight, this.lineHeight); 208 | }; 209 | 210 | /** 211 | * If a container has been 212 | * declared we mark it with 213 | * a class for styling purposes. 214 | * 215 | * @api private 216 | */ 217 | Ellipsis.prototype.markContainer = function() { 218 | if (!this.container) return; 219 | this.container.classList.add(containerClass); 220 | if (this.reRender) reRender(this.container); 221 | }; 222 | 223 | /** 224 | * Removes the class 225 | * from the container. 226 | * 227 | * @api private 228 | */ 229 | Ellipsis.prototype.unmarkContainer = function() { 230 | if (!this.container) return; 231 | this.container.classList.remove(containerClass); 232 | if (this.reRender) reRender(this.container); 233 | }; 234 | 235 | /** 236 | * Determines whether top margin should be 237 | * applied to the overflowing child. 238 | * 239 | * This is to counteract an annoying 240 | * column-count/-webkit-box bug, whereby the 241 | * flexbox element falls into the delta are under 242 | * the previous sibling. Top margin keeps it 243 | * in the correct column. 244 | * 245 | * @param {Element} el 246 | * @param {Ellipsis} instance 247 | * @return {Boolean} 248 | * @api private 249 | */ 250 | Ellipsis.prototype.shouldApplyTopMargin = function(child) { 251 | var el = child.el; 252 | 253 | // Dont't if it's not webkit 254 | if (!vendor.webkit) return; 255 | 256 | // Don't if it's a single column layout 257 | if (this.columnCount === 1) return; 258 | 259 | // Don't if the delta height is minimal 260 | if (this.deltaHeight <= 3) return; 261 | 262 | // Don't if it's the first child 263 | if (!el.previousElementSibling) return; 264 | 265 | // FINAL TEST: If the element is at the top or bottom of its 266 | // parent container then we require top margin. 267 | return (el.offsetTop === 0 || el.offsetTop === this.columnHeight); 268 | }; 269 | 270 | /** 271 | * Clamps the child element to the set 272 | * height and lines. 273 | * 274 | * @param {Object} child 275 | * @api private 276 | */ 277 | Ellipsis.prototype.clampChild = function() { 278 | var child = this.child; 279 | if (!child || !child.el) return; 280 | 281 | // Clamp the height 282 | child.el.style.height = child.clampedHeight + 'px'; 283 | 284 | // Use webkit line clamp 285 | // for webkit browsers. 286 | if (vendor.webkit) { 287 | child.el.style.webkitLineClamp = child.clampedLines; 288 | child.el.style.display = '-webkit-box'; 289 | child.el.style.webkitBoxOrient = 'vertical'; 290 | } 291 | 292 | if (this.shouldHideOverflow()) child.el.style.overflow = 'hidden'; 293 | 294 | // Apply a top margin to fix webkit 295 | // column-count mixed with flexbox bug, 296 | // if we have decided it is neccessary. 297 | if (child.applyTopMargin) child.el.style.marginTop = '2em'; 298 | 299 | // Add the overflowing 300 | // child class as a style hook 301 | child.el.classList.add(overflowingChildClass); 302 | 303 | // Non webkit browsers get a helper 304 | // element that is styled as an alternative 305 | // to the webkit-line-clamp ellipsis. 306 | // Must be position relative so that we can 307 | // position the helper element. 308 | if (!vendor.webkit) { 309 | child.el.style.position = 'relative'; 310 | child.helper = child.el.appendChild(this.helperElement()); 311 | } 312 | }; 313 | 314 | /** 315 | * Removes all clamping styles from 316 | * the overflowing child. 317 | * 318 | * @param {Object} child 319 | * @api private 320 | */ 321 | Ellipsis.prototype.unclampChild = function(child) { 322 | if (!child || !child.el) return; 323 | child.el.style.display = ''; 324 | child.el.style.height = ''; 325 | child.el.style.webkitLineClamp = ''; 326 | child.el.style.webkitBoxOrient = ''; 327 | child.el.style.marginTop = ''; 328 | child.el.style.overflow = ''; 329 | child.el.classList.remove(overflowingChildClass); 330 | 331 | if (child.helper) { 332 | child.helper.parentNode.removeChild(child.helper); 333 | } 334 | }; 335 | 336 | /** 337 | * Creates the helper element 338 | * for non-webkit browsers. 339 | * 340 | * @return {Element} 341 | * @api private 342 | */ 343 | Ellipsis.prototype.helperElement = function() { 344 | var el = document.createElement('span'); 345 | var columns = this.child.visibleColumnSpan - 1; 346 | var rightOffset, marginRight; 347 | 348 | el.className = 'ellipsis-helper'; 349 | el.style.display = 'block'; 350 | el.style.height = this.lineHeight + 'px'; 351 | el.style.width = '5em'; 352 | el.style.position = 'absolute'; 353 | el.style.bottom = 0; 354 | el.style.right = 0; 355 | 356 | // HACK: This is a work around to deal with 357 | // the wierdness of positioning elements 358 | // inside an element that is broken across 359 | // more than one column. 360 | if (vendor.moz && columns) { 361 | rightOffset = -(columns * 100); 362 | marginRight = -(columns * this.columnGap); 363 | el.style.right = rightOffset + '%'; 364 | el.style.marginRight = marginRight + 'px'; 365 | el.style.marginBottom = this.deltaHeight + 'px'; 366 | } 367 | 368 | return el; 369 | }; 370 | 371 | /** 372 | * Determines whether overflow 373 | * should be hidden on clamped 374 | * child. 375 | * 376 | * NOTE: 377 | * Overflow hidden is only required 378 | * for single column containers as 379 | * multi-column containers overflow 380 | * to the right, so are not visible. 381 | * `overflow: hidden;` also messes 382 | * with column layout in Firefox. 383 | * 384 | * @return {Boolean} 385 | * @api private 386 | */ 387 | Ellipsis.prototype.shouldHideOverflow = function() { 388 | var hasColumns = this.columnCount > 1; 389 | 390 | // If there is not enough room to show 391 | // even one line; hide all overflow. 392 | if (this.columnHeight < this.lineHeight) return true; 393 | 394 | // Hide all single column overflow 395 | return !hasColumns; 396 | }; 397 | 398 | /** 399 | * Re-render with no setTimeout, boom! 400 | * 401 | * NOTE: 402 | * We have to assign the return value 403 | * to something global so that Closure 404 | * Compiler doesn't strip it out. 405 | * 406 | * @param {Element} el 407 | * @api private 408 | */ 409 | function reRender(el) { 410 | el.style.display = 'none'; 411 | Ellipsis.r = el.offsetTop; 412 | el.style.display = ''; 413 | } 414 | 415 | /** 416 | * Sets the display property on 417 | * all siblingsafter the given element. 418 | * 419 | * Options: 420 | * - `display` the css display type to use 421 | * 422 | * @param {Node} el 423 | * @param {Options} options 424 | * @api private 425 | */ 426 | 427 | function siblingsAfter(el, options) { 428 | if (!el) return; 429 | var display = options && options.display; 430 | var siblings = el.parentNode.children; 431 | var index = indexOf.call(siblings, el); 432 | 433 | for (var i = index + 1, l = siblings.length; i < l; i++) { 434 | siblings[i].style.display = display; 435 | } 436 | } 437 | 438 | /** 439 | * Returns total line 440 | * count from a rect list. 441 | * 442 | * @param {Element} el 443 | * @param {Number} lineHeight 444 | * @return {Number} 445 | * @api private 446 | */ 447 | 448 | function getLinesFromRects(el, lineHeight) { 449 | var rects = el.getClientRects(); 450 | var lines = 0; 451 | 452 | each(rects, function(rect) { 453 | lines += lineCount(rect.height, lineHeight); 454 | }); 455 | 456 | return lines; 457 | } 458 | 459 | /** 460 | * Calculates a line count 461 | * from the passed height. 462 | * 463 | * @param {Number} height 464 | * @param {Number} lineHeight 465 | * @return {Number} 466 | * @api private 467 | */ 468 | 469 | function lineCount(height, lineHeight) { 470 | return Math.floor(height / lineHeight); 471 | } 472 | 473 | /** 474 | * Returns infomation about 475 | * the current vendor. 476 | * 477 | * @return {Object} 478 | * @api private 479 | */ 480 | 481 | function getVendorData() { 482 | var el = document.createElement('test'); 483 | var result = {}; 484 | var vendors = { 485 | 'Webkit': ['WebkitColumnCount', 'WebkitColumnGap'], 486 | 'Moz': ['MozColumnCount', 'MozColumnGap'], 487 | 'ms': ['msColumnCount', 'msColumnGap'], 488 | '': ['columnCount', 'columnGap'] 489 | }; 490 | 491 | for (var vendor in vendors) { 492 | if (vendors[vendor][0] in el.style) { 493 | result.columnCount = vendors[vendor][0]; 494 | result.columnGap = vendors[vendor][1]; 495 | result[vendor.toLowerCase()] = true; 496 | } 497 | } 498 | 499 | return result; 500 | } 501 | 502 | /** 503 | * Gets the column count of an 504 | * element using the vendor prefix. 505 | * 506 | * @param {CSSStyleDeclaration} style [description] 507 | * @return {Number} 508 | * @api private 509 | */ 510 | 511 | function getColumnCount(style) { 512 | return parseInt(style[vendor.columnCount], 10) || 1; 513 | } 514 | 515 | /** 516 | * Returns the gap between columns 517 | * 518 | * @param {CSSStyleDeclaration} style 519 | * @return {Number} 520 | * @api private 521 | */ 522 | 523 | function getColumnGap(style) { 524 | return parseInt(style[vendor.columnGap], 10) || 0; 525 | } 526 | 527 | /** 528 | * Gets the line height 529 | * from the style declaration. 530 | * 531 | * @param {CSSStyleDeclaration} style 532 | * @return {Number|null} 533 | * @api private 534 | */ 535 | 536 | function getLineHeight(el, style) { 537 | var lineHeightStr = style.lineHeight; 538 | 539 | if (lineHeightStr) { 540 | if (lineHeightStr.indexOf('px') < 0) { 541 | throw Error('The ellipsis container ' + elementName(el) + ' must have line-height set using px unit, found: ' + lineHeightStr); 542 | } 543 | 544 | var lineHeight = parseInt(lineHeightStr, 10); 545 | if (lineHeight) { 546 | return lineHeight; 547 | } 548 | } 549 | throw Error('The ellipsis container ' + elementName(el) + ' must have line-height set on it, found: ' + lineHeightStr); 550 | } 551 | 552 | /** 553 | * Returns the width and 554 | * height of the given element. 555 | * 556 | * @param {Element} el 557 | * @return {Array} 558 | * @api private 559 | */ 560 | 561 | function getSize(el) { 562 | return [el.offsetWidth, el.offsetHeight]; 563 | } 564 | 565 | /** 566 | * Little iterator 567 | * 568 | * @param {Array} list 569 | * @param {Function} fn 570 | * @api private 571 | */ 572 | 573 | function each(list, fn) { 574 | for (var i = 0, l = list.length; i < l; i++) if (fn(list[i])) break; 575 | } 576 | 577 | function elementName(el) { 578 | var name = el.tagName; 579 | if (el.id) name += '#' + el.id; 580 | if (el.className) name += (' ' + el.className).replace(/\s+/g,'.'); 581 | return name; 582 | } 583 | 584 | /** 585 | * Expose `Ellipsis` 586 | */ 587 | 588 | if (typeof exports === 'object') { 589 | module.exports = function(el, options) { 590 | return new Ellipsis(el, options); 591 | }; 592 | module.exports.Ellipsis = Ellipsis; 593 | } else if (typeof define === 'function' && define.amd) { 594 | define(function() { return Ellipsis; }); 595 | } else { 596 | window.Ellipsis = Ellipsis; 597 | } 598 | 599 | })(); 600 | --------------------------------------------------------------------------------