TheNomadicAspie
TheNomadicAspie

Reputation: 275

Wrapping and shrinking text if needed to fit button

For months this is the only problem I haven't been able to solve. I've hired multiple people on Freelancer to help and two gave up, and another said it was impossible.

I'm simply trying to get the text of my buttons to stay within the boundaries of the button. Right now it looks like this. enter image description here

All I need to do is wrap the text if it's too long to fit the horizontal boundaries of the button, and shrink the text if it's too big to fit within the boundaries of the button. Like this:

enter image description here

I've tried:

  1. Using Fitty, FitText, and other libraries which don't work at all. They'll sometimes make the text too big to fit within the boundaries of the button, and sometimes they'll make all of my text small unnecessarily.
  2. Creating my own function by looking at clientWidth and clientHeight, and shrinking the font as necessary. When I do that, clientWidth of my elements stay the same regardless of the actual size, I've also used getComputedStyle which doesn't seem to calculate properly either.
  3. Paying people. Again multiple people have given up, and I've spent months trying to solve this with no success.

I've created a codepen with a minimally reproducible example showing the problem.

https://codepen.io/TheNomadicAspie/pen/dyRLrej

And here is the code (I've removed all of the unnecessary code, but left in the parent divs of the buttons in case they are affecting whatever is keeping the libraries/my functions/other people from being able to do this).

<div id="screen" class="screen">
  <div id="display" class="display">
    <div id="bottom_bar" class="bottom-bar">
      <div id="bottom_display" class="bottom-display">
        <div id="answers_display" class="answers-display">
          <div id="answer_container_1" class="answer-button-1">
            <div id="answer_checkbox_1" class="checkbox">
            </div>
            <div id="answer_button_container_1" class="answer-button-container">
              <button id="answer_button_1" class="button lower-button">
              </button>
            </div>
          </div>
          <div id="answer_container_2" class="answer-button-2">
            <div id="answer_checkbox_2" class="checkbox">
            </div>
            <div id="answer_button_container_2" class="answer-button-container">
              <button id="answer_button_2" class="button lower-button">
              </button>
            </div>
          </div>
          <div id="answer_container_3" class="answer-button-3">
            <div id="answer_checkbox_3" class="checkbox">
            </div>
            <div id="answer_button_container_3" class="answer-button-container">
              <button id="answer_button_3" class="button lower-button">
              </button>
            </div>
          </div>
          <div id="answer_container_4" class="answer-button-4">
            <div id="answer_checkbox_4" class="checkbox">
            </div>
            <div id="answer_button_container_4" class="answer-button-container">
              <button id="answer_button_4" class="button lower-button">
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<link href="styles.css" rel="stylesheet">
* {
  outline: none;
  opacity: 1;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}

html,
body {
    position: fixed;
    height: 100%;
    background-color: #26004b;
    font-size: 2vh;
    margin: 0 auto;
    font-family: open_sans;
}

.screen {
  position: absolute;
  height: 100%;
}

.title {
  grid-column: 2/3;
  position: relative;
  color: #f5f5f5;
  height: 100%;
  width: 100%;
  margin: 0 auto;
  text-align: center;
  justify-content: center;
  align-items: center;
  font-family: hack;
  font-size: clamp(2vw, 8vw, 10vh);
  display: flex;
  top: 0%;
}

.display {
  position: relative;
  height: 86.286%;
  width: 100vw;
}

.bottom-bar {
  display: grid;
  grid-template-columns: 38.2% 61.8%;
  position: relative;
  height: 38.2%;
  width: 100vw;
  bottom: 0%;
}

.character {
  grid-column: 1/2;
  position: relative;
  background-size: contain;
  background-repeat: no-repeat;
  background-position-y: bottom;
  background-position-x: center;
}

.bottom-display {
  grid-column: 2/3;
  position: relative;
  height: 100%;
  padding-right: 5vw;
  padding-top: 1%;
  padding-bottom: 3%;
}

.answers-display {
  display: grid;
  gap: 1%;
  max-height: 99%;
  grid-template-columns: 100%;
  grid-template-rows: 25% 25% 25% 25%;
  height: 100%;
}

.answer-button-1 {
  position: relative;
  grid-row: 1/2;
  display: grid;
  grid-template-columns: 20% 80%;
  height: 98%;
}

.answer-button-2 {
  position: relative;
  grid-row: 2/3;
  display: grid;
  grid-template-columns: 20% 80%;
  height: 98%;
}

.answer-button-3 {
  position: relative;
  grid-row: 3/4;
  display: grid;
  grid-template-columns: 20% 80%;
  height: 99%;
}

.answer-button-4 {
  position: relative;
  grid-row: 4/5;
  display: grid;
  grid-template-columns: 20% 80%;
  height: 99%;
  width: 100%;
}

.checkbox {
  grid-column: 1/2;
  height: 100%;
  width: 100%;
  overflow: hidden;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center center;
  object-fit: contain;
}

.answer-button-container {
  grid-column: 2/3;
  padding-left: 2%;
  height: 100%;
}

.answer-button-container button {
  width: 100%;
  padding-left: 1%;
  padding-right: 1%;
  padding-top: 2%;
  padding-bottom: 2%;
}

.button {
  display: block;
  position: relative;
  height: 100%;
  width: 100%;
  background-color: black; /*Button Color*/
  color: #f5f5f5;
  font-family: open_sans;
  font-size: 1.5rem;
  border-radius: 20px;
  text-decoration: none;
  box-shadow: 0.1em 0.2em black;
  cursor: pointer;
}

.lower-button {
  white-space: nowrap;
}
const answer_button_1 = document.getElementById("answer_button_1");
const answer_button_2 = document.getElementById("answer_button_2");
const answer_button_3 = document.getElementById("answer_button_3");
const answer_button_4 = document.getElementById("answer_button_4");
answer_button_1.innerText = "This is a really really really really really really really really really really really really really really really really really really really really really long test answer";
answer_button_2.innerText = "This is a pretty long test answer but not as long as the other one";
answer_button_3.innerText = "This is a fairly short test answer";
answer_button_4.innerText = "Really short answer";

Edit: To clarify, I need the button to not get larger to fit the text, I need the text to get smaller to fit the button.

Upvotes: 2

Views: 1774

Answers (2)

Brett Donald
Brett Donald

Reputation: 14122

Hmmmm. Yes, this is a tricky one. Using this article as inspiration, I was able to come up with the following solution. The technique used is to start with a really small font size (I have set minSize to 8 for this example) and test whether the text overflows its container; if the text does not overflow, increase the font size by a small amount (I've set step to 0.5) and re-test; then if the text overflows, revert to the previous font size.

Note, however, that it uses regular divs rather than buttons. The solution relies on a set of nested elements, which the button element does not support. Buttons also seem to have some built-in padding or sizing which is difficult to control. I suspect that Fitty and FitText don't work on buttons. I did try swapping the innermost divs with buttons in this snippet, and while it still works fairly well, it's more complicated and doesn't look as good. The only reason to prefer a button over a div is purely a semantic one, so I'd recommend sticking to using divs. Just add your click handler and off you go.

You can try different values for minSize, step and so on to see how that affects the result. Note that because I have used a minSize of 8, there comes a point where very long texts still overflow the button. Setting minSize to 0 avoids this -- the text fits on the button regardless of its length, but for some reason the text doesn't quite fill the button: the bottom padding appears larger. But your results may vary.

const isOverflown = ({ clientHeight, scrollHeight }) => scrollHeight > clientHeight

const resizeText = ({ element, elements, minSize = 10, maxSize = 512, step = 1, unit = 'px' }) => {
  (elements || [element]).forEach(el => {
    let i = minSize
    let overflow = false

        const parent = el.parentNode

    while (!overflow && i < maxSize) {
        el.style.fontSize = `${i}${unit}`
        overflow = isOverflown(parent)

      if (!overflow) i += step
    }

    // revert to last state where no overflow happened
    el.style.fontSize = `${i - step}${unit}`
  })
}

resizeText({
  elements: document.querySelectorAll('.button>div>div'),
  minSize: 8,
  step: 0.5
})
body {
  background: #33A;
  font-family: sans-serif;
}

.button {
  margin: 1em 0;
  width: 300px;
  height: 50px;
  padding: 10px 10px 10px 15px;
  color: #f5f5f5;
  background-color: black;
  border: 1px outset;
  border-radius: 20px;
  box-shadow: 0.1em 0.2em black;
  cursor: pointer;
  text-align: center;
}

.button>div {
  width: 100%;
  height: 100%;
}
<div class="button">
  <div>
    <div>
      This text
    </div>
  </div>
</div>

<div class="button">
  <div>
    <div>
      This Text is a bit longer
      and should be wrapped correctly
    </div>
  </div>
</div>

<div class="button">
  <div>
    <div>
      This text is the longest and should appear quite small.
      This text is the longest and should appear quite small.
    </div>
  </div>
</div>

<div class="button">
  <div>
    <div>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
    </div>
  </div>
</div>

<div class="button">
  <div>
    <div>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </div>
  </div>
</div>

<div class="button">
  <div>
    <div>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </div>
  </div>
</div>

It's a real pity that web standards do not currently provide a mechanism for automatically adjusting font size to fill a fixed size container. There are mechanisms to do it with images, so why not with text? I myself have had regular situations over the years where I have wished this were possible.

I am curious, though, as to why it is so important for you for the buttons to be a fixed size? Doesn't readability become a problem when there's a lot of text, and the font-size gets so small? Would it not be a better solution to simply allow the buttons to grow vertically to contain longer pieces of text? Or even truncate the text at a maximum number of characters or words, and add an ellipsis to indicate that truncation occurred?

Upvotes: 1

Michza
Michza

Reputation: 39

This here does the job by wrapping the text and making the box larger, it does however mess up the spacing between the boxes. Took me a few minutes, hope it helps :)

#answer_button_1 {
  height: auto;
  max-width:30wv;
  hyphens: auto;
  white-space: normal;
}

The Spacing between the buttons can be fixed by removing the following lines from .answers-display:

  grid-template-rows: 25% 25% 25% 25%;
  height: 100%;

Upvotes: 1

Related Questions