jquery.textfill.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /**
  2. * @preserve textfill
  3. * @name jquery.textfill.js
  4. * @author Russ Painter
  5. * @author Yu-Jie Lin
  6. * @author Alexandre Dantas
  7. * @version 0.6.0
  8. * @date 2014-08-19
  9. * @copyright (c) 2014 Alexandre Dantas
  10. * @copyright (c) 2012-2013 Yu-Jie Lin
  11. * @copyright (c) 2009 Russ Painter
  12. * @license MIT License
  13. * @homepage https://github.com/jquery-textfill/jquery-textfill
  14. * @example http://jquery-textfill.github.io/jquery-textfill/index.html
  15. */
  16. ; (function($) {
  17. /**
  18. * Resizes an inner element's font so that the
  19. * inner element completely fills the outer element.
  20. *
  21. * @param {Object} options User options that take
  22. * higher precedence when
  23. * merging with the default ones.
  24. *
  25. * @return All outer elements processed
  26. */
  27. $.fn.textfill = function(options) {
  28. // ______ _______ _______ _______ _ _ _______ _______
  29. // | \ |______ |______ |_____| | | | | |______
  30. // |_____/ |______ | | | |_____| |_____ | ______|
  31. //
  32. // Merging user options with the default values
  33. var defaults = {
  34. debug : false,
  35. maxFontPixels : 40,
  36. minFontPixels : 4,
  37. innerTag : 'span',
  38. widthOnly : false,
  39. success : null, // callback when a resizing is done
  40. callback : null, // callback when a resizing is done (deprecated, use success)
  41. fail : null, // callback when a resizing is failed
  42. complete : null, // callback when all is done
  43. explicitWidth : null,
  44. explicitHeight : null,
  45. changeLineHeight : false
  46. };
  47. var Opts = $.extend(defaults, options);
  48. // _______ _ _ __ _ _______ _______ _____ _____ __ _ _______
  49. // |______ | | | \ | | | | | | | \ | |______
  50. // | |_____| | \_| |_____ | __|__ |_____| | \_| ______|
  51. //
  52. // Predefining the awesomeness
  53. // Output arguments to the Debug console
  54. // if "Debug Mode" is enabled
  55. function _debug() {
  56. if (!Opts.debug
  57. || typeof console == 'undefined'
  58. || typeof console.debug == 'undefined') {
  59. return;
  60. }
  61. console.debug.apply(console, arguments);
  62. }
  63. // Output arguments to the Warning console
  64. function _warn() {
  65. if (typeof console == 'undefined' ||
  66. typeof console.warn == 'undefined') {
  67. return;
  68. }
  69. console.warn.apply(console, arguments);
  70. }
  71. // Outputs all information on the current sizing
  72. // of the font.
  73. function _debug_sizing(prefix, ourText, maxHeight, maxWidth, minFontPixels, maxFontPixels) {
  74. function _m(v1, v2) {
  75. var marker = ' / ';
  76. if (v1 > v2)
  77. marker = ' > ';
  78. else if (v1 == v2)
  79. marker = ' = ';
  80. return marker;
  81. }
  82. _debug(
  83. '[TextFill] ' + prefix + ' { ' +
  84. 'font-size: ' + ourText.css('font-size') + ',' +
  85. 'Height: ' + ourText.height() + 'px ' + _m(ourText.height(), maxHeight) + maxHeight + 'px,' +
  86. 'Width: ' + ourText.width() + _m(ourText.width() , maxWidth) + maxWidth + ',' +
  87. 'minFontPixels: ' + minFontPixels + 'px, ' +
  88. 'maxFontPixels: ' + maxFontPixels + 'px }'
  89. );
  90. }
  91. /**
  92. * Calculates which size the font can get resized,
  93. * according to constrains.
  94. *
  95. * @param {String} prefix Gets shown on the console before
  96. * all the arguments, if debug mode is on.
  97. * @param {Object} ourText The DOM element to resize,
  98. * that contains the text.
  99. * @param {function} func Function called on `ourText` that's
  100. * used to compare with `max`.
  101. * @param {number} max Maximum value, that gets compared with
  102. * `func` called on `ourText`.
  103. * @param {number} minFontPixels Minimum value the font can
  104. * get resized to (in pixels).
  105. * @param {number} maxFontPixels Maximum value the font can
  106. * get resized to (in pixels).
  107. *
  108. * @return Size (in pixels) that the font can be resized.
  109. */
  110. function _sizing(prefix, ourText, func, max, maxHeight, maxWidth, minFontPixels, maxFontPixels) {
  111. _debug_sizing(
  112. prefix, ourText,
  113. maxHeight, maxWidth,
  114. minFontPixels, maxFontPixels
  115. );
  116. // The kernel of the whole plugin, take most attention
  117. // on this part.
  118. //
  119. // This is a loop that keeps increasing the `font-size`
  120. // until it fits the parent element.
  121. //
  122. // - Start from the minimal allowed value (`minFontPixels`)
  123. // - Guesses an average font size (in pixels) for the font,
  124. // - Resizes the text and sees if its size is within the
  125. // boundaries (`minFontPixels` and `maxFontPixels`).
  126. // - If so, keep guessing until we break.
  127. // - If not, return the last calculated size.
  128. //
  129. // I understand this is not optimized and we should
  130. // consider implementing something akin to
  131. // Daniel Hoffmann's answer here:
  132. //
  133. // http://stackoverflow.com/a/17433451/1094964
  134. //
  135. while (minFontPixels < (maxFontPixels - 1)) {
  136. var fontSize = Math.floor((minFontPixels + maxFontPixels) / 2);
  137. ourText.css('font-size', fontSize);
  138. if (func.call(ourText) <= max) {
  139. minFontPixels = fontSize;
  140. if (func.call(ourText) == max)
  141. break;
  142. }
  143. else
  144. maxFontPixels = fontSize;
  145. _debug_sizing(
  146. prefix, ourText,
  147. maxHeight, maxWidth,
  148. minFontPixels, maxFontPixels
  149. );
  150. }
  151. ourText.css('font-size', maxFontPixels);
  152. if (func.call(ourText) <= max) {
  153. minFontPixels = maxFontPixels;
  154. _debug_sizing(
  155. prefix + '* ', ourText,
  156. maxHeight, maxWidth,
  157. minFontPixels, maxFontPixels
  158. );
  159. }
  160. return minFontPixels;
  161. }
  162. // _______ _______ _______ ______ _______
  163. // |______ | |_____| |_____/ |
  164. // ______| | | | | \_ |
  165. //
  166. // Let's get it started (yeah)!
  167. _debug('[TextFill] Start Debug');
  168. this.each(function() {
  169. // Contains the child element we will resize.
  170. // $(this) means the parent container
  171. var ourText = $(Opts.innerTag + ':visible:first', this);
  172. // Will resize to this dimensions.
  173. // Use explicit dimensions when specified
  174. var maxHeight = Opts.explicitHeight || $(this).height();
  175. var maxWidth = Opts.explicitWidth || $(this).width();
  176. var oldFontSize = ourText.css('font-size');
  177. var lineHeight = parseFloat(ourText.css('line-height')) / parseFloat(oldFontSize);
  178. _debug('[TextFill] Inner text: ' + ourText.text());
  179. _debug('[TextFill] All options: ', Opts);
  180. _debug('[TextFill] Maximum sizes: { ' +
  181. 'Height: ' + maxHeight + 'px, ' +
  182. 'Width: ' + maxWidth + 'px' + ' }'
  183. );
  184. var minFontPixels = Opts.minFontPixels;
  185. // Remember, if this `maxFontPixels` is negative,
  186. // the text will resize to as long as the container
  187. // can accomodate
  188. var maxFontPixels = (Opts.maxFontPixels <= 0 ?
  189. maxHeight :
  190. Opts.maxFontPixels);
  191. // Let's start it all!
  192. // 1. Calculate which `font-size` would
  193. // be best for the Height
  194. var fontSizeHeight = undefined;
  195. if (! Opts.widthOnly)
  196. fontSizeHeight = _sizing(
  197. 'Height', ourText,
  198. $.fn.height, maxHeight,
  199. maxHeight, maxWidth,
  200. minFontPixels, maxFontPixels
  201. );
  202. // 2. Calculate which `font-size` would
  203. // be best for the Width
  204. var fontSizeWidth = undefined;
  205. fontSizeWidth = _sizing(
  206. 'Width', ourText,
  207. $.fn.width, maxWidth,
  208. maxHeight, maxWidth,
  209. minFontPixels, maxFontPixels
  210. );
  211. // 3. Actually resize the text!
  212. if (Opts.widthOnly) {
  213. ourText.css({
  214. 'font-size' : fontSizeWidth,
  215. 'white-space': 'nowrap'
  216. });
  217. if (Opts.changeLineHeight)
  218. ourText.parent().css(
  219. 'line-height',
  220. (lineHeight * fontSizeWidth + 'px')
  221. );
  222. }
  223. else {
  224. var fontSizeFinal = Math.min(fontSizeHeight, fontSizeWidth);
  225. ourText.css('font-size', fontSizeFinal);
  226. if (Opts.changeLineHeight)
  227. ourText.parent().css(
  228. 'line-height',
  229. (lineHeight * fontSizeFinal) + 'px'
  230. );
  231. }
  232. _debug(
  233. '[TextFill] Finished { ' +
  234. 'Old font-size: ' + oldFontSize + ', ' +
  235. 'New font-size: ' + ourText.css('font-size') + ' }'
  236. );
  237. // Oops, something wrong happened!
  238. // We weren't supposed to exceed the original size
  239. if ((ourText.width() > maxWidth) ||
  240. (ourText.height() > maxHeight && !Opts.widthOnly)) {
  241. ourText.css('font-size', oldFontSize);
  242. // Failure callback
  243. if (Opts.fail)
  244. Opts.fail(this);
  245. _debug(
  246. '[TextFill] Failure { ' +
  247. 'Current Width: ' + ourText.width() + ', ' +
  248. 'Maximum Width: ' + maxWidth + ', ' +
  249. 'Current Height: ' + ourText.height() + ', ' +
  250. 'Maximum Height: ' + maxHeight + ' }'
  251. );
  252. }
  253. else if (Opts.success) {
  254. Opts.success(this);
  255. }
  256. else if (Opts.callback) {
  257. _warn('callback is deprecated, use success, instead');
  258. // Success callback
  259. Opts.callback(this);
  260. }
  261. });
  262. // Complete callback
  263. if (Opts.complete)
  264. Opts.complete(this);
  265. _debug('[TextFill] End Debug');
  266. return this;
  267. };
  268. })(window.jQuery);