Плеер картинок

Преамбула

Если Вы занимаетесь веб-программированием, трудно быть специалистом только в одной области. Я занимаюсь разработкой скриптов на php, но фактически это означает, что требуется ещё знать и JS, HTML, CSS, если не в совершенстве, то хоть как-нибудь.

Амбула

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

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

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

Немного про фильтры

Фильтры устанавливаются через CSS в виде списка. Выглядит это следующим образом:

Старый стиль:

<img style="filter:Фильтр1(параметр=значенме, ...) Фильтр2() ..." />

Новый стиль, используя progid:

<img style="filter:progid:DXImageTransform.Microsoft.Фильтр1(параметр=значенме, ...) Фильтр2() ..." />

Старый стиль поддерживается IE, начиная с версии 4, новый только с 5.5.

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

Фильтры, которые накладываются на изображение, можно просто прописать через CSS, но применятся они, только если разрешено выполнение скриптов. Прописать их можно так:

<img src="1.jpg" style="filter: alpha(opacity=50) fliph flipv xray" /> 
Source Filtered

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

<img src="2.jpg" style="filter: blendtrans(duration=1)" id="i" />

<br />
<input type="button" onclick="i.filters['blendTrans'].apply();i.style.visibility='hidden';i.filters['blendTrans'].play()" value="Hide" /><br />
<input type="button" onclick="i.filters['blendTrans'].apply();i.style.visibility='visible';i.filters['blendTrans'].play()" value="Visible" /><br />

Помимо плавного перехода blendTrans для трансформации можно использовать анимированный фильтр revealTrans, у которого есть дополнительный параметр transition, отвечающий за вид трансформации. Параметр transition должен содержать целое положительное число.

<img src="3.jpg" style="filter: revealTrans(duration=1, transition=0)" id="i2" />

<br />
<input type="button" onclick="i2.filters['revealTrans'].apply();i2.style.visibility='hidden';i2.filters['revealTrans'].play()" value="Hide" /><br />
<input type="button" onclick="i2.filters['revealTrans'].apply();i2.style.visibility='visible';i2.filters['revealTrans'].play()" value="Visible" /><br />

Подробнее о фильтрах можно узнать на http://msdn.microsoft.com/en-us/library/ms532849(VS.85).aspx.

Постановка задачи

Нужно разработать ява-скрипт для простой смены картинок с возможностью использовать фильтры. Список картинок заранее известен. Скрипт должен корректно работать в браузерах IE, FF, Opera. Если JavaScript отключён, то должен отрабатывать простой HTML. Картинки должны отображаться циклично.

Разработка

Первоначально есть только список слайдов, на основе которого нужно сделать слайд-шоу.

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

/**
 * Image gallery viewer
 * Author: zg, 2008-03-10
 */
var ImageList = new Array();
   
ImageList.addImage = function (ImageSrc) {
   var Img = new Image();
   Img.src = ImageSrc;
   this[ this.length ] = Img;
}

Таким образом, ImageList изначально является объектом "массив", дополнительно расширен функцией, которая может добавлять в него картинки. Для добавления слайда в список воспроизведения достаточно вызвать ImageList.addImage('путь до картинки').

Уже можно формировать список слайдов.

// Preload images
ImageList.addImage('1.jpg');
ImageList.addImage('2.jpg');
ImageList.addImage('3.jpg');
ImageList.addImage('4.jpg');

В массиве ImageList будет находиться четыре объекта Image. К слову, подгрузка изображений начинётся сразу после добавления нового изображения к списку, поэтому не стоит изначально делать большой список, новые изображения можно добавлять в любой момент времени.

Далее я определил необходимые свойства нового объекта.

// Properties
ImageList.playing = false;
ImageList.timer = null;
ImageList.img = null;
ImageList.imgFilter = null;
ImageList.cur = 0;
ImageList.delay = 2000; // 2 sec.

Этих свойств вполне хватит, чтобы организовать требуемый функционал.

Следующим шагом нужно определить функции, которые будут управлять объектом. У меня получилось три:

  1. ImageList.play() - запускает цикличный показ слайдов
  2. ImageList.stop() - останавливает показ
  3. ImageList.next() - делает переход к следующему слайду

Запуск показа слайдов

ImageList.play = function() {
    if ( !this.playing )
    {
        this.playing = true;
        this.next();
    }
}

Никаких хитростей в этой функции нет - если флаг воспроизведения не установлен, то устанавливаем его и показываем следующее изображение. Все хитрости будут описаны в функции next().

Остановка показа

ImageList.stop = function() {
    if ( this.playing )
    {
        clearTimeout(this.timer);
        this.timer = null;
        this.playing = false;
    }
}

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

Переход к следующему слайду

