如何优雅的渲染下拉列表

78 阅读2分钟

背景

来自ui设计的一套文字与标签组合规则。

  1. 容器宽度一定,优先展示文字部分,缩略展示标签
  2. 当文字本身长度超出最大宽度,hover 时使用tooltip展示全部文字及标签

如图:(手动画图,暂时忽略间距)

image.png

思路1:

  • totalWidth: 获取item的总宽度,减去左侧icon的宽度和左间距
  • 根据获取的name字符去计算宽度,计算方法
  • 根据获取的label字符去计算宽度
  • 所有的宽度计算好再传入一个壳子组件,壳子组件里的主要就是渲染+拿到宽度根据组合规则书写逻辑

思路2:

  • totalWidth: 获取item的总宽度,减去左侧icon的宽度和左间距
  • 在组件内部定义两个ref,根据ref去获取元素的宽度
  • nameWidth:nameRef.current?.offsetWidth labelWidth:labelRef.current?.offsetWidth
  • 直接在壳子内部根据组合规则书写逻辑

组合规则

  • nameRef的宽度+label的宽度+margin<limitWith 全部展示
  • nameRef的宽度+label的宽度+margin>limitWith 先展示name,再看limitWith-nameRef的宽度-margin<label的宽度 label多的部分...
  • nameRef的宽度>limitWith 展示tooltip 不展示label

根据字符计算宽度方法

const calculateCharacterLength = (words: string | number) => {
  let nums = 0;
  for (let i = 0, length = words?.toString().length; i < length; i++) {
    let leg = words.toString().charCodeAt(i);
    if (leg > 255) {
      nums += 2;
    } else {
      nums += 1;
    }
  }
  return nums;
};

const calculateWordsFontSize = (nums: number, fontSize: number) => {
  return Math.ceil(nums / 2) * fontSize;
};

const calculateWordsLength = (words: string | number, fontSize: number) => {
  const nums = calculateCharacterLength(words);

  return calculateWordsFontSize(nums, fontSize);
};

代码实现1


type Props = {
  name: ReactNode; 
  label: string;
  limitWidth: number;
  marginWidth?: number;
  nameWidth?: number;
  labelWidth?: number;
  classes?: Record<string, any>; //自定义样式
};

const ItemWrapWithCalculate: FC<Props> = props => {
  const {
    name,
    label,
    limitWidth = 0,
    marginWidth = 0,
    nameWidth=0,
    labelWidth=0,
    classes: classesCustom = {}
  } = props;

  //label宽度不够的情况,超出部分...展示
  const showFullLabel = useMemo(() => {
    return limitWidth - nameWidth - marginWidth < labelWidth;
  }, [limitWidth, nameWidth, marginWidth, labelWidth]);

  const showLabel = useMemo(() => {
    //全部展示
    if (nameWidth + labelWidth + marginWidth < limitWidth) {
      return limitWidth - (nameWidth + marginWidth);
    }
    if (nameWidth > limitWidth) {
      return 0;
    }
    //将所剩宽度给label
    if (showFullLabel) {
      return limitWidth - nameWidth - marginWidth;
    }
    return 0;
  }, [nameWidth, labelWidth, showFullLabel]);


  //剩余的宽度与lable判断   剩余多就全部按照label的现实
  return (
    <>
      <span
        style={{ width: `${nameWidth > limitWidth ? limitWidth : nameWidth}px` }}
        className={clsx(styles.namewrap, showLabel < 36 && styles.fullshow)}
      >
        {name}
      </span>
      {showLabel !== 0 && showLabel > 16 && (
        <span
          style={{ width: `${labelWidth < showLabel ? labelWidth : showLabel}px` }}
          className={clsx(styles.label, showLabel && styles.fullshow, classesCustom.customlabel)}
        >
          {label}
        </span>
      )}
    </>
  );
};

样式

.center {
  display: flex;
  align-items: center;
}

.root {
  .center;
  & span {
    white-space: nowrap;
  }
}

.fullshow {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.label {
  height: 20px;
  line-height: 20px;
  padding: 0 5px;
  color: #6c798c;
  font-size: 12px;
  background: #ffffff;
  border: 1px solid #e9ecf0;
  border-radius: 2px 2px 2px 2px;
  text-align: center;
}

结论:

调用时,直接用这个组件包要渲染的列表item。展示上比较可控,有误差可以在外部计算的时候略加一点误差值,达到预期效果。

代码实现2

纯Css 更简单,性能更好


 <div className={classes.itemwrap}>
    {option.desc}
   <span>{option.label}</span>
 </div>
 
 //css
 
 .itemwrap {
  max-width:240px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  & > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-left: 4px;
  }
}

结论

最外层需要一个最大宽度的限制,然后内层应用超出的css,span也应用超出css,这样最外层容器宽度一定,优先展示文字部分,缩略展示标签,如果都够,就都会正常展示。