将文本拆分为页面并单独显示(HTML5)

时间:2012-08-30 17:29:10

标签: javascript html5 css3 canvas

假设我们有像Romeo& amp;朱丽叶,我们想在一个简单的电子书阅读器中呈现这个(没有动画,只有页面和自定义字体大小)。有什么方法可以解决这个问题?

到目前为止我想出了什么:

  • 使用css3列可以将整个文本加载到内存样式中,使得单个列占用整个页面的大小。这样做非常难以控制,需要将整个文本加载到内存中。
  • 使用css3区域(在任何主要浏览器中都不支持)将构成与前一解决方案相同的基本概念,主要区别在于它不会难以控制(因为每个'列'都是一个自包含的元素)。
  • 在画布上绘制文本可以让您准确了解文本的结束位置,从而根据该页面绘制下一页。其中一个优点是您只需要将所有文本加载到当前页面(仍然很糟糕,但更好)。缺点是文本无法与之交互(如选择文本)。
  • 将每个单词放在元素中并为每个元素赋予唯一的id(或在javascript中保留逻辑引用),然后使用document.elementFromPoint查找页面上最后一个元素(单词)并显示从那个词开始的下一页。尽管这是唯一对我来说实际上是现实的,但由此产生的开销必须是巨大的。

然而,这些似乎都不被接受(首先没有给予足够的控制以使其工作,第二个尚未得到支持,第三个是艰难的,没有文本选择而第四个给出了荒谬的开销),所以任何我还没有想到的好方法,或者解决上述方法的一个或多个缺点的方法(是的,我知道这是一个相当开放的问题,但它越开放,产生任何相关的机会越大)答复)?

7 个答案:

答案 0 :(得分:8)

SVG可能非常适合您的文字分页

  • SVG文本实际上是文本 - 与画布不同,它只显示文本图片。

  • SVG文本可读,可选,可搜索。

  • SVG文本不会自动换行,但使用javascript可以很容易地解决这个问题。

  • 灵活的页面大小是可能的,因为页面格式化是在javascript中完成的。

  • 分页不依赖于浏览器相关格式。

  • 文本下载小而有效。只需要下载当前页面的文本。

以下是有关如何进行SVG分页和演示的详细信息:

http://jsfiddle.net/m1erickson/Lf4Vt/

enter image description here

第1部分:从服务器上的数据库中有效地获取有关页面的单词

将整个文本存储在每行1个字的数据库中。

