【问题标题】:Fast JS Pagination for long texts长文本的快速 JS 分页
【发布时间】:2017-10-22 09:52:42
【问题描述】:

我正在尝试使用 JavaScript 创建一个分页系统。

基本情况:我有一个数据库,其中包含相当长的文本(故事章节,5000 字以上)。我想在网站上显示这些章节......但不是一次显示整个文本,因为这几乎会破坏可读性,而是在页面中。 我没有问题显示文本,而是让页面正确。

我一直在环顾四周,发现了一个 JQuery 代码,它可以实现我想要它做的事情......但是这种方法有一个主要的警告。完成文本分页大约需要 10 秒,等待时间太长了。

代码的基本作用: 它将文本拆分为单词(由空格分隔)。 然后它尝试将一个单词一个接一个地添加到 innerHTML,检查文本现在是否大于它应该适合的容器。

每次突破边界时,它都会恢复到前一个字符串并创建一个新页面。 (通过将文本封装到一个跨度中,然后可以立即隐藏/显示)这可行,但是它太慢了,因为它必须运行这些检查 5000 多次。

我尝试创建一个近似系统,它基本上采用字数,将其除以因子 0.5,检查缓冲区是否大于所需大小,并重复此过程,直到缓冲区“小于”第一次需要的大小,从那个位置开始,它会填满缓冲区,直到填满。

但是它似乎不能正常工作(双字,双线,没有完全填满,而且仍然太慢。)

这是我目前正在使用的代码,我将不胜感激任何修复和建议,以使其更容易,尤其是:更快。 哦,并且:不,在服务器端分页不是一个选项,因为它应该适合可变的浏览器格式......在 1280x768 分辨率的全屏浏览器中,它的页面将少于在 1024x768 分辨率的小型浏览器中的页面。

function CreateChild(contentBox, Len, pageText, words) {
    var Child = document.createElement("span");
Child.innerHTML = pageText;
contentBox.appendChild(Child);
if(Len == 0) ++Len;
words.splice(0, Len);
    return words.length;
}

$(document).ready(function(){  
    var src = document.getElementById('Source');
    var contentBox = document.getElementById('content');
var inner = document.getElementById('inner');
    //get the text as an array of word-like things
    var words = src.innerHTML.replace(/ +/g, " ").split(' '), wCount = words.length;

    //start off with no page text
    var pageText = null, cHeight = contentBox.offsetHeight;

    while(words.length > 0) {
        var Found = false;
        pageText = words[0];    //Prevents constant checking for empty
        wCount *= 0.5;      //Searches, until the words fit in.
        for(var i = 1; i < wCount; ++i) pageText += ' ' + words[i];
        inner.innerHTML = pageText;
        Distance = inner.offsetHeight - cHeight;
        if(Distance < 40) {         //Less than two lines
            wCount = Math.floor(wCount);
            if(Distance < 0) {      //Already shorter than required. Fill.
                for(var i = wCount; i < words.length; ++i) {
                    //add the next word to the pageText
                    var betterPageText = pageText + ' ' + words[i];
                    inner.innerHTML = betterPageText;
                    //Checks, whether the new words makes the buffer too big.
                    if(inner.offsetHeight > cHeight) {
                        wCount = CreateChild(contentBox, i, pageText, words);
                        Found = true;
                        break;
                    } else {
                        //this longer text still fits
                        pageText = betterPageText;             
        }
        }
    } else {
        for(var i = wCount; i >= 0; --i) {
            //Removes the last word from the text
        var betterPageText = pageText.slice(0, pageText.length - words[i].length - 1);
        inner.innerHTML = betterPageText;

        //Is the text now short enough?
        if(inner.offsetHeight <= cHeight) {
            wCount = CreateChild(contentBox, i, pageText, words);
            Found = true;
            break;
        } else {
            pageText = betterPageText;             
        }
        }   
    }
    if(!Found) CreateChild(contentBox, i, pageText, words);
    }
}

//Creates the final block with the remaining text.  
Child = document.createElement("span");
Child.innerHTML = pageText;
contentBox.appendChild(Child);

//Removes the source and the temporary buffer, only the result remains.     
contentBox.removeChild(inner);
src.parentNode.removeChild(src);

    //The rest is the actual pagination code, but not the issue
});

