Javascript запретить одновременный запуск нескольких таймеров setinterval. Таймеры в Javascript (setInterval, setTimeout). setTimeout с нулевой задержкой

Источник: http://learn.javascript.ru/settimeout-setinterval

Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.

В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.

setTimeout

Синтаксис:

var timerId = setTimeout(func/code, delay[, arg1, arg2...])

Параметры:

  • func/code
    • Функция или строка кода для исполнения.
    • Строка поддерживается для совместимости, использовать её не рекомендуется.
  • delay
    • Задержка в милисекундах, 1000 милисекунд равны 1 секунде.
  • arg1, arg2…
    • Аргументы, которые нужно передать функции. Не поддерживаются в IE9-.
    • Исполнение функции произойдёт спустя время, указанное в параметре delay .

Например, следующий код вызовет alert("Привет") через одну секунду:

function func () { alert("Привет" ); } setTimeout(func, 1000 );

Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.

То есть такая запись работает точно так же:

SetTimeout("alert("Привет")" , 1000 );

Вместо них используйте анонимные функции:

SetTimeout(function () { alert("Привет" ) }, 1000 );

Параметры для функции и контекст

Во всех современных браузерах, с учетом IE10, setTimeout позволяет указать параметры функции.

Пример ниже выведет "Привет, я Вася" везде, кроме IE9-:

function sayHi (who) { alert("Привет, я " + who); } setTimeout(sayHi, 1000 , "Вася" );

…Однако, в большинстве случаев нам нужна поддержка старого IE, а он не позволяет указывать аргументы. Поэтому, для того, чтобы их передать, оборачивают вызов в анонимную функцию:

function sayHi (who) { alert("Привет, я " + who); } setTimeout(function () { sayHi("Вася" ) }, 1000 );

Вызов через setTimeout не передаёт контекст this .

В частности, вызов метода объекта через setTimeout сработает в глобальном контексте. Это может привести к некорректным результатам.

Например, вызовем user.sayHi() через одну секунду:

function User (id) function () { alert(this .id); }; } var user = new User(12345 ); setTimeout(user.sayHi, 1000 ); // ожидается 12345, но выведет "undefined"

Так как setTimeout запустит функцию user.sayHi в глобальном контексте, она не будет иметь доступ к объекту через this .

Иначе говоря, эти два вызова setTimeout делают одно и то же:

// (1) одна строка setTimeout(user.sayHi, 1000 ); // (2) то же самое в две строки var func = user.sayHi; setTimeout(func, 1000 );

К счастью, эта проблема также легко решается созданием промежуточной функции:

function User (id) { this .id = id; this .sayHi = function () { alert(this .id); }; } var user = new User(12345 ); setTimeout(function () { user.sayHi(); }, 1000 );

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

Отмена исполнения

Функция setTimeout возвращает идентификатор timerId , который можно использовать для отмены действия.

Синтаксис:

ClearTimeout(timerId)

В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.

var timerId = setTimeout(function () { alert(1 ) }, 1000 ); clearTimeout(timerId);

setInterval

Метод setInterval имеет синтаксис, аналогичный setTimeout .

var timerId = setInterval(func/code, delay[, arg1, arg2...])

Смысл аргументов — тот же самый. Но, в отличие от setTimeout , он запускает выполнение функции не один раз, а регулярно повторяет её через указанный интервал времени. Остановить исполнение можно вызовом:

ClearInterval(timerId)

Следующий пример при запуске станет выводить сообщение каждые две секунды, пока вы не нажмете на кнопку «Стоп»:

<input type ="button" onclick ="clearInterval(timer)" value ="Стоп" > <script > var i = 1 ; var timer = setInterval(function () { alert(i++) }, 2000 ); script >

Очередь и наложение вызовов в setInterval

Вызов setInterval(функция, задержка) ставит функцию на исполнение через указанный интервал времени. Но здесь есть тонкость.

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

Для примера, возьмем setInterval(function() { func(i++) }, 100) . Она выполняет func каждые 100 мс, каждый раз увеличивая значение счетчика.

На картинке ниже, красный блок - это время исполнения func . Время между блоком — это время между запусками функции, и оно меньше, чем установленная задержка!

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

