Przejdź do treści

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