Получить точку, когда пользователь перестает читать

Пользователь А попадает в мой онлайн-журнал. Она начинает читать очень длинную статью, а затем посередине она решает сделать паузу. Она закрывает браузер и только через несколько часов возвращается на сайт. Теперь, когда она снова открывает ту же статью, мне хотелось бы, чтобы страница начиналась с того места, где она оставила ее.

Imaging User A на самом деле является зарегистрированным пользователем, как я могу это достичь? В частности, я хотел бы сохранить номер символа (как int) первого символа первой видимой строки на экране (в основном в верхнем левом углу).

Изменить: просто для того, чтобы прояснить еще больше, вы можете думать о таких приложениях, как Instapaper или Pocket, для решения аналогичной проблемы (как для мобильных, так и для веб-приложений).

Ответ 1

Обновленный ответ

Я не совсем уверен, как это можно сделать. Я бы рекомендовал хранить, сколько прокручивается документ с помощью localStorage.

Вы можете получить, как далеко прокручивался документ, и размеры документа в момент его закрытия. Если ширина документа одинакова, когда он перезагружается, вы можете просто прокручивать до той же точки. Если нет, вы можете оценить точку, в которой пользователь остановился, вычислив width * scrollTop чтения тома width * scrollTop и разделив ее на текущую ширину.

Вот псевдокод, который поможет вам понять концепцию:

if oldWidth == currentWidth
    set scroll = oldScroll
else
    set scroll = oldScroll * oldWidth / currentWidth

Вот пример JavaScript, приведенный выше, но некоторые заметки:

  • localStorage не допускается по фрагментам, поэтому текущий код здесь не работает, если вы хотите протестировать, вам придется скопировать его в другом месте
  • Метод leavePage() в конце имеет оператор return null, потому что некоторые браузеры не будут запускать функцию без оператора return on beforeunload

if (document.readyState === "complete") enterPage();
else addEventListener("load", enterPage);
addEventListener("beforeunload", leavePage);

function enterPage() {
  var magazineDom = document.querySelector("#magazine");
  if (magazineDom instanceof HTMLElement) {
    magazineDom.textContent = "Lorem Ipsum".repeat(800);
    //onRender();
  }
}

function onRender() {
  var magazineContainerDom = document.querySelector("#magazine-container");
  var magazineDom = document.querySelector("#magazine");
  if (magazineContainerDom instanceof HTMLElement && magazineDom instanceof HTMLElement) {
    var info = getMagazineInfo(magazineDom.getAttribute("magazine"));
    if (info) {
      var currentWidth = magazineDom.scrollWidth;
      if (currentWidth == info.width) magazineContainerDom.scrollTop = info.top;
      else magazineContainerDom.scrollTop = info.top * info.width / currentWidth;
    }
  }
}

function leavePage() {
  var magazineContainerDom = document.querySelector("#magazine-container");
  var magazineDom = document.querySelector("#magazine");
  if (magazineContainerDom instanceof HTMLElement && magazineDom instanceof HTMLElement) {
    setMagazineInfo(magazineDom.getAttribute("magazine"), magazineContainerDom.scrollTop, magazineDom.scrollWidth);
  }
  return null;
}

function getMagazineInfo(name) {
  return JSON.parse(localStorage.magazines || "{}")[name];
}

function setMagazineInfo(name, top, width) {
  var magazines = JSON.parse(localStorage.magazines || "{}");
  magazines[name] = {
    top: top,
    width: width
  };
  localStorage.magazines = JSON.stringify(magazines);
}
body {
  position: fixed;
  bottom: 0;
  right: 0;
  left: 0;
  top: 0;
}

#magazine-container {
  overflow-y: auto;
  height: 100%;
  width: 100%;
}

#magazine {
  max-width: 500px;
  margin: auto;
}
<body>
  <div id="magazine-container">
    <div id="magazine" magazine="First Document">

    </div>
  </div>
</body>

Ответ 2

