Unverified Commit ffea9d99 authored by niko's avatar niko Committed by GitHub

Add Ellipsis Component (#135)

* add Ellipsis

* remove title of span

* update scaffold example

* remove dump code

* maxHeight -> lines

* update Ellipsis for all case

* remove dump code

* use bisection to imporve performance
parent 6306c2f5
---
order: 2
title: 按照高度省略的覆盖后缀模式
---
通过设置 `lines` 属性指定最大行数,如果超过这个行数的文本会自动截取。通过设置 `cover` 属性设置后缀的覆盖模式,在这种模式下可以在 `children` 中使用 `ReactNode`
但是因为是覆盖形式的后缀,可能需要通过 `suffixOffset` 以及 `suffixColor` 来设置 `...` 的样式以修正。
````jsx
import Ellipsis from 'ant-design-pro/lib/Ellipsis';
const article = <p>There were injuries alleged in three <a href="#cover">cases in 2015</a>, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.</p>;
ReactDOM.render(
<div style={{ width: 200 }}>
<Ellipsis lines={3} cover>{article}</Ellipsis>
<h4 style={{ marginTop: 24 }}>Using SuffixOffset</h4>
<Ellipsis lines={3} cover suffixOffset={4}>{article}</Ellipsis>
</div>
, mountNode);
````
---
order: 1
title: 按照高度省略
---
通过设置 `lines` 属性指定最大行数,如果超过这个行数的文本会自动截取。但是在这种模式下所有 `children` 将会被转换成纯文本。
并且注意在这种模式下,外容器需要有指定的宽度(或设置自身宽度)。
````jsx
import Ellipsis from 'ant-design-pro/lib/Ellipsis';
const article = <p>There were injuries alleged in three <a href="#cover">cases in 2015</a>, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.</p>;
ReactDOM.render(
<div style={{ width: 200 }}>
<Ellipsis lines={3}>{article}</Ellipsis>
</div>
, mountNode);
````
---
order: 0
title: 按照字符数省略
---
通过设置 `length` 属性指定文本最长长度,如果超过这个长度会自动截取。
````jsx
import Ellipsis from 'ant-design-pro/lib/Ellipsis';
const article = 'There were injuries alleged in three cases in 2015, and a fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.';
ReactDOM.render(
<div>
<Ellipsis length={100}>{article}</Ellipsis>
<h4 style={{ marginTop: 24 }}>Show Tooltip</h4>
<Ellipsis length={100} tooltip>{article}</Ellipsis>
</div>
, mountNode);
````
import React, { PureComponent } from 'react';
import { Tooltip } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
/* eslint react/no-did-mount-set-state: 0 */
/* eslint no-param-reassign: 0 */
const EllipsisText = ({ text, length, tooltip, ...other }) => {
if (typeof text !== 'string') {
throw new Error('Ellipsis children must be string.');
}
if (text.length <= length || length < 0) {
return <span {...other}>{text}</span>;
}
const tail = '...';
let displayText;
if (length - tail.length <= 0) {
displayText = '';
} else {
displayText = text.slice(0, (length - tail.length));
}
if (tooltip) {
return <span>{displayText}<Tooltip title={text}>{tail}</Tooltip></span>;
}
return (
<span {...other}>
{displayText}{tail}
</span>
);
};
export default class Ellipsis extends PureComponent {
state = {
lineHeight: 0,
text: '',
targetCount: 0,
}
componentDidMount() {
const { lines, cover } = this.props;
if (this.node) {
if (lines && cover) {
this.setState({
lineHeight: parseInt(window.getComputedStyle(this.node).lineHeight, 10),
});
}
this.computeLine();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.lines !== nextProps.lines || this.props.cover !== nextProps.cover) {
this.setState({
lineHeight: parseInt(window.getComputedStyle(this.node).lineHeight, 10),
});
this.computeLine();
}
}
computeLine = () => {
const { lines, cover } = this.props;
if (lines && !cover) {
const fontSize = parseInt(window.getComputedStyle(this.node).fontSize, 10) || 14;
const text = this.shadowChildren.innerText;
const targetWidth = (this.node.offsetWidth || this.node.parentNode.offsetWidth) * lines;
const shadowNode = this.shadow.firstChild;
// bisection
const tw = (targetWidth - (lines * (fontSize / 2)) - fontSize);
const len = text.length;
const mid = Math.floor(len / 2);
const count = this.bisection(tw, mid, 0, len, text, shadowNode);
this.setState({
text,
targetCount: count,
});
}
}
bisection = (tw, m, b, e, text, shadowNode) => {
let mid = m;
let end = e;
let begin = b;
shadowNode.innerHTML = text.substring(0, mid);
let sw = shadowNode.offsetWidth;
if (sw < tw) {
shadowNode.innerHTML = text.substring(0, mid + 1);
sw = shadowNode.offsetWidth;
if (sw >= tw) {
return mid;
} else {
begin = mid;
mid = Math.floor((end - begin) / 2) + begin;
return this.bisection(tw, mid, begin, end, text, shadowNode);
}
} else {
if (mid - 1 < 0) {
return mid;
}
shadowNode.innerHTML = text.substring(0, mid - 1);
sw = shadowNode.offsetWidth;
if (sw <= tw) {
return mid;
} else {
end = mid;
mid = Math.floor((end - begin) / 2) + begin;
return this.bisection(tw, mid, begin, end, text, shadowNode);
}
}
}
handleRef = (n) => {
this.node = n;
}
handleShadow = (n) => {
this.shadow = n;
}
handleShadowChildren = (n) => {
this.shadowChildren = n;
}
render() {
const { text, targetCount, lineHeight } = this.state;
const {
children,
lines,
length,
cover = false,
suffixColor = '#fff',
suffixOffset = 0,
className,
tooltip,
...restProps
} = this.props;
const cls = classNames(styles.ellipsis, className, {
[styles.lines]: (lines && !cover),
[styles.linesCover]: (lines && cover),
});
if (!lines && !length) {
return (<span className={cls} {...restProps}>{children}</span>);
}
// length
if (!lines) {
return (<EllipsisText className={cls} length={length} text={children || ''} tooltip={tooltip} {...restProps} />);
}
// lines cover
if (cover) {
const id = `antd-pro-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`;
const style = `#${id}:before{background-color:${suffixColor};padding-left:${suffixOffset}px;}`;
return (
<div
{...restProps}
id={id}
ref={this.handleRef}
className={cls}
style={{
...restProps.style,
maxHeight: `${lines * lineHeight}px`,
}}
>
<style>{style}</style>
{children}
</div>
);
}
// lines no cover
const suffix = tooltip ? <Tooltip title={text}>...</Tooltip> : '...';
return (
<div
{...restProps}
ref={this.handleRef}
className={cls}
>
{
(targetCount > 0) && text.substring(0, targetCount)
}
{
(targetCount > 0) && (targetCount < text.length) && suffix
}
<div className={styles.shadow} ref={this.handleShadowChildren}>{children}</div>
<div className={styles.shadow} ref={this.handleShadow}><span>{text}</span></div>
</div>
);
}
}
.textOverflowMulti(@line: 3, @bg: #fff) {
overflow: hidden;
position: relative;
line-height: 1.5em;
max-height: @line * 1.5em;
text-align: justify;
margin-right: -1em;
padding-right: 1em;
&:before {
background: @bg;
box-shadow: 2px 0 2px 1px rgba(255, 255, 255, 0.2);
content: '...';
padding-left: 0;
position: absolute;
right: 14px;
bottom: 0;
}
&:after {
background: white;
content: '';
margin-top: 0.2em;
position: absolute;
right: 14px;
width: 1em;
height: 1em;
}
}
.ellipsis {
display: inline-block;
word-break: break-all;
}
.lines {
position: relative;
.shadow {
color: transparent;
opacity: 0;
display: block;
position: absolute;
top: 0;
left: 0;
width: 9999px;
z-index: -999;
}
}
.linesCover {
.textOverflowMulti();
display: block;
}
---
title:
en-US: Ellipsis
zh-CN: Ellipsis
subtitle: 文本自动省略号
cols: 1
order: 10
---
文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
## API
参数 | 说明 | 类型 | 默认值
----|------|-----|------
tooltip | 移动到 `...` 展示完整内容的提示,在长度截取和覆盖模式的行数截取下可用 | boolean | -
length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -
lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1`
cover | 在按照行数截取下开启覆盖模式,这种模式 `...` 是使用样式覆盖到文本上的,所以文本内容可以是 `ReactNode` | boolean | false
suffixColor | 在覆盖模式下后缀符号 `...` 的背景颜色 | string | `#fff`
suffixOffset | 在覆盖下后缀符号 `...` 位置偏移量,用于更精细的调整截取位置 | number | `0`
......@@ -3,6 +3,7 @@ import { connect } from 'dva';
import { Card, Button, Icon, List } from 'antd';
import PageHeaderLayout from '../../layouts/PageHeaderLayout';
import Ellipsis from '../../components/Ellipsis';
import styles from './CardList.less';
......@@ -67,9 +68,7 @@ export default class CardList extends PureComponent {
avatar={<img alt="" className={styles.cardAvatar} src={item.avatar} />}
title={<a href="#">{item.title}</a>}
description={(
<p className={styles.cardDescription}>
<span>{item.description}</span>
</p>
<Ellipsis lines={3} cover suffixOffset={2}>{item.description}</Ellipsis>
)}
/>
</Card>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment