Что может заставить функцию unbind в jQuery работать не так, как ожидалось?

#jquery #jquery-plugins #scroll #unbind

#jquery #jquery-плагины #прокрутка #отменить привязку

Вопрос:

Взгляните на следующий код (дополнительно вам понадобится jquery.js, jquery.viewport.js и jquery.scrollTo.js ).

Поведение, которое я ожидал бы от этого скрипта, заключается в том, что всякий раз, когда я прокручиваю страницу, красные строки ( <tr> элементы с классом alwaysVisible ) должны вставляться прямо под самой верхней видимой строкой ( <tr> элементом) этой таблицы. Затем страницу следует прокрутить так, чтобы первая из этих красных строк отображалась «точно» в верхней части окна просмотра. Что на самом деле происходит, так это то, что makeVisibleWhatMust(); вызывается неоднократно, пока я не дойду до конца страницы. Я думал, что $(window).unbind('scroll'); это удержит makeVisibleWhatMust(); от повторного вызова, но, по-видимому, это не работает.

Есть идеи, почему?

Вот JavaScript, который я написал:

 function makeVisibleWhatMust()
{
  $('#testContainer').text( $('#testContainer').text()   'calledn');
  $('table.scrollTable').each
  (
    function()
    {
      var table = this;
      $($('tr.alwaysVisible', table).get().reverse()).each
      (
    function()
    {
      $(this).insertAfter( $('tr:in-viewport:not(.alwaysVisible)', table)[0] );
    }
      );
      $(window).unbind('scroll');
      $(window).scrollTo( $('tr.alwaysVisible')[0] );
      $(window).bind('scroll', makeVisibleWhatMust);
    }
  );
}

$(document).ready
(
  function()
  {
    $(window).bind('scroll', makeVisibleWhatMust);
  }
);
  

И вот HTML-страница для ее тестирования:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Scroll Tables Test Page</title>

    <script type="text/javascript" src="jQuery.js"></script>
    <script type="text/javascript" src="jquery.viewport.js"></script>
    <script type="text/javascript" src="jquery.scrollTo.js"></script>
    <script type="text/javascript" src="scrollTable.js"></script>

    <style type="text/css">
      table th, table td
      {
    border: 1px solid #000;
    padding: .3em;
      }
      .alwaysVisible
      {
    background: #F66;
      }
    </style>
  </head>
  <body>
    <table class="scrollTable">
      <thead>
    <tr class="alwaysVisible">
      <th>Row name</th>
      <th>Content</th>
    </tr>
    <tr class="alwaysVisible">
      <th>Row 2</th>
      <th>Row 2</th>
    </tr>
      </thead>
      <tbody>
    <script type="text/javascript">
      for(var i = 0; i < 50;   i)
      {
        document.writeln("<tr><td>Row "   i   "</td><td>Content</td></tr>");
      }
    </script>
      </tbody>
      <tfoot>
    <tr>
      <td>Footer</td>
      <td>Footer 2</td>
    </tr>
      </tfoot>
    </table>
    <div id="testContainer">TEST CONTAINER</div>
  </body>
</html>
  

Ответ №1:

Я думаю, ваша проблема в том, что scrollTo использует animate :

 // From the plugin's source
function animate( callback ){  
    $elem.animate( attr, duration, settings.easing, callback amp;amp; function(){  
        callback.call(this, target, settings);  
    });  
};
  

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

Простым решением было бы использовать флаг, чтобы сообщить, makeVisibleWhatMust что scrollTo выполняется прокрутка, и использовать scrollTo обратный вызов, чтобы снять флаг, когда это будет сделано, что-то вроде этого:

 function makeVisibleWhatMust() {
  // Ignore the event if we're doing the scrolling.
  if(makeVisibleWhatMust.isScrolling)
    return;
  $('#testContainer').text( $('#testContainer').text()   'calledn');
  $('table.scrollTable').each(function() {
      var table = this;
      $($('tr.alwaysVisible', table).get().reverse()).each(function() {
        $(this).insertAfter( $('tr:in-viewport:not(.alwaysVisible)', table)[0] );
      });
      makeVisibleWhatMust.isScrolling = true;
      $(window).scrollTo($('tr.alwaysVisible')[0], {
        onAfter: function() { makeVisibleWhatMust.isScrolling = false; }
      });
    }
  );
}
makeVisibleWhatMust.isScrolling = false;
  

И вот живая версия, которая, похоже, работает:http://jsfiddle.net/ambiguous/ZEx6M/1 /

Комментарии:

1. @mu слишком короткое: я думаю, вы правы насчет проблемы, но ваше решение не работает. Я получаю точно такой же результат, как и раньше. Теперь я действительно не понимаю..

2. @Shawn: У меня была опечатка в JavaScript (слишком много закрывающих круглых скобок). Я исправил это и добавил ссылку jsfiddle.

3. @mu слишком короткое: так что это работает! Это очень любопытно, потому что точно такой же код не работает, когда я тестирую его «локально», но работает в JSFiddle. Интересно, чем может быть объяснена эта разница в поведении между моей «локальной настройкой» (сохранение кода в текстовых файлах и открытие html-файла в Firefox) и JSFiddle (также запущенным в Firefox).. Я думаю, эта проблема заслуживает отдельного вопроса. В любом случае, спасибо за (рабочее) решение!

4. @Shawn: Работает ли полностью развернутая версия скрипки? Попробуйте это: fiddle.jshell.net/ambiguous/ZEx6M/1/show/light

5. @mu слишком короткий: Да, это работает отлично (или, по крайней мере, это работает так, как ожидалось! Я действительно думаю, что конечный результат немного изменчивый, но это уже другая история : p)