textFit.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * textFit v2.3.1
  3. * Previously known as jQuery.textFit
  4. * 11/2014 by STRML (strml.github.com)
  5. * MIT License
  6. *
  7. * To use: textFit(document.getElementById('target-div'), options);
  8. *
  9. * Will make the *text* content inside a container scale to fit the container
  10. * The container is required to have a set width and height
  11. * Uses binary search to fit text with minimal layout calls.
  12. * Version 2.0 does not use jQuery.
  13. */
  14. /*global define:true, document:true, window:true, HTMLElement:true*/
  15. (function(root, factory) {
  16. "use strict";
  17. // UMD shim
  18. if (typeof define === "function" && define.amd) {
  19. // AMD
  20. define([], factory);
  21. } else if (typeof exports === "object") {
  22. // Node/CommonJS
  23. module.exports = factory();
  24. } else {
  25. // Browser
  26. root.textFit = factory();
  27. }
  28. }(typeof global === "object" ? global : this, function () {
  29. "use strict";
  30. var defaultSettings = {
  31. alignVert: false, // if true, textFit will align vertically using css tables
  32. alignHoriz: false, // if true, textFit will set text-align: center
  33. multiLine: false, // if true, textFit will not set white-space: no-wrap
  34. detectMultiLine: true, // disable to turn off automatic multi-line sensing
  35. minFontSize: 6,
  36. maxFontSize: 80,
  37. reProcess: true, // if true, textFit will re-process already-fit nodes. Set to 'false' for better performance
  38. widthOnly: false, // if true, textFit will fit text to element width, regardless of text height
  39. alignVertWithFlexbox: false, // if true, textFit will use flexbox for vertical alignment
  40. };
  41. return function textFit(els, options) {
  42. if (!options) options = {};
  43. // Extend options.
  44. var settings = {};
  45. for(var key in defaultSettings){
  46. if(options.hasOwnProperty(key)){
  47. settings[key] = options[key];
  48. } else {
  49. settings[key] = defaultSettings[key];
  50. }
  51. }
  52. // Convert jQuery objects into arrays
  53. if (typeof els.toArray === "function") {
  54. els = els.toArray();
  55. }
  56. // Support passing a single el
  57. var elType = Object.prototype.toString.call(els);
  58. if (elType !== '[object Array]' && elType !== '[object NodeList]' &&
  59. elType !== '[object HTMLCollection]'){
  60. els = [els];
  61. }
  62. // Process each el we've passed.
  63. for(var i = 0; i < els.length; i++){
  64. processItem(els[i], settings);
  65. }
  66. };
  67. /**
  68. * The meat. Given an el, make the text inside it fit its parent.
  69. * @param {DOMElement} el Child el.
  70. * @param {Object} settings Options for fit.
  71. */
  72. function processItem(el, settings){
  73. if (!isElement(el) || (!settings.reProcess && el.getAttribute('textFitted'))) {
  74. return false;
  75. }
  76. // Set textFitted attribute so we know this was processed.
  77. if(!settings.reProcess){
  78. el.setAttribute('textFitted', 1);
  79. }
  80. var innerSpan, originalHeight, originalHTML, originalWidth;
  81. var low, mid, high;
  82. // Get element data.
  83. originalHTML = el.innerHTML;
  84. originalWidth = innerWidth(el);
  85. originalHeight = innerHeight(el);
  86. // Don't process if we can't find box dimensions
  87. if (!originalWidth || (!settings.widthOnly && !originalHeight)) {
  88. if(!settings.widthOnly)
  89. throw new Error('Set a static height and width on the target element ' + el.outerHTML +
  90. ' before using textFit!');
  91. else
  92. throw new Error('Set a static width on the target element ' + el.outerHTML +
  93. ' before using textFit!');
  94. }
  95. // Add textFitted span inside this container.
  96. if (originalHTML.indexOf('textFitted') === -1) {
  97. innerSpan = document.createElement('span');
  98. innerSpan.className = 'textFitted';
  99. // Inline block ensure it takes on the size of its contents, even if they are enclosed
  100. // in other tags like <p>
  101. innerSpan.style['display'] = 'inline-block';
  102. innerSpan.innerHTML = originalHTML;
  103. el.innerHTML = '';
  104. el.appendChild(innerSpan);
  105. } else {
  106. // Reprocessing.
  107. innerSpan = el.querySelector('span.textFitted');
  108. // Remove vertical align if we're reprocessing.
  109. if (hasClass(innerSpan, 'textFitAlignVert')){
  110. innerSpan.className = innerSpan.className.replace('textFitAlignVert', '');
  111. innerSpan.style['height'] = '';
  112. el.className.replace('textFitAlignVertFlex', '');
  113. }
  114. }
  115. // Prepare & set alignment
  116. if (settings.alignHoriz) {
  117. el.style['text-align'] = 'center';
  118. innerSpan.style['text-align'] = 'center';
  119. }
  120. // Check if this string is multiple lines
  121. // Not guaranteed to always work if you use wonky line-heights
  122. var multiLine = settings.multiLine;
  123. if (settings.detectMultiLine && !multiLine &&
  124. innerSpan.scrollHeight >= parseInt(window.getComputedStyle(innerSpan)['font-size'], 10) * 2){
  125. multiLine = true;
  126. }
  127. // If we're not treating this as a multiline string, don't let it wrap.
  128. if (!multiLine) {
  129. el.style['white-space'] = 'nowrap';
  130. }
  131. low = settings.minFontSize + 1;
  132. high = settings.maxFontSize + 1;
  133. // Binary search for best fit
  134. while (low <= high) {
  135. mid = parseInt((low + high) / 2, 10);
  136. innerSpan.style.fontSize = mid + 'px';
  137. if(innerSpan.scrollWidth <= originalWidth && (settings.widthOnly || innerSpan.scrollHeight <= originalHeight)){
  138. low = mid + 1;
  139. } else {
  140. high = mid - 1;
  141. }
  142. }
  143. // Sub 1 at the very end, this is closer to what we wanted.
  144. innerSpan.style.fontSize = (mid - 1) + 'px';
  145. // Our height is finalized. If we are aligning vertically, set that up.
  146. if (settings.alignVert) {
  147. addStyleSheet();
  148. var height = innerSpan.scrollHeight;
  149. if (window.getComputedStyle(el)['position'] === "static"){
  150. el.style['position'] = 'relative';
  151. }
  152. if (!hasClass(innerSpan, "textFitAlignVert")){
  153. innerSpan.className = innerSpan.className + " textFitAlignVert";
  154. }
  155. innerSpan.style['height'] = height + "px";
  156. if (settings.alignVertWithFlexbox && !hasClass(el, "textFitAlignVertFlex")) {
  157. el.className = el.className + " textFitAlignVertFlex";
  158. }
  159. }
  160. }
  161. // Calculate height without padding.
  162. function innerHeight(el){
  163. var style = window.getComputedStyle(el, null);
  164. return el.clientHeight -
  165. parseInt(style.getPropertyValue('padding-top'), 10) -
  166. parseInt(style.getPropertyValue('padding-bottom'), 10);
  167. }
  168. // Calculate width without padding.
  169. function innerWidth(el){
  170. var style = window.getComputedStyle(el, null);
  171. return el.clientWidth -
  172. parseInt(style.getPropertyValue('padding-left'), 10) -
  173. parseInt(style.getPropertyValue('padding-right'), 10);
  174. }
  175. //Returns true if it is a DOM element
  176. function isElement(o){
  177. return (
  178. typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
  179. o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
  180. );
  181. }
  182. function hasClass(element, cls) {
  183. return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
  184. }
  185. // Better than a stylesheet dependency
  186. function addStyleSheet() {
  187. if (document.getElementById("textFitStyleSheet")) return;
  188. var style = [
  189. ".textFitAlignVert{",
  190. "position: absolute;",
  191. "top: 0; right: 0; bottom: 0; left: 0;",
  192. "margin: auto;",
  193. "display: flex;",
  194. "justify-content: center;",
  195. "flex-direction: column;",
  196. "}",
  197. ".textFitAlignVertFlex{",
  198. "display: flex;",
  199. "}",
  200. ".textFitAlignVertFlex .textFitAlignVert{",
  201. "position: static;",
  202. "}",].join("");
  203. var css = document.createElement("style");
  204. css.type = "text/css";
  205. css.id = "textFitStyleSheet";
  206. css.innerHTML = style;
  207. document.body.appendChild(css);
  208. }
  209. }));