Статья
Версия для печати
Обсудить на форуме
Галерея на GreyBox'е.


Автор: Sla
Написано: 21.09.2010

Для своих нужд понадобилось сделать фотогалерею. Казалось бы, что еще проще? Возьми готовое и вперед. Пересмотрел большое количество галерей, и в каждой мне что-то не нравилось. То привязка к jquery или к другому фреймворку, то размер, то не совсем понятная навигация. А хотелось что-то такого своего, маленького, понятного. Так нашелся jsimagebox. Казалось, что еще надо? Я ее даже применил, описывая прогулку по прекраснейшим местам Харьковщины.
После решения этой проблемы поиски прекратились. Но что-то в ней мне тогда не понравилось. Скорей всего, прописывание onclick на изображении. И мне тогда не удалось заставить его корректно динамически подгружать изображения. А тут появился в переработку сайт с установленным lightbox'ом. Я когда глянул в код, ужаснулся, и мне быстро-быстро захотелось с него спрыгнуть. Lightbox не понравился только мне, и это не значит, что он не понравится кому-то еще.
Я пересмотрел опять определенное количество галерей, мой выбор пал на GreyBox. Достаточно интересный скрипт, работающий с iframe, не знаю, плохо это или хорошо, динамически подгружаемые изображения. Один из недостатков, на мой взгляд, — плохая физика смены эффектов. Но эффекты можно было отключить, что я и сделал. Я даже встроил в свою админку включение сервиса галереи.
Прошло некоторое время; и тут мне в руки попалось огромное количество фотографий с Камчатки, и мне не хотелось каждый раз щелкать, а захотелось запустить слайд-шоу и наслаждаться сменой картинки, откинувшись в кресле, заложив руки за голову.
Наконец-то подошли к телу делу.

Подготовительные работы.

Первым делом сделал кнопку Play, переделал кнопки Prev и Next, потом, чуть позже, когда все уже запустилось, сделал кнопку Stop.

Изменения в скрипте и этапы написания функционала.

В скрипте gb_scripts.js описан объект GB_Sets, который и отвечает за навигацию при просмотре.

