Zoom Slider

19

Від автора: простий слайдер контенту з ефектом зума у визначених областях для кожного слайда.

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

Zoom SliderZoom Slider

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

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

HTML

The Classy iPhone 6

Previous product
View details
Next product

The iPhone 6

Incredible performance for powerful apps

Close content

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.75 em;
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.31 em;
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.55 em;
position: absolute;
top: 30px;
right: 30px;
margin: 0;
opacity: 0;
color: #50505a;
-webkit-transition: opacity 0.3 s;
transition: opacity 0.3 s;
}
.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.5 em;
font-weight: normal;
margin: 0;
}
.content h3 {
font-size: 1.95 em;
font-weight: normal;
margin: 0.25 em 0 0.5 em;
color: #685884;
}
.content p {
font-size: 1.25 em;
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,50 px,0);
transform: translate3d(0,50 px,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.5 s;
transition: transform 0.5 s;
-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.3 s 0.3 s;
transition: opacity 0.3 s 0.3 s;
}
.zoomer—active .zoomer__image {
opacity: 0;
-webkit-transition-delay: 0s;
transition-delay: 0s;
}
.preview img {
-webkit-transition: -webkit-transform 0.6 s 0.3 s;
transition: transform 0.6 s 0.3 s;
-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.3 s;
transition: transform 0.3 s;
}
.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.3 s;
transition-delay: 0.3 s;
}
.content—open,
.content__item—current {
-webkit-transition: none;
transition: none;
}
.content__item-img {
-webkit-transition: -webkit-transform 0.4 s;
transition: transform 0.4 s;
-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.6 s, opacity 0.3 s;
transition: transform 0.6 s, opacity 0.3 s;
-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.7 s;
transition-duration: 1.7 s;
}
/* Медіа запити */
@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 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