Разделить текст на страницы и представить отдельно (HTML5)

Скажем, у нас есть длинный текст, такой как "Ромео и Джульетта", и мы хотим представить это в простом ereader (без анимаций, только страниц и пользовательского размера шрифта). Какие существуют подходы к этому?

Что я до сих пор придумал:

  • Используя столбцы css3, можно будет загрузить весь текст в память, уложив его таким образом, чтобы один столбец занимал размер всей страницы. Выполнение этого оказалось чрезвычайно сложным для управления и требует, чтобы весь текст загружался в память.
  • Использование областей css3 (не поддерживается в любом крупном браузере) будет представлять собой ту же основную концепцию, что и предыдущее решение, с той большой разницей, что ее было бы не так сложно контролировать (поскольку каждый столбец является автономным элементом).
  • Рисование текста на холсте позволит вам точно знать, где заканчивается текст и, таким образом, нарисовать следующую страницу на основе этого. Одно из преимуществ заключается в том, что вам нужно всего лишь загрузить весь текст до текущей страницы (все еще плохо, но лучше). Недостатком является то, что текст не может взаимодействовать с (как и выбор текста).
  • Поместите каждое слово внутри элемента и дайте каждому элементу уникальный идентификатор (или сохраните логическую ссылку в javascript), затем используйте document.elementFromPoint, чтобы найти элемент (слово), который является последним на странице, и показать следующий страница вперед от этого слова. Несмотря на то, что это единственное, что кажется мне реально реалистичным, накладные расходы, порожденные этим, должны быть огромными.

Тем не менее, ни одна из них не кажется приемлемой (сначала не было достаточно контроля, чтобы даже заставить ее работать, второй пока не поддерживается, третий - жесткий и без выбора текста, а четвертый дает смешные накладные расходы), поэтому любой хорошие подходы, о которых я еще не думал, или способы решения одного или нескольких недостатков упомянутых методов (да, я знаю, что это довольно открытый вопрос, но чем более он открыт, тем выше вероятность создания каких-либо релевантных ответы)?

Ответ 1

Посмотрите мой ответ, чтобы обернуть текст каждые 2500 символов для разбивки на страницы с использованием PHP или 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

SVG может быть хорошо подходит для разбивки на страницы

  • Текст SVG - это текст, в отличие от холста, который отображает только изображение текста.

  • Текст SVG читается, выбирается, доступен для поиска.

  • Текст SVG не автообновляется изначально, но это легко исправить с помощью javascript.

  • Гибкие размеры страниц возможны, потому что форматирование страницы выполняется в javascript.

  • Pagination не зависит от форматирования, зависящего от браузера.

  • Загрузка текста небольшая и эффективная. Необходимо загрузить только текст для текущей страницы.

Ниже приведены сведения о том, как можно выполнить разбиение на страницы 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 слов при форматировании, тогда этот запрос базы данных будет извлекать первые 250 слов текста для страницы # 1

// select the first 250 words for page#1

Select top 250 word from RomeoAndJuliet order by wordIndex

Теперь хорошая часть!

Давайте скажем, что страница №1 использовала 212 слов после форматирования. Затем, когда вы готовы обработать страницу №2, вы можете получить еще 250 слов, начиная со слова # 213. Это приводит к быстрым и эффективным сборам данных.

// 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-в-время, пока текст не будет соответствовать указанной ширине страницы.

После того, как первая строка установлена, мы опустимся вниз по высоте линии и начнем строку # 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 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);
}

Вот полный код на всякий случай, если разрыв Demo:

<!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 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 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 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 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>

Ответ 3

У меня есть решение с довольно простой, изменяемой css-разметкой и тремя довольно короткими 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();

Весь этот скрипт работает для каждого текста и для каждого стиля страниц.

Таким образом, вы можете изменить шрифт и размер шрифта и даже размер страниц.

У меня также есть jsfiddle со всем, что там есть.

Если я что-то забыл или у вас есть вопрос, не стесняйтесь комментировать и делать предложения или задавать вопросы.

Ответ 4

У меня недостаточно комментариев, чтобы сделать комментарий, но я просто хотел сказать, что ответ Эрика работает красиво. Я создаю eReader, за исключением того, что он читает файлы HTML, и вы можете использовать его для текста, не готового для публикации. Есть две страницы, которые можно увидеть, и они изменяются только тогда, когда вы нажимаете кнопку.

Я сделал много изменений. Однако был только один небольшой недостаток. Когда вы проверяете, падает ли последнее слово с края страницы, и это нужно, вам нужно добавить это слово обратно в список. Проще говоря, в первом случае оператора if положите в строку i--; чтобы вернуться и поставить это слово на следующей странице.

Здесь мои модификации:

  • превратил все это в функцию с аргументами (контентом, целью).
  • добавлена ​​переменная backUpContent, для повторного использования при изменении размеров страниц.
  • изменил newPage на невидимый testPage и добавил страницу массива [i], содержащую содержимое каждой страницы, для удобства перехода вперед и назад после заказа страниц.
  • добавлена ​​строка "pС++;", pagecounter, в первую часть инструкции else.
  • изменил .text на .html, чтобы он не учитывал теги как их текстовые эквиваленты.
  • Я разработал его вокруг 1 или 2 div с изменяющимся контентом, а не с множеством разных div, которые скрывают и показывают.
  • Есть еще несколько вставок, которые я еще не получил.

Если вы хотите сохранить что-то вроде целых абзацев на одной странице, измените строку

pageText + ' ' + words[i]

к

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

и строка

words = content.split(' ');

к

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

Но вы должны использовать это, только если вы уверены, что каждый из таких элементов достаточно мал, чтобы идти на одну страницу.

Решение Эрика - это именно то, что мне не хватало. Я собирался задать свой вопрос, но, наконец, нашел эту страницу в предложениях после ввода почти всего моего вопроса. Однако формулировка вопроса немного запутанна.

Спасибо Эрик!

Ответ 5

Другая идея заключается в использовании CSS-столбца для разделения 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, чтобы помочь разбить результат столбца.

Ответ 6

Построитель абзацев пакета npm разбивает непрерывный текст на равномерно распределенные абзацы с одинаковым количеством слов. Вы можете определить количество слов для абзацев. Вы можете распространить принцип абзацев на страницы, учитывая, что на странице в среднем примерно столько же символов, включая пробел.

Этот скрипт узла построителя абзацев генерирует абзацы из непрерывного текста. Он выводит текст, в котором размер каждого абзаца примерно одинаков, обеспечивая равномерное распределение абзацев в тексте. Он не разбивает текст на числа, такие как "1.2".

Существует возможность определить символ разрыва между абзацами, или вы можете извлечь абзацы в массив строк, из которого вы можете применить html-тег <p>. Проверьте его документацию для дальнейшего уточнения.