Код:
GB_Sets = GB_Gallery.extend({
init:function(_3c,set){
this.parent(_3c);
if(!this.img_next){ this.img_next=this.root_dir + "next_new.gif"; }
if(!this.img_prev){ this.img_prev=this.root_dir + "prev_new.gif"; }
//Добавлено для слайд-шоу
if(!this.img_play){ this.img_play=this.root_dir + "play_new.gif"; }
if(!this.img_stop){ this.img_stop=this.root_dir + "stop_new.gif"; }
this.play = false;
this.pause = 5000; //время задержки
/**********************/
this.current_set=set;
},
showSet:function(_3e){
this.current_index=_3e;
var _3f = this.current_set[this.current_index];
this.show(_3f.url);
this._setCaption(_3f.caption);
this.btn_prev=AJS.IMG({"class":"lefti",src:this.img_prev,title:"Предыдущая",alt:""});
this.btn_next=AJS.IMG({"class":"righti",src:this.img_next,title:"Следующая",alt:""});
//Добавлено для слайд-шоу — Создаем кнопку Play
this.btn_play=AJS.IMG({"class":"righti",src:this.img_play,title:"Запустить слайд-шоу",alt:""});
/**********************/
AJS.AEV(this.btn_prev,"click",AJS.$b(this.switchPrev,this));
AJS.AEV(this.btn_next,"click",AJS.$b(this.switchNext,this));
//Добавлено для слайд-шоу — Привязка события onClick к кнопке Play
AJS.AEV(this.btn_play,"click",AJS.$b(this.switchPlay,this));
        /**********************/
GB_STATUS=AJS.SPAN({"class":"GB_navStatus"});
AJS.ACN(AJS.$("GB_middle"),this.btn_prev,GB_STATUS,this.btn_next,this.btn_play);
        /***********************************вот здесь добавляем кнопку Play контейнер ***/
this.updateStatus();
},

Замечательно. init сделан, Кнопка Play показывается.
Чтобы проверить, срабатывает ли событие, пишу тест-заглушку, которую потом продолжу работающим кодом:

Код:
		switchPlay:function(){
    alert("Play");
}

Теперь бы проверить хождение по кругу:

Код:
		switchPlay:function(){
            if(this.current_index < this.current_set.length - 1){
                this.current_index++;
            } else {
                this.current_index = 0;
            }
            this.updateFrame();
            this.updateStatus();
}

Итак, все, вроде, готово к запуску, нужно прикрутить таймер. В арсенале javascript существует метод setTimeout, который выполняет код (или функцию), указанный в первом аргументе, асинхронно, с задержкой в delay миллисекунд.
Какой код выполнять? А здесь сейчас его и опишу, впрочем, он уже описан в предыдущем коде. Но лучше сделать для него отдельный метод playPlay.

Код:
	playPlay:function(){
if(this.current_index < this.current_set.length - 1){
this.current_index++;
} else {
this.current_index = 0;
}
this.updateFrame();
this.updateStatus();
}

Внимание! Следующий код ошибочный. Так делать нельзя!

Код:
	switchPlay:function(){
if (this.play == false) {
this.play = true;
this.btn_play.src = this.img_stop;
this.btn_play.title = "Остановить слайд-шоу";
this.timer=setTimeout(function () {this.playPlay()},this.pause);
/**************************************ТАК ДЕЛАТЬ НЕЛЬЗЯ ***************/
} else {
this.play = false;
clearTimeout(this.timer);
this.btn_play.src = this.img_play;
this.btn_play.title = "Запустить слайд-шоу";
}
}

Но это мой первый опыт работы с таймером и объектами, мне можно, потому и сделал. Ничего страшного не произошло, через 5 сек скрипт останавливался из-за ошибки. Я уже себя корил, мол, взялся за неизвестное, не зная броду... Да не знал, но опыт есть. Ведь я привязываю локальный метод к глобальному пространству... Нужно передавать объект с методом. И быстро-быстро все исправляю:

Код:
	switchPlay:function(){
if (this.play == false) {
this.play = true;
this.btn_play.src = this.img_stop;
this.btn_play.title = "Остановить слайд-шоу";
/*********** Вот как надо. ********/
var tGB_Sets = this;
this.timer = setTimeout(function () {tGB_Sets.playPlay()},this.pause);
/***************************************   Вот ОНО *****************/

} else {
this.play = false;
clearTimeout(this.timer);
this.btn_play.src = this.img_play;
this.btn_play.title = "Запустить слайд-шоу";
}
}

Но тут опять неприятность. Происходит смена только одной картинки. Понятное дело — setTimeout устанавливает интервал для одиночного выполнения функции. Поэтому ... Вот как выглядит готовый код

Код:
	playPlay:function(){
if(this.current_index < this.current_set.length - 1){
this.current_index++;
} else {
this.current_index = 0;
}
this.updateFrame();
this.updateStatus();
var tGB_Sets = this;
this.timer = setTimeout(function () {tGB_Sets.playPlay()},this.pause);
}

В ходе написания заметки, появилось предложение закешировать следующее изображение, для того, чтобы смена изображений происходила более плавно. Создаем еще один метод в объекте GB_Sets:

Код:
	preloadNext: function(){
if(this.current_index < this.current_set.length-1){
next_index = this.current_index+1;
} else {
next_index = 0;
}
if (!this.current_set[next_index].pre) {
var pre = new Image();
pre.src = this.current_set[next_index].url;
this.current_set[next_index].pre = 1;
}
}

Здесь появилось еще одно свойство коллекции current_set[].pre, которое указывает, что предзагрузка уже произошла. Зачем? Не знаю, но посчитал, что так правильно, зачем нагружать канал уже однажды загруженным изображением. Этот метод можно также использовать и в методе switchNext.
И окончательный код. Таким образом, метод playPlay примет следующий вид:

Код:
	playPlay:function(){
if(this.current_index < this.current_set.length-1){
this.current_index++;
} else {
this.current_index=0;
}
this.updateFrame();
this.updateStatus();
this.preloadNext();
var tGB_Sets=this;
this.timer=setTimeout(function () {tGB_Sets.playPlay()},this.pause);
}

P.S.: У родного скрипта есть недостаток в качестве локализации. Все текстовые сообщения разбросаны по коду. Конечно, собрать все сообщения и тесты в одном глобальном языковом массиве, и будет это хорошо.

P.P.S.: Работа скрипта проверена в IE8, Opera10, Crome 6, FF 3.6.10
Версия для печати
Обсудить на форуме