0

I have found various libraries to resize text according to a containers size however my container is a circle and so I am having trouble with it still extending past the border.

Below is a Fiddle and the code for the circles. Is there a way to make it fit without overflowing? In the linked example, the font-size designated is the max font-size to be utilised.

.quizItemAnswerText {
  background: #333;
  justify-content: center;
  align-items: center;
  border-radius: 100%;
  text-align: center;
  margin: 5px 60px;
  font-family: 'News Gothic Std';
  font-size: 60px;
  font-weight: bold;
  padding: 15px;
  display: flex;
  height: 300px;
  width: 300px;
  color:#72B74D;
  border: 3px solid black;
  margin-bottom:200px;
}
<div data-answervalidation="false" data-answerindex="3" class="quizItemAnswerClick quizItemAnswerText">Proin ac libero sit amet metus semper aliquet. Proin ut tellus ut nulla pharetra</div>
<div data-answervalidation="false" data-answerindex="3" class="quizItemAnswerClick quizItemAnswerText">TRUE</div>
Ben H
  • 502
  • 1
  • 7
  • 24
  • You _may_ just have to write something yourself. There are some unique challenges, given that you need to worry about word breaking and the shape of your container-- it might involve a bit of math. At a cursory glance around I didn't find any obvious solutions. I'm assuming you've already found [this CSS-Tricks post](https://css-tricks.com/fitting-text-to-a-container/) and [this SO question](https://stackoverflow.com/questions/16056591/font-scaling-based-on-width-of-container). – Alexander Nied Mar 10 '20 at 01:33
  • I presume you are not asking for *another library* suggestion. Since *a library* implies the use of JavaScript - where is your code that: calculates the.... Wait, are you asking for a math function to calculate how to fit a rectangle inside a circle or how to fit a polyrectangle (line-per-line pixel-perfect) inside a circle? – Roko C. Buljan Mar 10 '20 at 01:48
  • I have stripped this back as the libraries that I had tried weren't working as required so did not see the need to included them in the code example. I've tried the methods in those links @AlexanderNied. – Ben H Mar 10 '20 at 01:55
  • @RokoC.Buljan I have no idea how to acheive this so am looking for guidance. I wouldn't know where to begin with the math functions to make this work unfortunately – Ben H Mar 10 '20 at 01:56
  • Check out [this list](https://codepen.io/tag/fittext) of solutions on Codepen. I would be surprized if they all fail your requirements. Or golden oldie [fittext.js](https://responsivedesign.is/resources/typography/fittext-js/) – Rene van der Lende Mar 10 '20 at 02:16
  • I've tried these and none worked well with circles. One of them did but you had to set the max font-size to be very small for it to work but then even single word elements were small which wasn't the goal. I'll give them another try and didn't have much luck originally – Ben H Mar 10 '20 at 02:20
  • Currently Testing this [fittext standalone version](https://github.com/adactio/FitText.js/blob/master/fittext.js) on your above code and it works just fine. Set the `min: '24px', max: '60px'`, add a `.fittext` class to your `div`s, loop over `querySelectorAll('.fittext')` and it works... – Rene van der Lende Mar 10 '20 at 03:02

1 Answers1

1

Here is a solution using the standalone version of fittext.js.

I added little javascript code executed on 'Document Load' traversing an array of querySelectorAll elements for class .fittext.

Parameters for 'fitText': element, compressor and {min,max} font-size. After fiddling with the parameters, this seems to get a nice result: window.fitText(elem, 1.2, { min: '14px', max: '60px' })

My javascipt code is at the end. Notice the 'responsive sizing' CSS code of html {font-size: calc(0.625vmin + 0.75rem)} and .quizItemAnswerText { width; height}, both calc(9vmin + 127.2px), all using equation y=mx+b, the 'definition of a straight line'.

Make sure to check comments in the CSS.

Now you add the JS code for a square inside a cirlce....

Intermediate Update (2)

The original 'fittext.js' use JS clientWidth including padding. Added option (and code) to choose whether to strip padding from clientWidth or not. The padding needs to be stripped for 'responsive padding' to work correctly.

Done:

  • Check whether the text already fits and do nothing. The original code does indeed ignore that, which I consider a flaw (or omission).

Now 'fitText' will leave the font-size as-is when the text fits in the element. Left some debugging code inside, will be gone next update...

Still to do:

  • create the CSS calc() for 'responsive padding' (y=mx+b adjusted with Pythagoras, this may take a while to figure out...)

'use-strict';

const crlf  = '\r\n';
const br    = '<br>';

// Standalone versions of 'getTextWidth' and 'getDimensions'
// for debugging or use elsewhere

function getTextWidth(txt, fontname, fontsize){
var c=document.createElement('canvas');     // Create a dummy canvas (render invisible with css)

var ctx=c.getContext('2d');                 // Get the context of the dummy canvas
    ctx.font = fontsize + ' ' + fontname;   // Set the context.font to the 'actual' font

var width = ctx.measureText(txt).width;     // Measure the string
    c.remove();                             // No loose ends...
return Math.ceil(width);                    // Return rounded up width
};

function getDimensions(element) {
    var cmp = getComputedStyle(element);

    // get dimenision including padding
    var width  = element.clientWidth;
    var height = element.clientHeight;

    // convert strings (+'px') to float
    var t = parseFloat(cmp.paddingTop);
    var r = parseFloat(cmp.paddingRight);
    var b = parseFloat(cmp.paddingBottom);
    var l = parseFloat(cmp.paddingLeft);

    // Remove padding
    width  -= r + l;
    height -= t + b;

    // Wit hand height (include padding for easy "already converted" access)
    return { width: width, height: height, padding: { top: t, right: r, bottom: b, left: l } };
};

/*! 
* FitText.js 1.01 jQuery free version
*
* Copyright 2011, Dave Rupert http://daverupert.com 
* Released under the WTFPL license 
* http://sam.zoy.org/wtfpl/
* Modified by Slawomir Kolodziej http://slawekk.info
*
*    v1.01 by Rene van der Lende, March 2020
*             - added 'getTextWidth' and 'getDimensions'
*             - extra boolean parameter to select
*               (default)  with padding
*               (optional) without padding
*/
(function(){

var addEvent = function (el, type, fn) {
  if (el.addEventListener)
    el.addEventListener(type, fn, false);
      else
          el.attachEvent('on'+type, fn);
};

var extend = function(obj,ext){
  for(var key in ext)
    if(ext.hasOwnProperty(key))
      obj[key] = ext[key];
  return obj;
};

window.fitText = function (el, kompressor, options, incpad) {
  var settings = extend({
    'minFontSize' : -1/0,
    'maxFontSize' :  1/0
  },options);

    // start RvdL, March 2020
    var getDimensions = function (el) {
        var cmp = getComputedStyle(el);

        // get dimenision including padding
        var width  = el.clientWidth;
        var height = el.clientHeight;

        // convert strings (+'px') to float
        var t = parseFloat(cmp.paddingTop);
        var r = parseFloat(cmp.paddingRight);
        var b = parseFloat(cmp.paddingBottom);
        var l = parseFloat(cmp.paddingLeft);

        // Remove padding
        width  -= r + l;
        height -= t + b;

        // width and height (include padding for easy "already converted" access)
        // height and padding redudant in `fitText`, but who knows...
        return { width: width, height: height, padding: { top: t, right: r, bottom: b, left: l } };
    };

    function getTextWidth(el) {
       var c = document.createElement('canvas');   // Create a dummy canvas (render invisible with css)

       var ctx = c.getContext('2d');               // Get the context of the dummy canvas
         ctx.font = getComputedStyle(el).fontSize + ' sans-serif'; 
       // TODO: Flawed because using fixed 'sans-serif'

       // Use only first node of element (the text) as 'innerText' may contain child elements
       var width = ctx.measureText(el.childNodes[0].nodeValue).width;
         c.remove();                               // No loose ends...
       
         return Math.ceil(width);                    // Return rounded up width
    };
    // end RvdL, March 2020

    var fit = function (el) {
      var compressor = kompressor || 1;     // if null default to 1
      var padded = (incpad=null) || incpad; // if null then 'old behaviour' with padding

      var resizer = function () { // include/exclude padding
        var elWidth = (padded) ? el.clientWidth : getDimensions(el).width;

        if ( elWidth < getTextWidth(el) ) { // Skip when text fits already
           el.style.fontSize = Math.max(Math.min(elWidth / (compressor*10),
                                         parseFloat(settings.maxFontSize)),
                                         parseFloat(settings.minFontSize)) + 'px';
        };
    };
    // Call once to set.
    resizer();

    // Bind events
    // If you have any js library which support Events, replace this part
    // and remove addEvent function (or use original jQuery version)
    addEvent(window, 'resize', resizer);
    addEvent(window, 'orientationchange', resizer);
  };

  if (el.length)
    for(var i=0; i<el.length; i++)
      fit(el[i]);
  else
    fit(el);

  // return set of elements
  return el;
};
})();

/******************************************************/
window.addEventListener('load', initialize); // Do when all HTML/CSS has been loaded
/******************************************************/
var forEachEntryIn = function (array, callback, scope) {
for (var i = 0; i < array.length; i++) { callback.call(scope, i, array[i]); } };

function initialize() {
var cmp;

    forEachEntryIn(document.querySelectorAll('.fittext'),
        function (idx,elem,scope) {
            cmp =  window.getComputedStyle(elem); // debug

            // Original font size
            console.log('canvas', getTextWidth(elem.childNodes[0].nodeValue, 'sans-serif', cmp.fontSize),
                        'fs'    , cmp.fontSize);

            // Params: element, compressor, min/max font-size, strip padding from width
            window.fitText(elem, 1.2, { min: '14px', max: '60px'}, false );

            // fitText font size
            console.log('canvas', getTextWidth(elem.childNodes[0].nodeValue, 'sans-serif', window.getComputedStyle(elem).fontSize),
                        'fs'    , window.getComputedStyle(elem).fontSize);
    });
};
/* 
     made responsive y=mx+b 
     points p1(320,14) p2(1280,20)
*/
html { font-size: calc(0.625vmin + 0.75rem) }

.quizItemAnswerText {
    display: flex;
    justify-content: center;
    align-items: center;

/* 
     made responsive y=mx+b 
     points p1(320,156) p2(1920,300)
*/
    height: calc(9vmin + 127.2px);
    width : calc(9vmin + 127.2px);

    padding: 15px;
    margin: 5px 60px;
    margin-bottom:200px;

    font-family: 'News Gothic Std',arial,sans-serif;
    font-weight: bold;

/* 
     made responsive y=mx+b 
     points p1(320,24) p2(1280,60)
*/
    font-size: calc(3.75vmin + 0.75rem); /* modified from 60px */
    text-align: center;

    border-radius: 50%; /* adjusted from 100% */
    border: 3px solid black;
    background: #333;
    color:#72B74D;
}
<div data-answervalidation="false" data-answerindex="3" class="fittext quizItemAnswerClick quizItemAnswerText">Proin ac libero sit amet metus semper aliquet. Proin ut tellus ut nulla pharetra</div>
<div data-answervalidation="false" data-answerindex="3" class="fittext quizItemAnswerClick quizItemAnswerText">TRUE</div>
Rene van der Lende
  • 4,992
  • 2
  • 14
  • 25
  • Thank you for this. I can see that it is resizing down now. I'm curious though, the circle with 'true' is still the same font-size as the circle with the multiple lines of text? @RenevanderLende – Ben H Mar 10 '20 at 09:10
  • I noticed that and was waiting on your reacting before wanting to look into that. The code is (probably) used and accepted by many people, didn't want to divert from the original just like that. Ok, so, I will have a look. Don't sit on your hands waiting for my reply, you have a look too and report back. Also: I have come up with a way to keep even more text inside the circle using 'responsive padding' (with 'y=mx+b' again) and let CSS do the calculations. I will post an update to my answer when it is done... FYI diagonal of square = diameter circle. Go do some math => Pythagoras – Rene van der Lende Mar 10 '20 at 13:05
  • Thanks man! Certainly am going to have to brush up on my maths! – Ben H Mar 10 '20 at 14:43
  • Added a new update (2). The code should now do what you were asking for, so you may accept the answer as is. I will nonetheless, do one more update (tomorrow) with 'responsive padding' incorporated in the CSS and some JS code cleanup. – Rene van der Lende Mar 10 '20 at 18:47