Мне было весело с этим. Сценарий создает массив строк. В массиве может быть найден термин, который хранится в Cookie или лучше локальное хранилище.

Увидеть

function findLine4(str)

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

<html>
<body>
    <div id="containerWrapper"></div>
    <div id="testWrapper"></div>

    <div id="originDiv" style="background:#fee;">
    Lorem ipsum dolor sit amet, consectetuer adipiscing 
elit. Aenean commodo ligula eget dolor. Aenean massa 
<strong>strong</strong>. Cum sociis natoque penatibus 
et magnis dis parturient montes, nascetur ridiculus 
mus. Donec quam felis, ultricies nec, pellentesque 
eu, pretium quis, sem. Nulla consequat massa quis 
enim. Donec pede justo, fringilla vel, aliquet nec, 
vulputate eget, arcu. In enim justo, rhoncus ut, 
imperdiet a, venenatis vitae, justo. Nullam dictum 
felis eu pede <a class="external ext" href="#">link</a> 
mollis pretium. Integer tincidunt. Cras dapibus. 
Vivamus elementum semper nisi. Aenean vulputate 
eleifend tellus. Aenean leo ligula, porttitor eu, 
consequat vitae, eleifend ac, enim. Aliquam lorem ante, 
dapibus in, viverra quis, feugiat a, tellus. Phasellus 
viverra nulla ut metus varius laoreet. Quisque rutrum. 
Aenean imperdiet. Etiam ultricies nisi vel augue. 
Curabitur ullamcorper ultricies nisi.
<br /><br /><br />
    Lorem ipsum dolor sit amet, consectetuer adipiscing 
elit. Aenean commodo ligula eget dolor. Aenean massa 
<strong>strong</strong>. Cum sociis natoque penatibus 
et magnis dis parturient montes, nascetur ridiculus 
mus. Donec quam felis, ultricies nec, pellentesque 
eu, pretium quis, sem. Nulla consequat massa quis 
enim. Donec pede justo, fringilla vel, aliquet nec, 
vulputate eget, arcu. In enim justo, rhoncus ut, 
imperdiet a, venenatis vitae, justo. Nullam dictum 
felis eu pede <a class="external ext" href="#">link</a> 
mollis pretium. Integer tincidunt. Cras dapibus. 
Vivamus elementum semper nisi. Aenean vulputate 
eleifend tellus. Aenean leo ligula, porttitor eu, 
consequat vitae, eleifend ac, enim. Aliquam lorem ante, 
dapibus in, viverra quis, feugiat a, tellus. Phasellus 
viverra nulla ut metus varius laoreet. Quisque rutrum. 
Aenean imperdiet. Etiam ultricies nisi vel augue. 
Curabitur ullamcorper ultricies nisi.
    </div>

