如何在HTML、CSS或JavaScript中将文本段落居中对齐到特定单词?

20

我有一个段落,它的居中显示在页面上,就像这样:

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

我需要调整居中使得一个特定标记的单词在页面水平居中。在这个例子中,单词"who"是居中的:

 _________________________
|                         |
|        Once upon        |
|  a time, there lived a  |
|  squirrel who lived in  |
|    the middle of the    |
|         forest.         |
|_________________________|
  • 在HTML中,“who”这个词是用<div>标签标记的,即<center>从前有一只松鼠<div style="centered">who</div>住在森林中央。</center>
  • HTML出现在一个应用程序中,我几乎无法编辑HTML本身(文本已经用CSS样式标记),但可以修改样式表或添加代码,例如JavaScript到头部以修改样式的呈现方式。

如何在HTML、CSS或JavaScript中让一段文字居中于特定的单词?


但是,当你应用居中后,如何知道特定的单词仍然会居中呢?你还需要考虑换行。 - rvighne
2
我很确定你无法仅使用CSS完成这个任务。我试图想象将每个单词用span包装起来,以便您可以自由地定位每个单词,但即使如此,您仍需要一些Javascript才能使其正常工作。 - Smith
我已经更新了问题,包括JavaScript。 - Village
2
@Village:我很好奇这个是用来做什么的?为什么不使用“正常文本流”如此重要?而且你当前的HTML标记在语义上是一个“噩梦”。如果可能的话,你应该在div内使用span元素(如果需要的话)。但是,如果你能解释一下你想要实现什么,也许有更好的解决方案!? - Netsurfer
16个回答

13

[编辑] 添加了示例代码,清理了一些代码,更新了下面的片段。添加了注释。

http://jsfiddle.net/2Hgur/

[原始内容] 我不相信这是可能的CSS。但是,我想JavaScript可能会有解决方案。所以,我尝试了一下,它起作用了!虽然这可能不是最终的解决方案,请考虑以下:

<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>

然后将其分解并重建,可能像这样:

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);
    }
});

});

[已更新以符合必要的命名规范... style="居中" :) ]


虽然问题中写着style='centered',但是style='centered'并不是有效的CSS,我建议不要在真正想要使用class="centered"时推广它。至少在答案中注明这一点。 - bdrx
我同意,但我只是想确保我符合所述标准。之前,我只引用了一个css class ="jsCenteredPhrase"。 - Levi Beckman
好主意(+1),但是当我运行它并更改目标单词时,代码可能会崩溃... 我猜它在某个点上找到了一个无限循环。 - Pebbl
非常不错。 我尝试改变右侧文本区域的大小,并发现上面标记单词的那几行并不总是均匀分割。 在某些情况下,前两行是“快速的棕色狐狸跳过懒狗。快速”和“褐色”。 在其他情况下,它们是“快速”和“棕色的狐狸跳过了懒狗。快速的棕色”。 - Jon Senchyna
这里的工作做得很好。我相信如果有更多时间,JS代码可以变得更简洁,但它能够正常运行。我有点生气于Web语言 - 这么简单的东西不应该需要这么多的代码量。 - serraosays

9

很不幸,你不能通过 HTML/CSS 实现你所要求的功能。

要实现这个功能,你需要设置一些宽度以强制文本换行到下一行,或者(更好的方法)在你想要换行的地方插入一个 <BR> 标签。HTML 和/或 CSS 无法为你完成这项工作。如下所述,如果需要,你可以使用 JS 找到一种方法来实现。


1
如何在HTML、CSS或JavaScript中将段落居中于特定单词?他确实指出可以使用JavaScript完成。 - Levi Beckman
是的,他在我写完这个答案几天后修改了他的帖子。 - Ryan

4

如果没有单词断行,你就无法确定所需的单词是否与所在行同样居中。

这里是另一段不同的文本,第一版本中单词“who”居中,但行不居中,第二个版本则相反:

 _________________________
|                         |
|        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      |
|_________________________|

正如您所看到的,不打破单词的基础上,它完全基于您拥有的文本和您想要居中的单词是否可能。

因此,有四种解决方案:

  • 只在该行中放置所需的单词,然后尝试确保前后恰好有相同数量的行。(仅当单词不是第一个或最后一个,或者它是文本中唯一的单词时才可能实现。如果单词大致位于文本的中间,您可能会得到非常尴尬的形状。)

  • 计算单词在文本中的位置,然后在同一行中断开单词,以匹配单词之前和之后的字符数。

  • 在文本语言中实现一个单词断句功能,并计算所有可能的单词和行断点的组合,以找到一个(如果有)使您想要的单词居中的断点。

  • 自己硬编码,有些事情可以通过人类比简单程序更有效地解决。毕竟,我们是经过良好捆绑的超级计算机。


3

将特定目标词移到单词允许的最佳水平中心

您可以使用非常简单的JavaScript实现粗略居中。它只是沿着标记扫描,将找到的第一个TextNode转换为用<span>包装的“单词”,然后尝试并出错地插入换行符,直到找到将目标单词最接近中心的那个。

在fiddle中,我已经用红色突出显示了代码认为应该在其后断开的跨度,并用绿色表示目标词。在大多数情况下,它做得很好,有一些情况可能会使单词单独成行 - 但是,这可以通过调整代码来修复到您的确切用例。

以防万一,人们不知道这是可以实现的示例,绝不是完美的 - 没有代码是完美的,因为它总是可以改进和应该改进的。

