小程序长文本展开折叠

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 显示省略号,如果刚好是最后一个字会被遮挡。这里直接换行。

posted @ 2024-11-01 20:10  乐盘游  阅读(29)  评论(0编辑  收藏  举报