三探【文字溢出省略】:纯css实现“任意行数”截断处理
发布在css研究2022年10月17日view:107SPA,WebApp
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

笔者之前有过对此功能的两次探究: https://blog.csdn.net/qq_43624878/article/details/110124299 这篇文章主要描述了文本溢出省略功能的实现,以及用js大致模拟了其原理和如何用js动态实现“溢出省略”; 后来的 https://blog.csdn.net/qq_43624878/article/details/120993013 这篇文章又进一步从实际场景研究了不同情况下的溢出对用户体验的影响;

本文将结合之前的文章用css代替js实现动态“多行溢出省略”效果,并描述由此带来的一些“周边”问题。

首先,在上一篇文章中也提到,很多情况下我们确实要去实现“多行溢出省略”的效果,单行的文字会给用户带来不好的体验。所以在第一篇文章中通过js实现了这一功能。 但抛开js中繁杂的计算不说,这已然‘违背’了笔者曾经提过的「优先策略」之一:HTML > CSS > JavaScript!

这里依然从实际场景(代码及截图已脱敏)一步一步分析:

多行溢出

一开始笔者的代码是这样写的:

<!-- region是“xxx;xxx;xxx”这样的字符串格式 -->
<div class="fss-r-detail" v-if="ele.region">{{ele.region}}</div>

要求最多显示两行,溢出省略:

.fss-r-detail {
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

微店商家版-满包邮

这样的代码不仅只能在webkit 内核浏览器中才能生效,还有一个关键问题:它必须和外层设置的“高度”合作 —— 如果外层设置的高度不足以支撑 -webkit-line-clamp: 2; ,那结果岂不是很尴尬?

如何让外层高度控制一切呢? 由于高度是固定的,如果让内容部分自适应剩余空间,就可以完美实现这个效果。这就要用到 flex 布局:

<div class="fss-r-detail" v-if="ele.region">
    <div class="fss_r-detail-li">{{ele.region}}</div>
</div>
.fss-r-detail {
    max-height: 81rpx; //这里给一个高度
}
.fss_r-detail-li {
    flex: 1;
    overflow: hidden;
}

当然,很多元素像「行内元素」我们就完全不需要考虑flex的问题:因为他们本身就是一个接一个铺满父元素的!

接下来就是省略号的问题:省略号永远在右侧,那我们其实可以考虑一个“很少被使用”的属性——浮动float

.fss-r-detail::before {
    content: "...";
    float: right;
}

现在看效果的话,其实省略号是在“右上角”的,和子元素顶部对齐。要将它移到右下角,有两种方案:

  • 通过一个浮动元素将其现在的位置占住
  • shapes布局

第一种方案显然是“不合时宜”的:从省略号本身来说,他是伪元素,脱离于文档流存在,能不借助于额外元素移动位置必然是我们首要考虑的。

在此之前,应当将省略号高度撑满整个父元素,并且向下对齐:

.fss-r-detail::before {
    content: "...";
    float: right;
    height: 100%;
    display: flex;
    align-items: flex-end;
}

然后现在又来了一个新问题:因为是高度撑满的,所以省略号所在位置上面的文字也被挤走了,形成了一个鲜明的左右布局而不是环绕效果! 这时候就需要 shapes布局 出手了:

.fss-r-detail::before {
    /** 其它样式 **/
    shape-outside: inset(calc(100% - 1.5em) 0 0);
}

shape-outside属性要想生效,本身需要是浮动float元素。

微店-恪愚-云小梦

可以看到,目前已经实现我们需要的效果了。

接下来是文章开头提到的问题:如何自动隐藏省略号?

这个其实就是取巧的方法,也可以说是“视觉屏蔽”:用一个足够大的色块(和背景色一样)盖住省略号,设置绝对定位后,色块会跟随内容文本。当文字较多时,色块也就跟随文本移动,看着像是“被挤下去了”。此时结合overflow:hidden的效果就隐藏了省略号:

.fss-r-detail {
    /** 其它属性 **/
    position: relative;
}
.fss-r-detail::after {
    content: "";
    position: absolute;
    width: 999vh;
    height: 999vh;
    background: #fff;
    box-shadow: -2em 2em #fff;
}

最后的box-shadow是向下的阴影,为了应对个别情况下可能遮挡不住省略号的问题!

结构改变

在上面的HTML结构中,提到“region是“xxx;xxx;xxx”这样的字符串格式”,现在因为要对其中某个地区文字做样式处理,所以它不能是纯字符串展示。于是我将其改为下面的样式:

<div class="fss-r-detail" v-if="ele.region">
    <ul>
        <li class="fss_r-detail-li" :class="这里做特殊样式处理" v-for="(item, index) in ele.region.split(';')" :key="index">{{item}}</li>
    </ul>
</div>

因为li是块元素,所以我要对ul做处理:很容易想到的是flex

ul {
    display: flex;
    flex-wrap: wrap; //flex默认是不允许换行的
}

然后问题就又出现了: 微店商家版-满包邮

咦?为嘛没有超过两行也显示出了省略号?这河里吗? 我们看一下此时的文本dom是什么样的: 微店商家版-满包邮

给浮动的省略号加一个margin-left微店商家版-满包邮

OK,可以看到,文字部分因为有了flex而默认铺满整个父元素的,而此时触碰了省略号元素的位置,由上面定制的规则来看,被认为是“有文字超出规定区域了”。

这时我突然想到,为什么要加ul呢?索性直接对li设置display属性让其变为行内元素:

<div class="fss-r-detail" v-if="ele.region">
    <li class="fss_r-detail-li" v-for="(item, index) in ele.region.split(';')" :key="index">{{item}}</li>
</div>
.fss_r-detail-li {
    position: relative;
    display: inline;
    margin-right: 8rpx;
    &:not(:nth-of-type(1)) {
        padding-left: 4rpx;
    }
    &::after { //不用字符串后为了不添加复杂结构的前提下不对现在文字的样式造成影响,用伪元素的形式实现文字后面的“;”
        content: ";";
        position: absolute;
        top: 0;
        right: 100%;
    }
}

就可以了!

inline-block导致的问题

上面我对li设置了inline,那能不能用inline-block呢? 不能。块级元素(行内块也有块级的效果)不能自动折行,这会导致我们前面辛辛苦苦设置的“环绕效果”毫无用武之地。哪怕你设置了word-break:break-all;

微店商家版-满包邮

而且最离谱的是:为了达到和纯字符串一样的效果,笔者用伪元素实现了每个词后面的分号“;”,但是为了最后一个没有分号而不加过多的css样式,采用了“前置伪元素”的做法:每个li元素后面的分号实际上是下一个元素的伪元素,第一个元素的伪元素被隐藏在前面了! 这样的话用block得到的效果就是如上面这张图一样:第一行最后的分号不见了。

涉及的样式代码

笔者的项目是使用less写样式的,这里就不做改变了,直接放出:

.fss-r-detail {
    max-height: 81rpx;
    position: relative;
    overflow: hidden;

    .fss_r-detail-li {
        position: relative;
        display: inline;
        // white-space: nowrap;
        margin-right: 8rpx;
            &:not(:nth-of-type(1)) {
                padding-left: 4rpx;
            }
        &::after {
            content: ";";
            position: absolute;
            top: 0;
            right: 100%;
        }
    }

    &::before{
        content: "...";
        float: right;
        height: 100%;
        display: flex;
        align-items: flex-end;
        shape-outside: inset(calc(100% - 1.5em) 0 0); // 以自身为边界,四个方向向内缩进到靠近省略号位置,神来之笔
    }
    &::after {
        content: "";
        position: absolute;
        width: 999vh;
        height: 999vh;
        background: #FFFFFF;
        box-shadow: -2em 2em #FFFFFF;
    }
}

2022-1-20更新

我去突然想到一件事,我为啥要用伪元素去实现一个分号“;”。是因为它普通吗?不是,那为什么?我也不知道了。

但是很显然,这是一个能够用简简单单没有脑袋的<template>就能完成的事情。而且,我违背了曾经说过的「HTML > CSS > JavaScript」。罪过 罪过~

评论
发表评论
暂无评论
WRITTEN BY
恪愚
一个自觉专注大前端,一心为了用户体验的人
TA的新浪微博
PUBLISHED IN
css研究

很喜欢这个前端论坛~所以在自己博客的每一篇原创文章都会在这边跟大家分享一下,暂定就这么多,纯粹的爱好者。

友情链接 大搜车前端团队博客
我的收藏