Cross-browser multi-line text overflow with ellipsis appended within a fixed width and height

前端 未结 25 2882
栀梦
栀梦 2020-11-22 16:19

I made an image for this question to make it easier to understand.

Is it possible to create an ellipsis on a

with a fixed width and multiple
25条回答
  •  说谎
    说谎 (楼主)
    2020-11-22 16:42

    I wrote an angular component that solves the problem. It splits a given text into span elements. After rendering, it removes all overflowing elements and places the ellipsis right after the last visible element.

    Usage example:

    
    

    Stackblitz demo: https://stackblitz.com/edit/angular-wfdqtd

    The component:

    import {
      ChangeDetectionStrategy,
      ChangeDetectorRef,
      Component,
      ElementRef, HostListener,
      Input,
      OnChanges,
      ViewChild
    } from '@angular/core';
    
    @Component({
      changeDetection: ChangeDetectionStrategy.OnPush,
      selector: 'app-text-overflow-ellipsis',
      template: `
        
        
      `,
      styles: [`
        :host {
          display: block; 
          position: relative;
        }
        .initializing {
          opacity: 0;
        }
      `
      ]
    })
    
    export class TextOverflowEllipsisComponent implements OnChanges {
      @Input()
      text: string;
    
      showEllipsis: boolean;
      initializing: boolean;
    
      words: string[];
    
      @ViewChild('ellipsis')
      ellipsisElement: ElementRef;
    
      constructor(private element: ElementRef, private cdRef: ChangeDetectorRef) {}
    
      ngOnChanges(){
        this.init();
      }
    
      @HostListener('window:resize')
      init(){
        // add space after hyphens
        let text = this.text.replace(/-/g, '- ') ;
    
        this.words = text.split(' ');
        this.initializing = true;
        this.showEllipsis = false;
        this.cdRef.detectChanges();
    
        setTimeout(() => {
          this.initializing = false;
          let containerElement = this.element.nativeElement;
          let containerWidth = containerElement.clientWidth;
          let wordElements = (Array.from(containerElement.childNodes)).filter((element) =>
            element.getBoundingClientRect && element !== this.ellipsisElement.nativeElement
          );
          let lines = this.getLines(wordElements, containerWidth);
          let indexOfLastLine = lines.length - 1;
          let lineHeight = this.deductLineHeight(lines);
          if (!lineHeight) {
            return;
          }
          let indexOfLastVisibleLine = Math.floor(containerElement.clientHeight / lineHeight) - 1;
    
          if (indexOfLastVisibleLine < indexOfLastLine) {
    
            // remove overflowing lines
            for (let i = indexOfLastLine; i > indexOfLastVisibleLine; i--) {
              for (let j = 0; j < lines[i].length; j++) {
                this.words.splice(-1, 1);
              }
            }
    
            // make ellipsis fit into last visible line
            let lastVisibleLine = lines[indexOfLastVisibleLine];
            let indexOfLastWord = lastVisibleLine.length - 1;
            let lastVisibleLineWidth = lastVisibleLine.map(
              (element) => element.getBoundingClientRect().width
            ).reduce(
              (width, sum) => width + sum, 0
            );
            let ellipsisWidth = this.ellipsisElement.nativeElement.getBoundingClientRect().width;
            for (let i = indexOfLastWord; lastVisibleLineWidth + ellipsisWidth >= containerWidth; i--) {
              let wordWidth = lastVisibleLine[i].getBoundingClientRect().width;
              lastVisibleLineWidth -= wordWidth;
              this.words.splice(-1, 1);
            }
    
    
            this.showEllipsis = true;
          }
          this.cdRef.detectChanges();
    
          // delay is to prevent from font loading issues
        }, 1000);
    
      }
    
      deductLineHeight(lines: HTMLElement[][]): number {
        try {
          let rect0 = lines[0][0].getBoundingClientRect();
          let y0 = rect0['y'] || rect0['top'] || 0;
          let rect1 = lines[1][0].getBoundingClientRect();
          let y1 = rect1['y'] || rect1['top'] || 0;
          let lineHeight = y1 - y0;
          if (lineHeight > 0){
            return lineHeight;
          }
        } catch (e) {}
    
        return null;
      }
    
      getLines(nodes: HTMLElement[], clientWidth: number): HTMLElement[][] {
        let lines = [];
        let currentLine = [];
        let currentLineWidth = 0;
    
        nodes.forEach((node) => {
          if (!node.getBoundingClientRect){
            return;
          }
    
          let nodeWidth = node.getBoundingClientRect().width;
          if (currentLineWidth + nodeWidth > clientWidth){
            lines.push(currentLine);
            currentLine = [];
            currentLineWidth = 0;
          }
          currentLine.push(node);
          currentLineWidth += nodeWidth;
        });
        lines.push(currentLine);
    
        return lines;
      }
    
      endsWithHyphen(index: number): boolean {
        let length = this.words[index].length;
        return this.words[index][length - 1] === '-' && this.words[index + 1] && this.words[index + 1][0];
      }
    }
    

提交回复
热议问题