在一系列div中截断文本

14

我有一个组件,目前会呈现出以下的内容:

Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion

我希望始终显示前两个和后两个项目,但是如果可能的话,将中间的所有内容截断为...。也就是说,如果上面的字符串溢出了包含它的div,应该得到以下结果:

Corvid / Games / ... / Night Elf / Malfurion

我尝试过创建如下结构:

<div className={styles.container}>
  <div className={styles.first}>
    {/** Contains first two items */}
  </div>
  <div className={styles.truncate}>
    {/** N arbitrary path items */}
  </div>
  <div className={styles.last}>
    {/** Last two items */}
  </div>
</div>

这个能用CSS实现吗?


1
看起来你正在使用React。如果是这样,我们可以使用JS,和/或者让组件处理截断吗? - Brett DeWoody
6个回答

11

有趣的问题-很遗憾我无法看到一个可靠的仅基于CSS的解决方案。也就是说,除非可以编辑HTML结构,即使这样也会有一些硬编码,我认为并没有可靠的CSS-only解决方案。

不过,以下是3个潜在的解决方案:

  1. 一个简单的JavaScript实用函数
  2. 一个功能性(无状态)React组件
  3. 一个带有状态的React组件

1. 一个JavaScript函数

在下面的示例中,我创建了一个函数truncateBreadcrumbs(),它接受3个参数:

  • selector- 匹配要截断的元素的CSS选择器
  • separator - 用于分隔元素的字符
  • segments - 您想要截断字符串的段数

它可以像下面这样使用:

truncateBreadcrumbs(".js-truncate", "/", 4);

该代码会查找所有类名为.js-truncate的元素,并将内容截断到4个元素,使用中间带有...分隔符的方式,例如:

Corvid / Games / ... / Night Elf / Malfurion

也可以使用奇数段,例如5将生成:

Corvid / Games / World of Warcraft / ... / Night Elf / Malfurion

如果 "segment" 参数等于或大于元素数量,就不会发生截断。
以下是完整的工作示例:

function truncateBreadcrumbs(selector, separator, segments) {
  const els = Array.from(document.querySelectorAll(selector));

  els.forEach(el => {
    const split = Math.ceil(segments / 2);
    const elContent = el.innerHTML.split(separator);

    if (elContent.length <= segments) {
      return;
    }

    el.innerHTML  = [].concat(
      elContent.slice(0, split),
      ["..."],
      elContent.slice(-(segments-split))
    ).join(` ${separator} `);
  });
}

truncateBreadcrumbs(".js-truncate--2", "/", 2);
truncateBreadcrumbs(".js-truncate--4", "/", 4);
truncateBreadcrumbs(".js-truncate--5", "/", 5);
<div class="js-truncate--2">Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion</div>

<div class="js-truncate--4">Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion</div>

<div class="js-truncate--5">Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion</div>

<div class="js-truncate--4">Corvid / Games / Night Elf / Malfurion</div>

2.一个函数式(无状态)的React组件

根据className属性,您正在使用React。如果是这种情况,我们可以创建一个简单的函数式组件来截断文本。我已经将上面的代码转换成了一个函数式组件<Truncate />,它可以完成相同的功能:

const Truncate = function(props) {
  const { segments, separator } = props;
  const split = Math.ceil(segments / 2);
  const elContent = props.children.split(separator);

  if (elContent.length <= segments) {
    return (<div>{props.children}</div>);
  }

  const newContent = [].concat(
    elContent.slice(0, split),
    ["..."],
    elContent.slice(-(segments-split))
  ).join(` ${separator} `);

  return (
    <div>{newContent}</div>
  )
}

它可以用作:

<Truncate segments="4" separator="/">
  Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
</Truncate>

以下是完整可工作的示例:

const Truncate = function(props) {
  const { segments, separator } = props;
  const split = Math.ceil(segments / 2);
  const elContent = props.children.split(separator);

  if (elContent.length <= segments) {
    return (<div>{props.children}</div>);
  }

  const newContent = [].concat(
    elContent.slice(0, split),
    ["..."],
    elContent.slice(-(segments-split))
  ).join(` ${separator} `);

  return (
    <div>{newContent}</div>
  )
}

