Как сделать навигацию по страницам для многих, многих страниц? Логарифмическая навигация по страницам - PullRequest
16 голосов
/ 20 октября 2011

Какой лучший способ отображения навигации по страницам для многих, многих страниц?

(Первоначально это было опубликовано в виде практического руководства, в котором мой ответ включен в вопрос. Теперь я разделил свой ответ на раздел "ответы" ниже).


Чтобы быть более конкретным:

Предположим, вы выводите пользователю набор записей, разбитых на страницы фиксированного размера (например, результаты поиска в Google). Если страниц всего несколько, в конце результатов можно отобразить область навигации по страницам, которая может выглядеть следующим образом:

[<<] [<] 1 2 3 <strong>4 5 6 7 8 9 10 11 12 13 [>] [>>]

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

Иногда вы увидите такие вещи:

[<<] [<] ... 665 666 667 668 <strong>669 670 671 672 673 ... [>] [>>]

или это:

[<<] [<] 1 2 3 ... 667 668 <strong>669 670 671 ... 845 846 847 [>] [>>]

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

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

Как бы этого лучше всего достичь?

Ответы [ 4 ]

25 голосов
/ 07 января 2013

Вот мое решение - используйте «Логарифмическая навигация по страницам»:

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

1 2 3 4 5 6. 10 20 30 40 50 .. 100 ... 200. 210 220 230 240 250 252 253 254 255 256 257 258 259 260 261 262. 270 280 290 300 310 .. 400 .. 500 .. 600 .. 700 .. 800 .. 900 .. 950. 960 970 980 990 995 996 997 998 999 1000

Обратите внимание, что в промежутках нумерация переходит от 1 с до 10 с до 100 с (и т. Д.). (Я использую степени 10, но в принципе вы можете использовать другую схему - скажем, степени 2).

Я написал код, который делает это еще в 2004 году, и решил поделиться им здесь. Существуют версии PHP и ASP, но логика должна быть простой для перевода на любой язык. Обратите внимание, что бит внизу (в обоих случаях) просто отображает некоторые примеры. Очевидно, что форматирование будет нуждаться в настройке в соответствии с вашей веб-страницей (или приложением), поэтому здесь все довольно просто. Измените LINKS_PER_STEP в paginationHTML, чтобы определить количество отображаемых чисел до увеличения размера шага при удалении от конечных точек или текущей страницы.

Для более компактного вывода вы также можете рассмотреть изменение кода, чтобы нумерация не была "плотной" вокруг конечных точек (то есть плотной только вокруг текущей страницы).

Вот код:

Версия PHP:

<?

// Used by paginationHTML below...
function paginationLink($p, $page, $URL)
{
  if ($p==$page) return '<b style="color:#C0C0C0">' . $p . '</b>';
  return '<a href="' . $URL . $p . '">' . $p . '</a>';
}


// Used by paginationHTML below...
function paginationGap($p1, $p2)
{
  $x = $p2-$p1;
  if ($x==0) return '';
  if ($x==1) return ' ';
  if ($x<=10) return ' . ';
  if ($x<=100) return ' .. ';
  return ' ... ';
}


