How to center a paragraph of text on a particular word in HTML, CSS, or JavaScript?


I have a passage that is centered on the page, like this:

 _________________________
|                         |
| Once upon a time, there |
|   lived a squirrel who  |
|  lived in the middle of |
|       the forest.       |
|_________________________|

I need to adjust the centering such that a specifically marked word is horizontally centered on the page. In this example, the word "who" is centered:

 _________________________
|                         |
|        Once upon        |
|  a time, there lived a  |
|  squirrel who lived in  |
|    the middle of the    |
|         forest.         |
|_________________________|
  • The word who is marked in the HTML with a <div> tag, i.e. <center>Once upon a time, there lived a squirrel <div style="centered">who</div> lived in the middle of the forest.</center>.
  • The HTML appears in an application in which I have little control over editing the HTML itself (the text is already marked with CSS style tags), but can modify the style sheet or add code, such as JavaScript to the header to modify how that style is rendered.

How can I center a passage on a particular word in HTML, CSS, or JavaScript?

shift a particular target word to the best horizontal center that words will allow

You can achieve a rough centering using quite simple JavaScript. All it does is scan along the mark-up, converts the first TextNode it finds to "words" wrapped with <span>s, and then trials and errors inserting line breaks until it finds the one that will get the target word nearest to the center.

In the fiddle I have highlighted in red the spans that the code deems should have a break after, and green for the target word. In the most cases it does quite well, there are a few occurrences where you may end up with a single word on a line — however, this could be fixed by tweaking the code to your exact use-case.

Just in case people aren't aware, this is an illustration of what can be achieved and is by no means perfect — no code ever is, because it can always be improved, and should be.

fiddle

http://jsfiddle.net/s8XgJ/2/

updated fiddle that shows behaviour with a random targeted word:

http://jsfiddle.net/s8XgJ/3/

markup

<center>
  Once upon a time, there was a squirrel who lived in the 
  <span class="centered">middle</span> of the forest. I 
  say middle, but without knowing where the edges were, 
  the squirrel had no idea where the center was.
</center>

I have kept your mark-up roughly, however, as others have stated, you should avoid using the center tag. I have also converted the inner div to a span to preserve inline spacing. If this is not possible for you, you can still use a div, you'll just have to override it's normal display to be display: inline-block;

JavaScript / jQuery

jQuery.fn.findYourCenter = function( target ){
  base = $(this); target = base.children(target);
  base.contents().eq(0).each(function(){
    var i = -1, word, words, found, dif,
      last = Infinity,
      node = $(this), 
      text = node.text().split(/\s+/),
      cwidth = Math.round(target.width() * 0.5),
      bwidth = Math.round(base.width() * 0.5),
      breaks = 2 // code will try to insert 2 line breaks
    ;
    node.replaceWith(
      '<span class="word">'+text.join(' </span><span class="word">')+' </span>'
    );
    words = base.find('.word');
    do {
      while ( ++i < words.length ){
        word && word.removeClass('clear');
        word = words.eq(i).addClass('clear');
        dif = Math.round(Math.abs((target.position().left + cwidth) - bwidth));
        if ( dif < last ) { last = dif; found = i; }
      }
      word.removeClass('clear');
      if ( found ) {
        words.eq(found).addClass('clear');
        i = found; found = word = null;
      }
      else {
        break;
      }
    } while ( i < words.length && --breaks );
  });
};

I have used jQuery for brevity.

CSS

You'll require this CSS item:

.clear:after {
  content: '';
  display: block;
  clear: both;
}

and perhaps the following if your .centered item is a <div>:

.centered {
  display: inline-block;
}

Then all you need execute is:

jQuery(function($){
  $('center').findYourCenter('.centered');
});


problems

To get things precise you'll have to offset something — due to the fact that depending on the length (and placement of) words you cannot always achieve perfect center for paragraph, line and target word all at the same time. The above code still keeps the line centered, at the cost of the target word being offset. You could improve the above to make margin modifications to the line that holds the target span, if you wanted to center the word at the cost of the line.

Currently, if the target word appears in the first say five words, centering is unlikely to be achieved, basically because this method relies on word-wrap and line-breaks. If neither can be used due to the word being at the beginning of the paragraph, then nothing can be done — save for introducing margin, padding or text-indent.

This code relies on being able to read the position of <span> elements correctly. Older browsers e.g. IE6 and early versions of Safari/Opera have had problems with this.

