Przejdź do treści

Galeria zdjęć HTML - Skrypty

Klasyczna galeria zdjęć

Jak wstawić galerię zdjęć (grafik) na stronie WWW? Co zrobić, aby po kliknięciu miniaturki obrazka, otworzyło się okno z jego dużą kopią?

Jeśli nie masz czasu czytać wprowadzenia, możesz przejść od razu do części właściwej ;-)

Na wielu stronach w Internecie można spotkać galerię zdjęć, stosowaną do przedstawienia kolekcji zdjęć, zwykle o dużych rozmiarach. Najczęściej jest ona tworzona w ten sposób, że na głównej stronie umieszcza się pomniejszone kopie obrazków oraz odsyłacze, po kliknięciu których następuje wczytanie obrazka w pełnych rozmiarach. Pozwala to uchronić się od wczytywania wszystkich dużych obrazków jednocześnie (użytkownik powiększa tylko te, które mu odpowiadają), a także zachowuje estetykę strony.

Uwaga! Miniaturki grafik muszą być pomniejszone fizycznie, tzn. nie mogą to być oryginalne duże obrazki ze zmniejszonymi rozmiarami wyświetlania za pomocą atrybutów width="..." oraz height="...". Tylko wstawienie naprawdę pomniejszonych obrazków uchroni od niepotrzebnego wczytywania dużych plików.

Zmniejszenia wymiarów obrazka można dokonać praktycznie w każdym programie graficznym. Najczęściej ustala się takie same rozmiary dla wszystkich miniatur, ponieważ ułatwia to utrzymanie estetyki strony. Obrazki w galerii zwykle ustawia się w komórkach tabeli (najczęściej bez obramowania), dzięki czemu można je dokładnie ustawić w rzędach i kolumnach. Jeżeli zamierzasz umieścić na swojej stronie obszerniejszą galerię grafik, zaleca się podzielenie jej na kilka części i stworzenie kilku podstron z miniaturkami.

Wczytanie oryginalnego dużego obrazka następuje najczęściej po kliknięciu bezpośrednio jego miniaturki. Aby zastosować taki efekt, należy użyć odsyłacza obrazkowego (podstawowego). Prawie zawsze stosuje się również atrybut target="_blank", który powoduje otwarcie nowego okna przeglądarki:

<a target="_blank" href="ścieżka do dużego obrazka"><img src="ścieżka do pliku miniaturki obrazka" alt="Tekst alternatywny" border="0"></a>

Większość nowych przeglądarek potrafi automatycznie otworzyć obrazek w nowym oknie, jednaki nie będzie wtedy możliwości szybkiej nawigacji pomiędzy kolejnymi zdjęciami z galerii. Natomiast starsze przeglądarki po kliknieciu przez użytkownika takiego linka często najpierw wyświetlały okienko z pytaniem co zrobić z obrazkiem, tzn. otworzyć czy zapisać, a po wybraniu pierwszej opcji, następowało wczytanie pliku w zewnętrznym programie (jeśli użytkownik miał taki zainstalowany), co mogło potrwać kilka sekund i było trochę irytujące w przypadku dużej liczby zdjęć. Dlatego w klasycznej galerii zdjęć zwykle zamiast podawać link bezpośrednio do dużego obrazka, tworzy się dodatkową stronę z grafiką wstawioną na niej za pomocą znacznika <img> i dopiero taki dokument podaje się w linku:

<a target="_blank" href="ścieżka do strony z dużym obrazkiem"><img src="ścieżka do pliku miniaturki obrazka" alt="Tekst alternatywny" border="0"></a>

Jak łatwo zauważyć, aby stworzyć klasyczną galerię zdjęć, trzeba napisać tyle stron, ile mamy obrazków - po jednej dla każdego dużego egzemplarza (a nawet co najmniej o jedną więcej).

Istnieją specjalne programy, które potrafią stworzyć statyczną galerię grafik. Wystarczy wybrać zdjęcia za pomocą selektora plików, a program automatycznie wygeneruje miniatury zdjęć oraz wszystkie potrzebne dokumenty HTML.