每行(字)按字的顺序依次索引(字#1有索引== 1,字#2有索引== 2等)。

例如,这将以正确的字顺序获取整个文本:

// select the entire text of Romeo and Juliet
// “order by wordIndex” causes the words to be in proper order

Select word from RomeoAndJuliet order by wordIndex

如果您假设任何页面在格式化时包含大约250个单词,那么此数据库查询将获取页面#1的前250个单词

// select the first 250 words for page#1

Select top 250 word from RomeoAndJuliet order by wordIndex

现在好了!

让我们说第1页在格式化后使用了212个单词。然后,当您准备好处理第2页时,您可以从单词#213开始再获取250个单词。这样可以快速有效地获取数据。

// select 250 more words for page#2
// “where wordIndex>212” causes the fetched words
// to begin with the 213th word in the text

Select top 250 word from RomeoAndJuliet order by wordIndex where wordIndex>212

第2部分:将提取的单词格式化为符合指定页面宽度的文本行

每行文字必须包含足够的字词来填充指定的页面,但不能更多。

用一个单词开始#1行,然后一次添加一个单词,直到文本符合指定的页面宽度。

在安装第一条线之后,我们向下移动一个线高并开始第2行。

在线上拟合单词需要测量一行中添加的每个附加单词。当下一个单词超出行宽时,该额外单词将移动到下一行。

可以使用Html Canvases context.measureText方法测量单词。

此代码将采用一组单词(如从数据库中提取的250个单词),并将格式化尽可能多的单词以填充页面大小。

maxWidth是一行文本的最大像素宽度。

maxLines是适合页面的最大行数。

function textToLines(words,maxWidth,maxLines,x,y){

    var lines=[];

    while(words.length>0 && lines.length<=maxLines){
        var line=getOneLineOfText(words,maxWidth);
        words=words.splice(line.index+1);
        lines.push(line);
        wordCount+=line.index+1;
    }

    return(lines);
}

function getOneLineOfText(words,maxWidth){
    var line="";
    var space="";
    for(var i=0;i<words.length;i++){
        var testWidth=ctx.measureText(line+" "+words[i]).width;
        if(testWidth>maxWidth){return({index:i-1,text:line});}
        line+=space+words[i];
        space=" ";
    }
    return({index:words.length-1,text:line});
}

第3部分:使用SVG显示文本行

SVG Text元素是一个真正的html元素,可以读取,选择和搜索。

使用SVG Tspan元素显示SVG Text元素中的每个单独的文本行。

此代码采用在第2部分中格式化的文本行,并使用SVG将行显示为文本页面。

function drawSvg(lines,x){
    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    var sText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    sText.setAttributeNS(null, 'font-family', 'verdana');
    sText.setAttributeNS(null, 'font-size', "14px");
    sText.setAttributeNS(null, 'fill', '#000000');
    for(var i=0;i<lines.length;i++){
        var sTSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
        sTSpan.setAttributeNS(null, 'x', x);
        sTSpan.setAttributeNS(null, 'dy', lineHeight+"px");
        sTSpan.appendChild(document.createTextNode(lines[i].text));
        sText.appendChild(sTSpan);
    }
    svg.appendChild(sText);
    $page.append(svg);
}

以下是演示链接中断的完整代码:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    .page{border:1px solid red;}
</style>
<script>
$(function(){

    var canvas=document.createElement("canvas");
    var ctx=canvas.getContext("2d");
    ctx.font="14px verdana";

    var pageWidth=250;
    var pageHeight=150;
    var pagePaddingLeft=10;
    var pagePaddingRight=10;
    var approxWordsPerPage=500;        
    var lineHeight=18;
    var maxLinesPerPage=parseInt(pageHeight/lineHeight)-1;
    var x=pagePaddingLeft;
    var y=lineHeight;
    var maxWidth=pageWidth-pagePaddingLeft-pagePaddingRight;
    var text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";

    // # words that have been displayed 
    //(used when ordering a new page of words)
    var wordCount=0;

    // size the div to the desired page size
    $pages=$(".page");
    $pages.width(pageWidth)
    $pages.height(pageHeight);


    // Test: Page#1

    // get a reference to the page div
    var $page=$("#page");
    // use html canvas to word-wrap this page
    var lines=textToLines(getNextWords(wordCount),maxWidth,maxLinesPerPage,x,y);
    // create svg elements for each line of text on the page
    drawSvg(lines,x);

    // Test: Page#2 (just testing...normally there's only 1 full-screen page)
    var $page=$("#page2");
    var lines=textToLines(getNextWords(wordCount),maxWidth,maxLinesPerPage,x,y);
    drawSvg(lines,x);

    // Test: Page#3 (just testing...normally there's only 1 full-screen page)
    var $page=$("#page3");
    var lines=textToLines(getNextWords(wordCount),maxWidth,maxLinesPerPage,x,y);
    drawSvg(lines,x);


    // fetch the next page of words from the server database
    // (since we've specified the starting point in the entire text
    //  we only have to download 1 page of text as needed
    function getNextWords(nextWordIndex){
        // Eg: select top 500 word from romeoAndJuliet 
        //     where wordIndex>=nextwordIndex
        //     order by wordIndex
        //
        // But here for testing, we just hardcode the entire text 
        var testingText="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
        var testingWords=testingText.split(" ");
        var words=testingWords.splice(nextWordIndex,approxWordsPerPage);

        // 
        return(words);    
    }


    function textToLines(words,maxWidth,maxLines,x,y){

        var lines=[];

        while(words.length>0 && lines.length<=maxLines){
            var line=getLineOfText(words,maxWidth);
            words=words.splice(line.index+1);
            lines.push(line);
            wordCount+=line.index+1;
        }

        return(lines);
    }

    function getLineOfText(words,maxWidth){
        var line="";
        var space="";
        for(var i=0;i<words.length;i++){
            var testWidth=ctx.measureText(line+" "+words[i]).width;
            if(testWidth>maxWidth){return({index:i-1,text:line});}
            line+=space+words[i];
            space=" ";
        }
        return({index:words.length-1,text:line});
    }

    function drawSvg(lines,x){
        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        var sText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        sText.setAttributeNS(null, 'font-family', 'verdana');
        sText.setAttributeNS(null, 'font-size', "14px");
        sText.setAttributeNS(null, 'fill', '#000000');
        for(var i=0;i<lines.length;i++){
            var sTSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
            sTSpan.setAttributeNS(null, 'x', x);
            sTSpan.setAttributeNS(null, 'dy', lineHeight+"px");
            sTSpan.appendChild(document.createTextNode(lines[i].text));
            sText.appendChild(sTSpan);
        }
        svg.appendChild(sText);
        $page.append(svg);
    }

}); // end $(function(){});
</script>
</head>
<body>
    <h4>Text split into "pages"<br>(Selectable & Searchable)</h4>
    <div id="page" class="page"></div>
    <h4>Page 2</h4>
    <div id="page2" class="page"></div>
    <h4>Page 3</h4>
    <div id="page3" class="page"></div>
</body>
</html>

答案 1 :(得分:6)

请参阅my answerWrap text every 2500 characters in a for pagination using PHP or javascript。我最终得到了http://jsfiddle.net/Eric/WTPzn/show

引用原帖:

  

只需将您的HTML设置为:

<div id="target">...</div>
     

为页面添加一些css:

#target {
    white-space: pre-wrap; /* respect line breaks */
}
.individualPage {
    border: 1px solid black;
    padding: 5px;    
}
     

