31

Is there a way to automatically justify words using letter spacing, each in its row, to a defined width, using CSS?

For example, "Something like this" would look, well, something like this:

"Something like this" would look something like this

Is there a non-obtrusive way to apply such styling to my text? I believe pure CSS doesn't have this option (at least not with CSS versions before 3, CSS3 seems to have a text-justify property, but it's not well supported yet), so js solutions would be fine also.

vgru
  • 49,838
  • 16
  • 120
  • 201
  • Are you willing to employ any particular JS library to achieve this, or vanilla JS only? – David Thomas Dec 06 '10 at 00:10
  • @David: jQuery would be preferred, but it's a rather simple task so it can be ported between frameworks without problems IMO. – vgru Dec 07 '10 at 11:33

10 Answers10

13

Here's a script which can do it. It isn't pretty, but maybe you can hack it to meet your needs. (Updated to handle resizing)

function SplitText(node) {
  var text = node.nodeValue.replace(/^\s*|\s(?=\s)|\s*$/g, "");

  for (var i = 0; i < text.length; i++) {
    var letter = document.createElement("span");
    letter.style.display = "inline-block";
    letter.style.position = "absolute";
    letter.appendChild(document.createTextNode(text.charAt(i)));
    node.parentNode.insertBefore(letter, node);

    var positionRatio = i / (text.length - 1);
    var textWidth = letter.clientWidth;

    var indent = 100 * positionRatio;
    var offset = -textWidth * positionRatio;
    letter.style.left = indent + "%";
    letter.style.marginLeft = offset + "px";

    //console.log("Letter ", text[i], ", Index ", i, ", Width ", textWidth, ", Indent ", indent, ", Offset ", offset);
  }

  node.parentNode.removeChild(node);
}

function Justify() {
  var TEXT_NODE = 3;
  var elem = document.getElementById("character_justify");
  elem = elem.firstChild;

  while (elem) {
    var nextElem = elem.nextSibling;

    if (elem.nodeType == TEXT_NODE)
      SplitText(elem);

    elem = nextElem;
  }
}
#character_justify {
  position: relative;
  width: 40%;
  border: 1px solid red;
  font-size: 32pt;
  margin: 0;
  padding: 0;
}

#character_justify * {
  margin: 0;
  padding: 0;
  border: none;
}
<body onload="Justify()">
  <p id="character_justify">
    Something<br/> Like
    <br/> This
  </p>
