Kaso
Kaso

Reputation: 123

Is it possible to made inline-block elements line-wrap like inline text?

CSS display: inline elements with text in them "flow" over the end of a line and then carry on the next line when the container isn't large enough for them.

inline text wrapping at container boundry

I want to achieve this same horizontal wrapping effect but with block elements that I can control the width of.

If I use display: inline-block this allows me to control the block width however it then prevents line breaking from working.

inline-block elements not wrapping at container boundry

I can't seem to find any set of CSS settings that will allow me to both wrap and control the width of the element. However it seems that this should be possible. Presumably internally the browser's own layout engine is doing this once it calculates the width of various text strings.

There are two work-arounds I've considered which give me the desired visual output:

Correct layout via work arounds

  1. Set the font to a monospaced font and fill inline elements with a calculated correct amount of text characters that are then hidden. This has issues with the fact that the text is still actually there and selectable on the page.
  2. Use javascript to when an inline-block element should wrap and inject two calculated size smaller blocks one for the first section and one for the remainder. This is very slow when dealing with a large number of these blocks.

Neither of these are very nice solutions and if this is possible by pure CSS i'm keen to learn how.

Here's a link to the examples in the screenshots interactively rendered:

https://jsfiddle.net/n3wzd29v/

let tags = document.getElementsByClassName("js");

let currentLeft = 0;
for (let i=0; i<tags.length; i++) {
    let tag = tags[i];
    
    // dont know how to get the "width this would be if it was not inline cleanly".
    tag.style.display="inline-block";
    let requestedWidth = tag.offsetWidth;
    tag.style.display="inline";

    let parentWidth = tag.parentNode.offsetWidth;
    
    console.log("requestedWidth:" + requestedWidth);
    console.log("parentWidth:" + parentWidth);
    console.log("currentLeft:" + currentLeft);

    
    //FIXME: handle spanning over arbitary number of lines.
    if ((currentLeft + requestedWidth) < parentWidth) {
    	// Got plenty of space for this element on this line so just make it a inline-block
      tag.style.display = "inline-block";
      currentLeft += requestedWidth;
      console.log("Just setting inline-block, its wide enough");
    } else {
    	// This element needs breaking up into two elements, one for the part that fits on this
      // line, and another for the remainder on the next line.
      let part1Width = parentWidth - currentLeft;
      let part1 = document.createElement("span");
      part1.style.display = "inline-block"
      part1.style.width = part1Width + "px";
			part1.style.verticalAlign = "top";
      part1.style.height = "16px";

      let part2Width = requestedWidth - part1Width;
      let part2 = document.createElement("span");
      part2.style.display = "inline-block"
      part2.style.width = part2Width + "px";
			part2.style.verticalAlign = "top";
      part2.style.height = "16px";
      
      currentLeft = 0 + part2Width;
      
      tag.appendChild(part1);
      tag.appendChild(part2);
      console.log("Adding two fixed width children: " + part1Width + " " + part2Width);
    }
}
.container {
  width: 200px;
  background-color: lightblue;
}

span {
  word-break: break-all;
  font-family: monospace;
}

.bad {
  display: inline-block;
  height: 16px;
}

.stuffed {
  line-height:22px;
}

.js {
  height: 15px;  
  vertical-align: top;
}

.color1 {
  background-color: hsl(0, 80%, 50%);
}
.color2 {
  background-color: hsl(20, 80%, 50%);
}
.color3 {
  background-color: hsl(40, 80%, 50%);
}
.color4 {
  background-color: hsl(60, 80%, 50%);
}

.color1.stuffed {
  color: hsl(0, 80%, 50%);
}
.color2.stuffed {
  color: hsl(20, 80%, 50%);  
}
.color3.stuffed {
  color: hsl(40, 80%, 50%);
}
.color4.stuffed {
  color: hsl(60, 80%, 50%);  
}
<html>
<body>

<p>Inline elements with text in them "flow" over the end of a line 
and then carry on the next line when the container isn't large enough for them.
</p>

