forked from STRML/textFit
-
Notifications
You must be signed in to change notification settings - Fork 2
/
textFit.js
301 lines (265 loc) · 10.4 KB
/
textFit.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/**
* textFit v2.3.1
* Previously known as jQuery.textFit
* 11/2014 by STRML (strml.github.com)
* MIT License
*
* To use: textFit(document.getElementById('target-div'), options);
*
* Will make the *text* content inside a container scale to fit the container
* The container is required to have a set width and height
* Uses binary search to fit text with minimal layout calls.
* Version 2.0 does not use jQuery.
*/
/*global define:true, document:true, window:true, HTMLElement:true*/
(function(root, factory) {
"use strict";
// UMD shim
if (typeof define === "function" && define.amd) {
// AMD
define([], factory);
} else if (typeof exports === "object") {
// Node/CommonJS
module.exports = factory();
} else {
// Browser
root.textFit = factory();
}
}(typeof global === "object" ? global : this, function () {
"use strict";
var defaultSettings = {
alignVert: false, // if true, textFit will align vertically using css tables
alignHoriz: false, // if true, textFit will set text-align: center
multiLine: false, // if true, textFit will not set white-space: no-wrap
stopOverflow: false, // if true, a error we be thrown if the content is overflowing
maxLines: false, // if true, textFit will throw and error if the text is over the supplied number of lines
detectMultiLine: true, // disable to turn off automatic multi-line sensing
fontUnit: 'px', // what unit should the final font be. using rems or mm is sometimes useful
fontChangeSize: 1, // how much should the font size by ajusted by each time. 0.1 and 0.01 is useful for when using a rem font unit
minFontSize: 6,
maxFontSize: 80,
reProcess: true, // if true, textFit will re-process already-fit nodes. Set to 'false' for better performance
widthOnly: false, // if true, textFit will fit text to element width, regardless of text height
alignVertWithFlexbox: false, // if true, textFit will use flexbox for vertical alignment
};
return function textFit(els, options) {
if (!options) options = {};
// Extend options.
var settings = {};
for(var key in defaultSettings){
if(options.hasOwnProperty(key)){
settings[key] = options[key];
} else {
settings[key] = defaultSettings[key];
}
}
// Convert jQuery objects into arrays
if (typeof els.toArray === "function") {
els = els.toArray();
}
// Support passing a single el
var elType = Object.prototype.toString.call(els);
if (elType !== '[object Array]' && elType !== '[object NodeList]' &&
elType !== '[object HTMLCollection]'){
els = [els];
}
// Process each el we've passed.
for(var i = 0; i < els.length; i++){
processItem(els[i], settings);
}
};
/**
* The meat. Given an el, make the text inside it fit its parent.
* @param {DOMElement} el Child el.
* @param {Object} settings Options for fit.
*/
function processItem(el, settings){
if (!isElement(el) || (!settings.reProcess && el.getAttribute('textFitted'))) {
return false;
}
// Set textFitted attribute so we know this was processed.
if(!settings.reProcess){
el.setAttribute('textFitted', 1);
}
var innerSpan, originalHeight, originalHTML, originalWidth;
var low, mid, high;
// Get element data.
originalHTML = el.innerHTML;
originalWidth = innerWidth(el);
originalHeight = innerHeight(el);
// Don't process if we can't find box dimensions
if (!originalWidth || (!settings.widthOnly && !originalHeight)) {
if(!settings.widthOnly)
throw new Error('Set a static height and width on the target element ' + el.outerHTML +
' before using textFit!');
else
throw new Error('Set a static width on the target element ' + el.outerHTML +
' before using textFit!');
}
// Add textFitted span inside this container.
if (originalHTML.indexOf('textFitted') === -1) {
innerSpan = document.createElement('span');
innerSpan.className = 'textFitted';
// Inline block ensure it takes on the size of its contents, even if they are enclosed
// in other tags like <p>
innerSpan.style['display'] = 'inline-block';
innerSpan.innerHTML = originalHTML;
el.innerHTML = '';
el.appendChild(innerSpan);
} else {
// Reprocessing.
innerSpan = el.querySelector('span.textFitted');
// Remove vertical align if we're reprocessing.
if (hasClass(innerSpan, 'textFitAlignVert')){
innerSpan.className = innerSpan.className.replace('textFitAlignVert', '');
innerSpan.style['height'] = '';
el.className.replace('textFitAlignVertFlex', '');
}
}
// Prepare & set alignment
if (settings.alignHoriz) {
el.style['text-align'] = 'center';
innerSpan.style['text-align'] = 'center';
}
// Check if this string is multiple lines
// Not guaranteed to always work if you use wonky line-heights
var multiLine = settings.multiLine;
if (settings.detectMultiLine && !multiLine &&
innerSpan.scrollHeight >= parseInt(window.getComputedStyle(innerSpan)['font-size'], 10) * 2) {
multiLine = true;
}
// If we're not treating this as a multiline string, don't let it wrap.
if (!multiLine) {
el.style['white-space'] = 'nowrap';
}
var startingSize = innerSpan.style.fontSize;
low = settings.minFontSize;
high = settings.maxFontSize;
// Binary search for highest best fit
var size = low;
while (low <= high) {
mid = parseFloat(((high + low) / 2).toFixed(2));
// mid = (high + low) >> 1;
// console.table('low: ' + low + ' mid: ', + mid + ' high: ' + high);
innerSpan.style.fontSize = mid + settings.fontUnit;
if(innerSpan.scrollWidth <= originalWidth && (settings.widthOnly || innerSpan.scrollHeight <= originalHeight)) {
size = mid;
low = mid + settings.fontChangeSize;
} else {
high = mid - settings.fontChangeSize;
}
// await injection point
}
if (startingSize !== size) {
console.log('textFit font changed size: ', size + settings.fontUnit)
}
// found, updating font if differs:
if( innerSpan.style.fontSize != size + settings.fontUnit ) innerSpan.style.fontSize = size + settings.fontUnit;
// add the required CSS in order to stop overflows
if (settings.maxLines || settings.stopOverflow) {
if (!document.getElementById("overflowStyleSheet")) {
var style = [
".overflow > span {",
"overflow: hidden;",
"}"].join("");
var css = document.createElement("style");
css.type = "text/css";
css.id = "overflowStyleSheet";
css.innerHTML = style;
document.body.appendChild(css);
}
el.classList.remove('overflow');
if (settings.maxLines) {
if (Number.isInteger(settings.maxLines)) {
const computedStyle = window.getComputedStyle(innerSpan);
// Checking if line-height is not set and has a normal value set by default
const isNormal = computedStyle.getPropertyValue('line-height') == 'normal';
// Getting the font-size of an element that will help us calculate the line-height
const fontSize = parseFloat(computedStyle.getPropertyValue('font-size'));
// If line-height is normal, we multiply the element's font-size with 1.14
if ( isNormal ) innerSpan.style.lineHeight = (fontSize * 1.14) + 'px';
const lineHeight = parseFloat(computedStyle.getPropertyValue('line-height'));
const blockHeight = innerSpan.scrollHeight;
// Setting the element's max-height
const limitHeight = (lineHeight * settings.maxLines);
innerSpan.style.maxHeight = limitHeight + 'px';
var overflow = blockHeight > limitHeight
if (overflow) {
el.classList.add('overflow')
}
} else {
console.error('TextFit maxLines needs to be given a number')
}
}
if (settings.stopOverflow) {
var overflow = innerHeight(el) < innerSpan.scrollHeight;
if (overflow) {
el.classList.add('overflow');
};
}
}
// Our height is finalized. If we are aligning vertically, set that up.
if (settings.alignVert) {
addStyleSheet();
var height = innerSpan.scrollHeight;
if (window.getComputedStyle(el)['position'] === "static"){
el.style['position'] = 'relative';
}
if (!hasClass(innerSpan, "textFitAlignVert")){
innerSpan.className = innerSpan.className + " textFitAlignVert";
}
innerSpan.style['height'] = height + "px";
if (settings.alignVertWithFlexbox && !hasClass(el, "textFitAlignVertFlex")) {
el.className = el.className + " textFitAlignVertFlex";
}
}
}
// Calculate height without padding.
function innerHeight(el){
var style = window.getComputedStyle(el, null);
return el.clientHeight -
parseInt(style.getPropertyValue('padding-top'), 10) -
parseInt(style.getPropertyValue('padding-bottom'), 10);
}
// Calculate width without padding.
function innerWidth(el){
var style = window.getComputedStyle(el, null);
return el.clientWidth -
parseInt(style.getPropertyValue('padding-left'), 10) -
parseInt(style.getPropertyValue('padding-right'), 10);
}
//Returns true if it is a DOM element
function isElement(o){
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
);
}
function hasClass(element, cls) {
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
// Better than a stylesheet dependency
function addStyleSheet() {
if (document.getElementById("textFitStyleSheet")) return;
var style = [
".textFitAlignVert{",
"position: absolute;",
"top: 0; right: 0; bottom: 0; left: 0;",
"margin: auto;",
"display: flex;",
"justify-content: center;",
"flex-direction: column;",
"}",
".textFitAlignVertFlex{",
"display: flex;",
"}",
".textFitAlignVertFlex .textFitAlignVert{",
"position: static;",
"}",].join("");
var css = document.createElement("style");
css.type = "text/css";
css.id = "textFitStyleSheet";
css.innerHTML = style;
document.body.appendChild(css);
}
}));