forked from joshgillies/text-overflow-clamp
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathclamp.js
166 lines (137 loc) · 4.5 KB
/
clamp.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// http://codepen.io/Merri/pen/Dsuim
/**
* TextOverflowClamp.js
*
* Updated 2013-05-09 to remove jQuery dependency.
* But be careful with webfonts!
*/
// the actual meat is here
module.exports = (function (d) {
var measure, text, lineWidth,
lineStart, lineCount, wordStart,
line, lineText, wasNewLine,
ce = d.createElement.bind(d),
ctn = d.createTextNode.bind(d);
// measurement element is made a child of the clamped element to get it's style
measure = ce('span');
Object.assign(measure.style, {
position: 'absolute', // prevent page reflow
whiteSpace: 'pre', // cross-browser width results
visibility: 'hidden' // prevent drawing
});
return function clamp(el, options) {
// make sure the element belongs to the document
if (!el || !el.ownerDocument || !el.ownerDocument === d) {
return;
}
options = options || {};
var lineClamp = options.lineClamp || 2;
var truncateText = options.truncateText || '';
var textAlign = options.textAlign || 'flex-start';
// reset to safe starting values
lineStart = wordStart = 0;
lineCount = 1;
wasNewLine = false;
lineWidth = el.clientWidth;
// remove truncate span first
if (el.lineContainer && el.truncSpan) {
el.lineContainer.removeChild(el.truncSpan);
}
// get all the text, remove any line changes
text = (el.textContent || el.innerText).replace(/\n/g, ' ') + ' ';
// remove all content
while (el.firstChild !== null) {
el.removeChild(el.firstChild);
}
// add measurement element within so it inherits styles
el.appendChild(measure);
// http://ejohn.org/blog/search-and-dont-replace/
text.replace(/ /g, function (m, pos) {
// ignore any further processing if we have total lines
if (lineCount === lineClamp) {
return;
}
// create a text node and place it in the measurement element
measure.appendChild(ctn(text.substr(lineStart, pos - lineStart)));
// have we exceeded allowed line width?
if (lineWidth < measure.clientWidth) {
if (wasNewLine) {
// we have a long word so it gets a line of it's own
lineText = text.substr(lineStart, pos + 1 - lineStart);
// next line start position
lineStart = pos + 1;
} else {
// grab the text until this word
lineText = text.substr(lineStart, wordStart - lineStart);
// next line start position
lineStart = wordStart;
}
// create a line element
line = ce('span');
// add text to the line element
line.appendChild(ctn(lineText));
// add the line element to the container
el.appendChild(line);
// yes, we created a new line
wasNewLine = true;
lineCount++;
} else {
// did not create a new line
wasNewLine = false;
}
// remember last word start position
wordStart = pos + 1;
// clear measurement element
measure.removeChild(measure.firstChild);
});
// remove the measurement element from the container
el.removeChild(measure);
// create truncation span element
var truncSpan;
if (truncateText) {
truncSpan = ce('span');
truncSpan.appendChild(ctn(truncateText));
Object.assign(truncSpan.style, {
flex: '0 0 auto',
whiteSpace: 'pre'
});
el.truncSpan = truncSpan;
}
// create last line container
var lineContainer = ce('span');
var lineContainerStyle = {
display: 'flex',
width: '100%'
};
if (truncSpan) {
lineContainerStyle.justifyContent = textAlign;
}
Object.assign(lineContainer.style, lineContainerStyle);
// save references
el.lineContainer = lineContainer;
// create the last line element
line = ce('span');
// give styles required for text-overflow to kick in
var lineStyle = {
display: 'block',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
paddingRight: '1ch',
marginRight: '-1ch'
};
if (!truncSpan) {
lineStyle.flex = '1 1 auto';
}
Object.assign(line.style, lineStyle);
// add all remaining text to the line element
line.appendChild(ctn(text.substr(lineStart)));
// add remaining text and truncation text to div
lineContainer.appendChild(line);
if (truncSpan) {
lineContainer.appendChild(truncSpan);
}
// add the line element to the container
el.appendChild(lineContainer);
};
})(document);