<template> <view class="t-wrap"> <!-- 虚拟view用于计算,计算完成则消失 --> <view class="t-txt-hide" :id="hid" v-if="!isCompute" :style="[computeStyle(0),{'text-align':oneRowTextAlign}]"> <text space="nbsp">{{testContent?testContent:content}}{{showSymbol?'...':''}}</text><text v-if="expandText && collapseText && showSymbol" class="t-button"> {{expandText}} </text><text></text> </view> <!-- 真实显示的内容 --> <view class="t-ellipsis" :id="id" :style="[!isCompute?computeStyle(1):computeStyle(2),{'text-align':oneRowTextAlign}]" @click="contentClick"> <text space="nbsp">{{(!isCompute || expand)?content:(actualContent+(showSymbol?'...':''))}}</text><text v-if="expandText && collapseText && showSymbol" class="t-button" @click.stop="changeCollapse" :style="{'color':actionFontColor}">{{!expand?expandText:collapseText}}</text><text></text> </view> <!-- 这里加入了自定义的动态骨架屏友好反馈 --> <view v-if="!isCompute && rows>0" class="t-skeleton"> <view class="skeletons" v-for="(item,index) in rows" :key="index"> <view class="empty"></view> </view> </view> </view> </template> <script> //为了兼容部分老机型,增加扰动因子参数 const factor = 5; export default { name: "KevyEllipsis", props: { /** * 文本唯一标识符,非必填 */ textId: { type: [String, Number], default: '' }, /** * 文本内容,默认'' */ content: { type: String, default: '' }, /** * 字体大小,单位rpx,默认28 */ fontSize: { type: [String, Number], default: 28 }, /** * 字体颜色,默认#666666 */ fontColor: { color: String, default: '#666666' }, /** * 收起操作的文案,默认'' */ collapseText: { type: String, default: '' }, /** * 展开操作的文案,默认'' */ expandText: { type: String, default: '' }, /** * 收起、展开操作文字颜色,默认'#007aff' */ actionFontColor: { color: String, default: '#007aff' }, /** * 展示行数,默认1 */ rows: { type: Number, default: 1 }, /** * 只有一行时文本对齐方式,支持left、right、justify */ oneRowTextAlign: { type: String, default: "justify" }, }, data() { return { //是否展开 expand: false, //是否已计算 isCompute: false, //内容高度 h: undefined, //内容宽度 w: undefined, //实际显示内容 actualContent: '', //高度探测内容 testContent: undefined, //是否显示省略号 showSymbol: false, //hid和id,唯一标识符 hid: 'hid' + Math.random().toString(36).substr(2), id: 'id' + Math.random().toString(36).substr(2), }; }, mounted() { this.$nextTick(() => { this.initEllipsis(); }) }, computed: { //动态计算组件样式 computeStyle() { return b => { let lines = this.rows > 0 ? this.rows : 1; let obj = {}; if (b == 1) { obj = { '-webkit-line-clamp': lines, 'display': '-webkit-box', 'text-overflow': 'ellipsis', 'overflow': 'hidden', '-webkit-box-orient': 'vertical' }; } else if (b == 2) { obj = { 'position': 'relative', 'left': '0rpx', ...obj }; } return { 'font-size': this.fontSize + 'rpx', 'color': this.fontColor, ...obj } } } }, watch: { content(newVal, oldVal) { this.expand=false; this.isCompute=false; this.h=undefined; this.w=undefined; this.actualContent=''; this.showSymbol=false; this.initEllipsis(); } }, methods: { //初始化 initEllipsis() { if (this.content?.length > 0) { this.$nextTick(() => { this.init(this, () => { this.compute(this); }) }) } }, //收起展开状态切换 changeCollapse() { this.expand = !this.expand; }, //文本点击事件 contentClick() { this.$emit('contentClick', this.textId); }, //组件参数初始化 init($this, callback, isali) { if (isali) { uni.createSelectorQuery().in().select('#' + $this.id).boundingClientRect(d => { $this.h = Number(d.height.toFixed(1)); $this.w = Number(d.width.toFixed(1)); if (callback) { callback() } }).exec(); } else { uni.createSelectorQuery().in($this).select('#' + $this.id).boundingClientRect(d => { $this.h = Number(d.height.toFixed(1)); $this.w = Number(d.width.toFixed(1)); if (callback) { callback() } }).exec(); } }, //动态计算组件内容 computeContent($this, isali, dr) { $this.$nextTick(() => { $this.getH($this, isali, (ch) => { if (ch - factor > $this.h) { if (dr === -1) { $this.testContent = $this.content.substring(0, $this.testContent.length - 1); $this.computeContent($this, isali, dr); } else { $this.actualContent = $this.content.substring(0, $this.testContent.length - 1); $this.isCompute = true; } } else { if (dr === -1) { $this.actualContent = $this.testContent; $this.isCompute = true; } else { $this.testContent = $this.content.substring(0, $this.testContent.length + 1); $this.computeContent($this, isali, dr); } } }) }); }, //计算工具方法 compute($this, isali) { let { rows, fontSize, content, h, w } = $this; $this.testContent = content; $this.$nextTick(() => { $this.getH($this, isali, (ch) => { if (ch - factor > h) { let lh = h / rows; let fn = Math.floor(w / $this.rpx2px(fontSize)); let sfn = fn * rows; let i = $this.fontNum(content, sfn * 2 - ($this.expandText ? $this.fontNum( $this.expandText) : 0) - 3); $this.showSymbol = true; $this.testContent = content.substring(0, i); $this.$nextTick(() => { $this.getH($this, isali, (ch1) => { if (ch1 - factor > h) { $this.testContent = content.substring(0, $this .testContent.length - 1); $this.computeContent($this, isali, -1); } else { $this.testContent = content.substring(0, $this .testContent.length + 1); $this.computeContent($this, isali, 1); } }); }); } else { $this.isCompute = true; $this.actualContent = content; } }) }); }, //动态计算字符数 fontNum(val, limit) { let c = 0; for (let i = 0; i < val.length; i++) { let a = val.charAt(i); if (a.match(/[^\x00-\xff]/ig) != null) { if (limit) { if (c + 2 > limit) { return i; } else { c += 2; } } else { c += 2; } } else { if (limit) { if (c + 1 > limit) { return i; } else { c += 1; } } else { c += 1; } } } if (!limit) { return c; } }, rpx2px(rpx) { return uni.getSystemInfoSync().windowWidth * Number(rpx) / 750; }, getH($, isali, callback) { if (isali) { uni.createSelectorQuery().in().select('#' + $.hid).fields({ size: true }, d => { if(d && d.height){ callback(Number(d.height.toFixed(1))); } }).exec(); } else { uni.createSelectorQuery().in($).select('#' + $.hid).fields({ size: true }, d => { if(d && d.height){ callback(Number(d.height.toFixed(1))); } }).exec(); } } } } </script> <style lang="scss" scoped> .t-wrap { width: 100%; box-sizing: border-box; position: relative; } .t-txt-hide { box-sizing: border-box; word-break: break-all; position: absolute; top: 999999px; left: 999999px; z-index: -1000; top: 0rpx; width: 100%; margin: 0rpx; text-align: justify; white-space: pre-line; line-height: 1.5 !important; .t-button { float: right; clear: both; } } .t-ellipsis { text-align: justify; box-sizing: border-box; width: 100%; word-break: break-all; position: relative; left: 99999px; white-space: pre-line; line-height: 1.5 !important; .t-button { float: right; clear: both; font-weight: 500; } } .t-skeleton { width: 100%; height: 100%; box-sizing: border-box; position: absolute; top: 0rpx; left: 0rpx; } .skeletons:first-child { margin-top: 0rpx !important; } .skeletons { position: relative; display: block; overflow: hidden; width: 100%; height: 32rpx; margin-top: 12rpx; background-color: rgba(0, 0, 0, 0.06); box-sizing: border-box; } .skeletons .empty { display: block; position: absolute; width: 100%; height: 100%; -webkit-transform: translateX(-100%); transform: translateX(-100%); background: linear-gradient(90deg, transparent, rgba(216, 216, 216, 0.753), transparent); -webkit-animation: loading .8s infinite; animation: loading .8s infinite; } @keyframes loading { 100% { -webkit-transform: translateX(100%); transform: translateX(100%); } } </style>