草庐IT

javascript - React Text Clamp 的性能改进?

coder 2024-05-12 原文

我正在尝试制作一个可重用的 React text-clamp 组件。用户传入要呈现的行数和他们想要显示的文本,然后组件呈现他们的文本,在指定的行数处将其 chop 并在末尾插入省略号 (...)。

我计算在哪里 chop 文本和插入省略号的方法是一次添加一个单词,直到 clientHeight文本大于 clientHeight容器 div。

虽然它有效,但我在 chrome 开发工具中看到以下内容:

[Violation] Forced reflow while executing JavaScript took 179ms .

这可能是因为阅读 clientHeight forces reflow .

这是我的代码:

class TextClamp extends React.PureComponent {

    constructor(props) {
        super(props);
        this.renderText = this.renderText.bind(this);
        this.state = {
            words: this.props.textToDisplay.split(' '),
        };
    }

    componentDidMount() {
        this.renderText(); 
    }

    renderText(isResizing = false) {
        const textEl = this.displayedText;
        const clampContainer = this.clampContainer;
        const heightToStop = isResizing ? clampContainer.style.height : this.letterHeightText.clientHeight * this.props.linesToRender;
        const dummyText = this.dummyText;
        const dummyDiv = this.dummyDiv;
        const words = this.state.words;
        const numWords = words.length;
        dummyDiv.style.cssText = `width: ${clampContainer.clientWidth}px; position: absolute; left: -1000px;`;

        let i = this.props.estimatedWordCount || 20;
        let strToRender = words.slice(0, i).join(' ');
        dummyText.textContent = strToRender;
        if (dummyText.clientHeight <= heightToStop && i>=numWords) {
            return;
        }
        while (dummyText.clientHeight <= heightToStop && i<numWords) {
           dummyText.textContent += ' ' + words[i++];
        };
        strToRender = dummyText.textContent;
        while (dummyText.clientHeight > heightToStop) {
            strToRender = strToRender.substring(0, strToRender.lastIndexOf(' '));
            dummyText.textContent = strToRender + '\u2026';
        }
        textEl.textContent = dummyText.textContent;
    }

    render() {
        const estimatedHeight = this.props.estimatedHeight || 20 * this.props.linesToRender;
        const containerStyle = { height: estimatedHeight, overflow: 'hidden'};
        if (typeof window !== 'undefined') {
            const dummyDiv = document.createElement('div');
            const dummyText = document.createElement('p');
            dummyDiv.appendChild(dummyText);
            this.dummyDiv = dummyDiv
            this.dummyText = dummyText
            document.body.appendChild(dummyDiv);
        }
        return (
            <div style={containerStyle} ref={(input) => {this.clampContainer = input;}}>
                <p ref={(input) => {this.displayedText = input;}}>{this.props.textToDisplay}</p>
                <p style={{visibility: 'hidden'}} ref={(input) => {this.letterHeightText = input;}}>Q</p>
            </div>
        );
    }
}

所以基本上,组件的主要主力是 renderText()功能。在那里,我一次添加一个词,直到文本的高度大于其容器的高度。从那里,我删除了最后一个词并添加了省略号。

我所做的优化如下:

  1. estimatedWordCount 允许每次添加一个单词的循环不必每次都从头开始。

  2. 我通过将实际容器 div 的尺寸复制到屏幕外来计算应该显示的文本,position:absolute div,因此它与其他 DOM 元素没有交互。

然而,即使经过我的优化,chrome 仍然提示由于 javascript 导致的重排花费的时间太长。

我的 renderText() 是否有任何优化?我可以做的功能以避免阅读 clientHeight这么频繁?

最佳答案

满足规定的要求:

The user passes in the number of lines to render and the text they want to display, and the component renders their text, cutting it off at the specified number of lines and inserting an ellipsis (...) at the end.

一种方法是放弃高度计算而只担心宽度,添加单词直到我们的行宽度与其容器碰撞,并跟踪添加的行直到达到指定行的最大数量。

这种方法大大加快了速度,因为它避免了过多地接触 DOM。 有趣的是,我看到渲染时间加快了 3 倍。使用此方法和一些其他优化,请参阅内联注释以了解更多信息。

