Еще один довод в пользу манипулирования свойством src тега <img> – это способ_ ность к анимации; когда смена изображений происходит достаточно часто, соз_ дается иллюзия плавного движения. Типичное применение этой методики – ото_ бражение серии погодных карт, иллюстрирующих существовавший или прогно_ зируемый процесс развития штормовой ситуации в часовых интервалах за двух_ дневный период.
В примере 22.4 приводится определение класса ImageLoop, с помощью которого создаются подобного рода эффекты. Он демонстрирует те же приемы работы со свойством src и предварительной загрузки изображений, которые были показа_ ны в примере 22.1. Здесь также добавлен обработчик события onload объекта Im_ age, который определяет момент окончания загрузки изображения (или, в дан_ ном случае, – серии изображений). Программный код, реализующий анимацию, управляется методом Window.setInterval(), который сам по себе чрезвычайно прост: он наращивает номер кадра и записывает в свойство src указанного тега <img> URL_адрес изображения для следующего кадра.
Вот пример HTML_файла, в котором используется класс ImageLoop:
Программный код примера 22.4 получился несколько сложнее, чем можно было бы ожидать, потому что и обработчик события Image.onload и функция таймера Window.setInterval() вызывают функции как функции, а не как методы. По этой причине в конструкторе ImageLoop() потребовалось определить вложенные функ_ ции, «знающие», как взаимодействовать с вновь созданным объектом ImageLoop.
Пример 22.4. Анимация
/**
* ImageLoop.js: Класс ImageLoop для создания эффекта анимации
*
* Аргументы конструктора:
22.1. Работа с готовыми изображениями
* imageId: идентификатор тега <img>, в котором воспроизводится анимация
* fps: количество кадров в секунду
* frameURLs: массив URL_адресов, по одному на каждый кадр в анимации
*
* Общедоступные методы:
* start(): начинает анимацию (но ждет, пока загрузятся все кадры)
* stop(): останавливает анимацию
*
* Общедоступные свойства:
* loaded: true _ если все кадры были загружены, иначе _ false
*/
function ImageLoop(imageId, fps, frameURLs) {
// Запомнить id элемента. Не искать его сейчас, потому что конструктор
// может быть вызван еще до того, как документ будет полностью загружен. this.imageId = imageId;
// Рассчитать время задержки между кадрами
this.frameInterval
= 1000/fps;
// Создать массив,
где будут
храниться объекты Image для каждого кадра
this.frames = new Array(frameURLs.length);
this.image = null;
//
Элемент <img>, найденный по атрибуту id
this.loaded = false;
//
Еще не все изображения загружены
this.loadedFrames = 0;
//
Количество загруженных кадров
this.startOnLoad =
false; //
Начать воспроизведение по окончании загрузки?
this.frameNumber =
_1;
//
Текущий отображаемый кадр
this.timer = null;
//
Возвращаемое значение функции setInterval()
// Инициализировать массив frames[] и загрузить изображения
for(var i = 0; i <
frameURLs.length; i++) {
this.frames[i]
= new Image( );
// Создать объект Image
// Зарегистрировать обработчик события, чтобы узнать,
// когда будет загружено изображение
this.frames[i].onload = countLoadedFrames; //
Определяется позже
this.frames[i].src = frameURLs[i];
//
Загрузить изображение
}
// Эта вложенная функция _ обработчик события, который подсчитывает
// количество загруженных кадров. Когда все изображения будут загружены,
// устанавливает флаг и в случае необходимости начинает анимацию.
var loop = this;
function countLoadedFrames() { loop.loadedFrames++;
if (loop.loadedFrames == loop.frames.length) { loop.loaded = true;
if (loop.startOnLoad) loop.start();
}
}
// Далее определяется функция, которая отображает следующий кадр анимации.
// Эта функция не может быть обычным методом, т. к. setInterval() может
// вызывать только функции, а не методы.
// Поэтому здесь создается замыкание, включающее ссылку на объект ImageLoop this._displayNextFrame = function() {
// Сначала нарастить номер кадра. Оператор деления по модулю (%)
// выполняет переход от последнего кадра к первому
554 Глава 22. Работа с графикой на стороне клиента
loop.frameNumber = (loop.frameNumber+1)%loop.frames.length; // Записать в свойство src URL_адрес нового кадра loop.image.src = loop.frames[loop.frameNumber].src;
};
}
/**
* Этот метод начинает анимацию ImageLoop. Если загрузка кадров еще
* не закончилась, он просто взводит флаг, в результате чего анимация
* начинается автоматически по окончании загрузки
*/
ImageLoop.prototype.start = function() {
if (this.timer != null) return; // Анимация уже начата
// Если загрузка еще не закончилась, установить флаг запуска if (!this.loaded) this.startOnLoad = true;
else {
// Если элемент <img> еще не был найден по id, сделать это сейчас
if (!this.image) this.image = document.getElementById(this.imageId);
// Сразу же отобразить первый кадр
this._displayNextFrame();
// И взвести таймер для воспроизведения последующих кадров