【问题讨论】:

    标签: javascript jquery pagination


    【解决方案1】:

    我设法解决了我的问题,也感谢 Rich 的建议。 我在做什么:首先,我从'Source'获取文本(或者,我可以将整个文本直接写入JS,效果是一样的)。

    接下来,我将在任何临时缓冲区中获取对目标的引用,临时缓冲区位于目标缓冲区内,因此它将保留宽度信息。

    之后,我将整个文本拆分为单词(标准正则表达式,用一个空格替换多个空格后)。之后,我创建了一些变量,用于缓冲函数结果,这样就不必重复调用函数了。

    现在主要区别:我取 20 个单词的块,检查当前块是否超出边界(同样,将结果缓冲在变量中,因此它们不会被多次调用,函数调用等于宝贵的微秒)。

    一旦越界(或达到字符总数),循环停止,并且(假设边界导致“停止”),每次运行将文本缩短一个单词,直到文本适合又进来了。

    最后,新文本被添加到一个新的 span-element 中,该元素被添加到内容框中(但变得不可见,我稍后会解释原因),我刚刚“使用”的词从word 数组和 wCount 变量按字数递减。

    冲洗并重复,直到呈现所有页面。 您可以将“20”与任何其他值交换,脚本可以使用任意数字,但是请记住,数字太小会导致“添加段”中的运行次数过多,而数字太大会导致在“回溯段”中有很多运行。

    至于不可见:如果 span 保持可见,迟早会导致滚动条出现,从而有效地缩小浏览器窗口的宽度。 反过来,这将允许更少的单词适合,并且所有后续页面都将被扭曲(因为它们将与带有滚动条的窗口匹配,而“分页结果”将没有滚动条)。

    以下是我使用的代码,希望对以后的人有所帮助。

    var src = document.getElementById('Source');
    var contentBox = document.getElementById('content');
    var inner = document.getElementById('inner');
    //get the text as an array of word-like things
    var words = src.innerHTML.replace(/ +/g, " ").split(' ');
    
    //start off with no page text
    var cHeight = contentBox.offsetHeight, wCount = words.length;
    
    while(wCount > 0) {
        var Len = 1, Overflow = false;
        var pageText = words[0];                        //Prevents the continued check on 'is pageText set'.
        while(!Overflow && Len < wCount) {              //Adds to the text, until the boundary is breached.
            //20 words per run, but never more than the total amount of words.
            for(var j = 0; j < 20 && Len < wCount; ++Len, ++j) pageText += ' ' + words[Len];
            inner.innerHTML = pageText;
            Overflow = (inner.offsetHeight > cHeight);  //Determines, whether the boundary has been crossed.
        }
        if(Overflow) {                                  //Will only be executed, if the boundary has been broken.
            for(--Len; Len >= 0; --Len) {               //Removes the last word of the text, until it fits again.
                var pageText = pageText.slice(0, -(words[Len].length + 1)); //Shortens the text in question.
                inner.innerHTML = pageText;
    
                //Checks, whether the text still is too long.
                if(inner.offsetHeight <= cHeight) break;//Breaks the loop
            }
        }
        var Child = document.createElement("span");
        Child.style.display = "none";                   //Prevents the sidebars from showing (and distorting the following pages)
                Child.innerHTML = pageText;
                contentBox.appendChild(Child);
                words.splice(0, Len);
                wCount -= Len;
    }   
    

    【讨论】:

      【解决方案2】:

      创建一个具有单页宽度的绝对定位容器。给它'auto'的高度。将容器放置在屏幕外的某个位置,例如左侧:-10000px,以便用户看不到它。将原始文本分成 20 个单词的块。 (查找完成此操作的正则表达式。)一次将一个块附加到容器中的字符串,直到容器的高度达到单个页面的最大高度。一旦达到最大高度,容器中的字符串基本上就是一页文本。将容器中的字符串推送到一个名为“pages”的数组上。清空容器并通过再次附加 20 个单词的块开始创建第 2 页,从您在上一页中中断的位置继续遍历数组。继续此过程,直到到达 20 字数组的末尾,每当容器的字符串达到最大高度时,将每个新页面推送到页面数组中。您现在应该有一个页面数组,其中的每个项目都包含每个页面的文本。

      【讨论】:

      • 首先,感谢您的回答,但我发现此解决方案存在一些问题。你看,假设页面有 70% 的宽度和 200px 的高度(当然只是示例性的),换行符会产生高度。假设你有很多小词,比如“我”、“你”等......你可以在一行中放置 20-30 个词,但是如果你有像 supercalifragilisticexpaligetic 这样的长词......每行的词数有点有限。假设每行 3 个单词,总共 10 行,这意味着容器中只有 30 个单词……明白我的意思吗?
      • 我相信我解决了这个问题。还要感谢您,因为您指出了我的“复发”问题的最明显(和有效)的解决方案。我将在单独的答案中发布我的代码。再次感谢你:)
      • 很高兴你知道了。您可以在跨度上放置溢出:可见以防止滚动条。
      • 啊,很高兴知道。这里没有太多的 CSS 人。但是在需要之前隐藏它也可以。另外,因为我们以后也可以通过这种方式省略隐藏“其他人”。
      【解决方案3】:

      由于没有事先搜索,我使用 getClientRects (https://developer.mozilla.org/en-US/docs/Web/API/Element/getClientRects) 制定了替代解决方案。如果有人对细节感兴趣,我会发布更多。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-08
        • 1970-01-01
        • 2021-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多