小程序长文本展开折叠
0.缘起
需要做长文本展开折叠功能
1.思路
看了网上各种说法,1 种是玩转 CSS,比如方案 1 就是在长文本前添加一个占位元素,后添加展开按钮,实测 ios 上有一处多出的白色方框。方案 2,是在微信小程序社区发现的,缺点是展开位于下面一行,优点是比较直白,就是比较原文和裁剪过的元素高度,如果高度变低则代表需要显示展开按钮。
2.方案
方案 1 CSS
这个方案参考了掘金文章
index.tsx
import { Image, View } from "@tarojs/components";
import styles from "./index.module.less";
import { useState } from "react";
import clsx from "clsx";
import { useJudgeIndentify } from "@/hooks/useJudgeIdentify";
import useTheme from "@/hooks/useTheme";
import {
SERVICE_CLOSE,
SERVICE_CLOSE_HIRER,
SERVICE_OPEN_HIRER,
SERVICE_UP,
} from "@/constant/imgUrl";
const defaultId = `${+new Date()}${Math.ceil(Math.random() * 10000)}`;
interface TextExpendsionType {
text: string;
id?: string;
}
const TextExpendsion = ({ text, id = defaultId }: TextExpendsionType) => {
const [isExpand, setIsExpand] = useState < boolean > false;
const { isVendor } = useJudgeIndentify();
const { color } = useTheme();
const changeShowMore = (e) => {
e.preventDefault();
e.stopPropagation();
setIsExpand(!isExpand);
};
const rectangleUrl = () => {
if (isExpand) {
if (isVendor) {
return SERVICE_CLOSE;
}
return SERVICE_CLOSE_HIRER;
} else {
if (isVendor) {
return SERVICE_UP;
}
return SERVICE_OPEN_HIRER;
}
};
return (
<View className={styles["text-expandsion"]} id={id}>
<View
className={clsx(
styles["text-expandsion__text"],
isExpand ? styles["text-expandsion__text--expand"] : ""
)}
>
<View
className={styles[`text-expandsion__button`]}
style={{ color }}
onClick={changeShowMore}
>
{isExpand ? "收起详情" : "展开详情"}
<Image className={styles["icon"]} src={rectangleUrl()} />
</View>
{text}
</View>
</View>
);
};
export default TextExpendsion;
index.module.less
值得注意的是,这里必须要给::before加上边框
.text-expandsion {
display: flex;
&__text {
position: relative;
font-size: 30rpx;
color: #31353d;
overflow: hidden;
line-height: 1.5;
max-height: 4.5em;
text-align: justify;
&::before {
content: '';
float: right;
height: calc(100% - 20px);
border: 1px solid white;
}
&::after {
content: '';
width: 100vw;
height: 100vh;
position: absolute;
box-shadow: inset calc(120rpx - 100vw) calc(1.45em - 100vh) 0 0 white;
margin-left: -120rpx;
}
}
&__button {
position: relative;
float: right;
clear: both;
height: 14px;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 12px;
text-align: left;
font-style: normal;
text-transform: none;
display: flex;
align-items: center;
&::before {
content: '...';
color: #31353d;
margin-right: 8rpx;
}
}
}
.text-expandsion__text--expand {
max-height: none;
&::after {
visibility: hidden;
}
.text-expandsion__button::before {
visibility: hidden;
}
.text-expandsion__button::after {
transform: rotate(180deg);
}
}
.icon {
width: 20px;
height: 20px;
}
方案 2 盒子高度
这个方案参考微信小程序社区
index.tsx
import { Image, View } from "@tarojs/components";
import styles from "./index.module.less";
import { useEffect, useState } from "react";
import clsx from "clsx";
import { useJudgeIndentify } from "@/hooks/useJudgeIdentify";
import useTheme from "@/hooks/useTheme";
import {
SERVICE_CLOSE,
SERVICE_CLOSE_HIRER,
SERVICE_OPEN_HIRER,
SERVICE_UP,
} from "@/constant/imgUrl";
import Taro, { NodesRef } from "@tarojs/taro";
const defaultId = `${+new Date()}${Math.ceil(Math.random() * 10000)}`;
interface TextExpendsionType {
text: string;
id?: string;
}
const TextExpendsion = ({ text, id = defaultId }: TextExpendsionType) => {
const [isExpand, setIsExpand] = useState < boolean > false;
const { isVendor } = useJudgeIndentify();
const { color } = useTheme();
const [needExpand, setNeedExpand] = useState(false);
const changeShowMore = (e) => {
e.preventDefault();
e.stopPropagation();
setIsExpand(!isExpand);
};
const pureId = `pure-text-${id}`;
const needExpandId = `need-expand-text-${id}`;
useEffect(() => {
Taro.nextTick(() => {
let pureBoxHeight;
let needExpandBoxHeight;
const q = Taro.createSelectorQuery();
q.select(`#${pureId}`)
.boundingClientRect(
(res: NodesRef.BoundingClientRectCallbackResult) => {
pureBoxHeight = res.height;
}
)
.exec();
q.select(`#${needExpandId}`)
.boundingClientRect(
(res: NodesRef.BoundingClientRectCallbackResult) => {
needExpandBoxHeight = res.height;
if (needExpandBoxHeight < pureBoxHeight) {
setNeedExpand(true);
}
}
)
.exec();
});
}, []);
const rectangleUrl = () => {
if (isExpand) {
if (isVendor) {
return SERVICE_CLOSE;
}
return SERVICE_CLOSE_HIRER;
} else {
if (isVendor) {
return SERVICE_UP;
}
return SERVICE_OPEN_HIRER;
}
};
return (
<View className={styles["text-expandsion"]} id={id}>
<View className={clsx(styles[`pure-text`])} id={pureId}>
{text}
</View>
{!isExpand ? (
<View
className={clsx(styles["text-expandsion__text"])}
id={needExpandId}
>
{text}
</View>
) : (
<View
className={clsx(styles["text-expandsion__text--expand"])}
id={needExpandId}
>
{text}
</View>
)}
{needExpand ? (
<View
className={styles[`text-expandsion__button`]}
style={{ color }}
onClick={changeShowMore}
>
{isExpand ? "收起详情" : "展开详情"}
<Image className={styles["icon"]} src={rectangleUrl()} />
</View>
) : null}
</View>
);
};
export default TextExpendsion;
index.module.less
.text-expandsion {
display: flex;
flex-direction: column;
.pure-text {
position: relative;
font-size: 28rpx;
color: #31353d;
line-height: 1.5;
overflow: hidden;
position: fixed;
top: 100vh;
left: -100vw;
}
&__text {
position: relative;
font-size: 28rpx;
color: #31353d;
overflow: hidden;
line-height: 1.5;
max-height: 4.5em;
text-align: justify;
}
&__button {
position: relative;
float: right;
clear: both;
height: 20px;
font-family: PingFang SC, PingFang SC;
font-weight: 500;
font-size: 12px;
text-align: left;
font-style: normal;
text-transform: none;
display: flex;
}
}
.text-expandsion__text--expand {
position: relative;
font-size: 28rpx;
color: #31353d;
}
.icon {
width: 20px;
height: 20px;
}
3.评价
方案 1 很炫,但兼容性非常难绷。方案 2 中有个巧思,隐藏正常文字,直接挪到不可视区域,但元素还在,这个操作挺有意思
发现让方案 2 显示省略号,如果刚好是最后一个字会被遮挡。这里直接换行。
人生到处知何似,应似飞鸿踏雪泥。