<div class="container">
<span class="good color1">These inline</span><span class="good color2">spans will wrap over</span><span class="good color3">the end of the container</span><span class="good color4">exactly how i would like them to</span>
</div>


<br/>

<p>
However i want to do the same layout but with block elements that i can set
the width of. Using inline-block allows me to control the block width however
it seems to prevent line-breaking from working.
</p>

<div class="container">
<span class="bad color1" style="width:96px"></span><span class="bad color2" style="width:160px"></span><span class="bad color3" style="width:192px"></span><span class="bad color4" style="width:256px"></span>
</div>


<p>
Obviously i can acheieve something of the same behaviour by stuffing my inline
elements with hidden text, but that seems sub-optimal
</p>
<div class="container">
<span class="stuffed color1">████████████</span><span class="stuffed color2">████████████████████</span><span class="stuffed color3">████████████████████████</span><span class="stuffed color4">████████████████████████████████</span>
</div>

<p>
Finally, it is ofc also possible to fix this problem in javascript, but i'm tenative
to do layout in javascript when the browser already clearly have a well optimised 
layout engine for doing this on text already.
</p>

<div class="container">

<span class="js color1" style="width:96px"></span><span class="js color2" style="width:160px"></span><span class="js color3" style="width:192px"></span><span class="js color4" style="width:256px"></span>

</div>

<br/>
<br/>
<br/>

</body>
</html>

Upvotes: 5

Views: 1530

Answers (2)

Temani Afif
Temani Afif

Reputation: 273626

If this is only for the visual purpose then you might have a way using a span element where you apply multiple gradient.

Here is a self-explanatory example:

.container {
  width: 200px;
  padding:5px;
  background-color: lightblue;
  text-align:justify;
}

span {
  padding:0;
  background-image:
    linear-gradient(hsl(0, 80%, 50%),hsl(0, 80%, 50%)),
    linear-gradient(hsl(20, 80%, 50%),hsl(20, 80%, 50%)),
    linear-gradient(hsl(40, 80%, 50%),hsl(40, 80%, 50%)),
    linear-gradient(hsl(60, 80%, 50%),hsl(60, 80%, 50%));
  background-size:
    100px 100%, /* 1st color width = 100px */
    280px 100%, /* 2nd color width = 280px - 100px = 180px */
    450px 100%, /* 3rd color width = 450px - 280px = 170px */
    520px 100%; /* 4th color width = 520px - 450px = 70px */
  background-repeat:no-repeat;
}

/* we fill the span with content */
span::before,
span::after{
  content:"█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █";
  color:transparent;
}
<div class="container">
<span ></span>
</div>

Here is another very crazy idea where I will rely on the use of columns:

.container {
  height: 120px; /* this will define the visual width of container */
  column-width: 20px; /* this will define the visual height of rows*/
  display: inline-block;
  column-gap: 4px; /* gap between rows */
  /* this transformation will invert everything like we want */
  transform-origin: top left;
  transform: rotate(90deg) scaleY(-1);
  /**/
}

span {
  display: block;
}

span:hover {
  filter: invert(1);
}
 /* all the height below will define the visual width */
span:nth-child(1) {
  background: hsl(0, 80%, 50%);
  height: 80px;
}

span:nth-child(2) {
  background: hsl(20, 80%, 50%);
  height: 100px;
}

span:nth-child(3) {
  background: hsl(40, 80%, 50%);
  height: 80px;
}

span:nth-child(4) {
  background: hsl(60, 80%, 50%);
  height: 200px;
}
<div class="container">
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</div>

Upvotes: 1

O. C. Christian
O. C. Christian

Reputation: 9

NO.

I do not think that the solution you're trying to reach is logical. If I understood your question correctly, you are trying to set an element's WIDTH to span across multiple lines.

Take a look at the first "BAD" option. You want to set a width for the element with the yellow background for instance, and then, have it continue that width on multiple lines?

Perhaps, you might want to use your JS solution, if it is absolutely necessary to layout your content in this manner.

Upvotes: 0

Related Questions