然后使用以下代码:

var contentBox = $('#target');
//get the text as an array of word-like things
var words = contentBox.text().split(' ');

function paginate() {
    //create a div to build the pages in
    var newPage = $('<div class="individualPage" />');
    contentBox.empty().append(newPage);

    //start off with no page text
    var pageText = null;
    for(var i = 0; i < words.length; i++) {
        //add the next word to the pageText
        var betterPageText = pageText ? pageText + ' ' + words[i]
                                      : words[i];
        newPage.text(betterPageText);

        //Check if the page is too long
        if(newPage.height() > $(window).height()) {
            //revert the text
            newPage.text(pageText);

            //and insert a copy of the page at the start of the document
            newPage.clone().insertBefore(newPage);

            //start a new page
            pageText = null;
        } else {
            //this longer text still fits
            pageText = betterPageText;             
        }
    }    
}

$(window).resize(paginate).resize();

答案 2 :(得分:2)

我有一个非常简单,可变的css标记和3个非常简短的js函数的解决方案。

首先,我创建了两个div元素,其中一个隐藏但包含整个文本,另一个显示但是空白。 HTML 将如下所示:

<div id="originalText">
some text here
</div>
<div id="paginatedText"></div>

这两者的 CSS 是:

#originalText{
    display: none; // hides the container
}

#paginatedText{
    width: 300px;
    height: 400px;
    background: #aaa;
}

我也让css为类名页面做好了准备,如下所示:

.page{
    padding: 0;
    width: 298;
    height: 398px; // important to define this one
    border: 1px solid #888;
}

真正重要的部分是定义高度,否则当我们稍后填写单词时页面就会被拉伸。


现在是重要的一部分。 JavaScript 功能。评论应该为自己说话。

function paginateText() {
    var text = document.getElementById("originalText").innerHTML; // gets the text, which should be displayed later on
    var textArray = text.split(" "); // makes the text to an array of words
    createPage(); // creates the first page
    for (var i = 0; i < textArray.length; i++) { // loops through all the words
        var success = appendToLastPage(textArray[i]); // tries to fill the word in the last page
        if (!success) { // checks if word could not be filled in last page
            createPage(); // create new empty page
            appendToLastPage(textArray[i]); // fill the word in the new last element
        }
    }
}

function createPage() {
    var page = document.createElement("div"); // creates new html element
    page.setAttribute("class", "page"); // appends the class "page" to the element
    document.getElementById("paginatedText").appendChild(page); // appends the element to the container for all the pages
}

function appendToLastPage(word) {
    var page = document.getElementsByClassName("page")[document.getElementsByClassName("page").length - 1]; // gets the last page
    var pageText = page.innerHTML; // gets the text from the last page
    page.innerHTML += word + " "; // saves the text of the last page
    if (page.offsetHeight < page.scrollHeight) { // checks if the page overflows (more words than space)
        page.innerHTML = pageText; //resets the page-text
        return false; // returns false because page is full
    } else {
        return true; // returns true because word was successfully filled in the page
    }
}

最后我刚用

调用了paginateText函数
paginateText();

这整个skript适用于每个文本和每种样式的页面。

因此,您可以更改字体和字体大小甚至页面大小。

我还有 jsfiddle ,其中包含所有内容。