</body>
<script>
    (function init() {
        var i;
        var originDiv = document.getElementById("originDiv");
        var allowedWidth = originDiv.scrollWidth;

        function testBreak(testDiv, strTest) {
            testDiv.innerHTML = strTest + "..."; // Yepp, that the point. No clue, how to fix this.
            return testDiv.scrollWidth > allowedWidth;
        }

        function tokenizeText() {
            return originDiv.innerHTML.trim()
                .split(/([\s]*<[a-zA-Z]+.*>.*<\/.*>[.,;:-]*[\s]*)|[\s]+/ig)
                .filter(function(a){return typeof(a) != 'undefined'})
                .map(function(a){return a.trim()}); 
        }

        function createLineArray() {
            var testDiv = originDiv.cloneNode(true);
            testDiv.innerHTML = "";
            testDiv.setAttribute("style", "white-space:nowrap;background:#ccc;visibility:hidden;");
            document.getElementById("testWrapper").appendChild(testDiv);

            var htmlWords = tokenizeText();
            var lines = [];
            var currLine = '';
            for(i = 0; i < htmlWords.length; i++) {
                currLine += htmlWords[i] + ' ';
                if(testBreak(testDiv, currLine)) {
                    lines.push(currLine.substr(0, currLine.length - htmlWords[i].length - 1));
                    currLine = '';
                    i--;
                }
            }
            lines.push(currLine);       
            return lines;
        }

        function createNewDiv(html) {
            var newDiv = document.createElement("DIV");
            newDiv.setAttribute("style", "background:#ddf");
            newDiv.setAttribute("id", "newContainer");  
            newDiv.innerHTML = html;
            return newDiv;  
        }

        (function doInit() {
            var lines = createLineArray();
            var formattedText = '';
            for (i = 0; i < lines.length; i++) {
                formattedText += '<div class="line'+i+'">' + lines[i] + '</div>';
            }           
            document.getElementById("containerWrapper").appendChild(createNewDiv(formattedText));
            // originDiv.style.display="none";
        })();

        (function findLine4(str) {
            //TODO: tokenize str and search for matching tokens.
            var nodes = document.getElementById("newContainer").childNodes;
            for(i = 0; i < nodes.length; i++) {
                if(nodes[i].textContent.indexOf(str) != -1) {
                    setTimeout(function() {
                        window.scrollTo(0, nodes[i].offsetTop);
                    },1);
                    break; 
                }
            }
        })("elementum semper");

        /* 
            I personally would prefer a timer instead of a listener here. 
        */
        window.addEventListener("scroll", function(evt) {
            var sct = window.scrollY;
            var nodes = document.getElementById("newContainer").childNodes;
            for(var i = 0; i < nodes.length; i++) {
                if(nodes[i].offsetTop > sct) {
                    console.log("line " + i + ": " + nodes[i].innerHTML);
                    break;
                }
            }
        }, true);       

    })();


</script>
</html>

Ответ 3

Вы можете просто сохранить количество полных остановок, пройденное, как и число строк, на самом деле. И в следующий раз, когда пользователь снова откроет веб-страницу, вы можете пропустить все эти полные остановки (линии) и выбрать следующую строку и переместить прокрутку к этой строке полного текста.

Или, если вы хотите, чтобы это было более конкретным, вы можете сохранить комбинацию числа полных остановок и количества слов, считанных с помощью ключа localStorage ("lastread", "f20w5") для 5-го слова после 20-й строки.

Ответ 4

Вот простое решение именно для того, что было запрошено: сохранение и возврат первого видимого символа прочитанного текста. В этом решении пользователь перемещает символы (или слова) только с помощью колесика прокрутки, но, конечно, могут быть и другие входы (например, прыгать большими секциями со стрелками или прокручивать по телефону или что угодно).

См. Https://jsfiddle.net/gasparl/re62ufy3/99/

HTML:

<div id="text_div"></div>

CSS:

#text_div {
    color: #ffffff;
    width: 400px;
    height: 250px;
    overflow:hidden;
    background-color: #000000;
}

JavaScript:

var scroll_speed = 100 // how many characters to move per scroll
var last_scroll_position = localStorage.scroll_position || 0; // get previous position if any
var the_text =
    "Lorem ipsum dolor sit amet, pellentesque libero metus quis sed et vestibulum, ut eget, turpis sed consequat, possimus quis, mi nec leo at wisi interdum posuere. Eu dui vitae, quis habitasse arcu arcu neque, odio enim.<br><br>Amet luctus lectus morbi massa nec, metus cras in molestie dolor, congue sodales viverra odio libero neque. Egestas vulputate vitae libero sodales, pharetra nec ligula vitae, tellus viverra ac vel, amet in sit eget.<br><br>Sit morbi tortor dictum viverra, massa molestie est urna suscipit felis massa, mi dictumst magna blandit, fringilla ut arcu nam urna ut in, et eget mauris metus integer ante.<br><br>Natoque egestas donec, ligula nam sit, ipsum a id, pellentesque in, commodo est eu phasellus sem et. Ut leo elit bibendum, posuere cras et nec, fames in wisi porta ac eleifend. Odio iaculis arcu, dapibus eget in imperdiet, dolor eleifend bibendum eget wisi quis, ultrices metus et lacinia integer. At pellentesque quam sapien, duis faucibus nisl eu dapibus, amet venenatis dictumst quis dictum.<br><br>Adipiscing gravida, massa mi ac, suscipit porta consequat pretium lobortis lacus, nullam facilisis ac gravida habitant, sed faucibus et at porta aliquet.";