Бывает, что исполнение функции занимает больше времени, чем задержка. Например, функция сложная, а задержка маленькая. Или функция содержит операторы alert / confirm / prompt , которые блокируют поток выполнения. В этом случае начинаются интересные вещи.

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

Изображение ниже иллюстрирует происходящее для функции, которая долго исполняется.

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

Второй запуск функции происходит сразу же после окончания первого:

Больше одного раза в очередь выполнение не ставится.

Если выполнение функции занимает больше времени, чем несколько запланированных исполнений, то в очереди она всё равно будет стоять один раз. Так что «накопления» запусков не происходит.

На изображении ниже setInterval пытается выполнить функцию в 200 мс и ставит вызов в очередь. В 300 мс и 400 мс таймер пробуждается снова, но ничего не просходит.

Вызов setInterval(функция, задержка) не гарантирует реальной задержки между исполнениями.

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

Повторение вложенным setTimeout

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

Ниже — пример, который выдает alert с интервалами 2 секунды между ними.

<input type ="button" onclick ="clearTimeout(timer)" value ="Стоп" > <script > var i = 1 ; var timer = setTimeout(function run () { alert(i++); timer = setTimeout(run, 2000 ); }, 2000 ); script >

На временной линии выполнения будут фиксированные задержки между запусками. Иллюстрация для задержки 100мс:

Минимальная задержка таймера

У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.

По стандарту, минимальная задержка составляет 4мс. Так что нет разницы между setTimeout(..,1) и setTimeout(..,4) .

В поведении setTimeout и setInterval с нулевой задержкой есть браузерные особенности.

  1. В Opera, setTimeout(.., 0) — то же самое, что setTimeout(.., 4) . Оно выполняется реже, чем setTimeout(.. ,2). Это особенность данного браузера.
  2. В Internet Explorer, нулевая задержка setInterval(.., 0) не сработает. Это касается именно setInterval , т.е. setTimeout(.., 0) работает нормально.

Реальная частота срабатывания

Срабатывание может быть и гораздо реже В ряде случаев задержка может быть не 4мс, а 30мс или даже 1000мс.

Большинство браузеров (десктопных в первую очередь) продолжают выполнять setTimeout / setInterval , даже если вкладка неактивна. При этом ряд из них (Chrome, FF, IE10) снижают минимальную частоту таймера, до 1 раза в секунду. Получается, что в «фоновой» вкладке будет срабатывать таймер, но редко.

При работе от батареи, в ноутбуке — браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек. При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски setInterval будут пропущены.

Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.

Вывод интервалов в консоль Код, который считает интервалы времени между вызовами, выглядит примерно так:

var timeMark = new Date ; setTimeout(function go () { var diff = new Date - timeMark; // вывести очередную задержку в консоль вместо страницы console .log(diff); // запомним время в самом конце, // чтобы измерить задержку именно между вызовами timeMark = new Date ; setTimeout(go, 100 ); }, 100 );

Трюк setTimeout(func, 0)

Этот трюк достоин войти в анналы JavaScript-хаков.

Функцию оборачивают в setTimeout(func, 0) , если хотят запустить ее после окончания текущего скрипта.

Дело в том, что setTimeout никогда не выполняет функцию сразу. Он лишь планирует ее выполнение. Но интерпретатор JavaScript начнёт выполнять запланированные функции лишь после выполнения текущего скрипта.

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

Например:

var result; function showResult () { alert(result); } setTimeout(showResult, 0 ); result = 2 *2 ; // выведет 4

Итого

Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.

Оба метода возвращают идентификатор таймера. Его используют для остановки выполнения вызовом clearInterval / clearTimeout .

| | setInterval | setTimeout | || ----------- | ---------- | | Тайминг | Идет вызов строго по таймеру. Если интерпретатор занят — один вызов становится в очередь. Время выполнения функции не учитывается, поэтому промежуток времени от окончания одного запуска до начала другого может быть различным. | Рекурсивный вызов setTimeout используется вместо setInterval там, где нужна фиксированная пауза между выполнениями. | | Задержка | Минимальная задержка: 4мс. | Минимальная задержка: 4мс. | | Браузерные особенности | В IE не работает задержка 0. | В Opera нулевая задержка эквивалентна 4мс, остальные задержки обрабатываются точно, в том числе нестандартные 1мс, 2мс и 3мс. |

JavaScript timeout представляет собой нативную javascript-функцию , которая исполняет фрагмент кода после установленной временной задержки (в миллисекундах ). Это может пригодиться, когда нужно вывести всплывающее окошко после того, как пользователь провел некоторое время на вашей странице. Или нужно, чтобы эффект при наведении курсора на элемент запускался лишь спустя какое-то время. Таким образом, можно избежать непреднамеренного запуска эффекта, если пользователь навел курсор случайно.

Простой пример setTimeout

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

Посмотреть демо

Синтаксис

В документации MDN приведен следующий синтаксис для setTimeout :

var timeoutID = window.setTimeout(func, ); var timeoutID = window.setTimeout(code, );

  • timeoutID – числовой id , который можно использовать в сочетании с clearTimeout() для отключения таймера;
  • func – функция, которая должна быть выполнена;
  • code (в альтернативном синтаксисе ) – строка кода, которую нужно исполнить;
  • delay – длительность задержки в миллисекундах, после которой будет запущена функция. По умолчанию установлено значение 0.

setTimeout vs window.setTimeout

В приведенном выше синтаксисе используется window.setTimeout . Почему?

На самом деле, setTimeout и window.setTimeout – это практически одна и та же функция. Единственная разница заключается в том, что во втором выражении мы используем метод setTimeout как свойство глобального объекта window .

Лично я считаю, что это лишь сильно усложняет код. Если бы мы определили альтернативный метод JavaScript timeout , который может быть найден и возвращен в приоритетном порядке, то столкнулись бы с еще большими проблемами.

В данном руководстве я не хочу связываться с объектом window , но в целом, вы сами решаете, какой синтаксис стоит использовать.

Примеры использования

Это может быть название функции:

function explode(){ alert("Boom!"); } setTimeout(explode, 2000);

Переменная, которая обращается к функции:

var explode = function(){ alert("Boom!"); }; setTimeout(explode, 2000);

Или же анонимная функция:

setTimeout(function(){ alert("Boom!"); }, 2000);

  • Такой код плохо воспринимается, а, следовательно, его сложно будет модернизировать или отладить;
  • Он предполагает использование метода eval() , который может стать потенциальной уязвимостью;
  • Этот метод работает медленнее других, так как ему требуется запускать JavaScript-интерпретатор .

Также обратите внимание, что для тестирования кода мы используем метод alert для JavaScript timeout .

Передаем параметры в setTimout

В первом (к тому же, кроссбраузерном ) варианте мы передаем параметры в callback-функцию , исполняемую при помощи setTimeout .

В следующем примере мы выделяем случайное приветствие из массива greetings и передаем его в качестве параметра функции greet() , которая исполняется setTimeout с задержкой в 1 секунду:

function greet(greeting){ console.log(greeting); } function getRandom(arr){ return arr; } var greetings = ["Hello", "Bonjour", "Guten Tag"], randomGreeting = getRandom(greetings); setTimeout(function(){ greet(randomGreeting); }, 1000);

Посмотреть демо

Альтернативный метод

В синтаксисе, приведенном в начале статьи, существует еще один метод, с помощью которого можно передать параметры в callback-функцию , исполняемую JavaScript timeout . Данный метод подразумевает под собой вывод всех параметров, следующих после задержки.

Опираясь на предыдущий пример, мы получаем:

setTimeout(greet, 1000, randomGreeting);

Этот метод не будет работать в IE 9 и ниже, где передаваемые параметры расцениваются как undefined . Но для решения этой проблемы на MDN есть специальный полифилл .

Сопутствующие проблемы и “this”

Код, исполняемый setTimeout , запускается отдельно от функции, которой он был вызван. Из-за этого мы сталкиваемся с определенными проблемами, в качестве решения которых можно использовать ключевое слово this .

var person = { firstName: "Jim", introduce: function(){ console.log("Hi, I"m " + this.firstName); } }; person.introduce(); // Outputs: Hi, I"m Jim setTimeout(person.introduce, 50); // Outputs: Hi, I"m undefined

Причина такого вывода кроется в том, что в первом примере this ведет к объекту person , а во втором примере — указывает на глобальный объект window , у которого отсутствует свойство firstName .

Чтобы избавиться от этой нестыковки, можно воспользоваться несколькими методами:

Принудительно установить значение this

Это можно сделать при помощи bind() – метода, который создает новую функцию, которая при вызове в качестве значения ключа this использует определенное значение. В нашем случае — указанный объект person . Это в результате дает нам:

setTimeout(person.introduce.bind(person), 50);

Примечание: метод bind был представлен в ECMAScript 5 , а значит, что он будет работать только в современных браузерах. В других при его применении вы получите ошибку выполнения JavaScript «function timeout error » .

Использовать библиотеку

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

setTimeout($.proxy(person.introduce, person), 50);

Посмотреть демо

Отключение таймера

Возвращенное значение setTimeout представляет собой числовой id , который можно использовать для отключения таймера при помощи функции clearTimeout() :

var timer = setTimeout(myFunction, 3000); clearTimeout(timer);

Давайте посмотрим на нее в действии. В следующем примере, если кликнуть по кнопке «Start countdown », начнется обратный отсчет. После того, как он завершится, котята получат свое. Но если нажать кнопку «Stop countdown », таймер JavaScript timeout будет остановлен и сброшен.

Посмотреть пример

Подведем итоги

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

Чрезвычайно важно понимать, как работают JavaScript таймеры. Зачастую их поведение не совпадает с нашим интуитивным восприятием многопоточности, и это связано с тем, что в действительности они выполняются в одном потоке. Давайте рассмотрим четыре функции, с помощью которых мы можем управлять таймерами:

  • var id = setTimeout(fn, delay); - Создает простой таймер, который вызовет заданную функцию после заданной задержки. Функция возвращает уникальный ID, с помощью которого таймер может быть приостановлен.
  • var id = setInterval(fn, delay); - Похоже на setTimeout, но непрерывно вызывает функцию с заданным интервалом (пока не будет остановлена).
  • clearInterval(id);, clearTimeout(id); - Принимает таймер ID (возвращаемый одной из функций, описанных выше) и останавливает выполнение callback"a.
Главная идея, которую нужно рассмотреть, заключается в том, что точность периода задержки таймера не гарантируется. Начнем с того, что браузер исполняет все асинхронные JavaScript-события в одном потоке (такие как клик мышью или таймеры) и только в то время, когда пришла очередь этого события. Лучше всего это демонстрирует следующая диаграмма:

На этом рисунке довольно много информации, которую нужно усвоить, но понимание этого даст вам более глубокое понимание механизма работы асинхронности выполнения JavaScript. на этой диаграмме вертикально представлено время в миллисекундах, синие блоки показывают блоки JavaScript кода, который был выполнен. Например, первый блок выполняется в среднем за 18мс, клик мышью блокирует выполнение примерно на 11мс и т.д.

JavaScript может выполнять только одну порцию кода (из-за однопоточной природы выполнения), каждая из которых блокирует выполнение других асинхронных событий. Это значит, что при возникновении асинхронного события (такого как клик мышью, вызов таймера или завершение XMLHttp-запроса) он добавляется в очередь и выполняется позже (реализация, конечно же, варьируется в зависимости от браузера, но давайте условимся называть это «очередью»).

Для начала представим, что внутри JavaScript блока стартуют два таймера: setTimeout с задержкой 10мс и setInterval с такой же задержкой. В зависимости от того, когда стартует таймер, он сработает в момент, когда мы еще не завершили первый блок кода. Заметьте, однако, что он не срабатывает сразу (это невозможно из-за однопоточности). Вместо этого отложенная функция попадает в очередь и исполняется в следующий доступный момент.

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

После того, как первый блок JavaScript кода был выполнен, браузер задается вопросом «Что ожидает исполнения?». В данном случае обработчик клика мышью и таймер находятся в состоянии ожидания. Браузер выбирает один из них (обработчик клика) и выполняет его. Таймер будет ожидать следующей доступной порции времени в очереди на исполнение.

Заметьте, что пока обработчик клика мышью выполняется, срабатывает первый interval-callback. Так же как и timer-callback, он будет поставлен в очередь. Тем не менее, учтите, что когда снова сработает interval (пока будет выполняться timer-callback), то он будет удален из очереди. Если бы все interval-callback"и попадали в очередь пока исполняется большой кусок кода, это бы привело к тому, что образовалась бы куча функций, ожидающих вызова без периодов задержек между окончанием их выполнения. Вместо этого браузеры стремятся ждать пока не останется ни одной функции в очереди прежде чем добавить в очередь еще одну.

Таким образом, мы можем наблюдать случай, когда третье срабатывание interval-callback совпадает с тем моментом, когда он уже исполняется. Это иллюстрирует важную особенность: интервалы не заботятся о том, что выполняется в текущий момент, они будут добавлены в очередь без учета периода задержки между исполнениями.

Наконец, после того как второй interval-callback завершится, мы увидим что не осталось ничего, что JavaScript-движок должен выполнить. Это значит, что браузер снова ждет появления новых асинхронных событий. Это случится на отметке 50мс, где interval-callback сработает опять. В этот момент не будет ничего, что блокировало бы его, поэтому он сработает незамедлительно.

Давайте рассмотрим пример, который хорошо иллюстрирует разницу между setTimeout и setInterval.
setTimeout(function(){ /* Some long block of code... */ setTimeout(arguments.callee, 10); }, 10); setInterval(function(){ /* Some long block of code... */ }, 10);
Эти два варианта эквивалентны на первый взгляд, но на самом деле это не так. Код, использующий setTimeout будет всегда иметь задержку хотя бы 10мс после предыдущего вызова (он может быть больше, но никогда не может быть меньше), тогда как код, использующий setInterval будет стремиться вызываться каждые 10мс независимо от того, когда отработал предыущий вызов.

Давайте резюмируем все сказанное выше:
- JavaScript движки используют однопоточную среду, преобразовывая асинхронные события в очередь, ожидающую исполнения,
- Функции setTimeout и setInterval принципиально по-разному исполняются в асинхронном коде,
- Если таймер не может быть выполнен в данный момент, он будет отложен до следующей точки исполнения (которая будет дольше, чем желаемая задержка),
- Интервалы (setInterval) могут исполняться друг за другом без задержек, если их исполнение занимает больше времени, чем указанная задержка.

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

В программировании на скриптовых языках периодически возникает необходимость создать паузу – приостановить выполнение программы на некоторое время, а потом продолжить работу. Например, в сценариях VBS и PHP возможны такие методы:

VBS: wscript.sleep 1500 (остановка на 1.5 секунды)

PHP: sleep(10); (остановка на 10 секунд)

Во время подобных пауз исполняющая система (PHP или VBS) ничего не делает . Разработчик, попытавшись интуитивно использовать нечто подобное в Javascript, будет неприятно удивлён. Типичная ошибка при попытке создать паузу в Javascript выглядит так:

Function badtest() { for (var i=1; i < 10; i++) { window.setTimeout("document.getElementById("test1").value += " + i, 900) } }

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

  1. i = 1;
  2. откладываем добавление цифры "1" к полю ввода на 0.9 секунды;
  3. немедленно за постановкой этой задачи цикл идёт дальше: i=2;
  4. откладываем добавление цифры "2" к полю ввода на 0.9 секунды;

Немедленно означает, например, 1 мс (то есть несоизмеримо мало, по сравнению с 900 мс): цикл прозведёт свою работу практически мгновенно, создав несколько отложенных задач от одной и той же точки времени. Это значит, все отложенные задачи по "рисованию" будут выполнены практически в одно и то же время, без пауз между добавлением новых цифр. Цикл запускается; всё замирает на 0.9 с; и ширрр – все цифры выстреливаются в ряд одна за другой.

А как в подобном случае правильно применить setTimeout ? Это сложно. Придётся вызывать функцию рекурсивно (изнутри функции ту же самую функцию), а чтобы этот процесс не был бесконечным, задать условие остановки (например, величину печатаемого числа):

Function welltest() { if (i < 9) { document.getElementById("test2").value += ++i window.setTimeout("welltest()", 400) } }

И ещё переменную i придётся инициализировать вне функции – например, так:

Вот теперь всё работает, как надо (мы уменьшили время задержки с 0.9 с до 0.4 с). Но для подобных задач логичнее всё-таки применять не setTimeout а setInterval (хотя при этом понадобится две функции):

Function besttest() { window.i = 0 window.timer1 = window.setInterval("draw()", 400) } function draw() { document.getElementById("test3").value += ++i if (i >= 9) clearInterval(window.timer1) }

Особенность метода Javascirpt setInterval в том, что он не проходит «сам собой», его надо останавливать специальным методом clearInterval . А чтобы было понятно, что именно останавливать, задаче по отложенному действию присваивается специальнй идентификатор – таймер: window.timer1 = window.setInterval(...) .

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

Вот пример страницы с несколькими Javascript-таймерами, работающими одновременно: setinterval.htm (Javascript-функции в файле setinterval.js). Работу всех таймеров страницы (кроме меню) можно остановить клавишей Esc . Все таймеры примеров опираются на «естественный» (а не абстрактное i++ ) отсчёт – времени или расстояния. Все «часы» специально рассинхронизированы (для наглядности). Таймеры, зависящие от расстояния, используются в «индикаторе» и в выпадающем («выезжающем») меню.

Выпадающее меню

Наше выезжающее меню – реально выезжающее (из-под «шапки»): между элементами специально оставлены зазоры, чтобы видеть, как оно выезжает. Неожиданно оказалось, что мы не можем сделать одинаково плавный выезд для списков разной длины – вероятно, из-за низкой производительности компьютера (AMD Athlon 999 МГц).

Достаточно очевидно, что для красоты и гармонии нужно, чтобы списки разных пунктов меню выпадали за одно и то же время. То есть более длинные списки должны выпадать с более высокой скоростью, более короткие – с меньшей скоростью. Казалось бы, это можно реализовать так:

  1. Устанавливаем общее время «выезжания», например, в 200 мс.
  2. Если выпадающий список имеет высоту 20 px, очевидно, что мы можем двигать его вниз по одному пикселу за интервал 10 мс – и тогда за 200 мс список вылезет весь.
  3. Если выпадающий список имеет высоту 40 px, чтобы уложиться в то же время, мы должны двигать его вниз по одному пикселу за 5 мс.

По этой логике, если выпадающий список имеет высоту 200 px, мы должны двигать его вниз по одному пикселу за 1 мс. Но такая скорость на нашем компьютере не прокатывает – браузер просто не успевает отрисовывать новое положение списка за одну миллисекунду. Да. Javascript считать успевает (что там считать-то?), а браузер (Firefox) отображать не успевает. Типичная ситуация для веб.

Поэтому более-менее уровнять время выезжания меню можно только с помощью костылей, и ещё неясно, как это будет работать на более быстром компьютере. Но мы ведь должны рассчитывать на самый медленный? Алгоритм (без учёта быстродействия компьютера) получается примерно такой:

  1. Устанавливаем общее время выезжания списка: time = 224 (ms).
  2. Устанавливаем минимальное время для одного интервала в цикле: delay = 3 (ms).
  3. Устанавливаем минимальный шаг для движения списка: offset = 1 (px).
  4. Меняем всё это в зависимости от высоты списка: 1) увеличиваем время задержки (интервала) обратно пропорционально высоте и прямо пропорционально общему времени time (при высоте 224 коэффициент равен 1); 2) если высота больше 40 px, увеличиваем минимальный шаг пропорционально высоте. Константа "40" получена опытным путём для наиболее медленного компьютера. Тесты на компьютере Pentium 4 CPU 2.53GHz выявили точно такое же число – 40. Иначе таймеры идут вразнос, списки выезжают не в ногу.

Вот теперь списки более-менее выезжают. За более-менее похожее время. На странице setinterval.htm .

А вот и Брю-ус:

Function slide_do(obj, maxtop, offset) { if (getTopLeft(obj).top < maxtop) { obj.style.top = getTopLeft(obj).top + offset } else { if (obj && obj.timer1) { clearInterval(obj.timer1) obj.timer1 = null } } }

Сама функция, выдвигающая вложенные списки из меню, как видим, очень проста. Осталось только запустить её примерно такой строкой:

Ts.timer1 = setInterval(function(){slide_do(ts, maxtop, offset)}, delay)

Ну, а перед запуском только вычислить все эти maxtop и offset, а также поместить список в положение mintop. Чем и занимается «предварительная» функция slide() размером в 40 строк. А всё вместе - в файле setinterval.js . Да, и эта хрень ни хрена не будет работать без подключенного файла стилей


Top