diff --git a/README.md b/README.md index c95a62d..711bdcd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Работа с API, верстка и анимации +[https://rawgithub.com/ArtKuz/6-api-and-markup/master/index.html](https://rawgithub.com/ArtKuz/6-api-and-markup/master/index.html) + Ваша задача — реализовать страницу с динамическим отображением результатов запроса к API «популярных фотографий» сервиса Яндекс.Фоткки. В качестве исходных данных — коллекция популярных фотографий в формате JSON, полученных из API diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..e93ac29 --- /dev/null +++ b/css/style.css @@ -0,0 +1,259 @@ +html, +body, +h1, +h2, +p +{ + margin: 0; +} + +ol, ul +{ + list-style: none; + padding: 0; +} + +a +{ + color: #6da3bd; +} + +a:hover +{ + color: #4d7285; +} + +body +{ + padding: 10px; + min-width: 950px; + background: #fff; + color: #000; + text-align: left; + font: 400 16px Arial, Helvetica, sans-serif; +} + +header +{ + height: 110px; + display: block; + border-bottom: 4px solid #fc3; +} + +h1 +{ + padding: 0 0 10px; + text-align: center; + font: 400 36px Verdana, Helvetica, sans-serif; +} + +h1 span +{ + color: #f00; +} + +h2 +{ + padding: 0 0 10px; + font: 700 16px Verdana, Helvetica, sans-serif; +} + +ul.nav li +{ + display: inline; + margin: 0 10px 0 0; +} + +ul.nav li a.active +{ + color: #f00; + text-decoration: none; +} + +#loading +{ + position: absolute; + z-index: 1; + display: none; + width: 100%; + height: 100%; + background: #fff; + opacity: 0.9; +} + +#loading img +{ + margin: 70px 50%; +} + +#loading .textload +{ + left: 30px; + position: relative; + text-align: center; + top: -60px; +} + +section +{ + margin: 30px auto; + display: block; +} + +header section +{ + margin: 0 auto; +} + +.miniatures .miniature +{ + display: inline-block; + margin: 10px; + position: relative; + vertical-align: top; +} + +.miniatures .miniature .image +{ + cursor: pointer; + display: block; + overflow: hidden; + height: 150px; + z-index: 1; + position: relative; +} + +.miniatures .miniature .image img +{ + height: 150px; +} + +.miniatures .miniature .shortDescription +{ + background: none repeat scroll 0 0 #2B2B2B; + display: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.8); + left: 0; + margin: -10px -10px 0; + padding: 170px 10px 10px; + position: absolute; + right: 0; + text-align: left; + top: 0; + z-index: 2; + color: #fff; + overflow: hidden; +} + +.miniatures .miniature .shortDescription .title +{ + font-weight: 700; +} + +.miniatures .miniature:hover .shortDescription, +.miniatures .miniature.selected .shortDescription +{ + display: block; +} + +.miniatures .miniature:hover .image, +.miniatures .miniature.selected .image +{ + z-index: 3; +} + +.miniatures .miniature.selected .title, +.miniatures .miniature.selected .author +{ + display: none; +} + +.preview +{ + background: none repeat scroll 0 0 #2b2b2b; + width: 100%; + position: relative; + display: none; + +} + +.preview .close +{ + color: #a7a7a7; + cursor: pointer; + font-size: 40px; + font-weight: 800; + position: absolute; + right: 20px; + top: 5px; +} + +.preview .close:hover +{ + color: #fff; +} + +.preview .slider +{ + position: relative; + margin: 0 430px 0 10px; + padding: 20px 0 10px; + text-align: center; +} + +.preview .slider .prev, +.preview .slider .next +{ + color: #a7a7a7; + cursor: pointer; + font-size: 50px; + margin: -40px 0 0; + position: absolute; + top: 50%; + display: none; +} + +.preview .slider .prev +{ + left: 0; +} + +.preview .slider .next +{ + right: 0; +} + +.preview .slider .prev:hover, +.preview .slider .next:hover +{ + color: #fff; +} + +.preview .description +{ + color: #eee; + position: absolute; + right: 10px; + text-align: left; + top: 50px; + width: 380px; +} + +.preview .description ul +{ + margin: 20px 0 0 0; +} + +.more +{ + border: 4px solid #fc3; + margin: 0 auto; + text-align: center; + width: 200px; + cursor: pointer; +} + +.more:hover +{ + background: #fc3; +} diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..2dd3837 Binary files /dev/null and b/favicon.ico differ diff --git a/images/loading.gif b/images/loading.gif new file mode 100644 index 0000000..b1d6c5e Binary files /dev/null and b/images/loading.gif differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..80a7eb7 --- /dev/null +++ b/index.html @@ -0,0 +1,56 @@ + + + + + + + Работа с API, верстка и анимации | 6-api-and-markup | Кузвесов Артём + + + + + + +
+ Загрузка +
Идёт загрузка...
+
+
+
+

Работа с API, верстка и анимации

+ +
+
+
+ +
Больше фотографий...
+
+ + \ No newline at end of file diff --git a/js/app/app.js b/js/app/app.js new file mode 100644 index 0000000..212f1f1 --- /dev/null +++ b/js/app/app.js @@ -0,0 +1,320 @@ +/** + * Работа с Яндекс API.Фото. AJAX + * + * @author Artem Kuzvesov + * @version 0.01 + * @copyright Artem Kuzvesov 2013 + * + */ +define(['jquery', + 'handlebars', + 'app/templateLoader'], function($, Handlebars, TemplateLoader) { + + /** @define {object} */ + var $preview = $('.preview'); // Элемент preview + $preview.remove(); + /** @define {object} */ + var $miniatures = $('.miniatures'); + + /** Расчитываем в шаблоне новую ширину изображения, + учитывая, что высота всех миниатюр 150px*/ + Handlebars.registerHelper('newWith', function() { + return newWith = Math.ceil((arguments[0] * 150)/arguments[1]); + }); + + /** + * Конструктор возвращаемого объекта + * @constructor + */ + App = function() { + /** @define {object} */ + this.dataJson = {}; // данные полученные с API для текущей категории + /** @define {string} */ + this.photoCategory = 'top'; // категория, по умолчанию 'top' + /** @define {number} */ + this.photoLimit = 20; // количество подгружаемых миниатюр + + /** Обработка клика по пункту меню */ + $('ul.nav').on('click', 'li a', $.proxy(this.categoryChoice, this)); + + /** Обработка клика "Больше фотографий" */ + $('.content').on('click', '.more', $.proxy(this.morePhotos, this)); + + /** Обработка клика на миниатюру */ + $miniatures.on('click', 'li.miniature img', $.proxy(this.loadPreviw, this)); + } + + /** + * Функция которая должна быть вызвана при запуске приложения + */ + App.prototype.first = function() { + this.ajaxCatalog(); + }; + + /** + * Загружаем фотографии через API, отрисовываем их и упорядочиаваем + * @param {[string]} photoCategory Категорию фотографий которую нужно загрузить + */ + App.prototype.ajaxCatalog = function() { + var app = this; + + $.ajax({ + url: '//api-fotki.yandex.ru/api/' + app.photoCategory +'/', + type: 'GET', + data: { + format : 'json', + limit : app.photoLimit + }, + dataType: 'jsonp', + beforeSend: function () { + $('#loading').css('display', 'block'); + $('.more').show(); + $('.miniatures li').remove(); + }, + success: function (answer) { + app.dataJson = answer; + + var $source = $('#miniature-template').html().trim(), + template = Handlebars.compile($source), + html = template(answer), // собираем миниатюры по шаблону + countMiniature = app.dataJson.entries.length; + + $miniatures.append(html); + if ((app.photoLimit > countMiniature) || (app.photoLimit === 100)) { + $('.more').hide(); + }; + $('#loading').hide(); + + tidyImages(); + } + }); + } + + /** + * События, которые происходят при клике на категорию + */ + App.prototype.categoryChoice = function(event) { + var element = event.target, // текущий элемент в jQuery + app = this; + + $('a.active').removeClass('active'); + $(element).addClass('active'); + + app.photoCategory = $(element).attr('id'); + app.photoLimit = 20; + app.ajaxCatalog(app.photoCategory, app.photoLimit); + } + + /** + * События, которые происходят при клике на "Больше фотографий" + */ + App.prototype.morePhotos = function(event) { + var element = event.target, // текущий элемент в jQuery + app = this; + + app.photoLimit += 20; + if (app.photoLimit > 100) { + app.photoLimit = 100; + } + app.ajaxCatalog(app.photoCategory, app.photoLimit); + } + + /** + * События, которые происходят при клике на миниатюру + */ + App.prototype.loadPreviw = function(event) { + var element = event.target; // текущий элемент в jQuery + + this.miniature = $(element).closest('li'); + this.renderPreview(this.miniature); + } + + /** + * Рендеринг превью + */ + App.prototype.renderPreview = function(selected) { + var app = this, + idPhoto = selected.attr('id'), + title = app.dataJson.entries[idPhoto].title, + $countMiniatures = $('.miniatures li.miniature').length - 1; + + if ($('.selected')) $('li').removeClass('selected'); + if ($preview) $preview.remove(); + + selected.addClass('selected'); + + if (selected.hasClass("first")) { + selected.before($preview).show(); + } else { + selected.prevAll('.first:first').before($preview); + } + + if ($('.preview ul li')) $('.preview ul li').remove(); + + $('.prev, .next').show(); + + if (idPhoto === '0') { + $('.prev').hide(); + prev = true; + } + if (idPhoto === String($countMiniatures)) { + $('.next').hide(); + next = true; + } + + if (idPhoto) { + $preview.find('img').attr({src: app.dataJson.entries[idPhoto].img.L.href, alt: title}); + $preview.find('h2').text(title); + $preview.find('.author a').attr('href', 'http://fotki.yandex.ru/users/' + app.dataJson.entries[idPhoto].author).text(app.dataJson.entries[idPhoto].author); + if (app.dataJson.entries[idPhoto].img.L.href) { + $preview.find('ul').append('
  • ' + app.dataJson.entries[idPhoto].img.L.width + 'x' + app.dataJson.entries[idPhoto].img.L.height + '
  • '); + } + if (app.dataJson.entries[idPhoto].img.XL.href) { + $preview.find('ul').append('
  • ' + app.dataJson.entries[idPhoto].img.XL.width + 'x' + app.dataJson.entries[idPhoto].img.XL.height + '
  • '); + } + if (app.dataJson.entries[idPhoto].img.XXL.href) { + $preview.find('ul').append('
  • ' + app.dataJson.entries[idPhoto].img.XXL.width + 'x' + app.dataJson.entries[idPhoto].img.XXL.height + '
  • '); + } + if (app.dataJson.entries[idPhoto].img.XXXL.href) { + $preview.find('ul').append('
  • ' + app.dataJson.entries[idPhoto].img.XXXL.width + 'x' + app.dataJson.entries[idPhoto].img.XXXL.height + '
  • '); + } + } + + $preview.slideDown(800); + + /** Обработка клика на кнопку закрыть */ + $preview.on('click', '.close', $.proxy(this.closePreview, this)); + + /** Обработка клика на кнопку предыдущяя фотография */ + $preview.on('click', '.prev', $.proxy(this.prevPreview, this)); + + /** Обработка клика на кнопку следующей фотография */ + $preview.on('click', '.next', $.proxy(this.nextPreview, this)); + + + document.onkeydown = NavigateThrough; + function NavigateThrough (event) { + switch (event.keyCode ? event.keyCode : event.which ? event.which : null) { + case 37: // стрелка влево + if ($('.prev').css('display') !== 'none') { + app.prevPreview(); + }; + break; + case 39: // стрелка впрво + if ($('.next').css('display') !== 'none') { + app.nextPreview(); + }; + break; + case 27: // esc + app.closePreview(); + break; + } + } + } + + /** + * События, которые происходят при клике на кнопку закрыть + */ + App.prototype.closePreview = function() { + $('.preview').slideUp(800, function() { + $(this).remove(); + }); + $('.selected').removeClass('selected') + } + + /** + * События, которые происходят при клике на кнопку предыдущяя фотография + */ + App.prototype.prevPreview = function() { + this.miniature = this.miniature.prev(); + + if (this.miniature.hasClass('preview')) { + this.miniature = this.miniature.prev(); + } + + this.renderPreview(this.miniature); + } + + /** + * События, которые происходят при клике на кнопку следующей фотография + */ + App.prototype.nextPreview = function() { + this.miniature = this.miniature.next(); + this.renderPreview(this.miniature); + } + + /** + * Упорядочивание изображений + */ + function tidyImages() { + var $widthBlock = $miniatures.width(), // ширина области с миниатюрами + withLine = 0, // ширина получаемой строки миниатюр + first = true, // текущий элемент является первым в строке + $countMin = $('.miniature').length - 1, // количество миниатюр + countLine = '', // количество миниатюр в строке + lineMin = [], // массив миниатюр в строке + $widthImage = '', // ширина миниатюры + difference = '', // разница между шириной строки и шириной всех выбранных минитаюр + newWith = ''; // знаение ширины миниатюры, на которое надо изменить + + /** + * @constructor + * @param {object} element миниатюра + * @param {number} width ширина миниатюры + */ + function minimage(image, widthImg){ + this.image = image; + this.widthImg = Number(widthImg); + }; + + $('.miniature').each(function(index, element) { + $(this).removeClass('first').removeClass('last').css('float', 'none'); + + if(index === 0) { + $(this).addClass('firsted'); + } + + $widthImage = $('img', this).width(); // ширина миниатюры + + withLine += $widthImage + 20; + + difference = $widthBlock - withLine; + + if (first) { + $(this).addClass('first'); + first = false; + }; + + lineMin.push(new minimage(element, $widthImage)); + + if (difference < 0) { + countLine = lineMin.length; + difference = (-1 * difference) + 40; + newWith = Math.ceil(difference / countLine); + + lineMin.forEach(function(el) { + $(el.image).css('width', el.widthImg - newWith + 'px'); + }); + + $(this).addClass('last').css({float: 'right', width: $widthImage - newWith - countLine + 'px'}); + first = true; + withLine = 0; + lineMin = []; + }; + + if (index === $countMin) { + $(this).addClass('last').addClass('lasted').css('float', 'none'); + }; + + }); + } + + /** + * Отслеживаем изменения размера браузера + */ + $(function() { + $(window).resize(tidyImages); + }); + + return App; +}); diff --git a/js/app/init.js b/js/app/init.js new file mode 100644 index 0000000..673b093 --- /dev/null +++ b/js/app/init.js @@ -0,0 +1,6 @@ +require(['jquery', + 'app/app'], function($, App) { + $(function() { + new App().first(); + }); +}); diff --git a/js/app/templateLoader.js b/js/app/templateLoader.js new file mode 100644 index 0000000..bfd9368 --- /dev/null +++ b/js/app/templateLoader.js @@ -0,0 +1,17 @@ +/** + * Работа с Яндекс API.Фото. AJAX. Подгрузка шаблонов + * + * @author Artem Kuzvesov + * @version 0.01 + * @copyright Artem Kuzvesov 2013 + * + */ +define(['jquery'], function($) { + $.ajax({ + url: 'js/templates/miniature.html', + dataType: 'text', + success: function (answer) { + $('section.content').after(answer); + } + }); +}); \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..6613753 --- /dev/null +++ b/js/main.js @@ -0,0 +1,14 @@ +require.config({ + paths: { + jquery : '//yandex.st/jquery/1.10.2/jquery.min', + handlebars : '//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.1.2/handlebars.min', + app : 'app' + }, + shim: { + handlebars: { + exports: 'Handlebars' + } + } +}); + +require(['app/init']); \ No newline at end of file diff --git a/js/templates/miniature.html b/js/templates/miniature.html new file mode 100644 index 0000000..88a3888 --- /dev/null +++ b/js/templates/miniature.html @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/js/templates/preview.html b/js/templates/preview.html new file mode 100644 index 0000000..29edade --- /dev/null +++ b/js/templates/preview.html @@ -0,0 +1,14 @@ +
  • +
    ×
    +
    + + + +
    +
    +

    +
    Автор:
    +
      +
    +
    +
  • \ No newline at end of file