22

ロミオとジュリエットのような長いテキストがあり、これをシンプルな電子書籍リーダー (アニメーションなし、ページのみ、カスタム フォント サイズ) で表示したいとします。これを取得するためにどのようなアプローチが存在しますか?

私がこれまでに思いついたこと:

  • css3 列を使用すると、テキスト全体をメモリ スタイルにロードして、1 つの列がページ全体のサイズになるようにすることができます。これを行うと、制御が非常に難しくなり、テキスト全体をメモリにロードする必要があります。
  • css3 領域 (主要なブラウザーではサポートされていません) を使用すると、以前のソリューションと同じ基本概念が構成されますが、制御がそれほど難しくないという大きな違いがあります (すべての「列」は自己完結型の要素であるため)。
  • キャンバスにテキストを描くと、テキストがどこで終わるかを正確に知ることができ、それに基づいて次のページを描くことができます。利点の 1 つは、現在のページまでのすべてのテキストをロードするだけでよいことです (それでも悪いが、より良い)。欠点は、テキストを操作できないことです (テキストの選択など)。
  • 要素内にすべての単語を配置し、すべての要素に一意の ID を付けます (または、javascript で論理参照を保持します)。次にdocument.elementFromPoint、ページの最後の要素 (単語) を検索し、その単語から次のページを表示します。これが実際に現実的に思える唯一のものであるにもかかわらず、これによって生成されるオーバーヘッドは計り知れません。

それでも、それらのどれも受け入れられないようです(最初はそれを機能させるのに十分な制御を与えていませんでした、2番目はまだサポートされていません、3番目は難しく、テキスト選択がなく、4番目はばかげたオーバーヘッドを与えます)または、言及された方法の1つ以上の欠点を解決する方法をまだ考えていませんか(はい、これはかなりオープンな質問であることは承知していますが、オープンであるほど、関連する回答が得られる可能性が高くなります)?

4

6 に答える 6

14

SVG はテキストのページ付けに適している可能性があります

  • テキストの画像だけを表示するキャンバスとは異なり、SVG テキストは実際にはテキストです。

  • SVG テキストは読み取り、選択、検索が可能です。

  • SVG テキストはネイティブでは自動ラップされませんが、これは JavaScript を使用して簡単に修正できます。

  • ページの書式設定は JavaScript で行われるため、柔軟なページ サイズが可能です。

  • ページネーションは、ブラウザ依存のフォーマットに依存しません。

  • テキストのダウンロードは小さくて効率的です。現在のページのテキストのみをダウンロードする必要があります。

SVG ページネーションの詳細とデモは次のとおりです。

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

ここに画像の説明を入力

パート 1: サーバー上のデータベースから約 1 ページ分の単語を効率的にフェッチする

テキスト全体をデータベースに 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 を 1 つの単語で開始し、テキストが指定されたページ幅に収まるまで単語を 1 つずつ追加します。

最初の行が適合された後、行の高さだけ下に移動し、行 #2 を開始します。

行に単語を合わせるには、行に追加された各単語を測定する必要があります。次の単語が行幅を超える場合、余分な単語は次の行に移動されます。

context.measureText単語は、Html Canvasesメソッド を使用して測定できます。

このコードは一連の単語 (データベースからフェッチされた 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 Text 要素内の個々のテキスト行は、SVG Tspan 要素を使用して表示されます。

このコードは、パート 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>
于 2014-05-09T04:37:44.987 に答える
9

PHP または javascript を使用したページネーションについては、2500 文字ごとにテキストをラップするに対する私の回答を参照してください。私は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();
于 2012-08-30T20:45:48.630 に答える
3

まだコメントするのに十分な担当者がいませんが、エリックの答えがうまく機能していると言いたかっただけです. 私は電子書籍リーダーを作成していますが、それは HTML ファイルを読み取るものであり、出版の準備ができていないテキストに使用できます。表示できるページは 2 つあり、ボタンを押したときにのみサイズが変更されます。

多くの修正を加えました。ただし、私が見つけた小さな欠陥は 1 つだけでした。最後の単語がページの端からはみ出しているかどうかを確認し、そうである場合は、その単語をリストに追加し直す必要があります。簡単に言えば、if ステートメントの最初のケースで、行 i--; に入れます。戻って次のページにその言葉を入れるために。

ここに私の変更があります:

  1. 引数(コンテンツ、ターゲット)を使用して、すべてを関数にしました。
  2. ページのサイズを変更するときに再利用するために、変数 backUpContent を追加しました。
  3. newPage を非表示の testPage に変更し、配列 page[i] を追加しました。これには各ページのコンテンツが含まれており、ページを並べ替えた後に簡単に前後に移動できます。
  4. else ステートメントの最初の部分に、ページカウンターである "pC++;" 行を追加しました。
  5. .text を .html に変更して、タグを同等のテキストとしてカウントしないようにしました。
  6. 表示と非表示を切り替える多数の div ではなく、コンテンツを変更する 1 つまたは 2 つの div を中心に設計しました。
  7. まだ手に入っていないインサートが他にもあります。

段落全体のようなものを同じページに保持したい場合は、行を変更します

pageText + ' ' + words[i]

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

そしてライン

words = content.split(' ');

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

ただし、そのような各要素が 1 ページに収まるほど小さいことが確実な場合にのみ、これを使用する必要があります。

エリックの解決策は、まさに私が見逃していたものです。私は自分で質問するつもりでしたが、ほとんどすべての質問を入力した後、最終的に提案でこのページを見つけました。ただし、質問の文言は少し紛らわしいです。

エリックありがとう!

于 2014-10-27T01:06:03.713 に答える
3

非常にシンプルで変更可能なcssマークアップと3つの非常に短いjs関数を使用したソリューションがあります。

最初に 2 つの div 要素を作成しました。1 つは非表示ですがテキスト全体が含まれ、もう 1 つは表示されていますがまだ空です。HTMLは次のようになります。

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

これら 2 つの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();

このスクリプト全体は、すべてのテキストとページのすべてのスタイルで機能します。

したがって、フォントとフォントサイズ、さらにはページのサイズを変更できます。

また、そこにすべてが含まれているjsfiddleもあります。

私が何かを忘れたり、質問がある場合は、お気軽にコメントして提案をしたり、質問したりしてください.

于 2014-05-13T19:42:51.643 に答える
-5

これは簡単で、javascript は必要ありません。はCSS2paged media type以降でサポートされています。サポートされているプロパティについては、 http://www.w3.org/TR/CSS21/page.html (または現在の CSS3 モジュール) を参照してください。

于 2012-08-30T17:38:31.913 に答える