You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
7.7 KiB
240 lines
7.7 KiB
7 years ago
|
/**
|
||
|
* 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
|
||
|
detectMultiLine: true, // disable to turn off automatic multi-line sensing
|
||
|
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';
|
||
|
}
|
||
|
|
||
|
low = settings.minFontSize;
|
||
|
high = settings.maxFontSize;
|
||
|
|
||
|
// Binary search for best fit
|
||
|
var ans = low;
|
||
|
while (low <= high) {
|
||
|
mid = parseInt((low + high) / 2, 10);
|
||
|
innerSpan.style.fontSize = mid + 'px';
|
||
|
if(innerSpan.scrollWidth <= originalWidth && (settings.widthOnly || innerSpan.scrollHeight <= originalHeight)){
|
||
|
low = mid + 1;
|
||
|
ans = mid;
|
||
|
} else {
|
||
|
high = mid - 1;
|
||
|
}
|
||
|
}
|
||
|
// Sub 1 at the very end, this is closer to what we wanted.
|
||
|
innerSpan.style.fontSize = ans + 'px';
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
}));
|