Pewną niedogodnością w przypadku programów tworzących statyczne galerie może jednak być utrudniona aktualizacja takiej galerii. Dodanie choćby jednego nowego zdjęcia zwykle będzie wiązało się z generowaniem galerii na nowo.

Powyższych wad pozbawiony jest skrypt galerii zdjęć opisany dalej w tym rozdziale.

Skrypt galerii zdjęć

Wymagana wiedza

Przykład galerii zdjęć

Kliknij wybrane zdjęcie lewym przyciskiem myszki, aby zobaczyć powiększenie:

Beskid Żywiecki Mogielica Szczebel

Zwróć uwagę, że po otworzeniu powiększenia możesz nawigować pomiędzy kolejnymi zdjęciami z galerii również za pomocą klawiatury - klawiszami strzałki w lewo (poprzednie zdjęcie) i w prawo (następne zdjęcie). Ponadto możesz zamknąć powiększenie przy pomocy klawisza Escape.

Dodatkową zaletą skryptu galerii zdjęć jest automatyczne wczytywanie w tle od razu poprzedniego i następnego zdjęcia z galerii. Dzięki temu kiedy użytkownik już do nich przejdzie, powinny się wyświetlić błyskawicznie.

Jeśli chcesz wstawić taką galerię zdjęć na swojej stronie, zobacz: Skrypt galerii zdjęć (gotowiec).

Galeria zdjęć (gotowiec)

Stosując specjalny skrypt, który automatycznie wyświetla zdjęcia bez potrzeby otwierania nowego okna, można zautomatyzować tworzenie galerii. Ponadto aktualizacja zdjęć w takiej galerii nie będzie w tym przypadku utrudniona - dodanie nowego zdjęcia będzie się wiązało tylko z dopisaniem jednej dodatkowej linijki bez potrzebny tworzenia całej galerii od nowa.

Aby wstawić na stronę skrypt galerii zdjęć, należy zapisać przedstawiony poniżej kod w dowolnym pliku z rozszerzeniem *.js - np. gallery.js:

/**
 * @author Sławomir Kokłowski {@link https://www.kurshtml.edu.pl}
 * @copyright NIE usuwaj tego komentarza! (Do NOT remove this comment!)
 * @version 2.5.1
 */

function GalleryModel(items) {
	this.items = items || [];
}

GalleryModel.prototype.get = function(index) {
	var item = this.items[index];
	if (!item) {
		return null;
	}
	return {
		title: item.getAttribute('data-gallery'),
		src: this._getSrc(item),
		alt: item.title,
		protected: !item.getAttribute('href')
	};
};

GalleryModel.prototype.indexOf = function(src) {
	for (var i = 0; i < this.items.length; ++i) {
		var item = this.items[i];
		if (this._getSrc(item) === src) {
			return i;
		}
	}
	return -1;
};

GalleryModel.prototype.count = function() {
	return this.items.length;
};

GalleryModel.prototype.preload = function(index) {
	var item = this.items[index];
	if (item) {
		new Image().src = item.href;
	}
};

GalleryModel.prototype._getSrc = function(item) {
	return item.getAttribute('href') || item.getAttribute('data-href');
};

function GalleryView(items) {
	this.items = items || [];
	this._reset();
	this.style = this._createStyle();
	this.delay = 500;
	this._overflow = '';
	this._timeout = null;
	this._listeners = [];
	this._protectedEvents = ['mousedown', 'contextmenu', 'selectstart', 'select', 'copy', 'dragstart', 'drag'];
}

GalleryView.prototype.start = function(callback) {
	this.items.forEach(function(item) {
		var src = item.getAttribute('href');
		if (!src) {
			src = item.getAttribute('data-href');
			item.style.cursor = 'pointer';
		}
		item.addEventListener('click', function(event) {
			callback(src);
			event.preventDefault();
		})
	});
};

