You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

264 lines
7.5 KiB
JavaScript

/**
* 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.getBoundingClientRect().height >=
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 highest best fit
var size = low;
while (low <= high) {
mid = (high + low) >> 1;
innerSpan.style.fontSize = mid + "px";
var innerSpanBoundingClientRect = innerSpan.getBoundingClientRect();
if (
innerSpanBoundingClientRect.width <= originalWidth &&
(settings.widthOnly || innerSpanBoundingClientRect.height <= originalHeight)
) {
size = mid;
low = mid + 1;
} else {
high = mid - 1;
}
// await injection point
}
// found, updating font if differs:
if (innerSpan.style.fontSize != size + "px") innerSpan.style.fontSize = size + "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.getBoundingClientRect().height -
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.getBoundingClientRect().width -
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);
}
});