document.getElementById("text_div").innerHTML = the_text.substring(localStorage.start_position || 0); // set text to previous position or to 0 (very beginning)
window.addEventListener("wheel", function(e) {
    if (e.deltaY < 0 && last_scroll_position > 0) { // move text upwards, unless already at the very beginning
        last_scroll_position--;
    }
    if (e.deltaY > 0 && last_scroll_position * scroll_speed < the_text.length ) { // move text downwards, unless reached end of text
        last_scroll_position++;
    }
    var text_start = last_scroll_position * scroll_speed; // speed up scrolling
    if (text_start != 0) { // unless the position is at the beginning, look for the first upcoming space (or the end of text) to show only entire words
        while (
            the_text.length > text_start &&
            the_text.charAt(text_start) != " "
        ) {
            text_start++;
        }
    }
    document.getElementById("text_div").innerHTML = the_text.substring(text_start); // set text according to position
    localStorage.scroll_position = last_scroll_position; // store scrolling position
    localStorage.start_position = text_start; // store starting character position
});

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

Ответ 5

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

Сохранение прогресса

Для хранения информации о ходе работы, когда пользователь читает статью. Когда пользователь посещает страницу через некоторое время, вы можете обнаружить, что пользователь пересматривает после долгой паузы, проверяя последний раз, когда был достигнут прогресс пользователя. Я бы сохранил прогресс как карту, где ключ - это идентификатор статьи, а значение - это объект, содержащий прогресс и дату. Прогресс может быть сохранен как процент с 0,0 в начале и 1.0 как конец. Я предлагаю использовать универсальную Date :: getTime в javascript для записи времени.

{
  "1234567": { progress: 0.5, date: 1529733009679 }
}

Измерение прогресса

Это зависит от содержания. Я составил список вещей, которые следует учитывать при записи и возвращении пользователя, где они остановились.

  • Содержимое может меняться со временем
  • Различные ширины символов шрифта могут варьироваться в зависимости от ширины браузера и разрывов строк и могут привести к неточным результатам. Это связано с тем, что большинство приложений имеют разную логику при решении перерыва абзаца слов на несколько строк.
  • Объявления различного размера могут меняться в зависимости от конфигурации и инвентаря
  • Адаптивные сайты могут изменять макет контента, а также показывать или скрывать контент
  • Типы контента
    • текст с параграфами
    • абзацы с изображениями
    • карусели, содержащие контент
    • видео

Вы понимаете. Этот список может стать огромным и может быть нелегким для обслуживания на некоторых сайтах. Просто запись положения прокрутки не является вариантом для макетов, содержащих текст, карусели или видео. Нам нужно подумать о том, что имеет общий контент. Похоже, что весь контент может быть разбит на куски, например, абзацы для текста или сцены для видео и т.д. Мы можем назвать каждую часть контрольной точкой. Я предлагаю найти детерминированный способ генерации контрольных точек прогресса в контенте и периодически регистрировать последний наблюдаемый контрольный пункт.

пример

Для статьи, содержащей абзацы и изображения, я бы обозначил каждый абзац и изображение как контрольную точку. Затем просто запишите контрольную точку, которую пользователь просматривает в данный момент. Мы можем определить последнюю просматриваемую контрольную точку, используя window.scrollY с HTMLElement.scrollTop для приложений браузера. Это будет работать, только если позиция каждой контрольной точки в содержимом будет одинаковой для всех макетов. Этот метод работает для всех проектов и всех типов контента. Логика может варьироваться в зависимости от вашего контента, поэтому я предлагаю абстрагировать эту функциональность для повторного использования, поскольку этот метод будет работать для всех типов контента. Надеюсь это поможет :)