One further thing to note. This code relies on the browser recalculating it's internal measurements immediately in the same synchronous execution loop. Most modern browsers seem to do this — in most cases — however you may find you need to implement setTimeout(func, 0) or setImmediate delays in order for this to work on older systems.


... and finally

This is a rather bonkers request, what exactly is it for? :)


[EDIT] Added fiddle. Cleaned up some code. Updated snippet below. Added comments.

http://jsfiddle.net/2Hgur/

[ORIGINAL] I don't believe that this is possible with css. However, I figured a solution could be possible with JavaScript. So, I tried it, and it worked! While this might not be the ultimate solution, consider the following:

<div class="jsCenterOnPhrase">

The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the <div class="jsCenteredPhrase">lazy dog.</div>
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog.
The quick brown fox jumped over the lazy dog.

</div>

Then break it down and rebuild it, perhaps like this:

jQuery(function ($) {

var $divParents = $('.jsCenterOnPhrase'); // all instances
var sMarker = '##'; // marker for phrase position

// for each instance
$divParents.each(function () {

    // closures
    var $parent = $(this);
    var $phrase = $parent.find('div.jsCenteredPhrase');

    // if there is only one phrase
    if ($phrase.size() == 1) {

        // more closures
        var iParentWidth = $parent.innerWidth();
        var sPhrase = $phrase.text();
        var iPhraseWidth = $phrase.outerWidth();
        $phrase.replaceWith(sMarker);
        var sFullText = $parent.text().replace(/\s+/g, ' ');
        // add html structure to container
        $parent.html(
            '<span class="jsLeftWrap"></span>' +
            '<span class="jsCenterWrap">' +
            '<span class="jsCenterPrefix"></span>' +
            '<span class="jsCenterPhrase"></span>' +
            '<span class="jsCenterSuffix"></span>' +
            '</span>' +
            '<span class="jsRightWrap"></span>');
        // more closures
        var $LeftWrap = $parent.find('.jsLeftWrap');
        var $CenterWrap = $parent.find('.jsCenterWrap');
        var $CenterPrefix = $parent.find('.jsCenterPrefix');
        var $CenterPhrase = $parent.find('.jsCenterPhrase');
        var $CenterSuffix = $parent.find('.jsCenterSuffix');
        var $RightWrap = $parent.find('.jsRightWrap');

        // get all the words left and right into arrays
        var iLeftStart = 0;
        var iLeftEnd = sFullText.indexOf(sMarker);
        var iRightStart = iLeftEnd + sMarker.length;
        var iRightEnd = sFullText.length - 1;
        var sFullLeft = sFullText.substring(iLeftStart, iLeftEnd);
        var sFullRight = sFullText.substring(iRightStart, iRightEnd);
        var aFullLeft = sFullLeft.split(' ');
        var aFullRight = sFullRight.split(' ');

        // build out each word as a node
        for (var i = 0; i < aFullLeft.length; i++) {
            var sVal = aFullLeft[i];
            if (sVal != '') {
                $('<span> ' + sVal + ' </span>').appendTo($LeftWrap);
            }
        }
        for (var i = 0; i < aFullRight.length; i++) {
            var sVal = aFullRight[i];
            if (sVal != '') {
                $('<span> ' + sVal + ' </span>').appendTo($RightWrap);
            }
        }

        // reset text as full html words
        sFullLeft = $LeftWrap.html();
        sFullRight = $RightWrap.html();

        var fResize = function () {
            // reset closures for dynamic sizing
            $LeftWrap.html(sFullLeft);
            $CenterPrefix.html('').css('padding-left', 0);
            $CenterPhrase.html('&nbsp;' + sPhrase + '&nbsp;');
            $CenterSuffix.html('').css('padding-right', 0);
            $RightWrap.html(sFullRight);
            iParentWidth = $parent.innerWidth();

            // private variables
            var $leftWords = $LeftWrap.find('span');
            var $rightWords = $RightWrap.find('span');
            var iMaxWidthRemaining = (iParentWidth - $CenterPhrase.outerWidth());
            var iMaxSideWidth = Math.floor(iMaxWidthRemaining / 2);
            var iLeftRemaining = iMaxSideWidth;
            var iRightRemaining = iMaxSideWidth;
            var iCurrentWidth = iPhraseWidth;
            var iLeftIndex = $leftWords.size() - 1; // work backwards
            var iRightIndex = 0; // work forwards
            var iKerningTollerance = 2; // wraps too much sometimes

            // loop trackers
            var bKeepGoing = true;
            var bKeepGoingRight = true;
            var bKeepGoingLeft = true;

            // loop while there is still room for the next word
            while (bKeepGoing) {

                // check the left side
                if (bKeepGoingLeft) {
                    // get the next left word
                    var $nextLeft = $leftWords.eq(iLeftIndex);
                    var iLeftWordWidth = $nextLeft.outerWidth();
                    if (iLeftWordWidth < iLeftRemaining) {
                        // there's enough room.  add it.
                        $nextLeft.prependTo($CenterPrefix);
                        iLeftRemaining = iMaxSideWidth - $CenterPrefix.outerWidth();
                        iLeftIndex--;
                    } else {
                        // not enough room.  add remaining room as padding
                        bKeepGoingLeft = false;
                        $CenterPrefix.css('padding-left', (iLeftRemaining - iKerningTollerance));
                        iLeftRemaining = 0;
                    }
                }

                // check the right side
                if (bKeepGoingRight) {
                    // get the next right word
                    var $nextRight = $rightWords.eq(iRightIndex);
                    var iRightWordWidth = $nextRight.outerWidth();
                    if (iRightWordWidth < iRightRemaining) {
                        // there's enough room.  add it.
                        $nextRight.appendTo($CenterSuffix);
                        iRightRemaining = iMaxSideWidth - $CenterSuffix.outerWidth();
                        iRightIndex++;
                    } else {
                        // not enough room.  add remaining room as padding
                        bKeepGoingRight = false;
                        $CenterSuffix.css('padding-right', (iRightRemaining - iKerningTollerance));
                        iRightRemaining = 0;
                    }
                }

                // is there room left on either side?
                bKeepGoing = bKeepGoingLeft || bKeepGoingRight;
            }

            // calculate where to put the breaks.
            var iTempWidth = iParentWidth;
            while (iLeftIndex > 0) {
                var $word = $leftWords.eq(iLeftIndex);
                iTempWidth = iTempWidth - $word.outerWidth();
                // account for kerning inconsistencies
                if (iTempWidth <= (2 * iKerningTollerance)) {
                    $('<br />').insertAfter($word);
                    iTempWidth = iParentWidth;
                } else {
                    iLeftIndex--;
                }
            }

        };

        // initial state
        fResize();

        $(window).resize(fResize);
    }
});

});

