PageSourceSearch

https://www.andreanhs.org/custom/js/clamp.js

js andreanhs.org collected 2026-09-24 12:34:21 UTC 10,023 bytes, 261 lines download raw bytes

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(function(){
10    /**
11     * Clamps a text node.
12     * @param {HTMLElement} element. Element containing the text node to clamp.
13     * @param {Object} options. Options to pass to the clamper.
14     */
15    function clamp(element, options) {
16        options = options || {};
17
18        var self = this,
19            win = window,
20            opt = {
21                clamp:              options.clamp || 2,
22                useNativeClamp:     typeof(options.useNativeClamp) != 'undefined' ? options.useNativeClamp : true,
23                splitOnChars:       options.splitOnChars || ['.', '-', '–', '—', ' '], //Split on sentences (periods), hypens, en-dashes, em-dashes, and words (spaces).
24                animate:            options.animate || false,
25                truncationChar:     options.truncationChar || '…',
26                truncationHTML:     options.truncationHTML
27            },
28
29            sty = element.style,
30            originalText = element.innerHTML,
31
32            supportsNativeClamp = typeof(element.style.webkitLineClamp) != 'undefined',
33            clampValue = opt.clamp,
34            isCSSValue = clampValue.indexOf && (clampValue.indexOf('px') > -1 || clampValue.indexOf('em') > -1),
35            truncationHTMLContainer;
36            
37        if (opt.truncationHTML) {
38            truncationHTMLContainer = document.createElement('span');
39            truncationHTMLContainer.innerHTML = opt.truncationHTML;
40        }
41
42
43// UTILITY FUNCTIONS __________________________________________________________
44
45        /**
46         * Return the current style for an element.
47         * @param {HTMLElement} elem The element to compute.
48         * @param {string} prop The style property.
49         * @returns {number}
50         */
51        function computeStyle(elem, prop) {
52            if (!win.getComputedStyle) {
53                win.getComputedStyle = function(el, pseudo) {
54                    this.el = el;
55                    this.getPropertyValue = function(prop) {
56                        var re = /(\-([a-z]){1})/g;
57                        if (prop == 'float') prop = 'styleFloat';
58                        if (re.test(prop)) {
59                            prop = prop.replace(re, function () {
60                                return arguments[2].toUpperCase();
61                            });
62                        }
63                        return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null;
64                    }
65                    return this;
66                }
67            }
68
69            return win.getComputedStyle(elem, null).getPropertyValue(prop);
70        }
71
72        /**
73         * Returns the maximum number of lines of text that should be rendered based
74         * on the current height of the element and the line-height of the text.
75         */
76        function getMaxLines(height) {
77            var availHeight = height || element.clientHeight,
78                lineHeight = getLineHeight(element);
79
80            return Math.max(Math.floor(availHeight/lineHeight), 0);
81        }
82
83        /**
84         * Returns the maximum height a given element should have based on the line-
85         * height of the text and the given clamp value.
86         */
87        function getMaxHeight(clmp) {
88            var lineHeight = getLineHeight(element);
89            return lineHeight * clmp;
90        }
91
92        /**
93         * Returns the line-height of an element as an integer.
94         */
95        function getLineHeight(elem) {
96            var lh = computeStyle(elem, 'line-height');
97            if (lh == 'normal') {
98                // Normal line heights vary from browser to browser. The spec recommends
99                // a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff.
100                lh = parseInt(computeStyle(elem, 'font-size')) * 1.2;
101            }
102            return parseInt(lh);
103        }
104
105
106// MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________
107        var splitOnChars = opt.splitOnChars.slice(0),
108            splitChar = splitOnChars[0],
109            chunks,
110            lastChunk;
111        
112        /**
113         * Gets an element's last child. That may be another node or a node's contents.
114         */
115        function getLastChild(elem) {
116            //Current element has children, need to go deeper and get last child as a text node
117            if (elem.lastChild.children && elem.lastChild.children.length > 0) {
118                return getLastChild(Array.prototype.slice.call(elem.children).pop());
119            }
120            //This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying
121            else if (!elem.lastChild || !elem.lastChild.nodeValue || elem.lastChild.nodeValue == '' || elem.lastChild.nodeValue == opt.truncationChar) {
122                elem.lastChild.parentNode.removeChild(elem.lastChild);
123                return getLastChild(element);
124            }
125            //This is the last child we want, return it
126            else {
127                return elem.lastChild;
128            }
129        }
130        
131        /**
132         * Removes one character at a time from the text until its width or
133         * height is beneath the passed-in max param.
134         */
135        function truncate(target, maxHeight) {
136            if (!maxHeight) {return;}
137            
138            /**
139             * Resets global variables.
140             */
141            function reset() {
142                splitOnChars = opt.splitOnChars.slice(0);
143                splitChar = splitOnChars[0];
144                chunks = null;
145                lastChunk = null;
146            }
147            
148            var nodeValue = target.nodeValue.replace(opt.truncationChar, '');
149            
150            //Grab the next chunks
151            if (!chunks) {
152                //If there are more characters to try, grab the next one
153                if (splitOnChars.length > 0) {
154                    splitChar = splitOnChars.shift();
155                }
156                //No characters to chunk by. Go character-by-character
157                else {
158                    splitChar = '';
159                }
160                
161                chunks = nodeValue.split(splitChar);
162            }
163            
164            //If there are chunks left to remove, remove the last one and see if
165            // the nodeValue fits.
166            if (chunks.length > 1) {
167                // console.log('chunks', chunks);
168                lastChunk = chunks.pop();
169                // console.log('lastChunk', lastChunk);
170                applyEllipsis(target, chunks.join(splitChar));
171            }
172            //No more chunks can be removed using this character
173            else {
174                chunks = null;
175            }
176            
177            //Insert the custom HTML before the truncation character
178            if (truncationHTMLContainer) {
179                target.nodeValue = target.nodeValue.replace(opt.truncationChar, '');
180                element.innerHTML = target.nodeValue + ' ' + truncationHTMLContainer.innerHTML + opt.truncationChar;
181            }
182
183            //Search produced valid chunks
184            if (chunks) {
185                //It fits
186                if (element.clientHeight <= maxHeight) {
187                    //There's still more characters to try splitting on, not quite done yet
188                    if (splitOnChars.length >= 0 && splitChar != '') {
189                        applyEllipsis(target, chunks.join(splitChar) + splitChar + lastChunk);
190                        chunks = null;
191                    }
192                    //Finished!
193                    else {
194                        return element.innerHTML;
195                    }
196                }
197            }
198            //No valid chunks produced
199            else {
200                //No valid chunks even when splitting by letter, time to move
201                //on to the next node
202                if (splitChar == '') {
203                    applyEllipsis(target, '');
204                    target = getLastChild(element);
205                    
206                    reset();
207                }
208            }
209            
210            //If you get here it means still too big, let's keep truncating
211            if (opt.animate) {
212                setTimeout(function() {
213                    truncate(target, maxHeight);
214                }, opt.animate === true ? 10 : opt.animate);
215            }
216            else {
217                return truncate(target, maxHeight);
218            }
219        }
220        
221        function applyEllipsis(elem, str) {
222            elem.nodeValue = str + opt.truncationChar;
223        }
224
225
226// CONSTRUCTOR ________________________________________________________________
227
228        if (clampValue == 'auto') {
229            clampValue = getMaxLines();
230        }
231        else if (isCSSValue) {
232            clampValue = getMaxLines(parseInt(clampValue));
233        }
234
235        var clampedText;
236        if (supportsNativeClamp && opt.useNativeClamp) {
237            sty.overflow = 'hidden';
238            sty.textOverflow = 'ellipsis';
239            sty.webkitBoxOrient = 'vertical';
240            sty.display = '-webkit-box';
241            sty.webkitLineClamp = clampValue;
242
243            if (isCSSValue) {
244                sty.height = opt.clamp + 'px';
245            }
246        }
247        else {
248            var height = getMaxHeight(clampValue);
249            if (height <= element.clientHeight) {
250                clampedText = truncate(getLastChild(element), height);
251            }
252        }
253        
254        return {
255            'original': originalText,
256            'clamped': clampedText
257        }
258    }
259
260    window.$clamp = clamp;
261})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.