class App extends React.Component {
  render() {
    return (
      <div>
        <Truncate segments="2" separator="/">
          Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
        </Truncate>

        <Truncate segments="4" separator="/">
          Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
        </Truncate>

        <Truncate segments="5" separator="/">
          Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
        </Truncate>

        <Truncate segments="4" separator="/">
          Corvid / Games / Night Elf / Malfurion
        </Truncate>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>

3. 一个有状态的React组件

我们也可以创建一个有状态的组件来响应屏幕/元素的宽度。这是一个对该想法的简单实现 - 一个组件测试元素的宽度并在必要时进行截断。理想情况下,组件只截断必要的部分,而不是固定数量的段落。

使用方法与上面相同:

<Truncate segments="4" separator="/">
  Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
</Truncate>

但不同之处在于该组件测试容器的宽度以查看段落是否适合,如果不适合,则会截断文本。单击“展开片段”按钮以全屏查看演示,以便您可以调整窗口大小。

class Truncate extends React.Component {
  constructor(props) {
    super(props);
    this.segments = props.segments;
    this.separator = props.separator;
    this.split = Math.ceil(this.segments / 2);
    this.state = {
      content: props.children
    } 
  }
  
  componentDidMount = () => {
    this.truncate();
    window.addEventListener("resize", this.truncate);
  }
  
  componentWillUnmount = () => {
    window.removeEventListener("resize", this.truncate);
  }
  
  truncate = () => {
    if (this.div.scrollWidth > this.div.offsetWidth) {
      const elContentArr = this.state.content.split(this.separator);
      this.setState({
        content: [].concat(
          elContentArr.slice(0, this.split),
          ["..."],
          elContentArr.slice(-(this.segments - this.split))
        ).join(` ${this.separator} `)
      })
    }
  }

  render() {
    return (
      <div className="truncate" ref={(el) => { this.div = el; }}>
        {this.state.content}
      </div>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <Truncate segments="2" separator="/">
          Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
        </Truncate>

        <Truncate segments="4" separator="/">
          Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
        </Truncate>

        <Truncate segments="5" separator="/">
          Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
        </Truncate>

        <Truncate segments="4" separator="/">
          Corvid / Games / Night Elf / Malfurion
        </Truncate>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
.truncate {
  display: block;
  overflow: visible;
  white-space: nowrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>


6
这是一种方法,只有在始终存在4个以上的项目时才有效。

ul { list-style-type: none; }
ul li { display: none; }
ul li:nth-last-child(n+2):after { content: " / "; }
ul li:nth-child(2):after { content: " / ... /"; }
ul li:nth-child(-n+2), ul li:nth-last-of-type(-n+2) { display: inline-block; }
<ul>
  <li><a href="#">Corvid</a></li>
  <li><a href="#">Games</a></li>
  <li><a href="#">World of Warcraft</a></li>
  <li><a href="#">Assets</a></li>
  <li><a href="#">Character Models</a></li>
  <li><a href="#">Alliance</a></li>
  <li><a href="#">Night Elf</a></li>
  <li><a href="#">Malfurion</a></li>
</ul>

一种解决方法是在选择器前添加一个类,并且仅当有超过4个项时才进行截短。理想情况下,这应该在渲染时添加,但也可以通过JavaScript来添加,就像这个例子中的做法。

document.querySelectorAll("ul").forEach( el => el.childElementCount > 4 && el.classList.add("truncate") );
ul { list-style-type: none; }
ul li { display: inline-block;}
ul li:nth-last-child(n+2):after { content: " / "; }
ul.truncate li { display: none; }
ul.truncate li:nth-child(2):after { content: " / ... /"; }
ul.truncate li:nth-child(-n+2), ul li:nth-last-of-type(-n+2) { display: inline-block; }
<ul>
  <li><a href="#">Corvid</a></li>
  <li><a href="#">Games</a></li>
  <li><a href="#">World of Warcraft</a></li>
  <li><a href="#">Assets</a></li>
  <li><a href="#">Character Models</a></li>
  <li><a href="#">Alliance</a></li>
  <li><a href="#">Night Elf</a></li>
  <li><a href="#">Malfurion</a></li>
</ul>

编辑:

不知道问题中是否有我漏掉的内容,但是如果你不想改变你的结构,你也可以这样做:

.truncate div { display: inline-block; }
.truncate .mid { display: none; }
.truncate .first:after { content: "... /"; }
<div class="truncate">
  <div class="first">
    Corvid / Games / 
  </div>
  <div class="mid">
    World of Warcraft / Assets / Character Models / Alliance / 
  </div>
  <div class="last">
    Night Elf / Malfurion
  </div>
</div>

或者,如果你想要一个简单的纯JS函数来实现它

document.querySelectorAll(".turncate").forEach( el => {
  const parts = el.innerText.split(" / ");
  if(parts.length > 4){
    parts.splice(2, parts.length-4); //ensure we only have two first and last items
    parts.splice(2, 0, "..."); // add ... after 2 first items.
    el.innerText = parts.join(" / ");
  }
});
<div class="turncate">
Corvid / Games / World of Warcraft / Assets / Character Models / Alliance / Night Elf / Malfurion
</div>


这个答案需要更多的赞。答案开头的纯CSS方法更适合问题,特别是问题中没有标记[javascript]... - JustCarty

3
.enumeration > div{display:inline-block;font-size:150%}
.enumeration > div:after{content: " / ";color:blue;font-weight:bold;}
.enumeration > div:nth-child(n+3) {display:none;}
.enumeration > div:nth-child(n+2):after {content: " / ... / ";color: red;}
.enumeration > div:nth-last-child(-n+2) {display:inline-block;}
.enumeration > div:nth-last-child(-n+3):after{content:" / ";color:green;}
.enumeration > div:nth-last-child(-n+2):after{content:" / ";color:orange;}
.enumeration > div:last-child:after{content:""}

有点棘手,但只涉及CSS,并适用于任何列表大小。:) (颜色和粗体仅用于更容易查看应用每个选择器的位置。)
  1. 在每个项目后面显示“/”。
  2. 不显示第二个项目之后的项目。
  3. 在第二个项目后显示“/.../”。
  4. 显示最后两个项目(它们在2中被隐藏)。
  5. 在倒数第三个项目后显示“/”。
  6. 在倒数第二个项目后显示“/”。
  7. 不要在最后一个项目后显示“/”。

.enumeration > div{display:inline-block;font-size:150%}
.enumeration > div:after{content: " / ";color:blue;font-weight:bold;} /*1*/
.enumeration > div:nth-child(n+3) {display:none;}/*2*/
.enumeration > div:nth-child(n+2):after {content: " / ... / ";color: red;}/*3*/
.enumeration > div:nth-last-child(-n+2) {display:inline-block;}/*4*/
.enumeration > div:nth-last-child(-n+3):after{content:" / ";color:green;}/*5*/
.enumeration > div:nth-last-child(-n+2):after{content:" / ";color:orange;}/*6*/
.enumeration > div:last-child:after{content:""}/*7*/
<h2>
More than 4 items list
</h2>
<div class="enumeration">
<div>item 1</div>
<div>item 2</div>
<div>item 3</div>
<div>item 4</div>
<div>item 5</div>
<div>item 6</div>
<div>item 7</div>
<div>item 8</div>
<div>item 9</div>
</div>

<h2>
4 items list
</h2>
<div class="enumeration">
<div>item 1</div>
<div>item 2</div>
<div>item 3</div>
<div>item 4</div>
</div>


<h2>
3 items list
</h2>
<div class="enumeration">
<div>item 1</div>
<div>item 2</div>
<div>item 3</div>
</div>


<h2>
2 items list
</h2>
<div class="enumeration">
<div>item 1</div>
<div>item 2</div>
</div>


<h2>
1 item list
</h2>
<div class="enumeration">
<div>item 1</div>
</div>


2
我已经提供了这个概念验证,使用伪元素和媒体查询。请在完整页面上运行片段,并将宽度更改为小于500px。
我已经学到了媒体查询不能应用于单个元素的宽度,所以这可能不是很有用,您需要根据浏览器的宽度调整max-width的实际值。

DIV.truncate::before {
  content: 'World of Warcraft / Assets / Character Models / Alliance';
}

@media (max-width: 500px) {
  DIV.truncate::before {
    content: '...';
  }
}

DIV.container {
  font-weight: bold;
  font-family: Arial, sans-serif;
  font-size: 14px;
  white-space: nowrap;
  overflow-x: auto;
}

DIV.container>DIV {
  display: inline-block;
}
<div class="container">
  <div class="first">Corvid / Games /</div>
  <div class="truncate"></div>
  <div class="last">/ Night Elf / Malfurion</div>
</div>


2
您可以使用flex和截断文本的组合来实现:

.grid-container {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  width: 320px;
  margin-top: 30vh;
  margin-right: auto;
  margin-left: auto;
}

.grid-item {
  overflow: hidden;
  height: 1.5em;
  line-height: 1.5em;
}

.grid-item.expand {
  -webkit-box-flex: 1;
  -webkit-flex: 1;
  -ms-flex: 1;
  flex: 1;
  white-space: nowrap;
  text-overflow: ellipsis;
}
<div class="grid-container">
    <div class="grid-item" style="background: red">Corvid / Games </div>
    <div class="expand grid-item">/ World of Warcraft / Assets / Character Models / Alliance </div>
    <div class="grid-item" style="background: blue">/ Night Elf / Malfurion</div>
</div>

Flex会使中心元素的宽度动态变化,而text-overflow会使其截断。 如果这不是您想要的,请进一步解释。


2

这里使用了我从Lea Verou那里学到的技巧。
请查看https://codepen.io/HerrSerker/pen/JOaqjL以获取一个 SCSS 示例。

ul.breadcrumb, ul.breadcrumb > li {
  list-style: none;
  margin: 0;
  padding: 0;
}
ul.breadcrumb > li {
  display: inline-block;
}

/* this adds the slash between the `<li>` */
ul.breadcrumb > li:not(:last-child):after {
  content: ' /';
}

/* this is the second `<li>`, but only if there are 5 or more `<li>` */
/* 5 = 2 + 4 - 1 */
ul.breadcrumb > li:nth-child(2):nth-last-child(n+4):after {
  content: ' / … /';
}
/* these are `<li>` No. 3 up to 3rd to last `<li>`, but only if there are 5 or more `<li>` */
/* 5 = 3 + 3 - 1 */

ul.breadcrumb > li:nth-child(n+3):nth-last-child(n+3) {
  display: none;
}
<ul class="breadcrumb">
  <li>Corvid</li>
</ul>

<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
</ul>

<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
  <li>World of Warcraft</li>
</ul>

<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
  <li>World of Warcraft</li>
  <li>Assets</li>
</ul>


<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
  <li>World of Warcraft</li>
  <li>Assets</li>
  <li>Character Models</li>
</ul>

<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
  <li>World of Warcraft</li>
  <li>Assets</li>
  <li>Character Models</li>
  <li>Alliance</li>
</ul>
<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
  <li>World of Warcraft</li>
  <li>Assets</li>
  <li>Character Models</li>
  <li>Alliance</li>
  <li>Night Elf</li>
</ul>
<ul class="breadcrumb">
  <li>Corvid</li>
  <li>Games</li>
  <li>World of Warcraft</li>
  <li>Assets</li>
  <li>Character Models</li>
  <li>Alliance</li>
  <li>Night Elf</li>
  <li>Malfurion</li>
</ul>


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