1. 实现效果
组件内文字样式取决与外侧定义
组件大小发生变化时,文本仍可以省略到指定行数
文本不超过时, 无展开,收起按钮
传入文本发生改变后, 组件展示新的文本
2. 代码
文件名TextEllipsis.vue
<template>
<div ref="compRef" class="wq-text-ellipsis">
<div v-if="!isExpanded" class="ellipsis-content">
<span>{{ truncatedText }}</span>
<slot v-if="textOver" name="ellipsis">
<span>{{ ellipsis }}</span>
</slot>
<span v-if="textOver" class="show-more" @click="toggleExpand">
<slot name="more">
{{ moreText }}
</slot>
</span>
</div>
<div v-else class="full-content">
<span>{{ fullText }}</span>
<span class="show-less" @click="toggleExpand">
<slot name="less">
{{ lessText }}
</slot>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch, nextTick } from 'vue';
import { useResizeObserver } from '@vueuse/core';
import { debounce } from 'lodash';
type Prop = {
text?: string;
maxLines?: number;
// 省略显示
ellipsis?: string;
moreText?: string;
lessText?: string;
};
// 定义 props
const props = withDefaults(defineProps<Prop>(), {
maxLines: 3,
text: '',
ellipsis: '...',
moreText: '展开',
lessText: '收起',
});
const compRef = ref<HTMLElement>();
// 定义是否展开的状态
const isExpanded = ref(false);
// 定义展开和收起的方法
const toggleExpand = () => {
isExpanded.value = !isExpanded.value;
};
// 计算截断后的文本
const truncatedText = ref(props.text);
// 定义完整的文本
const fullText = computed<string>(() => props.text);
// 判断是否超过限制行数
const textOver = computed(() => truncatedText.value !== fullText.value);
watch(
fullText,
(newValue) => {
truncatedText.value = fullText.value;
isExpanded.value = false;
},
{
immediate: true,
}
);
// 判断是否超过限制行数
const isOver = () => {
const { height, lineHeight } = getComputedStyle(compRef.value as Element);
return parseFloat(height) > props.maxLines * parseFloat(lineHeight);
};
// 对字符串进行二分, 直到 找到一个合适的截断位置
const refresh = async () => {
// if (!isOver()) return;
let left = 0;
let right = props.text.length;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
truncatedText.value = props.text.slice(0, mid);
await nextTick(() => {
if (isOver()) {
right = mid - 1;
} else {
left = mid + 1;
}
});
}
truncatedText.value = props.text.slice(0, left - 1);
};
const init = () => {
if (!isExpanded.value) refresh();
};
// 对init 进行防抖
const debounceInit = debounce(init, 50);
useResizeObserver(compRef, () => {
debounceInit();
});
</script>
<style lang="scss" scoped>
.wq-text-ellipsis {
position: relative;
white-space: normal;
word-break: break-all;
.show-more,
.show-less {
//float: right;
cursor: pointer;
color: lightblue;
margin-left: 2px;
}
.ellipsis-content {
position: relative;
}
}
</style>
这里使用到了两个外部库, 分别用于监听, 和防抖, 若没哟引入这两个库可自行封装
debounce
函数封装: js之防抖函数
useResizeObserver
hook封装: 这个参考第二部分的文件ResizeObserverStore.ts这里 <-可以直接跳转到指定位置
3. 使用说明
代码样例
<div class="mw6">
<text-ellipsis :max-lines="3" :text="textContent"></text-ellipsis>
</div>
prop参数
name | 类型 | 说明 | 默认值 |
---|---|---|---|
text | string | 内容 | “” |
maxLines | number | 最大行数 | 3 |
ellipsis | string | 省略时显示 | “…” |
moreText | string | 展示按钮文字 | “展示” |
lessText | string | 收起按钮文字 | “收起” |
slot
name | 说明 |
---|---|
ellipsis | 省略时尾部元素 |
more | 省略时按钮元素 |
less | 展开时按钮元素 |
4. 原理说明
主要原理利用二分法, 对字符串进行恰当的截取
// 对字符串进行二分, 直到 找到一个合适的截断位置
const refresh = async () => {
let left = 0;
let right = props.text.length;
// left > right 时为截取合理位置
while (left <= right) {
const mid = Math.floor((left + right) / 2);
truncatedText.value = props.text.slice(0, mid);
// 下一次刷新后判断是否截取合理
await nextTick(() => {
// isOver()函数用来判断行数是否合理
if (isOver()) {
// 实际行数超过理想行数就切掉
right = mid - 1;
} else {
// 行数符合理想行数,就得寸进尺, 向更合理出发
left = mid + 1;
}
});
}
// 获取到合理位置后进行最后一次截取
truncatedText.value = props.text.slice(0, left - 1);
};
// 判断是否超过限制行数
const isOver = () => {
const { height, lineHeight } = getComputedStyle(compRef.value as Element);
return parseFloat(height) > props.maxLines * parseFloat(lineHeight);
};
本组件用到了
useResizeObserver
, 主要作用是对组件元素进行监听, 当组件大小发生变化时会重新触发字符串截取操作, 并使用节流防止截取操作频繁触发
最后
如果在使用过程中出现了问题, 或者组件有没靠略到的地方, 欢迎评论或留言