GalleryView.prototype.open = function(goBack, goForward, onClose, onLoad) {
	this.onLoad = onLoad;
	this._overflow = document.body.style.overflow;
	document.body.style.overflow = 'hidden';
	this.container = this._createContainer();
	this.progress = this._createPropress();
	this.container.appendChild(this.progress);
	this.caption = this._createCaption();
	this.container.appendChild(this.caption);
	this.back = this._createNavigation('&#10094;', this.style.back, goBack);
	this.container.appendChild(this.back);
	this.forward = this._createNavigation('&#10095;', this.style.forward, goForward);
	this.container.appendChild(this.forward);
	this.container.appendChild(this._createCloseButton(onClose));
	document.body.appendChild(this.container);
	this._addListener(document, 'keydown', this._onKeyDown.bind(this, goBack, goForward, onClose));
};

GalleryView.prototype.close = function(callback) {
	clearTimeout(this._timeout);
	this._listeners.forEach(function(listener) {
		listener.target.removeEventListener(listener.type, listener.callback);
	});
	this._listeners = [];
	this.container.parentNode.removeChild(this.container);
	document.body.style.overflow = this._overflow;
	this._reset();
	if (callback) {
		callback();
	}
};

GalleryView.prototype.display = function(image, first, last) {
	this[first ? '_hide' : '_show']('back');
	this[last ? '_hide' : '_show']('forward');
	this._hide('caption');
	this.setCaption(image.title, image.alt);
	if (this.image) {
		this.image.style.visibility = 'hidden';
		this._displayProgress();
		if (image.protected) {
			this._protect();
		}
		this.image.src = image.src;
		if (!image.protected) {
			this._unprotect();
		}
	} else {
		this._displayProgress();
		this.image = this._createImage(image.src);
		this.image.style.visibility = 'hidden';
		if (image.protected) {
			this._protect();
		}
		this.container.insertBefore(this.image, this.container.firstChild);
	}
};

GalleryView.prototype.setCaption = function(title, alt) {
	this.caption.innerHTML = '';
	if (title) {
		var b = document.createElement('b');
		b.innerText = title;
		this.caption.appendChild(b);
	}
	if (alt) {
		if (title) {
			this.caption.appendChild(document.createElement('br'));
		}
		this.caption.appendChild(document.createTextNode(alt));
	}
};

GalleryView.prototype._protect = function() {
	var callback = function(event) {
		event.preventDefault();
	};
	this._protectedEvents.forEach(function(type) {
		this._addListener(this.image, type, callback);
	}, this);
	this.image.galleryimg = 'no';
};

GalleryView.prototype._unprotect = function() {
	this.image.removeAttribute('galleryimg');
	var listeners = [];
	this._listeners.forEach(function(listener) {
		if (listener.target === this.image && this._protectedEvents.indexOf(listener.type) >= 0) {
			listener.target.removeEventListener(listener.type, listener.callback);
		} else {
			listeners.push(listener);
		}
	}, this);
	this._listeners = listeners;
};

GalleryView.prototype._reset = function() {
	this.container = null;
	this.image = null;
	this.progress = null;
	this.caption = null;
	this.back = null;
	this.forward = null;
	this.onLoad = null;
};

GalleryView.prototype._addListener = function(target, type, callback) {
	this._listeners.push({
		target: target,
		type: type,
		callback: callback
	});
	target.addEventListener(type, callback);
};

GalleryView.prototype._onKeyDown = function(goBack, goForward, onClose, event) {
	switch (event.keyCode) {
		case 27:
			this.close(onClose);
			break;
		case 37:
			goBack(event);
			break;
		case 39:
			goForward(event);
			break;
	}
};

