Zoom Slider

Zoom Slider

От автора: простой слайдер контента с эффектом зума в предопределенных областях для каждого слайда.

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

скачать исходникидемо

При нажатии на стрелки навигации внутренние секции слайда оживают, причем, изображения и заголовок ведут себя независимо друг от друга. Для анимации компонентов слайда мы используем CSS свойство transition и dynamic.js. Dynamic.js от Michaël Villar это JavaScript библиотека, позволяющая создавать анимацию основанную на законах физики.

Заметьте, что мы используем несколько современных свойств CSS, которые поддерживаются только современными браузерами.

HTML

<!—Основной контейнер -->
<div class="container">
	<!-- Blueprint хедер -->
	<header class="bp-header cf">
		<!-- Page title etc. -->
	</header>
	<!-- Grid -->
	<section class="slider">
		<div class="slide slide--current" data-content="content-1">
			<div class="slide__mover">
				<div class="zoomer flex-center">
					
					<div class="preview">
						
						<div class="zoomer__area zoomer__area--size-2"></div>
					</div>
				</div>
			</div>
			<h2 class="slide__title"><span>The Classy</span> iPhone 6</h2>
		</div>
		<div class="slide" data-content="content-2">
			<!-- ... -->
		</div>
		<!-- ... -->
		<nav class="slider__nav">
			<button class="button button--nav-prev">
				<i class="icon icon--arrow-left"></i>
				<span class="text-hidden">Previous product</span>
			</button>
			<button class="button button--zoom">
				<i class="icon icon--zoom"></i>
				<span class="text-hidden">View details</span>
			</button>
			<button class="button button--nav-next">
				<i class="icon icon--arrow-right"></i>
				<span class="text-hidden">Next product</span>
			</button>
		</nav>
	</section>
	<!-- /конец слайдера-->
	<!-- контент -->
	<section class="content">
		<div class="content__item" id="content-1">
			
			<div class="content__item-inner">
				<h2>The iPhone 6</h2>
				<h3>Incredible performance for powerful apps</h3>
				<p>...</p>
			</div>
		</div>
		<div class="content__item" id="content-2">
			<!-- ... -->
		</div>
		<!-- ... -->
		<button class="button button--close">
			<i class="icon icon--circle-cross"></i>
			<span class="text-hidden">Close content</span>
		</button>
	</section>
	<!-- /конец контента -->
</div>
<script src="js/classie.js"></script>
<script src="js/dynamics.min.js"></script>
<script src="js/main.js"></script>

CSS

/* Классы помощники */
html,
body {
	overflow: hidden;
	height: 100%;
}

.container {
	position: relative;
	overflow: hidden;
	overflow-y: scroll;
	width: 100%;
	height: 100%;
	-webkit-overflow-scrolling: touch;
}

.noscroll .container {
	overflow-y: hidden;
}

.slider {
	position: relative;
	z-index: 200;
	width: 100%;
	margin: 0 auto;
	padding: 0 0 7em;
	text-align: center;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	-webkit-touch-callout: none;
	-khtml-user-select: none;
}

.slide {
	position: absolute;
	top: 0;
	visibility: hidden;
	width: 100%;
	opacity: 0;
}

.slide--current {
	position: relative;
	z-index: 100;
	visibility: visible;
	opacity: 1;
}

.slide__mover {
	position: relative;
	z-index: 100;
}

.slide__title {
	font-size: 1.75em;
	font-weight: normal;
	margin: 0 auto;
	padding: 1em 0 0 0;
}

.slide__title span {
	font-size: 55%;
	font-weight: bold;
	display: block;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: #35303d;
}

.slider__nav {
	position: absolute;
	bottom: 2em;
	width: 100%;
	text-align: center;
}

.button {
	font-size: 1.31em;
	position: relative;
	display: inline-block;
	overflow: hidden;
	margin: 0 25px;
	padding: 0;
	cursor: pointer;
	color: #5c5edc;
	border: none;
	background: none;
}

.button:focus {
	outline: none;
}

.button:hover {
	color: #fff;
}

.text-hidden {
	position: absolute;
	top: 200%;
}

.button--close {
	font-size: 1.55em;
	position: absolute;
	top: 30px;
	right: 30px;
	margin: 0;
	opacity: 0;
	color: #50505a;
	-webkit-transition: opacity 0.3s;
	transition: opacity 0.3s;
}

.content--open .button--close {
	opacity: 1;
}

/* Zoomer */
.zoomer {
	position: relative;
	height: 360px; /* this is needed for IE10 so that vertical flexbox centering works */
}

.flex-center {
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;
	-webkit-align-items: center;
	-ms-flex-align: center;
	align-items: center;
	-webkit-justify-content: center;
	-ms-flex-pack: center;
	justify-content: center;
}

.zoomer__image {
	display: block;
	margin: 0;
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}

.zoomer__area,
.preview {
	position: absolute;
	top: 50%;
	left: 50%;
	-webkit-transform: translate3d(-50%,-50%,0);
	transform: translate3d(-50%,-50%,0);
}

.zoomer__area:focus {
	outline: none;
}

.zoomer__area--size-1 {
	/* Apple Watch */
	width: 96px;
	height: 118px;
}

.zoomer__area--size-2 {
	/* iPhone */
	width: 112px;
	height: 198px;
}

.zoomer__area--size-3 {
	/* MacBook */
	width: 315px;
	height: 200px;
}

.zoomer__area--size-4 {
	/* iPad */
	width: 150px;
	height: 200px;
}

.zoomer__area--size-5 {
	/* iMac */
	width: 315px;
	height: 189px;
}

.preview {
	overflow: hidden;
	background: #18191b;
}

.preview img {
	display: block;
	border-radius: inherit;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}

.zoomer--active .preview img {
	-webkit-transform: translate3d(100%,0,0);
	transform: translate3d(100%,0,0);
}

.rounded {
	border-radius: 15px;
}

.rounded-right {
	border-radius: 0 15px 15px 0;
}

.preview__content {
	position: absolute;
	top: 0;
	left: 100%;
	width: 100%;
	height: 100%;
	border-radius: inherit;
}
/* Контент */
.content {
	position: fixed;
	z-index: 1000;
	top: 0;
	left: -100%;
	overflow: hidden;
	overflow-y: scroll;
	width: 100%;
	height: 100vh;
	background: #18191b;
	-webkit-overflow-scrolling: touch;
}

.content--open {
	left: 0;
}

.content__item {
	position: absolute;
	top: 0;
	display: -webkit-flex;
	display: -ms-flexbox;
	display: flex;
	overflow: hidden;
	height: 0;
	min-height: 100%;
	margin: 0 auto;
	padding: 2em 0;
	pointer-events: none;
	opacity: 0;
	color: #fff;
	-webkit-align-items: center;
	-ms-flex-align: center;
	align-items: center;
}

.content__item--current {
	pointer-events: auto;
	opacity: 1;
}

.content__item--reset {
	height: auto;
}

.content h2 {
	font-size: 3.5em;
	font-weight: normal;
	margin: 0;
}

.content h3 {
	font-size: 1.95em;
	font-weight: normal;
	margin: 0.25em 0 0.5em;
	color: #685884;
}

.content p {
	font-size: 1.25em;
	line-height: 1.5;
}

.content__item-img {
	display: block;
	max-width: 40vw;
	max-height: 80vh;
	-webkit-transform: translate3d(-120%,0,0);
	transform: translate3d(-120%,0,0);
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}

.content__item--current .content__item-img {
	-webkit-transform: translate3d(-10px,0,0);
	transform: translate3d(-10px,0,0);
}

.content__item-inner {
	padding: 0 10vw 0;
	opacity: 0;
	-webkit-transform: translate3d(0,50px,0);
	transform: translate3d(0,50px,0);
}

.content__item--current .content__item-inner {
	opacity: 1;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}


/**************************/
/* Все синхронные переходы */
/**************************/

.zoomer {
	-webkit-transition: -webkit-transform 0.5s;
	transition: transform 0.5s;
	-webkit-transition-timing-function: cubic-bezier(0.7,0,0.3,1);
	transition-timing-function: cubic-bezier(0.7,0,0.3,1);
}

.zoomer.zoomer--notrans {
	-webkit-transition: none;
	transition: none;
}

.zoomer__image {
	-webkit-transition: opacity 0.3s 0.3s;
	transition: opacity 0.3s 0.3s;
}

.zoomer--active .zoomer__image {
	opacity: 0;
	-webkit-transition-delay: 0s;
	transition-delay: 0s;
}

.preview img {
	-webkit-transition: -webkit-transform 0.6s 0.3s;
	transition: transform 0.6s 0.3s;
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	transition-timing-function: cubic-bezier(0.2,1,0.3,1);
}

.zoomer--active .preview img {
	-webkit-transition: -webkit-transform 0.3s;
	transition: transform 0.3s;
}

.content {
	-webkit-transition: left 0s;
	transition: left 0s;
}

.content__item {
	-webkit-transition: opacity 0s;
	transition: opacity 0s;
}

.content,
.content__item {
	/* задержка исчезновения контента, zoomer начинает двигаться обратно в 0 */
	-webkit-transition-delay: 0.3s;
	transition-delay: 0.3s;
}

.content--open,
.content__item--current {
	-webkit-transition: none;
	transition: none;
}

.content__item-img {
	-webkit-transition: -webkit-transform 0.4s;
	transition: transform 0.4s;
	-webkit-transition-timing-function: cubic-bezier(0.7,1,0.8,1);
	transition-timing-function: cubic-bezier(0.7,1,0.8,1);
}

.content__item--current .content__item-img {
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	transition-timing-function: cubic-bezier(0.2,1,0.3,1);
	-webkit-transition-duration: 1s;
	transition-duration: 1s;
}

.content__item-inner {
	-webkit-transition: -webkit-transform 0.6s, opacity 0.3s;
	transition: transform 0.6s, opacity 0.3s;
	-webkit-transition-timing-function: cubic-bezier(0.7,1,0.8,1), ease;
	transition-timing-function: cubic-bezier(0.7,1,0.8,1), ease;
}

.content__item--current .content__item-inner {
	-webkit-transition-timing-function: cubic-bezier(0.2,1,0.3,1), ease;
	transition-timing-function: cubic-bezier(0.2,1,0.3,1), ease;
	-webkit-transition-duration: 1.7s;
	transition-duration: 1.7s;
}

/* Медиа запросы */
@media screen and (max-width: 50em) {
	.content__item {
		display: block;
	}
	.content__item-img {
		max-width: calc(100% - 80px);
		max-height: 70vh;
	}
	.content h2 {
		font-size: 3em;
	}
	.content__item-inner {
		font-size: 82%;
		padding: 4em 3em 2em;
	}
}

JavaScript

/**
 * main.js
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2015, Codrops
 * http://www.codrops.com
 */