如果我忘记了任何问题,或者您有任何问题可以随意发表评论并提出建议或提出问题。

答案 3 :(得分:2)

我还没有足够的代表发表评论,但我只是想说Eric的答案很有效。我正在创建一个电子阅读器,除了它读取HTML文件,您可以将它用于未准备好发布的文本。可以看到两个页面,只有在按下按钮时才会调整大小。

我做了很多修改。但是,我发现只有一个小瑕疵。当您检查最后一个单词是否脱离页面边缘时,您需要将该单词添加回列表。简单地说,在if语句的第一种情况下,放入行i--;为了回头把这个词放在下一页。

以下是我的修改:

  1. 使用参数(内容,目标)将其全部变成一个函数。
  2. 添加了一个变量backUpContent,以便在我调整页面大小时重复使用。
  3. 将newPage更改为不可见的testPage并添加了一个包含每个页面内容的数组页[i],以便在订购页面后轻松来回。
  4. 将一个页面计数器“pC ++;”添加到else语句的第一部分。
  5. 将.text更改为.html,以便它不会将标记计为其文本等效项。
  6. 我设计了大约1或2个div的内容,而不是隐藏和显示的许多div。
  7. 还有更多我尚未到过的插页。
  8. 如果您想在同一页面上保留类似整段的内容,请更改行

    pageText + ' ' + words[i]
    

    pageText + '</p><p>' + words[i]
    

    和行

    words = content.split(' ');
    

    words = content.split('</p><p>');
    

    但是你应该只使用它,如果你确定这样的每个元素都小到可以在一个页面上。

    Eric的解决方案正是我所缺少的。我打算问自己的问题,但在输入几乎所有问题之后,我终于在建议中找到了这个页面。但问题的措辞有点令人困惑。

    谢谢Eric!

答案 4 :(得分:0)

另一个想法是使用CSS column拆分html内容,此重排由浏览器自行完成,因此速度非常快,下一步是将每个页面内容插入dom,我通过复制整个内容来完成此操作列,然后将每个页面滚动到裁剪的窗口,请参见codepen示例:

https://codepen.io/julientaq/pen/MBryxr

const pageWidth = 320;
const content = document.getElementById('content');
const totalWidth = content.scrollWidth;
const totalPages = totalWidth / pageWidth;
console.log('totalPages', totalPages);

let contentVisible = true;
const button = document.getElementById('btn-content');
const buttonText = document.getElementById('btn-content-text');
const showHideContent = () => {
  contentVisible = !contentVisible;
  content.style.display = contentVisible ? 'block' : 'none';
  buttonText.innerText = contentVisible ? 'Hide' : 'Show';
}
button.addEventListener('click', showHideContent);

const html = content.innerHTML;
const container = document.getElementById('container');
// console.log('content', content);
for (let p = 0; p < totalPages; p++) {
  const page = document.createElement('div');
  page.innerHTML = html;
  page.className = 'page';
  page.style.cssText = `
    width: ${totalWidth}px;
    transform: translateX(-${p * pageWidth}px);
  `;
  const pageClip = document.createElement('div');
  pageClip.className = 'page-clip';
  pageClip.appendChild(page);
  const pageWrapper = document.createElement('div');
  pageWrapper.className = 'page-wrapper';
  pageWrapper.appendChild(pageClip);
  container.appendChild(pageWrapper);
}

showHideContent();

这非常适用于少量分页内容,但不适用于大容量内容,您将获得很多永远不会显示的DOM元素浪费。

但是我认为必须有更好的主意,例如结合其他答案,使用javascript来帮助拆分列结果。

答案 5 :(得分:0)

npm软件包paragraph-builder将连续文本分成均匀分布的段落,并且所有段落的字数大致相同。您可以定义段落的单词数。您可以将段落的原理扩展到页面,考虑到页面平均具有大约相同数量的字符,包括空格。

  

此段落构建器节点脚本从连续文本生成段落。它输出一个文本,其中每个段落的大小大致相同,从而在文本中提供均匀的段落分布。它不会将数字拆分为“ 1.2”之类的数字。

有一个选项可以定义段落之间的分隔符,或者您可以将段落提取到一个字符串数组中,并从中应用html标签<p>。检查其文档以进一步澄清。

答案 6 :(得分:-4)

这很简单,不需要javascript。自CSS2起支持paged media type。有关支持的属性,请参阅http://www.w3.org/TR/CSS21/page.html(或current CSS3 module)。