// URL requires the $page number be appended to it.
// e.g. it should end in '&page=' or something similar.
function paginationHTML($page, $lastPage, $URL)
{
  $LINKS_PER_STEP = 5;

  // Nav buttons

  if ($page>1)
    $result = '<form action="' . $URL . '1" method="POST" style="display:inline"><input type="submit" value="&nbsp;|&lt;&nbsp;"></form>&nbsp;' .
              '<form action="' . $URL . ($page-1) . '" method="POST" style="display:inline"><input type="submit" value="&nbsp;&lt;&nbsp;"></form>';
  else $result = '<input type="button" value="&nbsp;|&lt;&nbsp;" disabled>&nbsp;<input type="button" value="&nbsp;&lt;&nbsp;" disabled>';

  $result .= '&nbsp;&nbsp;' . $page . '&nbsp;&nbsp;';
  if ($page<$lastPage)
    $result .= '<form action="' . $URL . ($page+1) . '" method="POST" style="display:inline"><input type="submit" value="&nbsp;&gt;&nbsp;"></form>&nbsp;' .
               '<form action="' . $URL . $lastPage . '" method="POST" style="display:inline"><input type="submit" value="&nbsp;&gt;|&nbsp;"></form>';
  else $result .= '<input type="button" value="&nbsp;&gt;&nbsp;" disabled>&nbsp;<input type="button" value="&nbsp;&gt;|&nbsp;" disabled>';
  $result .= "<br>";

  // Now calculate page links...

  $lastp1 = 1;
  $lastp2 = $page;
  $p1 = 1;
  $p2 = $page;
  $c1 = $LINKS_PER_STEP+1;
  $c2 = $LINKS_PER_STEP+1;
  $s1 = '';
  $s2 = '';
  $step = 1;
  while (true)
  {
    if ($c1>=$c2)
    {
      $s1 .= paginationGap($lastp1,$p1) . paginationLink($p1,$page,$URL);
      $lastp1 = $p1;
      $p1 += $step;
      $c1--;
    }
    else
    {
      $s2 = paginationLink($p2,$page,$URL) . paginationGap($p2,$lastp2) . $s2;
      $lastp2 = $p2;
      $p2 -= $step;
      $c2--;
    }
    if ($c2==0)
    {
      $step *= 10;
      $p1 += $step-1;         // Round UP to nearest multiple of $step
      $p1 -= ($p1 % $step);
      $p2 -= ($p2 % $step);   // Round DOWN to nearest multiple of $step
      $c1 = $LINKS_PER_STEP;
      $c2 = $LINKS_PER_STEP;
    }
    if ($p1>$p2)
    {
      $result .= $s1 . paginationGap($lastp1,$lastp2) . $s2;
      if (($lastp2>$page)||($page>=$lastPage)) return $result;
      $lastp1 = $page;
      $lastp2 = $lastPage;
      $p1 = $page+1;
      $p2 = $lastPage;
      $c1 = $LINKS_PER_STEP;
      $c2 = $LINKS_PER_STEP+1;
      $s1 = '';
      $s2 = '';
      $step = 1;
    }
  }
}

?>

<br><br><br>
<?=paginationHTML(1,1,'?page=')?>

<br><br><br>
<?=paginationHTML(2,3,'?page=')?>

<br><br><br>
<?=paginationHTML(3,3,'?page=')?>

<br><br><br>
<?=paginationHTML(73,100,'?page=')?>

<br><br><br>
<?=paginationHTML(4,100,'?page=')?>

<br><br><br>
<?=paginationHTML(257,1000,'?page=')?>

<br><br><br>
<?=paginationHTML(7062,10555,'?page=')?>

<br><br><br>
<?=paginationHTML(22080,503456,'?page=')?>

ASP версия:

<%