GalleryView.prototype._createStyle = function() {
	var margin = '10px';
	var overlayStyle = {
		left: 0,
		top: 0,
		width: '100%',
		height: '100%',
		display: 'flex',
		justifyContent: 'center',
		alignItems: 'center'
	};
	var buttonStyle = {
		border: 0,
		padding: 0,
		lineHeight: 1,
		color: '#000',
		background: 'rgba(255, 255, 255, 0.5)',
		cursor: 'pointer'
	};
	var navigationStyle = {
		fontSize: '50px',
		width: '75px',
		height: '75px',
		borderRadius: '50%',
		position: 'absolute',
		fontWeight: 'bold'
	};
	var style = {
		container: {
			position: 'fixed',
			zIndex: 6000000,
			background: '#000'
		},
		image: {
			maxWidth: '100%',
			maxHeight: '100%'
		},
		progress: {
			position: 'absolute'
		},
		caption: {
			position: 'absolute',
			bottom: 0,
			left: 0,
			boxSizing: 'border-box',
			width: '100%',
			padding: margin,
			color: '#fff',
			background: 'rgba(0, 0, 0, 0.5)',
			font: '15px sans-serif',
			textAlign: 'center'
		},
		back: {
			left: margin
		},
		forward: {
			right: margin
		},
		close: {
			position: 'absolute',
			top: margin,
			right: margin,
			fontSize: '25px',
			width: '35px',
			height: '35px'
		}
	};
	this._forEach(overlayStyle, function(key, value) {
		style.container[key] = style.progress[key] = value;
	});
	this._forEach(buttonStyle, function(key, value) {
		style.close[key] = style.back[key] = style.forward[key] = value;
	});
	this._forEach(navigationStyle, function(key, value) {
		style.back[key] = style.forward[key] = value;
	});
	return style;
};

GalleryView.prototype._forEach = function(data, callback) {
	for (var key in data) {
		if (Object.prototype.hasOwnProperty.call(data, key)) {
			callback.call(this, key, data[key]);
		}
	}
};

GalleryView.prototype._show = function(name) {
	this[name].style.display = this.style[name].display || '';
};

GalleryView.prototype._hide = function(name) {
	this[name].style.display = 'none';
};

GalleryView.prototype._setStyle = function(element, style) {
	this._forEach(style, function(key, value) {
		element.style[key] = value;
	});
};

GalleryView.prototype._createContainer = function() {
	var div = document.createElement('div');
	this._setStyle(div, this.style.container);
	return div;
};

GalleryView.prototype._createImage = function(src) {
	var img = document.createElement('img');
	this._addListener(img, 'load', this._onLoad.bind(this));
	this._setStyle(img, this.style.image);
	img.src = src;
	return img;
};

GalleryView.prototype._createPropress = function() {
	var div = document.createElement('div');
	this._setStyle(div, this.style.progress);
	div.style.display = 'none';
	div.appendChild(document.createElement('progress'));
	return div;
};

GalleryView.prototype._displayProgress = function() {
	clearTimeout(this._timeout);
	this._timeout = setTimeout(this._show.bind(this, 'progress'), this.delay);
};

GalleryView.prototype._createCaption = function() {
	var div = document.createElement('div');
	this._setStyle(div, this.style.caption);
	div.style.display = 'none';
	return div;
};

GalleryView.prototype._createNavigation = function(text, style, callback) {
	var button = document.createElement('button');
	button.type = 'button';
	this._setStyle(button, style);
	button.style.display = 'none';
	button.innerHTML = text;
	this._addListener(button, 'click', callback);
	return button;
};

GalleryView.prototype._createCloseButton = function(callback) {
	var button = document.createElement('button');
	button.type = 'button';
	this._setStyle(button, this.style.close);
	button.innerHTML = '&#10006;';
	this._addListener(button, 'click', this.close.bind(this, callback));
	return button;
};

GalleryView.prototype._onLoad = function() {
	clearTimeout(this._timeout);
	this._hide('progress');
	this.image.style.visibility = 'visible';
	if (this.caption.innerText) {
		this._show('caption');
	}
	if (this.onLoad) {
		this.onLoad();
	}
};

function Gallery(model, view) {
	this.model = model;
	this.view = view;
	this.current = 0;
	this.opened = false;
	this.hash = '';
}

Gallery.prototype.start = function() {
	this.view.start(this.display.bind(this));
	if (location.hash) {
		var current = this.model.indexOf(location.hash.substring(1));
		if (current >= 0) {
			this.display(current);
		}
	}
};

Gallery.prototype.display = function(data) {
	var index = typeof data === 'string' ? this.model.indexOf(data) : data;
	var image = this.model.get(index);
	if (image) {
		this.current = index;
		if (!this.opened) {
			this.hash = location.hash;
			this.view.open(this.goBack.bind(this), this.goForward.bind(this), this._onClose.bind(this), this._onLoad.bind(this));
			this.opened = true;
		}
		this.view.display(image, this.current <= 0, this.current >= this.model.count() - 1);
		this._navigate(image.protected ? '' : '#' + image.src);
	}
};

Gallery.prototype.goBack = function() {
	this.display(this.current - 1);
};

Gallery.prototype.goForward = function() {
	this.display(this.current + 1);
};

Gallery.prototype._navigate = function(hash) {
	if (location.hash !== hash && typeof history !== 'undefined' && history.replaceState) {
		history.replaceState(null, '', location.pathname + location.search + hash);
	}
};

Gallery.prototype._onClose = function() {
	this.opened = false;
	this._navigate(this.hash);
};

Gallery.prototype._onLoad = function() {
	this.model.preload(this.current + 1);
	this.model.preload(this.current - 1);
};

(function() {
	var data = {};
	Array.prototype.forEach.call(document.querySelectorAll('[data-gallery]'), function(item) {
		var key = item.getAttribute('data-gallery') || '';
		if (data[key]) {
			data[key].push(item);
		} else {
			data[key] = [item];
		}
	});
	Object.keys(data).forEach(function(key) {
		var items = data[key];
		new Gallery(new GalleryModel(items), new GalleryView(items)).start();
	});
})();

Następnie na stronie wstaw znaczniki odnośników i obrazków - podobnie jak się to robi w klasycznej galerii zdjęć. Jedyną zmianą, którą musisz dodatkowo zrobić, to do znaczników odsyłaczy z powiększonymi zdjęciami dodaj atrybut data-gallery. Na przykład:

<a data-gallery href="zdjecie1.jpg"><img src="miniatura1.jpg" border="0"></a>
<a data-gallery href="zdjecie2.jpg"><img src="miniatura2.jpg" border="0"></a>
<a data-gallery href="zdjecie3.jpg" title="Opis"><img src="miniatura3.jpg" border="0"></a>
miniatura1.jpg, miniatura2.jpg, miniatura3.jpg
Lokalizacje fizycznie pomniejszonych odpowiedników pełnowymiarowych zdjęć
zdjęcie1.jpg, zdjęcie2.jpg, zdjęcie3.jpg
Lokalizacje odpowiednich pełnowymiarowych zdjęć
Opis
Opcjonalny opis zdjęcia, który pojawi się w dymku narzędziowym po wskazaniu myszką miniatury zdjęcia oraz po powiększeniu zdjęcia

Oczywiście możesz dodać do galerii więcej niż trzy miniatury i zdjęcia.

Następnie na stronie po powyższym kodzie (to ważne - nie może być przed!) wystarczy wstawić jeszcze tylko (w wyróżnionym miejscu oczywiście należy podać lokalizację utworzonego wcześniej pliku gallery.js):

<script src="gallery.js" async></script>

...i gotowe. Prawda że szybko poszło? ;-)

Zbiory zdjęć

Czasami w jednym dokumencie HTML możemy chcieć wstawić kilka osobnych galerii - np. jako fotorelacje z różnych wydarzeń. W takim przypadku nawigowanie kolejno we wszystkich zdjęciach ze strony mogłoby być tylko mylące. Lepiej żeby po dotarciu do końca pierwszej galerii, użytkownik musiał zamknąć okno powiększenia, a następnie jeśli będzie chciał, może wybrać kolejną galerię. Aby uzyskać taki efekt, wystarczy do odnośników z każdej kolejnej galerii przypisać inną wartość atrybutu data-gallery="...". Na przykład:

<a data-gallery="galeria1" href="galeria1/zdjecie1.jpg"><img src="galeria1/miniatura1.jpg" border="0"></a>
<a data-gallery="galeria1" href="galeria1/zdjecie2.jpg"><img src="galeria1/miniatura2.jpg" border="0"></a>

<a data-gallery="galeria2" href="galeria2/zdjecie1.jpg"><img src="galeria2/miniatura1.jpg" border="0"></a>
<a data-gallery="galeria2" href="galeria2/zdjecie2.jpg"><img src="galeria2/miniatura2.jpg" border="0"></a>
galeria1
Nazwa pierwszej galerii
galeria1/miniatura1.jpg, galeria1/miniatura2.jpg
Lokalizacje fizycznie pomniejszonych odpowiedników pełnowymiarowych zdjęć z pierwszej galerii
galeria1/zdjęcie1.jpg, galeria1/zdjęcie2.jpg
Lokalizacje odpowiednich pełnowymiarowych zdjęć z pierwszej galerii
galeria2
Nazwa drugiej galerii
galeria2/miniatura1.jpg, galeria2/miniatura2.jpg
Lokalizacje fizycznie pomniejszonych odpowiedników pełnowymiarowych zdjęć z drugiej galerii
galeria2/zdjęcie1.jpg, galeria2/zdjęcie2.jpg
Lokalizacje odpowiednich pełnowymiarowych zdjęć z drugiej galerii

Oczywiście możesz dodać więcej niż dwie galerie na stronie, a każda z nich może mieć dowolną liczbę zdjęć.

Ważne! Nawet jeśli w pojedynczym pliku *.html umieścisz kilka osobnych zbiorów zdjęć, linijka:

<script src="gallery.js" async></script>

musi być wstawiona tylko jeden raz - koniecznie po ostatniej galerii.

Przykład

Galeria zdjęć z Beskidu Żywieckiego:

Beskid Żywiecki

Galeria zdjęć z Beskidu Wyspowego:

Mogielica Szczebel

Zwróć uwagę, że po dojściu do końca zdjęć każdej z powyższych galerii, aby zobaczyć kolejną galerię, trzeba najpierw zamknąć powiększenie.

Ochrona przed kopiowaniem zdjęć

Nie każdy pragnie udostępniać swoje zdjęcia w takiej formie, aby internauci mogli sobie je kopiować na swój dysk lokalny. Czasami zachodzi potrzeba jedynie prezentacji grafik, bez możliwości ich zapisu. Aby to zrobić, należy w odsyłaczach galerii zdjęć zamiast atrybutu href="..." użyć atrybutu data-href="...":

<a data-gallery data-href="zdjecie.jpg"><img src="miniatura.jpg" border="0"></a>

Pamiętaj, że w tym przypadku oprócz atrybutu data-href="..." trzeba dodać również atrybut data-gallery. W przeciwnym razie galeria nie będzie działać.

Nie ma stuprocentowego sposobu zablokowania kopiowania zdjęć ze stron WWW. Przedstawiona tutaj metoda stanowi tylko pewne utrudnienie dla osób początkujących.

Przykład

Kliknij wybrane zdjęcie lewym przyciskiem myszki, aby zobaczyć powiększenie:

Beskid Żywiecki Mogielica Szczebel

Zwróć uwagę, że tym razem po otworzeniu powiększenia kliknięcie prawym przyciskiem myszki na zdjęciu nie powinno być możliwe. Z tego powodu opcja "Zapisz zdjęcie jako..." będzie zablokowana. Możesz porównać jak to wygląda w podstawowym wariancie galerii zdjęć (bez blokady kopiowania).

Komentarze

Zobacz więcej komentarzy

Facebook