util.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import ReactDOM from 'react-dom';
  2. import React from 'react';
  3. let ellipsisContainer: HTMLElement;
  4. function pxToNumber(value: string) {
  5. if (!value) {
  6. return 0;
  7. }
  8. const match = value.match(/^\d*(\.\d*)?/);
  9. return match ? Number(match[0]) : 0;
  10. }
  11. function styleToString(style: CSSStyleDeclaration): string {
  12. // There are some different behavior between Firefox & Chrome.
  13. // We have to handle this ourself.
  14. const styleNames = Array.prototype.slice.apply(style);
  15. return styleNames.map((name: string) => `${name}: ${style.getPropertyValue(name)};`).join('');
  16. }
  17. const getRenderText = (
  18. originEle: HTMLElement,
  19. rows: number,
  20. content = '',
  21. fixedContent: any[],
  22. ellipsisStr: string,
  23. suffix: string,
  24. ellipsisPos: string
  25. // eslint-disable-next-line max-params
  26. ) => {
  27. if (!ellipsisContainer) {
  28. ellipsisContainer = document.createElement('div');
  29. ellipsisContainer.setAttribute('aria-hidden', 'true');
  30. document.body.appendChild(ellipsisContainer);
  31. }
  32. // Get origin style
  33. const originStyle = window.getComputedStyle(originEle);
  34. const originCSS = styleToString(originStyle);
  35. const lineHeight = pxToNumber(originStyle.lineHeight);
  36. const maxHeight = Math.round(
  37. lineHeight * (rows + 1) +
  38. pxToNumber(originStyle.paddingTop) +
  39. pxToNumber(originStyle.paddingBottom)
  40. );
  41. // Set shadow
  42. ellipsisContainer.setAttribute('style', originCSS);
  43. ellipsisContainer.style.position = 'fixed';
  44. ellipsisContainer.style.left = '0';
  45. ellipsisContainer.style.height = 'auto';
  46. ellipsisContainer.style.top = '-999999px';
  47. ellipsisContainer.style.zIndex = '-1000';
  48. // clean up css overflow
  49. ellipsisContainer.style.textOverflow = 'clip';
  50. ellipsisContainer.style.whiteSpace = 'normal';
  51. ellipsisContainer.style.webkitLineClamp = 'none';
  52. // Render fake container
  53. ReactDOM.render(
  54. <></>,
  55. ellipsisContainer
  56. );
  57. // Check if ellipsis in measure div is height enough for content
  58. function inRange() {
  59. // console.log('inrange?', ellipsisContainer.scrollHeight, ellipsisContainer.scrollHeight < maxHeight)
  60. return ellipsisContainer.scrollHeight < maxHeight;
  61. }
  62. // ========================= Find match ellipsis content =========================
  63. // Create origin content holder
  64. const ellipsisContentHolder = document.createElement('span');
  65. const ellipsisTextNode = document.createTextNode(suffix);
  66. ellipsisContentHolder.appendChild(ellipsisTextNode);
  67. ellipsisContainer.appendChild(ellipsisContentHolder);
  68. fixedContent.map((node: Node) => node && ellipsisContainer.appendChild(node.cloneNode(true)));
  69. // Append before fixed nodes
  70. function appendChildNode(node: ChildNode) {
  71. ellipsisContentHolder.insertBefore(node, ellipsisTextNode);
  72. }
  73. function getCurrentText(text: string, pos: number) {
  74. const end = text.length;
  75. if (!pos) {
  76. return ellipsisStr;
  77. }
  78. if (ellipsisPos === 'end' || pos > end - pos) {
  79. return text.slice(0, pos) + ellipsisStr;
  80. }
  81. return text.slice(0, pos) + ellipsisStr + text.slice(end - pos, end);
  82. }
  83. // Get maximum text
  84. function measureText(
  85. textNode: Text,
  86. fullText: string,
  87. startLoc = 0,
  88. endLoc = fullText.length,
  89. lastSuccessLoc = 0
  90. ): string {
  91. const midLoc = Math.floor((startLoc + endLoc) / 2);
  92. const currentText = getCurrentText(fullText, midLoc);
  93. textNode.textContent = currentText;
  94. // console.log('calculating....', currentText);
  95. if (startLoc >= endLoc - 1 && endLoc > 0) { // Loop when step is small
  96. for (let step = endLoc; step >= startLoc; step -= 1) {
  97. const currentStepText = getCurrentText(fullText, step);
  98. textNode.textContent = currentStepText;
  99. if (inRange() || !currentStepText) {
  100. return step === fullText.length ? fullText : currentStepText;
  101. }
  102. }
  103. } else if (endLoc === 0) {
  104. return ellipsisStr;
  105. }
  106. if (inRange()) {
  107. return measureText(textNode, fullText, midLoc, endLoc, midLoc);
  108. }
  109. return measureText(textNode, fullText, startLoc, midLoc, lastSuccessLoc);
  110. }
  111. const textNode = document.createTextNode(content);
  112. appendChildNode(textNode);
  113. const resText = measureText(textNode, content);
  114. ellipsisContainer.innerHTML = '';
  115. return resText;
  116. };
  117. export default getRenderText;