fiddle

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

更新的fiddle,显示具有随机目标词的行为:

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

标记

<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>

我大致保留了您的标记,但正如其他人所说,应避免使用center标签。我还将内部div转换为span以保留行内间距。如果您无法这样做,仍然可以使用div,只需覆盖其正常显示为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 );
  });
};

我使用了jQuery来简化。

CSS

您需要此CSS项:

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

如果您的 .centered 元素是一个 <div>,那么可能还需要添加以下内容:

.centered {
  display: inline-block;
}

那么您所需要执行的就是:
jQuery(function($){
  $('center').findYourCenter('.centered');
});

问题

为了精确对齐,你需要偏移一些东西——因为根据单词的长度(和位置),你不能总是同时实现段落、行和目标单词的完美中心。上述代码仍然保持了行的居中,代价是目标单词被偏移。如果想以行为代价来让单词居中,可以改进上述方法,对包含目标的行进行边距调整。

目前,如果目标单词出现在前五个单词中,居中可能难以实现,基本上是因为这种方法依赖于单词换行和断行。如果由于单词位于段落开头而无法使用这两种情况,则除了引入边距、填充或文本缩进之外,无可奈何。

这段代码依赖于能够正确读取元素的位置。旧版浏览器(如IE6和早期版本的Safari/Opera)存在这方面的问题。

还有一件事要注意。这段代码依赖于浏览器在同步执行循环中立即重新计算其内部测量值。大多数现代浏览器似乎都会这样做——在大多数情况下——但你可能会发现你需要实现setTimeout(func, 0)setImmediate延迟,以使其在旧系统上正常工作。


...最后

这是一个相当疯狂的请求,它到底是为了什么? :)


你可能误解了我的写作,实际上可以同时保持段落、行和单词居中,事实上我提出了四种可能的方法来实现。此外,你的代码计算得非常糟糕。 - user2509223
@derylius ~ 或许我有误解,如果您这样认为的话,但我个人认为在不采用断词或丑陋的填充hack的情况下,不可能始终将所有内容居中。无论如何,如果您提交了一些代码,也许您的答案会更受欢迎。您已经得到我的+1,但如果您愿意,我可以从我的答案中删除对您答案的引用?就目前而言,我的答案是解决该问题的粗略方法,我并没有声称它是完美的——但它肯定有所帮助,才被接受。 - Pebbl
请不要介意,但我在这里并不是为了解决别人的问题,而是帮助他们自己解决问题。在我看来,如果有人看到这个答案,可能会直接复制粘贴,而不理解其中的潜在问题,特别是因为在你的答案中你说“...无法实现完美居中...”,这显然是不正确的。我的问题不是你提到了我,而是你将我列为这个错误信息的来源之一,而我从未这样说过。请删除错误的参考资料或修正它们以反映真相。尽管如此,你的答案并不是这个问题的解决方案。 - user2509223

2
正如其他人指出的,无法通过html/Css实现这一点,我也同意。但是你可以按照句子而不是单词进行某种居中对齐。希望这能帮到你。
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>

演示版

更新:根据@disule的指出,我已删除了过时的center标签。


2

最简单的方法是在每行后面插入一个换行符<br>:

这里有一个例子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
}

如果楼主可以更改HTML,我认为这将是最理想的解决方案。它简单易行,不需要任何JavaScript等。但我相信楼主无法更改HTML的结构,因此可能不符合他的需求。 - TalkativeTree

2

我为你特制了一个使用JavaScript实现的非常酷的方法来完成这个任务!哈哈,下面是示例代码:http://jsfiddle.net/7Ykzp/2/

var words = text.split(" ");

我们将其分成单词,然后尝试将它们放在一行上,我们将其置于前面和后面文本所包含的两个中心之间的中心位置。
注意:每次我们尝试添加两个以上的单词以考虑空格的大小时,我们会将行宽增加8。
编辑:根据单词长度正确放置who
编辑2:现在已经正确向后换行居中单词上方的文本!看看吧!

哎呀...我忘记在将字符转换为单词时考虑单词长度了...我会尽快修复并更新这个小工具,或者如果你想的话,其他人也可以帮忙。 - clancer
同时,它应该对所有之前的单词正确进行反向换行,否则看起来很奇怪。 - clancer
不错的尝试,但这并不是完美的解决方案,请查看我的答案中的解释。 - user2509223
@clancer 不错的尝试,但这不是通用解决方案。例如将“who”替换为“松鼠”,它就会失败。 - Netsurfer

1
标签已经被弃用,HTML5不再支持,请勿使用。相反,您可以给.centered类添加一些CSS规则,例如:
.centered { 
    clear: both;
    display: block;
    text-align: center;
}

然而,这会产生自己的一行。如果您仍希望围绕它的文本换行,则必须系统地选择您的换行符 - 目前没有将文本环绕在居中节点周围的方法,并且float:center不存在...

1
<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;

   }

1

虽然我同意html结构存在问题,但你提到你对它的控制很小,所以我只能基于现有的HTML给出答案。如果不触及HTML,使用纯CSS实现居中内容的唯一方法是让居中内容单独占据一行。因此这样可以实现(请参见fiddle):

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

@Village:是的,那就是我的回答的重点(我甚至在回答文本中说了)。让那个词始终“水平居中于页面”的唯一方法是将其放在自己的一行中。当然,我意识到这并不总是理想的情况,但这比“无法完成”要好得多。 - ScottS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接