我正在参加「掘金·启航计划」
展示效果如下,详情各位看官请往下看:
那天摸鱼的我想到刚刚写的文本省略,我大腿一拍,我能不能实现一个自动省略文本的组件呢? 本着摸鱼不如学习的心态!(其实是我坐**旁边,不然摸鱼不爽吗?)
使用
<div style="width:180px" >
<AutoEllipsis text="1. 单行展示默认省略 单行展示默认省略"/>
</div>
<AutoEllipsis style="width:200px" isShowSuffix text="2. 单行带后缀省略单行带后缀省略单行带后缀省略.jpeg"/>
<AutoEllipsis style="width:80px" :showLine="2" text="3. 多行展示默认省略展示默认省略.jpeg"/>
<AutoEllipsis style="width:220px" isShowSuffix :showLine="2" text="4. 多行展示带后缀省略多行展示带后缀省略.jpg多行展示带后缀省略多行展示带后缀省略.jpg"/>
第一步:省略的前提,文本超出。
所以第一步需要知道文本的宽度,但是怎么计算文本的宽度呢?这里就需要用到canvas了,大家知道canvas的measureText可以获取文本的宽度,所以有了下面第一个方法。
这里给文本的字体样式定死啦,留给 jym 自己优化了,相信你们,代码注释很清楚,不再赘述
/**
* @description: 计算文字的宽度
* @param {*} text 展示的文本
* @param {*} fontSize 文本字体大小
* @return 文本的宽度
* @Author: zml
*/
const getTextWidth = (text, fontSize = 10) => {
// 创建一个canva元素
const canvasDom = document.createElement("canvas");
const ctx = canvasDom.getContext("2d");
const dpr = window.devicePixelRatio || 1;
const fs = dpr * fontSize;
const fontStyle = optionsStyle.fontStyle || "sans-serif";
ctx.font = `${fs}px ${fontStyle}`; // 设置字体样式 默认字体大小是10px
// 通过canvas来获取文本的宽度
const textMetrics = ctx.measureText(text);
// 直接得到文本的宽度
const width = textMetrics.width;
// mdn 上说这样获取的是文本绝对宽度更准确,但是我们现在要获取他们计算中的最大值
const actualBoundingBoxWidth =
textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft;
// 返回它们两中最大的那个元素
return Math.max(width * dpr, actualBoundingBoxWidth * dpr);
};
第二步:文本既然省略了,那么就需要计算出最多能展示的文本
这里需要一个方法计算出当前能够展示的最大文本长度,,并且这里有默认展示,和展示后缀名称两种方式,下面方法中使用的有props大家看下方完整代码
/**
* @description: 自动计算展示省略的文本
* @param {HTMLElement} container 父级元素 dom/ref
* @param {String} text 展示的文本
* @return {Boolean} isShowEllipsis 文本宽度 是否超过 容器宽度 * 展示的行数
* @return {String} 展示的文本
* @Author: zml
*/
const autoEllipsisHandler = (container, text) => {
let isShowEllipsis;
// 获取容器的宽度
const autoEllipsisWidth = container && container.clientWidth;
// 计算出文本宽度
const textWidth = getTextWidth(text.trim(), fs);
// 判断 文本宽度 和 容器宽度 * 展示的行数
// 为 false 什么都不做,直接展示,否则添加类名 'overflow-hide-moreline'
isShowEllipsis = textWidth >= autoEllipsisWidth * props.showLine;
// 如果为true 并且展示后缀的话 ==》需要修改展示的文本 (说明已经超过了)
// 如果为true 不展示后缀的话 ==》css里设置就是直接省略,不用处理
if (isShowEllipsis && props.isShowSuffix) {
// 后缀
const suffix = text.split(".").reverse()[0];
// 展示的后缀长度
const suffixLength = Math.round((suffix.length + 1) * 0.5);
// 一行能展示的文本数量 ==> (容器的宽度 / 字体大小 * 展示的行数)- 后缀长度 - 2
const inlineNum =
(Math.ceil(autoEllipsisWidth / fs) * props.showLine).toFixed(0) -
suffixLength -
2;
const beginText = text.slice(0, inlineNum);
text = beginText + "..." + suffix;
}
return { text, isShowEllipsis };
};
第三步:css 的设置和注意项
相信大家都会多行省略,我就不赘述了,这里需要注意的是,因为要hover时展示所有文本,所以需要使用自定义元素配合**伪元素的attr**来实现,css 大家看完整代码就行,不然都是重复代码
::: tip 注意点
-
通过自定义
data和 伪元素的attr显示展示的文本attr() : 可以将自定义属性值作用于伪元素
<p data-foo="hello">world</p> &:before { content: attr(data-foo)}:::
第四步:在onMounted中拿到要展示的文本和是否需要省略
onMounted(() => {
// 拿到计算后的文本展示
const { text, isShowEllipsis } = autoEllipsisHandler(
autoEllipsisRef.value,
props.text
);
showText.value = text;
isEllipsis.value = isShowEllipsis;
});
第五步:可以优化的地方
- 在第二步,中间省略文本是写死的,并且后缀展示自己取的,这里其实可以通过
props传递进行处理; hover时文本太长,展示的过于宽,可以对hover时样式进行优化
第六步:全部代码
<template>
<div
class="auto-ellipsis hover-tip"
:data-text="props.text"
ref="autoEllipsisRef"
>
<span
:class="[isEllipsis ? 'overflow-hide-moreline' : '']"
:style="optionsStyle"
>
{{ showText }}
</span>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
// props
const props = defineProps({
// 显示的文本
text: {
type: String,
default: "",
},
// 展示几行
showLine: {
type: [Number, String],
default: 1,
},
// 是否展示后缀
isShowSuffix: {
type: Boolean,
default: false,
},
// 展示文本的样式
textStyle: {
type: Object,
default: () => {},
},
});
// 合并展示的style,默认的 fontSize 需要计算文本长度要用,默认一个,可以外面传进来
const optionsStyle = { fontSize: "12px", ...props.textStyle };
// ref
const autoEllipsisRef = ref(null);
// 展示的文本,后面多行展示需要更改,单项数据流,不修改props
const showText = ref(props.text);
// 如果设置的是插槽的话 通过 textRef.value.innerText 拿到文本
// 得到设置的 fontSize 大小,只需要数字,parseInt 可以对只有前面是数字的字符串进行处理 拿到数字
const fs = parseInt(optionsStyle.fontSize);
// 判断文本宽度是否 >= 容器宽度
const isEllipsis = ref(false);
// nextTick 这里每次页面变化都会执行到 理论上在onMounted里面就行,只有页面加载运行一次就OK
onMounted(() => {
// 拿到计算后的文本展示
const { text, isShowEllipsis } = autoEllipsisHandler(
autoEllipsisRef.value,
props.text
);
showText.value = text;
isEllipsis.value = isShowEllipsis;
});
/**
* @description: 自动计算展示省略的文本
* @param {HTMLElement} container 父级元素 dom/ref
* @param {String} text 展示的文本
* @return {Boolean} isShowEllipsis 文本宽度 是否超过 容器宽度 * 展示的行数
* @return {String} 展示的文本
* @Author: zhs
*/
const autoEllipsisHandler = (container, text) => {
let isShowEllipsis;
// 获取容器的宽度
const autoEllipsisWidth = container && container.clientWidth;
// 计算出文本宽度
const textWidth = getTextWidth(text.trim(), fs);
// 判断 文本宽度 和 容器宽度 * 展示的行数
// 为 false 什么都不做,直接展示,否则添加类名 'overflow-hide-moreline'
isShowEllipsis = textWidth >= autoEllipsisWidth * props.showLine;
// 如果为true 并且展示后缀的话 ==》需要修改展示的文本 (说明已经超过了)
// 如果为true 不展示后缀的话 ==》css里设置就是直接省略,不用处理
if (isShowEllipsis && props.isShowSuffix) {
// 后缀
const suffix = text.split(".").reverse()[0];
// 展示的后缀长度
const suffixLength = Math.round((suffix.length + 1) * 0.5);
// 一行能展示的文本数量 ==> (容器的宽度 / 字体大小 * 展示的行数)- 后缀长度 - 2
const inlineNum =
(Math.ceil(autoEllipsisWidth / fs) * props.showLine).toFixed(0) -
suffixLength -
2;
const beginText = text.slice(0, inlineNum);
text = beginText + "..." + suffix;
}
return { text, isShowEllipsis };
};
/**
* @description: 计算文字的宽度
* @param {*} text 展示的文本
* @param {*} fontSize 文本字体大小
* @return 文本的宽度
* @Author: zhs
*/
const getTextWidth = (text, fontSize = 10) => {
// 创建一个canva元素
const canvasDom = document.createElement("canvas");
const ctx = canvasDom.getContext("2d");
const dpr = window.devicePixelRatio || 1;
const fs = dpr * fontSize;
const fontStyle = optionsStyle.fontStyle || "sans-serif";
ctx.font = `${fs}px ${fontStyle}`; // 设置字体样式 默认字体大小是10px
// 通过canvas来获取文本的宽度
const textMetrics = ctx.measureText(text);
// 直接得到文本的宽度
const width = textMetrics.width;
// mdn 上说这样获取的是文本绝对宽度更准确,但是我们现在要获取他们计算中的最大值
const actualBoundingBoxWidth =
textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft;
// 返回它们两中最大的那个元素
return Math.max(width * dpr, actualBoundingBoxWidth * dpr);
};
</script>
<style lang="scss" scoped>
$color-info: #999;
.auto-ellipsis {
position: relative;
width: 100%; // 写 100% 外面可以直接读取父级元素设置的宽度
height: 100%;
}
.overflow-hide-moreline {
cursor: pointer;
font-size: 12px;
overflow: hidden;
/* 用来限制在一个块元素显示的文本的行数 */
-webkit-line-clamp: v-bind("props.showLine");
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
// hover 展示文本
.hover-tip:hover {
position: relative;
z-index: 99;
&:before {
content: attr(data-text); // 通过 attr 显示自定义文本
min-width: 230px;
white-space: nowrap;
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
font-size: 12px;
background-color: $color-info;
padding: 2px 4px;
border-radius: 6px;
box-shadow: inset;
}
&:after {
content: "";
position: absolute;
top: -3px;
left: 30%;
border: 6px solid transparent;
border-top: 6px solid $color-info;
}
}
</style>