[ UPDATED FOR REQUIRED NAMING CONVENTION... style="centered" :) ]


Answer is, unfortunately, you can't do what you are asking with HTML/CSS.

To do this, you would need to either set some widths to force the text to break to the next line, or (preferably) insert a <BR> tag where you want to break the line. HTML and/or CSS have no way of doing it for you. As Anderson notes below, you will be able to find a way to do it with JS if need be.


Without word-break you can't be sure that the desired word is centered just as the line it's in.

Here is a different text, the first version the word 'who' centered, but the line not, the seccond the opposite:

 _________________________
|                         |
|        Once upon        |
|  a time, there lived a  |
|  squirrel who lived     |
|    somewhere else but   |
|      in the forest      |
|_________________________|
 _________________________
|                         |
|        Once upon        |
|  a time, there lived a  |
|    squirrel who lived   |
|    somewhere else but   |
|      in the forest      |
|_________________________|

As you can see without breaking the words it's entirely based on the text you have and the word you want to center if its possible or not.

Due this there's four solution:

  • Put only the desired word in that line and then try to be sure that theres exactly as much line before as after. (Only possible if the word is not the first or not the last or it's the only word in the text, and if the word is not roughly in the middle of the text you might end up with really arkward shapes.)

  • Count the position of the word inside the text and then break the words before and after to match the number of characters before and after the word in the same line.

  • Implement a word-breaking function in the language of the text, and calculate all the combinations of possible word- and line-breaks to find one (if any) where your desired word is centered.

  • Hardcode it by yourself, there are things that can be solved much more efficiently by a human than a simple program could (yet). After all we are well bundled supercomputers.


As other pointed out its not possible to do through html/Css which I agree too, but you can do some sort of centering based on a line(sentence) rather then by a word. Hope this helps you !!

CSS:

.centered  { display:inline-block; text-align:center;}
div {  width:100%; text-align:center; }

HTML :

 <div>  <div class="centered"> Once upon a time, </div> there lived a  <div class="centered"> squirrel who lived  in </div>  the middle of the forest.  </div>

Fiddle Demo

Update: As pointed by @disule I have removed the deprecated center tag.


This is a really cool way to do this using javascript I made just for you! haha here is the fiddle http://jsfiddle.net/7Ykzp/2/

var words = text.split(" ");

we split it into words then try to fit them on a line we put this in a center in between two centers containing the previous and following text

a note we add 8 to the line width each time we try to add two more words to account for the size of spaces

edit: updated to properly place who based on word length
edit2: updated to now properly backwards wrap the text above the centered word! check it out!


The easiest way is to just insert a line break <br> after each line:

Here is an example jsfiddle

HTML:

<div class="container">
   <p>
     Once upon<br>
     a time, there lived a<br>  
     squirrel who lived in<br>
     the middle of<br>
     the forest.
   </p>
 </div>

CSS:

.container {
    border: 1px solid #666;
    padding: 1em;
    width: auto;
    text-align: center
}

The only answer to what you want to achieve is: It's not possible!

And I also doubt that it would make any sense. To center a word in an already centered line in a centered text - what should this be good for!?

Especially when it comes to fonts and font sizes there are so many unknown variables (used font, user stylesheets etc.), that every attempt based on these things will fail.

And even any JS solution will fail, as it is most likely that due to the word lengths it is not possible to exactly center the "word" in the line without changing/ adjusting the word spaces before and after the "word" to get it absolutely centered.

If you really want such a "dalliance" go and create an image on the fly. In an image you might achieve your goal. With HTML, CSS and JS you won't.

And if I think of such useful things like CSS hyphenation, it really makes no sense to me what you are trying to achieve. Sadly you didn't answer on my comment on your question what this all should be good for.


The center tag is deprecated and not supported in HTML5, so do not use it. Instead give your .centered class some CSS rules like this:

.centered { 
    clear: both;
    display: block;
    text-align: center;
}

However, this will give its own line. If you still want the text around it to wrap, then you'll have to systematically choose your line-breaks – there is no current way to wrap text around a centered node, and float: center doesn't exist…


<div>
<p class ="center"> Once upon </p>
<p class ="center">a time, there lived a</p><p class = "center">squirrel who  
 lived in</p><p class = "center"> the middle of </p>
<p class = "center">
   the forest. 
</p>
</div>


  Try This with css code:

  .center{
    text-align:center;
    letter-spacing:2px;

   }

While I agree that the html structure has issues, you mention that you have "little control" of it, so I am offering this answer purely with HTML as is. Without touching the HTML, the only way to get this with pure CSS is to allow the centered content to exist on its own line. So this works (see the fiddle):

center {
    text-align: center;
}
center div {
    width: 100%; /* really, the div should default to this, but just in case */
}

Is your div a fixed width, or a variable width? If it is a fixed width (the demo width you have given), then add the previous and following word to the div, and add
before and after.

Once upon a time, there lived a
squirrel who lived
in the middle of the forest..

and then center-align the paragraph.

.centered{ text-align: center }

If the div is a variable width, you could try a JavaScript function that accepts the div width and the font size as arguments, and that will decide how many words (both before and after) should be added.

It's a workaround that basically tries to fit as many words in a line as possible. Since a word is added after the "center" for every word added before the "center", your word should remain in the middle.


Use CSS more specifically in the parent elements to target your words alignment. For example if there is a paragraph (p) and then after it their are two divs (div) then use the specific parent css property like this.

p div div {text-align:center; display:inline-block;} 

that's it and it will do the work for you. Similarly you can target your given example with

center div {text-align:center; display:inline-block;}

The woodcutter method is the <br/> tags

html:

<div class="center">
    <p>Once upon<br>
    a time, there lived a<br>  
    squirrel who lived in<br> 
    the middle of<br>
    the forest.</p>
</div>

css:

.center {
    width: auto;
    text-align: center;
}

You can very simply do it in this way.

Fiddle Demo

Code:

<div style="position:absolute; left:50%; margin-left:-108px;">
 <pre>
 _________________________
|                         |
| Once upon a time, there |
|   lived a squirrel who  |
|  lived in the middle of |
|       the forest.       |
|_________________________|
    </pre>
    </div>

Do you have a CSS file linked? If you do, I would recommend changing the div tag to

<div class="who">who</div>

In the CSS file, you should have

.who { 
  text-align: center; 
}

It is also possible that you force the html to include spaces with the following code: &nbsp (Include a semicolon at the end, it won't let me do it). So instead of having one space, you can have multiple non-breaking spaces.