;(function(window) {

	'use strict';

	var bodyEl = document.body, 
		docElem = window.document.documentElement,
		support = { transitions: Modernizr.csstransitions },
		// имя события конца перехода
		transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' },
		transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],
		onEndTransition = function( el, callback ) {
			var onEndCallbackFn = function( ev ) {
				if( support.transitions ) {
					if( ev.target != this ) return;
					this.removeEventListener( transEndEventName, onEndCallbackFn );
				}
				if( callback && typeof callback === 'function' ) { callback.call(this); }
			};
			if( support.transitions ) {
				el.addEventListener( transEndEventName, onEndCallbackFn );
			}
			else {
				onEndCallbackFn();
			}
		},
		// размеры окна
		win = {width: window.innerWidth, height: window.innerHeight},
		// несколько вспомогательных переменных для запрета скроллинга
		lockScroll = false, xscroll, yscroll,
		scrollContainer = document.querySelector('.container'),
		// основной слайдер со слайдами
		sliderEl = document.querySelector('.slider'),
		items = [].slice.call(sliderEl.querySelectorAll('.slide')),
		// общее количество слайдов
		itemsTotal = items.length,
		// стрелки навигации
		navRightCtrl = sliderEl.querySelector('.button--nav-next'),
		navLeftCtrl = sliderEl.querySelector('.button--nav-prev'),
		zoomCtrl = sliderEl.querySelector('.button--zoom'),
		// основное поле контента
		contentEl = document.querySelector('.content'),
		// закрыть поле дополнительного контента
		closeContentCtrl = contentEl.querySelector('button.button--close'),
		// индекс текущего слайда
		current = 0,
		// проверить «открыт» ли слайд
		isOpen = false,
		isFirefox = typeof InstallTrigger !== 'undefined',
		// Если не Firefox масштабируем (производительность в Firefox не самая хорошая)
		bodyScale = isFirefox ? false : 3;	

	// парочка вспомогательных функций:
	function scrollX() { return window.pageXOffset || docElem.scrollLeft; }
	function scrollY() { return window.pageYOffset || docElem.scrollTop; }
	// ресурс http://www.sberry.me/articles/javascript-event-throttling-debouncing
	function throttle(fn, delay) {
		var allowSample = true;

		return function(e) {
			if (allowSample) {
				allowSample = false;
				setTimeout(function() { allowSample = true; }, delay);
				fn(e);
			}
		};
	}

	function init() {
		initEvents();
	}

	// обработка события
	function initEvents() {
		// открыть слайд
		zoomCtrl.addEventListener('click', function() {
			openItem(items[current]);
		});

		// закрыть контент
		closeContentCtrl.addEventListener('click', closeContent);

		// навигация
		navRightCtrl.addEventListener('click', function() { navigate('right'); });
		navLeftCtrl.addEventListener('click', function() { navigate('left'); });

		// изменения размера окна
		window.addEventListener('resize', throttle(function(ev) {
			// сбросить размеры окна
			win = {width: window.innerWidth, height: window.innerHeight};

			// сбросить изменения для слайдов
			items.forEach(function(item, pos) {
				if( pos === current ) return;
				var el = item.querySelector('.slide__mover');
				dynamics.css(el, { translateX: el.offsetWidth });
			});
		}, 10));

		// события навигации с клавиатуры
		document.addEventListener( 'keydown', function( ev ) {
			if( isOpen ) return; 
			var keyCode = ev.keyCode || ev.which;
			switch (keyCode) {
				case 37:
					navigate('left');
					break;
				case 39:
					navigate('right');
					break;
			}
		} );
	}

	// открываем слайд
	function openItem(item) {
		if( isOpen ) return;
		isOpen = true;

		// элемент, который будет преобразован
		var zoomer = item.querySelector('.zoomer');
		// превью экрана слайда
		classie.add(zoomer, 'zoomer--active');
		// запрет скроллинга
		scrollContainer.addEventListener('scroll', noscroll);
		// применяем трансформацию
		applyTransforms(zoomer);
		// Одновременно масштабируем body для эффекта приближения к объекту
		if( bodyScale ) {
			dynamics.animate(bodyEl, { scale: bodyScale }, { type: dynamics.easeInOut, duration: 500 });
		}
		// после завершения перехода:
		onEndTransition(zoomer, function() {
			// сбрасываем трансформации body
			if( bodyScale ) {
				dynamics.stop(bodyEl);
				dynamics.css(bodyEl, { scale: 1 });
				
				// фикс для safari (позволяет фиксированным дочерним элементам сохранить позицию)
				bodyEl.style.WebkitTransform = 'none';
				bodyEl.style.transform = 'none';
			}
			// запрет скроллинга
			classie.add(bodyEl, 'noscroll');
			classie.add(contentEl, 'content--open');
			var contentItem = document.getElementById(item.getAttribute('data-content'))
			classie.add(contentItem, 'content__item--current');
			classie.add(contentItem, 'content__item--reset');


			// сбрасываем трансформации zoomer’а – обратно к его изначальной позиции/трансформация без перехода
			classie.add(zoomer, 'zoomer--notrans');
			zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
			zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
		});
	}

	// закрываем контент слайда
	function closeContent() {
		var contentItem = contentEl.querySelector('.content__item--current'),
			zoomer = items[current].querySelector('.zoomer');

		classie.remove(contentEl, 'content--open');
		classie.remove(contentItem, 'content__item--current');
		classie.remove(bodyEl, 'noscroll');
				
		if( bodyScale ) {
			// сбрасываем фикс для safari (позволяет фиксированным дочерним элементам сохранить позицию)
			bodyEl.style.WebkitTransform = '';
			bodyEl.style.transform = '';
		}

		/* фикс мерцания в safari */
		var nobodyscale = true;
		applyTransforms(zoomer, nobodyscale);
		/* фикс мерцания в safari */

		// ждем внутренний контент для завершения анимации
		onEndTransition(contentItem, function(ev) {
			classie.remove(this, 'content__item--reset');
			
			// отменяем запрет скроллинга
			lockScroll = false;
			scrollContainer.removeEventListener('scroll', noscroll);

			/* фикс мерцания в safari */
			zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
			zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
			/* фикс мерцания в safari */
			
			// масштабируем слайд еще раз – «за сценой»- (без анимации)
			applyTransforms(zoomer);
			
			// анимированное уменьшения масштаба 
			setTimeout(function() {	
				classie.remove(zoomer, 'zoomer--notrans');
				classie.remove(zoomer, 'zoomer--active');
				zoomer.style.WebkitTransform = 'translate3d(0,0,0) scale3d(1,1,1)';
				zoomer.style.transform = 'translate3d(0,0,0) scale3d(1,1,1)';
			}, 25);

			if( bodyScale ) {
				dynamics.css(bodyEl, { scale: bodyScale });
				dynamics.animate(bodyEl, { scale: 1 }, {
					type: dynamics.easeInOut,
					duration: 500
				});
			}

			isOpen = false;
		});
	}

	// применяем необходимые значения трансформации для увеличения элемента
	function applyTransforms(el, nobodyscale) {	
		// область zoomer и значение масштаба
		var zoomerArea = el.querySelector('.zoomer__area'), 
			zoomerAreaSize = {width: zoomerArea.offsetWidth, height: zoomerArea.offsetHeight},
			zoomerOffset = zoomerArea.getBoundingClientRect(),
			scaleVal = zoomerAreaSize.width/zoomerAreaSize.height < win.width/win.height ? win.width/zoomerAreaSize.width : win.height/zoomerAreaSize.height;

		if( bodyScale && !nobodyscale ) {
			scaleVal /= bodyScale; 
		}

		// применяем трансформацию
		el.style.WebkitTransform = 'translate3d(' + Number(win.width/2 - (zoomerOffset.left+zoomerAreaSize.width/2)) + 'px,' + Number(win.height/2 - (zoomerOffset.top+zoomerAreaSize.height/2)) + 'px,0) scale3d(' + scaleVal + ',' + scaleVal + ',1)';
		el.style.transform = 'translate3d(' + Number(win.width/2 - (zoomerOffset.left+zoomerAreaSize.width/2)) + 'px,' + Number(win.height/2 - (zoomerOffset.top+zoomerAreaSize.height/2)) + 'px,0) scale3d(' + scaleVal + ',' + scaleVal + ',1)';
	}

	// навигация по слайдеру
	function navigate(dir) {
		var itemCurrent = items[current],
			currentEl = itemCurrent.querySelector('.slide__mover'),
			currentTitleEl = itemCurrent.querySelector('.slide__title');

		// обновить текущее значение на новое
		if( dir === 'right' ) {
			current = current < itemsTotal-1 ? current + 1 : 0;
		}
		else {
			current = current > 0 ? current - 1 : itemsTotal-1;
		}

		var itemNext = items[current],
			nextEl = itemNext.querySelector('.slide__mover'),
			nextTitleEl = itemNext.querySelector('.slide__title');
		
		// анимация скольжения за область видимости текущего слайда
		dynamics.animate(currentEl, { opacity: 0, translateX: dir === 'right' ? -1*currentEl.offsetWidth/2 : currentEl.offsetWidth/2, rotateZ: dir === 'right' ? -10 : 10 }, {
			type: dynamics.spring,
			duration: 2000,
			friction: 600,
			complete: function() {
				dynamics.css(itemCurrent, { opacity: 0, visibility: 'hidden' });
			}
		});

		// анимация скольжения за область видимости текущего заголовка
		dynamics.animate(currentTitleEl, { translateX: dir === 'right' ? -250 : 250, opacity: 0 }, {
			type: dynamics.bezier,
			points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
			duration: 450
		});

		// установка необходимых значений для появления следующего слайда
		dynamics.css(itemNext, { opacity: 1, visibility: 'visible' });
		dynamics.css(nextEl, { opacity: 0, translateX: dir === 'right' ? nextEl.offsetWidth/2 : -1*nextEl.offsetWidth/2, rotateZ: dir === 'right' ? 10 : -10 });

		// анимация появления следующего слайда
		dynamics.animate(nextEl, { opacity: 1, translateX: 0 }, {
			type: dynamics.spring,
			duration: 2000,
			friction: 600,
			complete: function() {
				items.forEach(function(item) { classie.remove(item, 'slide--current'); });
				classie.add(itemNext, 'slide--current');
			}
		});

		// установка необходимых значений для появления следующего заголовка
		dynamics.css(nextTitleEl, { translateX: dir === 'right' ? 250 : -250, opacity: 0 });
		// анимация появления следующего заголовка
		dynamics.animate(nextTitleEl, { translateX: 0, opacity: 1 }, {
			type: dynamics.bezier,
			points: [{"x":0,"y":0,"cp":[{"x":0.2,"y":1}]},{"x":1,"y":1,"cp":[{"x":0.3,"y":1}]}],
			duration: 650
		});
	}

	// запрещаем скроллинг (в scrollContainer)
	function noscroll() {
		if(!lockScroll) {
			lockScroll = true;
			xscroll = scrollContainer.scrollLeft;
			yscroll = scrollContainer.scrollTop;
		}
		scrollContainer.scrollTop = yscroll;
		scrollContainer.scrollLeft = xscroll;
	}

	init();

})(window);

Посмотреть проект на GitHub

Автор: Mary Lou

Источник: http://tympanus.net/

Редакция: Команда webformyself.

Фреймворк Bootstrap - верстаем адаптивно, просто, быстро!

Получите видеокурс по основам Bootstrap

Получить

Метки:

Комментарии Вконтакте:

Комментарии Facebook:

Комментарии (1)

  1. Сергей

    Отличная вещь, хочу попробовать на своем сайте организовать, как пример моих работ

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree