index.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import React, { Component } from 'react';
  2. import { Tooltip } from 'antd';
  3. import classNames from 'classnames';
  4. import styles from './index.less';
  5. /* eslint react/no-did-mount-set-state: 0 */
  6. /* eslint no-param-reassign: 0 */
  7. const isSupportLineClamp = (document.body.style.webkitLineClamp !== undefined);
  8. const EllipsisText = ({ text, length, tooltip, ...other }) => {
  9. if (typeof text !== 'string') {
  10. throw new Error('Ellipsis children must be string.');
  11. }
  12. if (text.length <= length || length < 0) {
  13. return <span {...other}>{text}</span>;
  14. }
  15. const tail = '...';
  16. let displayText;
  17. if (length - tail.length <= 0) {
  18. displayText = '';
  19. } else {
  20. displayText = text.slice(0, (length - tail.length));
  21. }
  22. if (tooltip) {
  23. return <Tooltip title={text}><span>{displayText}{tail}</span></Tooltip>;
  24. }
  25. return (
  26. <span {...other}>
  27. {displayText}{tail}
  28. </span>
  29. );
  30. };
  31. export default class Ellipsis extends Component {
  32. state = {
  33. text: '',
  34. targetCount: 0,
  35. }
  36. componentDidMount() {
  37. if (this.node) {
  38. this.computeLine();
  39. }
  40. }
  41. componentWillReceiveProps(nextProps) {
  42. if (this.props.lines !== nextProps.lines) {
  43. this.computeLine();
  44. }
  45. }
  46. computeLine = () => {
  47. const { lines } = this.props;
  48. if (lines && !isSupportLineClamp) {
  49. const text = this.shadowChildren.innerText;
  50. const lineHeight = parseInt(getComputedStyle(this.root).lineHeight, 10);
  51. const targetHeight = lines * lineHeight;
  52. this.content.style.height = `${targetHeight}px`;
  53. const totalHeight = this.shadowChildren.offsetHeight;
  54. const shadowNode = this.shadow.firstChild;
  55. if (totalHeight <= targetHeight) {
  56. this.setState({
  57. text,
  58. targetCount: text.length,
  59. });
  60. return;
  61. }
  62. // bisection
  63. const len = text.length;
  64. const mid = Math.floor(len / 2);
  65. const count = this.bisection(targetHeight, mid, 0, len, text, shadowNode);
  66. this.setState({
  67. text,
  68. targetCount: count,
  69. });
  70. }
  71. }
  72. bisection = (th, m, b, e, text, shadowNode) => {
  73. const suffix = '...';
  74. let mid = m;
  75. let end = e;
  76. let begin = b;
  77. shadowNode.innerHTML = text.substring(0, mid) + suffix;
  78. let sh = shadowNode.offsetHeight;
  79. if (sh <= th) {
  80. shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
  81. sh = shadowNode.offsetHeight;
  82. if (sh > th) {
  83. return mid;
  84. } else {
  85. begin = mid;
  86. mid = Math.floor((end - begin) / 2) + begin;
  87. return this.bisection(th, mid, begin, end, text, shadowNode);
  88. }
  89. } else {
  90. if (mid - 1 < 0) {
  91. return mid;
  92. }
  93. shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
  94. sh = shadowNode.offsetHeight;
  95. if (sh <= th) {
  96. return mid - 1;
  97. } else {
  98. end = mid;
  99. mid = Math.floor((end - begin) / 2) + begin;
  100. return this.bisection(th, mid, begin, end, text, shadowNode);
  101. }
  102. }
  103. }
  104. handleRoot = (n) => {
  105. this.root = n;
  106. }
  107. handleContent = (n) => {
  108. this.content = n;
  109. }
  110. handleNode = (n) => {
  111. this.node = n;
  112. }
  113. handleShadow = (n) => {
  114. this.shadow = n;
  115. }
  116. handleShadowChildren = (n) => {
  117. this.shadowChildren = n;
  118. }
  119. render() {
  120. const { text, targetCount } = this.state;
  121. const {
  122. children,
  123. lines,
  124. length,
  125. className,
  126. tooltip,
  127. ...restProps
  128. } = this.props;
  129. const cls = classNames(styles.ellipsis, className, {
  130. [styles.lines]: (lines && !isSupportLineClamp),
  131. [styles.lineClamp]: (lines && isSupportLineClamp),
  132. });
  133. if (!lines && !length) {
  134. return (<span className={cls} {...restProps}>{children}</span>);
  135. }
  136. // length
  137. if (!lines) {
  138. return (<EllipsisText className={cls} length={length} text={children || ''} tooltip={tooltip} {...restProps} />);
  139. }
  140. const id = `antd-pro-ellipsis-${`${new Date().getTime()}${Math.floor(Math.random() * 100)}`}`;
  141. // support document.body.style.webkitLineClamp
  142. if (isSupportLineClamp) {
  143. const style = `#${id}{-webkit-line-clamp:${lines};}`;
  144. return (
  145. <div id={id} className={cls} {...restProps}>
  146. <style>{style}</style>
  147. {
  148. tooltip ? (<Tooltip title={text}>{children}</Tooltip>) : children
  149. }
  150. </div>);
  151. }
  152. const childNode = (
  153. <span ref={this.handleNode}>
  154. {
  155. (targetCount > 0) && text.substring(0, targetCount)
  156. }
  157. {
  158. (targetCount > 0) && (targetCount < text.length) && '...'
  159. }
  160. </span>
  161. );
  162. return (
  163. <div
  164. {...restProps}
  165. ref={this.handleRoot}
  166. className={cls}
  167. >
  168. <div
  169. ref={this.handleContent}
  170. >
  171. {
  172. tooltip ? (
  173. <Tooltip title={text}>{childNode}</Tooltip>
  174. ) : childNode
  175. }
  176. <div className={styles.shadow} ref={this.handleShadowChildren}>{children}</div>
  177. <div className={styles.shadow} ref={this.handleShadow}><span>{text}</span></div>
  178. </div>
  179. </div>
  180. );
  181. }
  182. }