</body>
Towkir
  • 3,889
  • 2
  • 22
  • 41
Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
  • 1
    Thanks, it works great. For those who are interested, this script finds a div by its Id, inserts a separate div for each character at calculated indents, and then removes the initial div. – vgru Dec 04 '10 at 23:38
  • There is also a slight issue that characters are positioned at regular intervals instead of having fixed spacing between them. This looks strange when you have characters of different width (like "WiW" for example). I wondered if I could simply add spaces between letters and use browser justification like each character was a word, but they don't get justified if there is `
    ` at the end. Do you have a suggestion?
    – vgru Dec 07 '10 at 11:36
9

The css only solution is text-justify: distribute https://www.w3.org/TR/css-text-3/#text-justify but the support is still very poor.

A small experiment using text-align-last: justify and adding spaces between letters.

div{
 display:inline-block;
 text-align: justify;
 text-align-last: justify;
 letter-spacing: -0.1em;
}
<div>
S o m e t h i n g<br>
l i k e<br>
t h i s
</div>
PoseLab
  • 1,841
  • 1
  • 16
  • 22
  • That's a neat "hack", but will probably quickly fall apart when adding spaces and whole sentences. But still, gave you an upvote. – Fusseldieb Jul 23 '20 at 11:24
4

I know this is an old topic, but I faced this the other night. And found a suitable solution using tables.

Every letter shall be put into a <td> </td> I know it looks tedious, but if you wanna do this, it would be for a word or two, right? Or you always can use JS to fill it if is too much. However, this is only CSS and very versatile solution.

Using letter-spacing the letters get distributed properly. You should play around with it, depending on the width of the table.

#justify {
  width: 300px;
  letter-spacing: 0.5em;
}
<table id="justify">
  <tbody>
    <tr>
      <td>J</td>
      <td>U</td>
      <td>S</td>
      <td>T</td>
      <td>I</td>
      <td>F</td>
      <td>Y</td>
    </tr>
  </tbody>
</table>

See the example here

Crossbrowser safe, virtually nothing shall differ. Is just CSS.

I used it in My website which is in english and spanish. the subtitle under my name in spanish has an additional letter and it will step out the width. Using the tables explained above, it gets distributed to the same width automatically. Spacing it manually I'd had to define a whole condition for each language to go around that.

Igor Ivancha
  • 3,413
  • 4
  • 30
  • 39
Tony B
  • 41
  • 3
  • 2
    Canceling your downvote because you have a point: this is usually for a scope-limited situation. Plus I dig your web site. But I guess if I were going to "go nuclear" (physically split up the letters), I'd still use flexbox, or inline block. Tables carry with them other layout oddities (i.e. they don't behave the same as other block-level boxes). – harpo Mar 03 '15 at 03:13
  • Definitely the best and cleanest way to do it in my opinion. And yes, an easy script would create and fill the table for you. Much better than the other options here. – ElDoRado1239 Mar 04 '16 at 18:30
  • Unfortunately, `letter-spacing` adds a whitespace at the end of the word, which won't look good among other right-aligned content. – Robert Synoradzki Jul 21 '20 at 16:42
3

Here is an other aproach using a jQuery snippet I wrote for this question : Stretch text to fit width of div :

DEMO

HTML :

<div class="stretch">Something</div>
<div class="stretch">Like</div>
<div class="stretch">This</div>

jQuery :

$.fn.strech_text = function () {
    var elmt = $(this),
        cont_width = elmt.width(),
        txt = elmt.html(),
        one_line = $('<span class="stretch_it">' + txt + '</span>'),
        nb_char = elmt.text().length,
        spacing = cont_width / nb_char,
        txt_width;

    elmt.html(one_line);
    txt_width = one_line.width();

    if (txt_width < cont_width) {
        var char_width = txt_width / nb_char,
            ltr_spacing = spacing - char_width + (spacing - char_width) / nb_char;

        one_line.css({
            'letter-spacing': ltr_spacing
        });
    } else {
        one_line.contents().unwrap();
        elmt.addClass('justify');
    }
};


$(document).ready(function () {
    $('.stretch').each(function () {
        $(this).strech_text();
    });
});
Community
  • 1
  • 1
web-tiki
  • 99,765
  • 32
  • 217
  • 249
2

Needed this too, so I've bundled the suggested technique in a simple to use jquery-plugin you can find here: https://github.com/marc-portier/jquery-letterjustify#readme.

It uses the same procedure at its core, and adds some options to tweak. Comments welcome.

mpo
  • 21
  • 1
1

Found another way to achieve this with pure CSS, alas you need to spell out your words.

In my situation, this was the only solution that worked (some letters had classes), plus it also produced the straightest right-alignment among answers here, without using hacks.

.box {
  width: min-content;
  border: solid red;
}

.word {
  display: flex;
  justify-content: space-between;
}
<div class="box">
  <div class="word">
    <span>S</span>
    <span>o</span>
    <span>m</span>
    <span>e</span>
    <span>t</span>
    <span>h</span>
    <span>i</span>
    <span>n</span>
    <span>g</span>
    <span>&nbsp;</span>
    <span>w</span>
    <span>i</span>
    <span>c</span>
    <span>k</span>
    <span>e</span>
    <span>d</span>
  </div>

  <div class="word">
    <span>t</span>
    <span>h</span>
    <span>i</span>
    <span>s</span>
    <span>&nbsp;</span>
    <span>w</span>
    <span>a</span>
    <span>y</span>
  </div>

  <div class="word">
    <span>c</span>
    <span>o</span>
    <span>m</span>
    <span>e</span>
    <span>s</span>
  </div>
</div>
Robert Synoradzki
  • 1,766
  • 14
  • 20
0

Again, I know this is REALLY old, but why not just put a space between each letter and then text-align:justify? Then each letter would be regarded as a 'word' and justified accordingly

JBarnes
  • 59
  • 6
  • 2
    That would work, yes, but the question was how to do it without mixing markup and presentation. Solutions of this type are similar to using tables instead of css for page layout, while in this case you also lose SEO points because search engines will not recognize them as full words. It has been solved in CSS3. – vgru Aug 15 '14 at 09:18
0

An alternate way to handle this might be to use the "vw" sizing unit. This unit type can be used in font size properties and represents a percent of the window's width.

Disclaimer: It is not exactly what you are looking for, but requires no scripting. It does adjust the text size, but will also scale to the width of your page.

For example,

.heading {
  font-size: 4vw;
}

will make the width of one character in the current font 4% of the window width.

You could then use media queries if you wish to lock the font size to a minimum size based on the window's width.

@media only screen and (max-width: 768px) {
    font-size: 2rem;
}

Use the browser inspector to play with the font-size property and tweak the value to what makes sense for your application.

The "vw" unit works in IE9+, iOS 8.3+ and Android 4.4+ and all other mainstream browsers. I wouldn't worry about the mobile support too much, as you can use media queries to put the right sizing for these devices as described above.

http://caniuse.com/#feat=viewport-units

https://css-tricks.com/viewport-sized-typography/

Viewport units are a powerful way to scale many different aspects of your site with little code.

Neil Monroe
  • 1,201
  • 1
  • 15
  • 20
  • But I don't think it changes kerning? – vgru May 01 '15 at 20:12
  • No. It does not. It is a slightly different approach. You are configuring the text size to fit the container. Then when resizing the window, the whole text block adjusts with the window width. It is definitely an alternate approach, and not a 100% answer to the question, but acceptable if a scripting method is not possible or desired. – Neil Monroe May 04 '15 at 14:25
0

I just made a JQuery script from table's Tony B approach. Here is the JSFiddle https://jsfiddle.net/7tvuzkg3/7/

This script creates a table with each char in a row. This works with full sentence. I'm not sure this is fully optimized.

justifyLetterSpacing("sentence");

function justifyLetterSpacing(divId) {

 // target element
 domWrapper = $('#' + divId).clone().html('');
 // construct <td>
 $('#' + divId).contents().each(function(index){
      // store div id to td child elements class
   var tdClass = divId ;
      // split text 
   $textArray = $(this).text().split('');
      // insert each letters in a <td>
   for (var i = 0; i < $textArray.length; i++) {
        // if it's a 'space', replace him by an 'html space'
        if( $textArray[i] == " " )  {
            $('<td>')
                .addClass(tdClass)
                .html("&nbsp;&nbsp;")
                .appendTo(domWrapper);
        }// if it's a char, add it
        else{  
            $('<td>')
                .addClass(tdClass)
                .text($textArray[i])
                .appendTo(domWrapper);
        }
   }
 });
    // create table
    table = 
    $( "<table id='"+divId+"'/>" ).append( 
        $( "<tbody>" ).append( 
            $( "<tr>" ).append( 
                ( domWrapper ).children('td') 
            ) 
        )
    );
    // replace original div
 $('#' + divId).replaceWith( table );
} 
#sentence {
  width : 100%;
  background-color : #000;
  color : #fff;
  padding : 1rem;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="sentence">LOREM IPSUM DOLOR</div>
0

I usually try to write my answer on time. and this is exactly the same time (after 10 years) =)

myText.innerHTML = myText.textContent
    .split(/\s+/g)
    .map((line) => line.trim().split("").join(" "))
    .join("<br>");
#myText {
    display: inline-block;
    text-align: justify;
    text-align-last: justify;
    letter-spacing: -0.125em;
    font-family: sans-serif;
}
<div id="myText">Something like this</div>
jt3k
  • 642
  • 6
  • 17