Take a look at this component这是我编码的,在此处列出以供引用。另请查看下面的示例用法。

import React, {Component} from "react";

class TextClamp extends Component {
    constructor(props) {
        super(props);
        this.state = {
            lines: []
        }
    }

    computeText = () => {

        // Our desired text width we are trying to hit
        const width = this.container.clientWidth;

        // we reverse the word list so can take grab elements efficiently using pops
        // pops are O(1) while unshift is O(n).
        let words = this.props.textToDisplay.split(/\s+/).reverse();

        // we keep lines separate, rather than all concatenated together with \n,
        // because react will remove new lines unless we resort to using
        // dangerouslySetInnerHTML, which we should prefer to avoid
        let lines = [];

        // we reset any previous text to avoid bugs if we happen to call computeText more than once
        this.textContainer.textContent = "";

        let lineNumber = 0;

        // first word and line init
        let word = words.pop();
        lines[lineNumber] = "";

        // Our goal is to build up the lines array to contain at most
        // linesToRender elements, with each line's width being at most
        // the width of our container
        while (word ) {

            // add our word
            lines[lineNumber] += " " + word;
            this.textContainer.textContent += " " + word;


            // too wide, so we instead start a new line
            if (this.textContainer.clientWidth >= width) {
                // add back the word for the next line
                words.push(word);
                // remove our last added and clean up
                lines[lineNumber] = lines[lineNumber].slice(0, -word.length).trim();

                // already at linesToRender, therefore we cannot render complete text,
                // so we add our ellipsis
                if(lineNumber === this.props.linesToRender-1) {
                    lines[lineNumber] += " ..."
                    break;
                }

                // remove current text so we can calculate our next line width
                this.textContainer.textContent = "";

                console.log(lineNumber, this.props.linesToRender)


                lineNumber++;
                // init our next line
                lines[lineNumber] = "";
            }



            // next word
            word = words.pop()
            console.log(word)
        }

        // clean up just like we added a new line,
        lines[lineNumber] = lines[lineNumber].trim();


        // remove current text so when react renders it has a clean slate to add text elements
        this.textContainer.textContent = "";

        this.setState({
            lines: lines,
        })
    };

    componentDidMount() {
        this.computeText();
    }

    render() {

        // we need our 'pre for our whiteSpace, to explicitly control when our text breaks
        const containerStyle = {whiteSpace: 'pre'};
        // we need 'inline-block' so our p tag's width reflects the amount of text added, not its parent
        const textStyle = {display: 'inline-block'};

        // put line breaks between all the lines, except the first
        const lines = this.state.lines.map((text, i) => i ? [<br/>, text] : text);
        console.log(this.state.lines)
        return (
            <div style={containerStyle} ref={(input) => {
                this.container = input;
            }}>
                <p style={textStyle} ref={(input) => {
                    this.textContainer = input;
                }}>
                    {lines}
                </p>
            </div>
        );
    }
}

TextClamp.defaultProps = {
    linesToRender: 2,
    textToDisplay: ""

};

用法:

const exampleText = "This is an example piece of text. It should properly break lines at the correct width of it's parent, until it a certain max number of lines have been created. However sometimes the text, is too long to fit on the specified number of lines. At that point the line should be cut off."
const lines = 3
<TextClamp  linesToRender={lines} textToDisplay={exampleText} />

关于javascript - React Text Clamp 的性能改进?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45091779/