ImageList.next = function() {
    if ( this.playing && this.length && this.img )
    {
        // Check end
        if ( this.cur >= this.length ) this.cur = 0;
        
        // Check complete status
        if ( !this[this.cur].complete )
        {
            setTimeout("ImageList.next()", 50); // wait, while image is loading
            return false;
        }
        
        if ( this.imgFilter )
        {
            // Set random transition
            if ( typeof(this.imgFilter.transition) != 'undefined' )
                this.imgFilter.transition = Math.floor(Math.random() * 99);
            
            this.imgFilter.apply();
        }
        
        // Change image
        this.img.src = this[ this.cur++ ].src;
        
        // Set next timer
        this.timer = setTimeout("ImageList.next()", this.delay);
        
        // Play filter
        if ( this.imgFilter ) this.imgFilter.play();
        
        return true;
    }
}

Логика работы этой функции следующая - если флаг воспроизведения установлен, в списке есть изображения и объект, который будет отображать слайды установлен, то выполняем работу по смене изображения. Проверяем, может быть, пора повторить список сначала? Дальше проверяем, загрузилось ли изображение, если нет, то повторим попытку через 50 мс, затем применяем фильтр, если таковой установлен. Потом меняем слайд, увеличивая текущий индекс, после этого устанавливаем таймер для показа следующего изображения. И, наконец, если фильтр был задан, то проигрываем его.

Установка объекта <img>

ImageList.setImg = function(Img) {
    this.img = Img;
    this.imgFilter = null;
    
    try
    {
        // Try to get filter (IE only)
        for (var i in Img.filters)
        {
            if ( typeof(Img.filters[i]) != 'object' ) continue;
            if ( typeof(Img.filters[i].apply) == 'undefined' ) continue;
            if ( typeof(Img.filters[i].play ) == 'undefined' ) continue;
            
            this.imgFilter = Img.filters[i];
            break;
        }
    }
    catch(e)
    {
        // No filter :'(
    }
}

Изначально эта функция не планировалась, но встал вопорос, каким образом извлекать установленный на объекте <img> фильтр, и я решил сделать специальную функцию. Поскольку фильтры доступны пока только в IE, то нужно поместить код для выбора фильтра в конструкцию try ... catch, чтобы избежать проблем с теми браузерами, которые не поддерживают фильтры.

Тестирование

Код - пример использования плеера
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<html>

<head>
<script language="javascript">
/**
 * Image gallery viewer
 * Author: zg, 2008-03-10
 */
var ImageList = new Array();

ImageList.addImage = function (ImageSrc) {
    var Img = new Image();
    Img.src = ImageSrc;
    this[ this.length ] = Img;
}

// Preload images
ImageList.addImage('1.jpg');
ImageList.addImage('2.jpg');
ImageList.addImage('3.jpg');
ImageList.addImage('4.jpg');

// Properties
ImageList.playing   = false;
ImageList.timer     = null;
ImageList.img       = null;
ImageList.imgFilter = null;
ImageList.cur       = 0;
ImageList.delay     = 2000; // 2 sec.

// Functions
ImageList.play = function() {
    if ( !this.playing )
    {
        this.playing = true;
        this.next();
    }
}

ImageList.stop = function() {
    if ( this.playing )
    {
        clearTimeout(this.timer);
        this.timer = null;
        this.playing = false;
    }
}

ImageList.next = function() {
    if ( this.playing && this.length && this.img )
    {
        // Check end
        if ( this.cur >= this.length ) this.cur = 0;
        
        // Check complete status
        if ( !this[this.cur].complete )
        {
            setTimeout("ImageList.next()", 50); // wait, while image is loading
            return false;
        }
        
        if ( this.imgFilter )
        {
            // Set random transition
            if ( typeof(this.imgFilter.transition) != 'undefined' )
                this.imgFilter.transition = Math.floor(Math.random() * 99);
            
            this.imgFilter.apply();
        }
        
        // Change image
        this.img.src = this[ this.cur++ ].src;
        
        // Set next timer
        this.timer = setTimeout("ImageList.next()", this.delay);
        
        // Play filter
        if ( this.imgFilter ) this.imgFilter.play();
        
        return true;
    }
}

ImageList.setImg = function(Img) {
    this.img = Img;
    this.imgFilter = null;
    
    try
    {
        // Try to get filter (IE only)
        for (var i in Img.filters)
        {
            if ( typeof(Img.filters[i]) != 'object' ) continue;
            if ( typeof(Img.filters[i].apply) == 'undefined' ) continue;
            if ( typeof(Img.filters[i].play ) == 'undefined' ) continue;
            
            this.imgFilter = Img.filters[i];
            break;
        }
    }
    catch(e)
    {
        // No filter :'(
    }
}
</script>
</head>

<body>

<img style="filter:blendtrans(duration=1)" id="imgMain" src="1.jpg" />

<script language="javascript">

ImageList.setImg(imgMain);  // Set image object
ImageList.play();  // Play gallery

// Control buttons
document.writeln('<br />');
document.writeln('<input type="button" onclick="ImageList.play()" value="Play" />');
document.writeln('<input type="button" onclick="ImageList.stop()" value="Stop" />');

</script>

</body>

</html>

Если у Вас включён JavaScript, то изображение, которое расположено ниже, будет меняться. Если используется Internet Explorer 4+, то слайды должны плавно сменять друг друга.

TestingControl