Урок jQuery для продвинутых.
Открываем демо: http://moodo.co/
Или здесь: http://project106.digiproduct.co.il/
Внимание! Это коммерческий сайт, копировать и дублировать его ЗАПРЕЩЕНО!
Скроллим до блока с бейджиками — такие круглые этикетки а-ля парфюм (сайт про домашнюю ароматизацию через приложение). Кликаем на кругляшок.
Oh my God! It’s alive! Alive!!!
Тут у нас несколько задач, которые мы рассмотрим по отдельности.
- Если вы кликните на бейдж из правой колонки, вы увидите, что он (и ВЕСЬ ряд) сдвигается влево, активный бейдж встает на одну и ту же точку в веб-пространстве
- Разворот объекта по кругу вокруг своей оси — это язычки бейджиков;
- Вывод html-текста по окружности (html, jQuery).
Вы можете открыть инспектор (кликаем правой кнопкой мыши на объект в Хроме, выбираем последний пункт Inspect) и посмотреть в динамике, как это работает.
Я объясню здесь общий принцип работы, как подходить к решению подобной задачи.
Если у нас есть ряд свернутых объектов (это могут быть квадраты, прямоугольники и прочее), которые разворачиваются по горизонтали, то содержащий их контейнер должен быть в какой позиции?
.row_of_fragrancies { position: absolute; overflow: hidden; width: 7777px; }
Весь ряд, который сдвигается целиком, должен быть absolute внутри relative. Relative у нас — синий блок.
Иначе просто абсолютно выставленный блок не будет иметь высоты, следующий контент наедет поверх него, как если того и не существует. А абсолют внутри релатива дает нам возможность манипулировать объектом, и при этом он имеет свою высоту в общем потоке.
overflow: hidden; — чтобы не появлялся горизонтальный скролл при заезде объекта за правую или левую границу экрана.
width: 7777px; — даем ширину с запасом, так как бейджи раскрываются и занимают больше ширины. Если не дать запас, то левый бейджик будет сталкивать правый вниз (как если бы они не влезали в одну корзину).
Итак, обозначили общий ряд. Внутри располагаем кубики по 4 бейджа в каждом — тут ничего необычного — ширина и выравнивание влево, чтобы следующий кубик вставал справа в 1 ряд.
То же касается каждого кругляшка.
Самый сложный момент — сдвиг содержащего все элементы ряда таким образом, чтобы активный (раскрытый) бейдж вставал с левой стороны окна — у нас же адаптив! Никогда не узнаешь, какой будет ширина устройства. А значит, раскрытый бейдж должен быть гарантированно в зоне видимости — от десктопа до мобилки.
Как угадать, на какой бейдж кликнули и соответственно — на какое расстояние должен сдвинуться ВЕСЬ ряд?
Я решила, что можно «поймать» на каком расстоянии от левого края экрана находится КАЖДЫЙ бейдж и на клик на него сдвигать весь ряд именно на это расстояние влево. То есть кликаем на первый — он сдвигается на 50 пикселей влево, кликаем на крайний правый — он сдвигается на 700 пикселей. После закрытия возвращаем ряд в исходное положение.
Мы не можем дать конркетные значения для каждого бейджа — опять же адаптив. На мобилке и планшете бейджи стоят по одному блоку в ряд (проверьте), у нас 3 разных размера для бейджев и для текстовых язычков, для шрифтов. Совершенно невозможно учесть все. Значит, мы должны вычислить эти offsets (сдвиг, расстояние от края монитора) динамически.
Для начала нужно дать каждому бейджу свой id, чтобы цепляться к нему через jQuery.
Есть в jQuery функция, которая делает именно то, что нам нужно, и она так и называется — offset (сдвиг).
Выглядит так:
var offset = $(this).offset(); // сначала берем общий офсет - от всех сторон var offset_left = offset.left; // теперь левый из общего, работает только так - в паре
Теперь как мы сохраним все значения, и как мы выцепим нужное нам на тык бейджа? Логика подсказывает, что мы должны сделать массив, где будут храниться данные в таком виде:
бейдж id (такой-то номер) — имеет офсет в столько-то пискелей.
(В HTML5 появилась возможность давать id числовые значения, это значительно упрощает нам задачу в последующем выводе всех бейджев php-циклом, к тому же сокращает код)
Итак, сначала объявляем массив:
var offsetsArray = [];
Далее — очень полезная функция jQuery map — настоящее спасение для таких случаев — больше не нужно гонять объекты через цикл, чтобы вытащить их значения. Функция map пробегается по ВСЕМ объектам с заданным классом и собирает данные о них, причем сохраняет их для КАЖДОГО объекта.
Функция jQuery — map
$(".badges_out").map(function(){ var item_id = $(this).attr('id'); var offset = $(this).offset(); var offset_left = offset.left; offsetsArray.push({key: item_id, value: offset_left}); });
То есть, вы видите, что мы сделали здесь? Мы берем каждый badges_out (бейдж) и сохраняем сначала в переменные: id, offset.left, а потом загоняем в наш массив эти значения под переменными key (ключевое слово) и value (значение).
Вот как это выглядит в консоли в инспекторе при тестировании.
Массив в ява-скриптах — это объект, поэтому в консоли мы видим слово Object. Чтобы посмотреть значения — объект в консоли нужно развернуть (нажать стрелочку справа от него).
Как теперь «вытащить» значение офсета для каждого id:
var i = offsetsArray.inArray(«badge1», «key»);
console.log(offsetsArray[i].value);
Ну а дальше пишите функцию.
На тык на бейдж сохраняем значения офсета бейджа, а также офсет самого первого (куда должен вернуться весь ряд после закрытия бейджа). Анимированно сдвигаем все влево, при этом раскрываем текст, разворачиваем язычок по кругу и все в обратном порядке на закрытие.
Внимание — функция ниже не для копирования, а для образовательных целей!
/* Это функция для разворота язычка, пояснения в другом уроке */ $.fn.animateRotate = function(startAngle, endAngle, duration, easing, complete){ return this.each(function(){ var elem = $(this); $({deg: startAngle}).animate({deg: endAngle}, { duration: duration, easing: easing, step: function(now){ elem.css({ '-moz-transform':'rotate('+now+'deg)', '-webkit-transform':'rotate('+now+'deg)', '-o-transform':'rotate('+now+'deg)', '-ms-transform':'rotate('+now+'deg)', 'transform':'rotate('+now+'deg)' }); }, complete: complete || $.noop }); }); }; var anime_time = 200; /* задаем время анимации */ var badges_length = $(".badges_out").length; /* количество всех бейджев на странице */ var row_of_fragr_width = parseInt($('.four_fragrances').css('width')); /*снимаем ширину ВСЕГО ряда из css */ var fragr_margin_right = parseInt($('.four_fragrances').css('margin-right')); /* снимаем отступ слева для ВСЕГО ряда из css*/ /* Здесь для адаптива - раскидываем по 3 или по 1 блоку в ряд в зависимости от ширины окна если планшет или моб - добавляем после каждого блока <div class='clear'></div>, чтобы перебить float: left*/ if(w_width <= 1160){ $( ".four_fragrances" ).after( "<div class='clear'></div>" ); var fragr_left = (w_width - row_of_fragr_width - fragr_margin_right)/2; $('.row_of_fragrancies').css({left : fragr_left}); } else { $( ".clear" ).remove(); var fragr_left = (w_width - row_of_fragr_width*3 - fragr_margin_right*3)/2; $('.row_of_fragrancies').css({left : fragr_left}); var badges_text_active_width = 570; } /*пошла знакомая нам функция - добавления массива */ var offsetsArray = []; $(".badges_out").map(function(){ var item_id = $(this).attr('id'); var offset = $(this).offset(); var offset_left = offset.left; offsetsArray.push({key: item_id, value: offset_left}); }); console.log(offsetsArray); /* Внимание, дальше идет прототип для нашего массива, который вытаскивает офсеты для каждого айди */ // create a function to get offsets by element's ids var utils = {}; utils.inArray = function(searchFor, property) { var retVal = -1; var self = this; for(var index=0; index < self.length; index++){ var item = self[index]; if (item.hasOwnProperty(property)) { if (item[property].toLowerCase() === searchFor.toLowerCase()) { retVal = index; return retVal; } } }; return retVal; }; Array.prototype.inArray = utils.inArray; /*test: var i1 = offsetsArray.inArray("badge1", "key"); var i = offsetsArray.inArray("badge4", "key"); console.log(offsetsArray[i1].value); console.log(offsetsArray[i].value); */ $(".badges_out").click(function(){ var w_width = viewportSize.getWidth(); /*ширина окна, для этого вам нужно также включить джс-плагин viewportSize, он дает реальную ширину экрана без учета скроллбара, такую же, как в query */ if(w_width <= 480){ var badges_text_active_width = 300; /* ширина текстовых языков на разных ширинах экрана */ } else if(w_width > 480 && w_width <=1500){ var badges_text_active_width = 500; } else { var badges_text_active_width = 570; } var badge_width = parseInt($('.badges').css('width')); /* ширина бейджа*/ var id = $(this).attr('id'); /* берем айди*/ var i = offsetsArray.inArray(id, "key"); /*задаем поиск по айди как по ключу */ var offset_now = offsetsArray[i].value; /* вытаскиваем значения для этого ключа */ var k = offsetsArray.inArray("badge1", "key"); /* загоняем в отдельную переменную айди первого бейджа как ключ */ var offset_first = offsetsArray[k].value /* офсет конкретно первого бейджа - исходная точка всего ряда, куда нужно вернуться */ /*на клик закрываем любой открытый бейдж и удаляем из него класс active */ $(".badges_out.active").each(function(){/* вот он наш активный открытый бейдж (если есть) */ setTimeout($.proxy(function() { /*ставим таймаут, чтобы дать время на закрытие его, а потом уже разворачивать язычок */ $(this).find('.badges_toungue').animateRotate(-90, 0, anime_time); /* разворот язычка, об этом в другом уроке */ }, this), anime_time*4); /* anime_time - это переменная, у меня равно 200, вы можете выставить свои значения */ $(this).find('.txt').fadeOut(anime_time); /*здесь находим текст и фейд-аутим его, медленно растворяем */ $(this).find('.badges_text').delay(anime_time).animate({width : badge_width}, anime_time*2, function(){ /* наконец схлопываем ширину текстового поля */ }); $(this).removeClass('active'); /* удаляем класс active */ }); /*открыть или закрыть кликнутый бейдж*/ var badge_txt_width = parseInt($(this).find('.badges_text').css('width')); /* снимаем ширину текстового поля. Внимание. Мы будем определять открыт или закрыт бейдж именно по этой ширине! В закрытом состоянии она = 0, в открытом - значению для данной ширины окна, см. выше*/ if(badge_txt_width <= badge_width){/* если ширина текста меньше ширины бейджа, то: */ $('.row_of_fragrancies').delay(500).animate({left : - (offset_now-offset_first)}, anime_time*2); /* Аллилуйя - сдвигаем весь ряд влево! На ширину, равную офсет бейджа минус офсет самого первого, чтобы открытый встал не в начало экрана, а на место первого, его положение тоже разное в зависимости от ширины окна */ $(this).addClass('active'); /* присваиваем класс active */ $(this).find('.badges_toungue').animateRotate(0, -90, anime_time); /* поворачиваем язычок */ $(this).find('.txt').fadeOut(0);/* для профилактики скрываем текст сначала */ $(this).find('.badges_text').delay(anime_time*2).animate({width : badges_text_active_width}, anime_time*2, function(){ /* делаем таймаут для текста, чтобы он появился ПОСЛЕ выдвижения всего текстового поля */ $(this).find('.txt').fadeIn(anime_time);/*а теперь фэйд-иним текст - делаем его видимым */ }); } else if(badge_txt_width > badge_width) {/*ИНАЧЕ, то есть, если текст уже открыт и шире чем бейдж: */ $('.row_of_fragrancies').animate({left : fragr_left}, anime_time); /* возвращаем все на исходную, значение fragr_left указано выше */ } });
Ну вот и все, если что-то не работает — выдерните код из демо (может что-то не включила).
Еще раз повторюсь — НЕ ВОРОВАТЬ! Сделайте лучше, чем у меня!
Все разобью на составляющие, если не понятно ничего, будет в категории «Кодерские фишки».
You must be logged in to post a comment.