有关javascript - React Text Clamp 的性能改进?的更多相关文章

  1. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  2. Ruby 的数字方法性能 - 2

    我正在使用Ruby解决一些ProjectEuler问题,特别是这里我要讨论的问题25(Fibonacci数列中包含1000位数字的第一项的索引是多少?)。起初,我使用的是Ruby2.2.3,我将问题编码为:number=3a=1b=2whileb.to_s.length但后来我发现2.4.2版本有一个名为digits的方法,这正是我需要的。我转换为代码:whileb.digits.length当我比较这两种方法时,digits慢得多。时间./025/problem025.rb0.13s用户0.02s系统80%cpu0.190总计./025/problem025.rb2.19s用户0.0

  3. ruby - Ruby 性能中的计时器 - 2

    我正在寻找一个用ruby​​演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent

  4. ruby-on-rails - 如果条件与 &&,是否有任何性能提升 - 2

    如果用户是所有者,我有一个条件来检查说删除和文章。delete_articleifuser.owner?另一种方式是user.owner?&&delete_article选择它有什么好处还是它只是一种写作风格 最佳答案 性能不太可能成为该声明的问题。第一个要好得多-它更容易阅读。您future的自己和其他将开始编写代码的人会为此感谢您。 关于ruby-on-rails-如果条件与&&,是否有任何性能提升,我们在StackOverflow上找到一个类似的问题:

  5. ruby-on-rails - 我可以用鸭子类型(duck typing)改进这种方法吗? - 2

    希望我没有误解“ducktyping”的含义,但从我读到的内容来看,这意味着我应该根据对象如何响应方法而不是它是什么类型/类来编写代码。代码如下:defconvert_hash(hash)ifhash.keys.all?{|k|k.is_a?(Integer)}returnhashelsifhash.keys.all?{|k|k.is_a?(Property)}new_hash={}hash.each_pair{|k,v|new_hash[k.id]=v}returnnew_hashelseraise"CustomattributekeysshouldbeID'sorPropertyo

  6. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

  7. ruby - 如何找到我的 Ruby 应用程序中的性能瓶颈? - 2

    我编写了一个Ruby应用程序,它可以解析来自不同格式html、xml和csv文件的源中的大量数据。我如何找出代码的哪些区域花费的时间最长?有没有关于如何提高Ruby应用程序性能的好资源?或者您是否有任何始终遵循的性能编码标准?例如,你总是用加入你的字符串吗?output=String.newoutput或者你会使用output="#{part_one}#{part_two}\n" 最佳答案 好吧,有一些众所周知的做法,例如字符串连接比“#{value}”慢得多,但是为了找出您的脚本在哪里消耗了大部分时间或比所需时间更多,您需要进行分

  8. STM32的HAL和LL库区别和性能对比 - 2

    LL库和HAL库简介LL:Low-Layer,底层库HAL:HardwareAbstractionLayer,硬件抽象层库LL库和hal库对比,很精简,这实际上是一个精简的库。LL库的配置选择如下:在STM32CUBEMX中,点击菜单的“ProjectManager”–>“AdvancedSettings”,在下面的界面中选择“AdvancedSettings”,然后在每个模块后面选择使用的库总结:1、如果使用的MCU是小容量的,那么STM32CubeLL将是最佳选择;2、如果结合可移植性和优化,使用STM32CubeHAL并使用特定的优化实现替换一些调用,可保持最大的可移植性。另外HAL和L

  9. javascript - jQuery 的 jquery-1.10.2.min.map 正在触发 404(未找到) - 2

    我看到有关未找到文件min.map的错误消息:GETjQuery'sjquery-1.10.2.min.mapistriggeringa404(NotFound)截图这是从哪里来的? 最佳答案 如果ChromeDevTools报告.map文件的404(可能是jquery-1.10.2.min.map、jquery.min.map或jquery-2.0.3.min.map,但任何事情都可能发生)首先要知道的是,这仅在使用DevTools时才会请求。您的用户不会遇到此404。现在您可以修复此问题或禁用sourcemap功能。修复:获取文

  10. ruby - GC.disable 的任何性能缺点? - 2

    是否存在GC.disable会降低性能的情况?只要我使用的是真正的RAM而不是交换内存,就可以这样做吗?我正在使用MRIRuby2.0,据我所知,它是64位的,并且使用的是64位的Ubuntu:ruby2.0.0p0(2013-02-24revision39474)[x86_64-linux]Linux[redacted]3.2.0-43-generic#68-UbuntuSMPWedMay1503:33:33UTC2013x86_64x86_64x86_64GNU/Linux 最佳答案 GC.disable将禁用垃圾回收。像rub

随机推荐