3

How can I align text around a "pivot" word, such that the pivot word appears in the center of its line (when displayed in a browser)?

Say [ and ] mark the beginning and end of lines, and X is a mid-line marker, included here for clarity.

So, for single-line:

                                       X
[            I am centered around my PIVOT word.                               ]
[                        Here is the PIVOT word of this second example.        ]

With multi-line, it can be something like:

                                       X
[                                                  This is multiline text with ]
[    one word which functions as the PIVOT.  Then we have some more boring     ]
[ multiline text you don't have to worry about                                 ]
shx2
  • 61,779
  • 13
  • 130
  • 153
  • Yo can do a three column page. "pivot" in the middle column with `center` align. And `right align` for the left column and `left align` for rigth column – Flowen May 15 '13 at 14:05
  • @Flowen, would it work if the text spans over multiple lines? would the spacing on the sides of the pivot word look "natural"? – shx2 May 15 '13 at 14:11
  • If columns are aligned to the mid column, spacing on sides should always be the same. – Flowen May 15 '13 at 14:16
  • You can set a `margin-left` or `margin-rigth` in order to look like natural "spacing" – Flowen May 15 '13 at 14:21
  • I'think this can be done only with JavaScript. – vlgalik May 15 '13 at 14:36
  • You can insert the line in container `

    ` also the text PIVOT into container ``. Now using JS you would find the position of span element within p _(X offset)_. When the span position is in the **1st half** of the div you would increase p's CSS `text-indent` property so that you would end with the Pivot in the middle. If the PIVOT's span position is in the 2nd half. Do the same, but just before you reach the center, you'll have to isolate the first part and add it to ist own span container and the rest to other span container and continue with indention. http://jsfiddle.net/ruLEt/

    – vlgalik May 15 '13 at 15:14
  • Quick question, is the text displayed in a monospaced font? If so it is "just" a matter of inserting the required spaces. – HBP May 15 '13 at 17:07
  • @HBP, nope... I'm looking for a solution as general as possible. – shx2 May 15 '13 at 17:17
  • Do you always know the amount of text before and after the pivot point? – Jimmy Breck-McKye May 15 '13 at 18:27
  • @JimmyBreck-McKye, nope. I decide what the pivot point is according to some condition, e.g. word=="PIVOT". – shx2 May 15 '13 at 18:39
  • I think I have a CSS-only solution for a single line, at least. Hang on... – Jimmy Breck-McKye May 15 '13 at 18:47
  • Finding line-breaks http://stackoverflow.com/a/3744491/1837457 and measure string widths http://jsperf.com/measure-string-display-width will probably help – Elle May 16 '13 at 12:55

4 Answers4

1

Reworked my first answer. Works a lot better now. See the fiddle. It is based on the idea that you split the paragraph on the pivot word. The pivotword and the last section are placed back in the paragraph. The first half (before the pivot word) is then split into an array, which is traversed backwards (each time popping the last element) to fill the span until it reaches its width. This will repeat itself until there are no more words left in the array. I am not a native English speaker, so I hope this will all make some sense.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title></title>
        <script type="text/javascript" src="js/jquery-1.9.0/jquery.min.js"></script>
        <style type="text/css">
            .container {
                width: 500px;
                border: 1px solid #ddd;
            }
            .pivotWord {
                background-color: red;
                color: white;
                font-weight: bold;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <p>
                Lorem ipsum dolor sit amet, consectetur adipisicing 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 PIVOT 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.
            </p>
            <p>
                Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. PIVOT Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
            </p>
            <p>
                Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et PIVOT 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.
            </p>
            <p>
                Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.  Excepteur sint occaecat cupidatat non PIVOT proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
            </p>
        </div>




        <script type="text/javascript">
            function pivotAround(pivotword){
                $('p').each(function(){
                    //initialize a few things
                    var sentence = $(this).html();
                    var splittedSentence = sentence.split(pivotword);
                    var paragraphWidth = $(this).width();
                    $(this).html("");

                    //create elements from the sentence parts.
                    var pivotSpan = $("<span />", {
                        'class': 'pivotWord'
                    }).html(pivotword);

                    var postSpan = $("<span />", {

                    }).html(splittedSentence[1]);

                    //append them to the DOM
                    $(this).append(pivotSpan)
                                 .append(postSpan);

                    //get size of pivotSpan
                    var pivotSpanWidth = pivotSpan.width();
                    //calculate where the pivot word should be
                    var pivotSpanLeftMargin = (paragraphWidth / 2) - (pivotSpanWidth / 2);
                    //make global array of splittedSentence[0]
                    preSpanArray = splittedSentence[0].split(' ');

                    distributeWithinMargin(pivotSpanLeftMargin, this);

                    //array not empty?
                    while(preSpanArray.length > 0){
                        distributeWithinMargin(paragraphWidth, this);
                    }
                });
            }

            function distributeWithinMargin(margin, element) {
                var span = $("<span />", {
                    'style': 'margin-left: -40000px'
                });
                $(element).prepend(span);
                while (preSpanArray.length > 0 && span.width() <= margin) {
                    var lastElement = preSpanArray.pop();
                    span.prepend(lastElement + " ");
                }
                /*
                 * last word makes the span too wide, so push it back to the stack
                 * only do this when array not empty, or you will end up in an
                 * endless loop
                 */ 
                if (preSpanArray.length > 0) {
                    preSpanArray.push(lastElement);
                    //remove that word from the span
                    var firstSpaceIndex = $(span).html().indexOf(" ");
                    $(span).html($(span).html().substring(firstSpaceIndex + 1));
                }

                //calculate the text-indent from that value
                var textIndent = margin - span.width();
                $(span).css('margin-left', textIndent);
            }

            pivotAround("PIVOT");       
        </script>
    </body>
</html>
Dirk McQuickly
  • 2,099
  • 1
  • 17
  • 19
  • Interesting. I'll try it out. – shx2 May 15 '13 at 20:04
  • It seems to be working. I got to admit I haven't managed to understand all of it yet (my js is a bit rusty). Well done. – shx2 May 18 '13 at 15:52
  • Thank you. Feel free to ask for more info when needed. – Dirk McQuickly May 18 '13 at 18:35
  • can you explain how/why you use the global `preSpanArray` array? – shx2 May 18 '13 at 18:52
  • That array is filled with all the words that have to be placed before the pivot word. I pop the last element off the array and prepend it in the span before the pivot word until the line is filled to its maximum. After that, repeat for the next line, until array is empty. Maybe it did not _have_ to be global, but it was easy to do it like this. – Dirk McQuickly May 18 '13 at 19:11
1

So I've made a fiddle it's not totally finished and it has some bugs, each time you resize container you'll have to hit RUN button and if there are 2 lines above the pivot it starts to break, but it works in basics: http://jsfiddle.net/dFv3b/1/

HTML:

<div class="container">
    <p>I am centered around my PIVOT word.</p>
    <p>Here is the PIVOT word of this second example.</p>
    <p>This is multiline text with one word which functions as the PIVOT then we have some more boring multiline text you don't have to worry about.</p>
</div>

JS/jQuery:

var containerWidth = $(".container").width();

$("p:contains('PIVOT')").html(function() {

    var text = $(this).html().split(' ');
    for( var i = 0, len = text.length; i < len; i++ ) {
        var p = ("PIVOT" == text[i]) ? " pivot" : "";
        text[i] = '<span class="word-' + i + p + '">' + text[i] + '</span>';;
    }
    return text.join(' ');

}).each(function() {
    var $pivot   = $(this).find(".pivot");

    var pivotPos   = $pivot.offset().left;
    var pivotWidth = $pivot.width();

    if (pivotPos + pivotWidth / 2 < containerWidth / 2) {
        // one line in the 1nd half
        $(this).css("text-indent", (containerWidth / 2) - (pivotWidth / 2) - pivotPos);
    } else {
        // multi line in the 2nd half
        var indent;    

        // indent half width
        $(this).css("text-indent", containerWidth / 2);
        pivotPos = $pivot.offset().left;
        while (pivotPos + pivotWidth / 2 < containerWidth / 2) {
            var indent = Number($(this).css("text-indent").replace(/[^-\d\.]/g, ''));
            $(this).css("text-indent", indent + 1);
            pivotPos = $pivot.offset().left;
        }
        // return just before half
        $(this).css("text-indent", indent -1);
        pivotPos = $pivot.offset().left;

        var words = $(this).find("span").toArray();
        var begin;

        // find the first word on pivot line       
        for(var i=0, len=words.length; i<len; i++) {
            if (0 === $(words[i]).offset().left) {
                begin = words[i];
                break;
            }
        }

        $(begin).css("margin-left", String((containerWidth /2) - (pivotWidth /2) - pivotPos) + "px");
    }
});
vlgalik
  • 477
  • 2
  • 6
  • 25
0

Finally I found a way to do this with tables. If you have different width of the table, you should play with the width value (must be equal in percent) of the left and right td-s.

<table width="700px">
<tr>
<td style="width:47%;height:25px;text-align:right;overflow:hidden;whitespace: nowrap;">text</td>
<td style="min-width:40px;width:40px;height:25px;">pivot</td>
<td style="width:47%;height:25px;overflow:hidden;whitespace: nowrap;">text</td>
</tr>

<tr>
<td style="width:47%;height:25px;text-align:right;overflow:hidden;whitespace: nowrap;">longer text tot the left</td>
<td style="min-width:40px;width:40px;height:25px;">pivot</td>
<td style="width:47%;height:25px;overflow:hidden;whitespace: nowrap;">short here</td>
</tr>

<tr>
<td style="width:47%;height:25px;text-align:right;overflow:hidden;whitespace: nowrap;">sample</td>
<td style="min-width:40px;width:40px;height:25px;">pivot</td>
<td style="width:47%;height:25px;overflow:hidden;whitespace: nowrap;">text text text text text text text</td>
</tr>

</table>

I also did a fiddle for you!

Patartics Milán
  • 4,858
  • 4
  • 26
  • 32
0

I can think of a CSS-only solution, but it only works if the preceding and succeeding text doesn't go over a line. You can find it at http://jsfiddle.net/2UQhC/5/ (you will need to hit 'run' to see it properly).

The basic idea is this:

  • There is a parent container with position: relative
  • There is a para with the start of the sentence, and a para with the end of the sentence. Each contains a span at the start containing the pivot word.
  • Both paras are put into place with position:absolute. The start has right: 50% (so it's right edge is in the middle) and the end has left:50% likewise.
  • The spans in the paras are both floated towards the center.
  • The spans are then given pseudo-elements with percentage based negative margins, that push their respective containers to the center. The percentage-based widths use the widths of the containing floats (not the line block), meaning that the basis for these percentage widths will always be the real width of laying out the pivot word in whatever font or font size you choose.

Here's the code, in case that's too esoteric, using 'lilacs' as the pivot word:

The HTML -

<div>    
    <p id="left"><span>LILACS</span>April is the cruellest month, breeding&nbsp;</p>
    <p id="right"><span>LILACS</span>&nbsp;out of the dead land</p>
</div>

And the CSS -

div {
    position: relative;
}

#left {
    position: absolute;
    right: 50%;
}

#right {
    position: absolute;
    left: 50%;
}

#left span {
    float: right;
}

#right span {
    float: left;
    visibility: hidden;
}

#left span:after {
    content: "";
    margin-right: -50%;
}

#right span:before {
    content: "";
    margin-left: -50%;
}

This will work whatever the widths of the two sides of the sentence (so long as they don't go to multiple lines), and should work from IE8 plus.

Jimmy Breck-McKye
  • 2,923
  • 1
  • 22
  • 32
  • This is clever. But I'm really looking for a solution which also works if text is multi-line. Also, there are other consequences to duplicating the pivot word, e.g., searching (Ctrl-F) will find it twice. (Also, the words don't perfectly-overlap on my firefox...) – shx2 May 15 '13 at 19:59