' Used by paginationHTML below...
Function paginationLink(p, page, URL)
  if p=page then
    paginationLink = "<b style=""color:#C0C0C0"">" & p & "</b>"
  else
    paginationLink = "<a href=""" & URL & p & """>" & p & "</a>"
  end if
End Function


' Used by paginationHTML below...
Function paginationGap(p1, p2)
  Dim x
  x = p2-p1
  if x=0 then
    paginationGap = ""
  elseif x=1 then
    paginationGap = " "
  elseif x<=10 then
    paginationGap = " . "
  elseif x<=100 then
    paginationGap = " .. "
  else
    paginationGap = " ... "
  end if
End Function


' URL requires the page number be appended to it.
' e.g. it should end in "&page=" or something similar.
Function paginationHTML(page, lastPage, URL)
  const LINKS_PER_STEP = 5
  Dim p1, p2, c1, c2, s1, s2, lastp1, lastp2, step

  ' Nav buttons
  if page>1 then
    paginationHTML = "<form action=""" & URL & "1"" method=""POST"" style=""display:inline""><input type=""submit"" value=""&nbsp;|&lt;&nbsp;""></form>&nbsp;" & _
                    "<form action=""" & URL & (page-1) & """ method=""POST"" style=""display:inline""><input type=""submit"" value=""&nbsp;&lt;&nbsp;""></form>"
  else
    paginationHTML = "<input type=""button"" value=""&nbsp;|&lt;&nbsp;"" disabled>&nbsp;<input type=""button"" value=""&nbsp;&lt;&nbsp;"" disabled>"
  end if
  paginationHTML = paginationHTML & "&nbsp;&nbsp;" & page & "&nbsp;&nbsp;"
  if page<lastPage then
    paginationHTML = paginationHTML & "<form action=""" & URL & (page+1) & """ method=""POST"" style=""display:inline""><input type=""submit"" value=""&nbsp;&gt;&nbsp;""></form>&nbsp;" & _
                                    "<form action=""" & URL & lastPage & """ method=""POST"" style=""display:inline""><input type=""submit"" value=""&nbsp;&gt;|&nbsp;""></form>"
  else
    paginationHTML = paginationHTML & "<input type=""button"" value=""&nbsp;&gt;&nbsp;"" disabled>&nbsp;<input type=""button"" value=""&nbsp;&gt;|&nbsp;"" disabled>"
  end if
  paginationHTML = paginationHTML & "<br>"

  ' Now calculate page links...

  lastp1 = 1
  lastp2 = page
  p1 = 1
  p2 = page
  c1 = LINKS_PER_STEP+1
  c2 = LINKS_PER_STEP+1
  s1 = ""
  s2 = ""
  step = 1
  do
    if c1>=c2 then
      s1 = s1 & paginationGap(lastp1, p1) & paginationLink(p1, page, URL)
      lastp1 = p1
      p1 = p1+step
      c1 = c1-1
    else
      s2 = paginationLink(p2, page, URL) & paginationGap(p2, lastp2) & s2
      lastp2 = p2
      p2 = p2-step
      c2 = c2-1
    end if
    if c2=0 then
      step = step*10
      p1 = p1+step-1         ' Round UP to nearest multiple of step
      p1 = p1-(p1 mod step)
      p2 = p2-(p2 mod step)  ' Round DOWN to nearest multiple of step
      c1 = LINKS_PER_STEP
      c2 = LINKS_PER_STEP
    end if
    if p1>p2 then
      paginationHTML = paginationHTML & s1 & paginationGap(lastp1, lastp2) & s2
      if (lastp2>page) or (page>=lastPage) then exit do
      lastp1 = page
      lastp2 = lastPage
      p1 = page+1
      p2 = lastPage
      c1 = LINKS_PER_STEP
      c2 = LINKS_PER_STEP+1
      s1 = ""
      s2 = ""
      step = 1
    end if
  loop
End Function

%>


<br><br><br>
<%=paginationHTML(1,1,"?page=")%>

<br><br><br>
<%=paginationHTML(2,3,"?page=")%>

<br><br><br>
<%=paginationHTML(3,3,"?page=")%>

<br><br><br>
<%=paginationHTML(73,100,"?page=")%>

<br><br><br>
<%=paginationHTML(4,100,"?page=")%>

<br><br><br>
<%=paginationHTML(257,1000,"?page=")%>

<br><br><br>
<%=paginationHTML(7062,10555,"?page=")%>

<br><br><br>
<%=paginationHTML(22080,503456,"?page=")%>

Версия Javascript (на полной тестовой странице):

<!doctype html>
<html>
<head>
  <title>Logarithmic Pagination Demo</title>
  <style>

body {background:#C0C0C0;font-family:Arial,Helvetica,sans-serif;font-size:16px;text-align:left}
div {margin:0;padding:0}
div#setupDiv {margin:40px;text-align:center}
table#datarows {border-collapse:collapse;margin:40px auto}
table#datarows th {padding:5px 10px;background:#80B0FF;color:#FFFFFF;border:2px solid #80B0FF;width:1000px;text-align:center}
table#datarows td {padding:2px 10px;background:#FFFFFF;color:#D0D0D0;border:2px solid #80B0FF;width:1000px;text-align:left;font-style:italic}
input.err {border:2px solid #FF0000;background-color:#FFF0F0}
form.pager {display:table;margin:0 auto;padding:20px;border:2px solid #E0E0E0;border-radius:10px;background-color:#D0D0D0;text-align:left;white-space:nowrap}
form#pager1 {margin-top:40px}
form#pager2 {margin-bottom:60px}
form.pager div {display:table-cell;vertical-align:middle;padding:0 20px;white-space:nowrap}
form.pager div + div {border-left:2px solid #E0E0E0}
form.pager div.plinks {padding:0;border:0 none;font-size:14px;line-height:24px;max-width:800px;white-space:normal}
form.pager div.plinks b {display:inline-block;vertical-align:bottom;font-size:24px;line-height:21px;height:24px;overflow:hidden;color:#808080}
form.pager div.plinks a {text-decoration:none;color:black}
form.pager div.plinks a:hover {color:#0000FF;font-weight:bold}
form.pager div.plinks + div {border:0 none}

  </style>
  <script>

var NumPages, RecsPerPage, els1, els2, plinks1, plinks2;

function setupClick()
{
  var el, n, r;
  el = document.getElementById("NumPages");
  el.className = ((n = (el.value >>> 0)) ? "" : "err");
  el = document.getElementById("RecsPerPage");
  el.className = ((r = (el.value >>> 0)) ? "" : "err");
  if (n&&r) { NumPages = n; RecsPerPage = r; setupServerPage(); }
}

// This function sets up what would normally be part of the server's HTML output.
function setupServerPage()
{
  var totRecs = NumPages * RecsPerPage, tbdy = document.getElementById("datarows").tBodies[0], l = tbdy.rows.length;
  document.getElementById("plength1").innerHTML = document.getElementById("plength2").innerHTML = totRecs + " record" + ((totRecs===1)?"":"s") + "<br>" + NumPages + " page" + ((NumPages===1)?"":"s");
  els1["pcount"].value = els2["pcount"].value = NumPages;
  while (l>RecsPerPage) tbdy.deleteRow(--l);
  while (l<RecsPerPage) tbdy.insertRow(l++).insertCell(0).innerHTML = "Some data...";
  pageNavigate(1);
}

// This would be handled by a return trip to the server, if not using AJAX.
function pageClick(e)
{
  e = e||window.event;
  var s = e.target||e.srcElement, n, p, el;
  if (s.tagName==="A") { n = (p = s.href).lastIndexOf("=")+1; pageNavigate(p.substring(n) >>> 0); return false; }
  else if ((s.tagName!=="INPUT")||(s.type!=="submit")) return;
  if (!(n = s.name)) { p = ((el = this.elements["p"]).value >>> 0); if ((p<=0)||(p>NumPages)) { el.className = "err"; return false; }}
  else if (n==="p1") p = 1;
  else if (n==="pprev") p = (this.elements["pcurr"].value >>> 0)-1;
  else if (n==="pnext") p = (this.elements["pcurr"].value >>> 0)+1;
  else if (n==="plast") p = (this.elements["pcount"].value >>> 0);
  pageNavigate(p);
  return false;
}

// This would also be handled by a return trip to the server, or else data records could be retrieved via AJAX.
function pageNavigate(p)
{
  els1["p"].className = els2["p"].className = els1["p"].value = els2["p"].value = "";
  if (p<1) p = 1; else if (p>NumPages) p = NumPages;
  els1["p1"].disabled = els2["p1"].disabled = els1["pprev"].disabled = els2["pprev"].disabled = (p===1);
  els1["pnext"].disabled = els2["pnext"].disabled = els1["plast"].disabled = els2["plast"].disabled = (p===NumPages);
  els1["pcurr"].value = els2["pcurr"].value = p;
  // if the server is handling this, insert NON-logarithmic page links here (can be just first, current, and last page).
  plinks1.innerHTML = plinks2.innerHTML = logarithmicPaginationLinks(NumPages,p,"?p=");
}

// This function produces the logarithmic pagination links.
function logarithmicPaginationLinks(lastPage,matchPage,linkURL)
{
  function pageLink(p, page) { return ((p===page) ? "<b>"+p+"</b>" : '<a href="'+linkURL+p+'">'+p+"</a>"); }
  function pageGap(x) { if (x===0) return ""; if (x===1) return " "; if (x<=10) return " . "; if (x<=100) return " .. "; return " ... "; }

  var page = (matchPage ? matchPage : 1), LINKS_PER_STEP = 5, lastp1 = 1, lastp2 = page, p1 = 1, p2 = page, c1 = LINKS_PER_STEP+1, c2 = LINKS_PER_STEP+1, s1 = "", s2 = "", step = 1, linkHTML = "";

  while (true)
  {
    if (c1>=c2)
    {
      s1 += pageGap(p1-lastp1) + pageLink(p1,matchPage);
      lastp1 = p1;
      p1 += step;
      c1--;
    }
    else
    {
      s2 = pageLink(p2,matchPage) + pageGap(lastp2-p2) + s2;
      lastp2 = p2;
      p2 -= step;
      c2--;
    }
    if (c2===0)
    {
      step *= 10;
      p1 += step-1;        // Round UP to nearest multiple of step
      p1 -= (p1 % step);
      p2 -= (p2 % step);   // Round DOWN to nearest multiple of step
      c1 = LINKS_PER_STEP;
      c2 = LINKS_PER_STEP;
    }
    if (p1>p2)
    {
      linkHTML += s1 + pageGap(lastp2-lastp1) + s2;
      if ((lastp2>page)||(page>=lastPage)) break;
      lastp1 = page;
      lastp2 = lastPage;
      p1 = page+1;
      p2 = lastPage;
      c1 = LINKS_PER_STEP;
      c2 = LINKS_PER_STEP+1;
      s1 = '';
      s2 = '';
      step = 1;
    }
  }
  return linkHTML;
}

window.onload = function()
{
  els1 = document.getElementById("pager1").elements;
  els2 = document.getElementById("pager2").elements;
  plinks1 = document.getElementById("plinks1");
  plinks2 = document.getElementById("plinks2")
  document.getElementById("pager1").onclick = document.getElementById("pager2").onclick = pageClick;
  (document.getElementById("setupDiv").lastChild.onclick = setupClick)();
}

  </script>
</head>
<body>
  <div id="setupDiv">Select number of pages: <input type="text" id="NumPages" value="100" size="7"> &nbsp; &nbsp; and records per page:  <input type="text" id="RecsPerPage" value="20" size="7"> &nbsp; &nbsp; <input type="button" value=" Go "></div>

  <hr>

  <form id="pager1" class="pager" method="GET"><input type="hidden" name="pcount" value=""><input type="hidden" name="pcurr" value="1">
    <div>Go to page: <input type="text" name="p" size="7"> <input type="submit" value=" Go "></div>
    <div><input type="submit" name="p1" value=" |< " disabled> <input type="submit" name="pprev" value=" < " disabled></div>
    <div id="plinks1" class="plinks"></div>
    <div><input type="submit" name="pnext" value=" > "> <input type="submit" name="plast" value=" >| "></div>
    <div id="plength1"></div>
  </form>

  <table id="datarows"><thead><tr><th>Column Heading...</th></tr></thead><tbody></tbody></table>


  <form id="pager2" class="pager" method="GET"><input type="hidden" name="pcount" value=""><input type="hidden" name="pcurr" value="1">
    <div>Go to page: <input type="text" name="p" size="7"> <input type="submit" value=" Go "></div>
    <div><input type="submit" name="p1" value=" |< " disabled> <input type="submit" name="pprev" value=" < " disabled></div>
    <div id="plinks2" class="plinks"></div>
    <div><input type="submit" name="pnext" value=" > "> <input type="submit" name="plast" value=" >| "></div>
    <div id="plength2"></div>
  </form>
</body>
</html>
3 голосов
/ 06 июня 2013

Упрощенная версия «Логарифмическая навигация по страницам» в JavaScript:

Создает выпадающее меню. Этот пример просто document.write, но вы можете развивать его в соответствии с вашими потребностями (добавить onChange и т. Д.). Преобразование Javascript благодаря http://www.basereality.com/PHPToJavascript.

<script>

    function paginationLink(p, page)
    {
        if (p == page)
            return '<option selected value="' + p + '">' + p + '</option>';
        return '<option value="' + p + '">' + p+ '</option>';
    }

    function paginationHTML(page, lastPage)
    {
        var LINKS_PER_STEP = 5;

        // Now calculate page links...
        var lastp1 = 1;
        var lastp2 = page;
        var p1 = 1;
        var p2 = page;
        var c1 = LINKS_PER_STEP + 1;
        var c2 = LINKS_PER_STEP + 1;
        var s1 = '';
        var s2 = '';
        var step = 1;
        var result = 0;
        while (true)
        {
            if (c1 >= c2)
            {
                s1 += paginationLink(p1, page);
                lastp1 = p1;
                p1 += step;
                c1--;
            }
            else
            {
                s2 = paginationLink(p2, page) + s2;
                lastp2 = p2;
                p2 -= step;
                c2--;
            }
            if (c2 == 0)
            {
                step *= 10;
                p1 += step - 1;         // Round UP to nearest multiple of $step
                p1 -= (p1 % step);
                p2 -= (p2 % step);   // Round DOWN to nearest multiple of $step
                c1 = LINKS_PER_STEP;
                c2 = LINKS_PER_STEP;
            }
            if (p1 > p2)
            {
                result += s1 + s2;
                if ((lastp2 > page) || (page >= lastPage))
                    return result;
                lastp1 = page;
                lastp2 = lastPage;
                p1 = page + 1;
                p2 = lastPage;
                c1 = LINKS_PER_STEP;
                c2 = LINKS_PER_STEP + 1;
                s1 = '';
                s2 = '';
                step = 1;
            }
        }
    }

    document.write('Menu generated with JavaScript <select>' + paginationHTML(765, 5055))+'</select>';

</script>
1 голос
/ 22 февраля 2013

Как насчет:

a) добавить <-100 <-10 [pagination] +10> +100> вместо взрыва самой нумерации страниц

b) предложить прямой ввод страницы[# ..] [просмотреть], отфильтровать входные данные по допустимому диапазону страниц

c) требуется правильное кодирование, но: расширить внутренний плавающий диапазон, скажем, +/- 10, +/- 25+/- 100 страниц вместо того, чтобы взорвать весь диапазон подкачки

0 голосов
/ 07 января 2013

Я думаю о двух вариантах логарифмической нумерации страниц:

  1. Если это уместно, вы можете разбить свои данные на разделы, главы и книги. Это старый способ, когда бумага была королем, а библиотеки делали Интернет. Некоторые документы PDF все еще имеют это.

  2. Вы можете предложить окно поиска, в частности Wikipedia, если кто-то хочет перейти к той части больших данных, где упоминается supercalifragilisticexpialidocious.

...