水平列表的响应式分隔符

19

这个问题是在 'Separators For Navigation' 的基础上展开的,询问如何在视口尺寸导致的换行处移除分隔符。

宽视口
->       Item 1 | Item 2 | Item 3 | Item 4 | Item 5       <-
小视口
->  Item 1 | Item 2 | Item 3  <-
->      Item 4 | Item 5       <-

这里有一个示例展示了如何让管道符号保留在换行符之后:

示例.

我对仅使用CSS的解决方案很感兴趣,但如果只有使用JavaScript才能实现,那也可以接受。


可能是CSS:行中的最后一个元素的重复。 - dippas
在撰写我的问题之前,我实际上没有找到这个问题。然而,我觉得这个问题意图着稍微不同的答案和使用情况。此外,这个Redux不能提供符合当前规范的解决方案吗? - roydukkey
5个回答

28

解释

你可以利用尾部和行尾的空白自动折叠的事实:

document.write(
 'word<b style="background: red; outline: 1px solid blue;"> </b>'
 .repeat(42)
);

sequence of words on three lines, with red rectangles between each two words

如您所见,单词之间有红色空白区域,并带有蓝色轮廓线,但是最后一个单词和行尾的两个单词缺少红色区域,因为它们的宽度折叠为零:这就是空白折叠效果。
可以使用word-spacing调整宽度,并使用伪元素替代,例如设置内联样式::after { content: ' '; word-spacing: 2em; },这样就可以得到宽度较大的内联矩形,可以具有装饰性的背景或边框,但在不位于单词之间时会消失。
简化示例
简化用例(来自https://codepen.io/myf/pen/dyOzpZM,仅在2021年02月的最新版Firefox和Chromium中测试通过,在旧版Chromium Edge中无法正常工作;更稳健的示例请参见下面的第二个代码片段)。

ul {
  text-align: center;
  padding: 0;
}
li {
  display: inline;
}
li::after {
  /*
   This has to be space, tab or other
   breakable white-space character:
  */
  content: " ";
  word-spacing: 1em;
  background-image: linear-gradient(
    -0.2turn,
    transparent 0 calc(50% - 0.03em),
    currentcolor 0 calc(50% + 0.03em),
    transparent 0
  );
}
/*
 That's it: just inline text
 with styled ::after spaces
 that collapse at line breaks
 and at the end of the element.
 
 That's basically how spaces work in text.
*/

/*
 Unrelated whimsical effects:
*/
body { background: #456; color: #fed; min-height: 100vh; margin: 0; display: flex; align-items: center; }
ul { --dur: 3s; font-family: Georgia, serif; font-size: min(7vw, calc(100vh / 7)); margin: 0 auto; position: relative; padding: 0 1em; -webkit-text-fill-color: #999; text-transform: capitalize; animation: poing var(--dur) infinite alternate ease-in-out; }
@keyframes poing { from { max-width: 3.4em; } to { max-width: min(19em, calc(100vw - 2em)); color: lime; } }
ul::before, ul::after { -webkit-text-fill-color: currentcolor; position: absolute; top: 50%; transform: translatey(-50%); animation: calc(var(--dur) * 2) calc(var(--dur) * -1.5) infinite forwards linear; }
ul::before { content: "☜"; left: 0; animation-name: a !important; }
ul::after { content: "☞"; right: 0; animation-name: b !important; }
@keyframes a { 50% { content: "☛"; } }
@keyframes b { 50% { content: "☚"; } }
ul:hover, ul:hover::before, ul:hover::after { animation-play-state: paused; }
<ul>
 <li>foo</li>
 <li>bar</li>
 <li>baz</li>
 <li>gazonk</li>
 <li>qux</li>
 <li>quux</li>
</ul>

Two lines of metasyntactic variables, tall slashes between each two words. Manicule on both sides.

它使用单词项的平面列表,因此对于实际应用来说并不是非常相关。

更现实的示例,突出元素

nav {
  text-align: center;
  padding-right: 1em; /* = li::after@word-spacing */
}
ul {
  display: inline;
  margin: 0;
  padding: 0;
}
li {
  display: inline;
  /*
   white-space: nowrap should be moved to child A
   because IE fails to wrap resulting list completely
  */
}
li::before {
  content: ' ';
  /*
   this content is important only for Chrome in case
   the HTML will be minified with *no whitespaces* between </li><li>
  */
}
li::after {
  content: ' ';
  /*
   this is actual placeholder for background-image
   and it really must be space (or tab)
  */
  white-space: normal;
  word-spacing: 1em;
  /*
   = nav@padding-right - this actually makes width
  */
  background-image: radial-gradient(circle, black, black 7%, transparent 15%, transparent 35%, black 45%, black 48%, transparent 55%);
  background-size: 1em 1em;
  background-repeat: no-repeat;
  background-position: center center;
  opacity: 0.5;
}
/*
 no need to unset content of li:last-child::after
 because last (trailing) space collapses anyway
*/
a {
  white-space: nowrap;
  display: inline-block; /* for padding */
  padding: 1em;
  text-decoration: none;
  color: black;
  transition-property: background-color;
  transition-duration: 500ms;
}
a:hover {
  background-color: #ccc;
}
/*
 For demonstrative purposes only
 Give items some content and uneven width
*/
nav:hover > ul > li {
  outline: 3px dotted rgba(0,0,255,.5);
  outline-offset: -3px;
}
nav:hover > ul > li::after {
  opacity: 1;
  background-color: rgba(255, 0, 0, .5);
}
nav:hover > ul > li:hover {
  outline-style: solid;
}
nav:hover > ul > li:hover::after  {
  background-color: cyan;
}

nav:hover > ul > li > a {
  outline: 3px solid rgba(0,255,0,.5);
  outline-offset: -3px;
}

nav > ul {
  counter-reset: c;
}
nav > ul > li {
  counter-increment: c;
}
nav > ul > li > a::before {
  content: counter(c, upper-roman) '. ';
  letter-spacing: .3em;
}
nav > ul > li > a::after {
  content: ' item ' counter(c, lower-roman);
  word-spacing: .3em;
  letter-spacing: .1em;
  transform: translatex(.1em);
  display: inline-block;
}
<nav>
  <ul><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li>
  </ul>
</nav>
<!--  For demonstrative purposes is content of links made by CSS
-->

three lines of items, each with Latin number. Colourful outlines show various boundaries.

(原文来自https://jsfiddle.net/vnudrsh6/7/)这个概念验证使用了“最终折叠”的CSS生成内容的背景图像,每个<li>后面都有一个空格。在2016年在Firefox、Chrome和IE11中进行了测试。
显然,你可能需要使用一些字符或更复杂的形状作为分隔符。当然,你可以使用(向量)背景图像,甚至可以在SVG中使用文本,尽管使其与周围的(“真实”)文本相对应可能会很困难。

仅包含SVG的基本样式

没有任何“列表”元素的最简工作示例,带有文字❦花纹:

body {
  text-align: center;
}
b::after {
  content: " ";
  word-spacing: 16px;
  background: url("data:image/svg+xml;charset=utf-8,\
  <svg xmlns='http://www.w3.org/2000/svg' \
    viewBox='-3,-15,16,16'>\
      <text>❦</text>\
  </svg>");
}
<b>foo</b> <b>bar</b> <b>baz</b> <b>gazonk</b> <b>qux</b> <b>quux</b> 
<b>foo</b> <b>bar</b> <b>baz</b> <b>gazonk</b> <b>qux</b> <b>quux</b> 
<b>foo</b> <b>bar</b> <b>baz</b> <b>gazonk</b> <b>qux</b> <b>quux</b>

metasyntactic variables separated with fleurons


其他值得注意的答案:

  • 同样的技术在2014年被忽视的Liphtier的答案中使用。(我在发布这个答案后很久才发现那个,所以我不能声称我的答案是最早的。)
  • 同样的技术在几个月后被用在Tom Robinson的答案中。
  • 令人印象深刻的基于弹性盒子的解决方案,使用超出边界和不同间距的平整边框,在gfullam的答案中。
  • 对于左对齐的列表,您可以设置溢出隐藏,并在伪元素中切割重叠的真实字符:Oriol的答案Nathan Arthur的答案。 。

嗯...我喜欢这个。让我们看看其他人能想出什么。 - roydukkey
2
仍然希望有一个可以处理其他字符(例如子弹、右尖角引号等)的解决方案。 - roydukkey
@roydukkey 是对的,实际上我是把这个当做一个有趣的 hack 来想的,但是结果发现它可以进一步开发,包括将折叠分隔线加入背景图片的可能性。但是也有另外一个缺点;请参见更新后的答案。 - myf
@myf 我对这个解决方案特别感兴趣,因为它是纯CSS实现的。由于这是为移动设备提供服务的,如果可能的话,我不想使用调整大小处理程序。 - roydukkey
不错!文本偏离了分隔符的距离。如果添加 nav@padding-left 来进行补偿,是否正确呢?https://jsfiddle.net/vnudrsh6/8/ 在这个例子中,我们将大小调大并添加了背景色,以使对齐更加明显。 - roydukkey
显示剩余8条评论

4
一种不同的解决方案来自于同样的CSS:最后一个行元素,似乎在这里可以起作用。
HTML:
<div>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
</div>

CSS:

div {overflow: hidden; margin: 1em; }
div ul { list-style: none; padding: 0; margin-left: -4px; }
div ul li { display: inline; white-space: nowrap; }
div ul li:before { content: " | "; }

(Fiddle)


这样做有风险,因为-4px只是对 " | "宽度的估计,可能无法对齐父级元素。 - Merchako
为什么不在 CSS 的最后一行加上 div ul li:not(:first-child)::before { content: " | "; } 呢?这样就不需要使用 4px 技巧了。 - Joomy Korkut
我认为那是一个不错的解决方案,但它不能与居中对齐的菜单一起使用。@JoomyKorkut 在换行后 not(:first-child) 不起作用。 - wittich
我觉得这是一个不错的解决方案,但它在居中菜单上不起作用。@JoomyKorkut not(:first-child) 在换行后不起作用。 - undefined

2
如果您的元素具有静态宽度,则可以通过媒体屏幕进行计算。如果没有,请使用脚本。
body {
  text-align: center;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

li {
  display: inline-block;

  &:not(:last-child):after {
  content: ' |';  
  }
}
@media screen and (max-width: 265px) {
  li {
  display: inline-block;

  &:not(:last-child):after {
  content: '';  
  }
}

}

不,我最终将使用这种技术的元素只有一个最大宽度。 - roydukkey

1
很好的问题。就我而言,恐怕我想不出一个CSS-only的完美解决方案...
我修改了一个旧的解决方案,针对类似问题发布了一段时间:CSS:行中的最后一个元素。有趣的是,我曾经在寻找解决另一个问题的方法,偶然间发现了这个 - 从那时起一直被收藏夹保存!
这里是我的更新的fiddle:https://jsfiddle.net/u2zyt3vw/1/ HTML:
<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
  <li>Item 5</li>
</ul>

CSS:
body {
  text-align: center;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

li {
  display: inline-block;

  &:not(:last-child):after {
    content: ' |'

  }

}
li.remove:after {
  content: none;
}

jQuery:

$(window).on("resize", function () {
    var lastElement = false;
    $("ul > li").each(function() {
        if (lastElement && lastElement.offset().top != $(this).offset().top) {
            lastElement.addClass("remove");
        }
        lastElement = $(this);
    }).last().addClass("remove");
}).resize();

注意 - 目前最好使用 onload,调整大小会出现一些问题,即使我使用 toggleClass()。因此,请每次调整视图时都保持按下“运行”按钮。我会继续努力并回到您那里。


不错!这个问题是它不是流体响应式的,这意味着它不会在从纵向到移动设备(例如)后显示,但如果这不是问题,那就太好了! - Riskbreaker
@Riskbreaker 说得好。这个问题有很多可能性需要考虑...非常有趣的问题。 - David Wilkinson

0

我的JavaScript实现:https://jsfiddle.net/u2zyt3vw/5/

在调整窗口大小后再次点击“运行”按钮。

您还可以添加事件监听器,例如onresize。以下是JS代码:

var listItems = document.getElementsByTagName("li");
var listItemsWidth = [];
var listItemsDistance = [];

for (let i = 0; i < listItems.length; i++) {
  listItemsWidth[i] = listItems[i].offsetWidth;
  listItemsDistance[i] = listItems[i].getBoundingClientRect().right;
}

for (let i = 0; i < listItems.length; i++) {
  if (listItemsDistance[i] == Math.max.apply(null, listItemsDistance)) {
    listItems[i].classList -= "notLast";
  } else {
    listItems[i].classList = "notLast";
  }
}

我给你所有的元素添加了notLast类,这个类包含带有管道符号的:after伪元素。这个脚本会从那些靠近容器右边缘的元素中删除这个类。

我还调整了:after伪元素,并将其设置为position:absolute;,出于某些黑暗的原因。


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