В проекте, в котором я недавно участвовал, меня попросили воссоздать функциональность расширения поиска картинок Google , похожую на скриншот, показанный ниже, но в формате жесткой сетки.
Моя немедленная реакция заключалась в том, что мне нужно было использовать JavaScript для установки некоторых свойств макета и блочной модели, которые я всегда неохотно делаю и буду делать только в крайнем случае. Поскольку был уже отлично работающий пример, я решил открыть инструменты разработчика, чтобы посмотреть, как Google это делает (зачем изобретать велосипед, верно?)
Оказывается, что Google разбивает структуру на два элемента: один элемент содержит все ячейки изображения, а другой элемент предназначен для расширяющейся области. Когда изображение щелкается (и разворачивается), JavaScript вставляет разделительный элемент div после последней ячейки изображения в строке выбранного элемента div. JavaScript устанавливает его высоту так же, как расширенный div, и позиционирует расширенный div абсолютно в положение, которое занимает разделитель div. Это умно, но не идеально из-за сильной зависимости от JavaScript.
У меня была основная идея, которую мне удалось развить в рабочую демонстрацию с использованием CSS для всех свойств макета и блочной модели. Единственный необходимый JavaScript-код — это изменение имен классов на основе переключателя расширения.
Основная разметка
Прежде всего, нам нужно .image-grid
контейнер .image-grid
вместе с каждым .image__cell
. Вот HTML-код:
<section class="image-grid"> <div class="image__cell is-collapsed"> <div class="image--basic"> <a href="#expand-jump-1"> <img id="expand-jump-1" class="basic__img" src="http://lorempixel.com/250/250/fashion/1" alt="Fashion 1"> </a> <div class="arrow--up"></div> </div> <div class="image--expand"> <a href="#close-jump-1" class="expand__close"></a> <img class="image--large" src="http://lorempixel.com/400/400/fashion/1" alt="Fashion 1"> </div> </div> ... </section>
Разметка выше содержит один пример .image cell
который необходимо будет дублировать для каждого изображения в сетке. Обратите внимание на идентификаторы для #close-jump-1
и #expand-jump-1
, и последующие атрибуты href
должны быть уникальными для .image__cell
. Хеш-ссылки, такие как: href="#expand-jump-1"
позволяют браузеру при нажатии переходить к активной ячейке изображения.
CSS
Сначала мы применяем box-sizing: border-box;
ко всем элементам, включая :before
и :after
псевдоэлементы, используя универсальный селектор. Это позволит легко обрабатывать элементы, которые смешивают процентную ширину с фиксированными значениями заполнения, так как объединяет их.
/* apply a natural box layout model to all elements, but allowing components to change */ html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; }
Элемент .image-grid
имеет явное overflow: hidden;
поддерживать макет на основе изображения ячейки с плавающей точкой.
.image-grid { width: 100%; max-width: 1310px; margin: 0 auto; overflow: hidden; padding: 10px 5px 0; } .image__cell { float: left; position: relative; width: 20%; } .image--basic { padding: 0 5px; } .basic__img { display: block; max-width: 100%; height: auto; margin: 0 auto; } .image__cell.is-collapsed .arrow--up { display: block; height: 10px; width: 100%; } .image--large { max-width: 100%; height: auto; display: block; padding: 40px; margin: 0 auto; box-sizing: border-box; }
Ширина, заданная для ячейки изображения, эквивалентна 100, деленному на количество элементов в строке, выраженное в процентах. В этом примере в строке 5 элементов, что означает, что каждая .image__cell
имеет ширину 20%.
Обратите внимание, что padding: 10px 5px 0;
применяется к .image-grid
сочетании с .image-grid
padding: 0 5px;
на .image--basic
, а height: 10px;
на .image__cell.is-collapsed .arrow--up
дают одинаковый эффект рамки окна вокруг мозаичных изображений. Мы могли бы увеличить разрыв между изображениями, изменив эти значения.
Наконец, .basic__img
изображения .basic__img
получает display: block;
чтобы предотвратить любые пробелы. max-width: 100%;
и height: auto;
Объявления позволяют масштабировать изображение до ширины его контейнера, не превышая его собственную ширину.
Приведенный ниже CSS предоставляет макет для расширяемой области.
.image__cell.is-collapsed .image--basic { cursor: pointer; } .image__cell.is-expanded .image--expand { max-height: 500px; margin-bottom: 10px; } .image--expand { position: relative; left: -5px; padding: 0 5px; box-sizing: content-box; overflow: hidden; background: #222; max-height: 0; transition: max-height .3s ease-in-out, margin-bottom .1s .2s; width: 500%; }
Вот некоторые примечания, которые можно взять из приведенного выше кода:
- Курсор изменится на указатель при наведении
.image--basic
на.image--basic
когда он находится только в.image--basic
состоянии. Это дает пользователю визуальный индикатор того, что нажатие на изображение что-то сделает. -
max-height
элемента.image-expand
установлена в 0 в его начальном состоянии.max-height
получает значение 500px, если элемент.image-cell
имеет класс.image-cell
. Обратите внимание, что если бы увеличивалась площадь расширения, необходимо было бы также увеличить максимальное значение высоты, чтобы отображалась вся область. - Переходы, примененные к
max-height
иmargin-bottom
позволяют эффект скольжения при переключении расширенной области. -
Визуально мы хотим, чтобы расширяющаяся область
.image-grid
с.image-grid
. Для этого нам нужно.image-grid
набор горизонтальных.image-grid
в.image-grid
.- Во-первых,
.image--expand
задается.image--expand
box-sizing: content-box;
чтобы исключить значение отступа из его ширины. - Элемент
.image--expand
имеет ширину, в 5 раз превышающую его родительский элемент, на500%
. Это позволяет расширенной области занимать всю ширину.image-grid
, минус отступ. - Чтобы занять оставшееся пространство,
.image--expand
задается.image--expand
влево и вправо. -
position: relative;
иleft: -5px
объявленияleft: -5px
смещают расширенную область влево, чтобы свести на нет значение.image-grid
padding-left.
- Во-первых,
Умный Бит
Мы хотим сместить все .image--expand
элементы в .image--expand
левое положение в соответствии с левой стороной .image-grid
. Для этого мы устанавливаем отрицательную маржу в зависимости от ее позиции в ряду.
Вот где приходит nth-of-type
:
.image__cell:nth-of-type(5n+2) .image--expand { margin-left: -100%; } .image__cell:nth-of-type(5n+3) .image--expand { margin-left: -200%; } .image__cell:nth-of-type(5n+4) .image--expand { margin-left: -300%; } .image__cell:nth-of-type(5n+5) .image--expand { margin-left: -400%; }
Первоначально я использовал nth-child
для достижения того же эффекта, но в других проектах я обнаружил, что iOS8 Safari довольно глючит, поэтому я стараюсь избегать его использования. Вместо этого я использую nth-of-type
поскольку он в основном служит той же цели. Если вам интересно, вы можете найти краткое объяснение nth-of-type
здесь .
В приведенном выше CSS мы нацеливаемся на вторую, третью и четвертую расширяемые области .image__cell
в каждой строке. Значение margin-left
зависит также от положения элемента в строке. Обратите внимание, что первый элемент в каждой строке не нуждается в заданном значении margin-left
поскольку он уже находится в нужной позиции. Чем дальше элемент находится слева, тем больше нам нужно отодвинуть расширяемую область обратно в левую сторону (с шагом -100%). Без этого расширяемая область будет выровнена с ее родителем, как показано ниже:
Нам также нужно вставить CSS, показанный ниже, чтобы гарантировать, что первый .image__cell
в каждой строке, кроме первой строки, .image__cell
на своей позиции, когда более .image__cell
элементы .image__cell
развернуты.
.image__cell:nth-of-type(5n+6) { clear: left; }
Теперь, когда основной макет готов, мы можем добавить несколько стилей для улучшения взаимодействия с пользователем.
Во-первых, стрелка вверх, указывающая, к какому изображению относится расширенный блок:
.image__cell.is-expanded .arrow--up { display: block; border-bottom: 8px solid #222; border-left: 8px solid transparent; border-right: 8px solid transparent; height: 0; width: 0; margin: 2px auto 0; }
Обратите внимание, что стиль стрелки достигается путем создания треугольника CSS , что позволяет сохранить HTTP-запрос. Этот эффект легко достигается благодаря умному использованию границ и установке высоты и ширины на 0.
Мы также хотим, чтобы стрелка появлялась только при .image__cell
элемента .image__cell
. Это делается путем добавления класса .is-expanded
. Наконец, мы хотим, чтобы стрелка оставалась в горизонтальном центре элемента .image__cell
поэтому margin: 0 auto;
применены.
Теперь мы готовы стилизовать кнопку «Закрыть», которая позволит пользователю закрыть расширенную область.
.expand__close { position: absolute; top: 10px; right: 20px; color: #454545; font-size: 50px; line-height: 50px; text-decoration: none; } .expand__close:before { content: '×'; } .expand__close:hover { color: #fff; }
Обратите внимание, что с помощью псевдоэлемента :before
мы можем вставить символ «×» на страницу без его появления в DOM, снова сохранив хотя бы один HTTP-запрос. Вставленный специальный символ — это символ умножения, который также использует Boostrap .
JQuery
Наконец, приведенный ниже jQuery просто переключается между .is-expanded
и .is-collapsed
при нажатии каждой ячейки изображения и кнопки закрытия.
var $cell = $('.image__cell'); $cell.find('.image--basic').click(function() { var $thisCell = $(this).closest('.image__cell'); if ($thisCell.hasClass('is-collapsed')) { $cell.not($thisCell).removeClass('is-expanded').addClass('is-collapsed'); $thisCell.removeClass('is-collapsed').addClass('is-expanded'); } else { $thisCell.removeClass('is-expanded').addClass('is-collapsed'); } }); $cell.find('.expand__close').click(function() { var $thisCell = $(this).closest('.image__cell'); $thisCell.removeClass('is-expanded').addClass('is-collapsed'); });
.var $cell = $('.image__cell'); $cell.find('.image--basic').click(function() { var $thisCell = $(this).closest('.image__cell'); if ($thisCell.hasClass('is-collapsed')) { $cell.not($thisCell).removeClass('is-expanded').addClass('is-collapsed'); $thisCell.removeClass('is-collapsed').addClass('is-expanded'); } else { $thisCell.removeClass('is-expanded').addClass('is-collapsed'); } }); $cell.find('.expand__close').click(function() { var $thisCell = $(this).closest('.image__cell'); $thisCell.removeClass('is-expanded').addClass('is-collapsed'); });
.var $cell = $('.image__cell'); $cell.find('.image--basic').click(function() { var $thisCell = $(this).closest('.image__cell'); if ($thisCell.hasClass('is-collapsed')) { $cell.not($thisCell).removeClass('is-expanded').addClass('is-collapsed'); $thisCell.removeClass('is-collapsed').addClass('is-expanded'); } else { $thisCell.removeClass('is-expanded').addClass('is-collapsed'); } }); $cell.find('.expand__close').click(function() { var $thisCell = $(this).closest('.image__cell'); $thisCell.removeClass('is-expanded').addClass('is-collapsed'); });
.var $cell = $('.image__cell'); $cell.find('.image--basic').click(function() { var $thisCell = $(this).closest('.image__cell'); if ($thisCell.hasClass('is-collapsed')) { $cell.not($thisCell).removeClass('is-expanded').addClass('is-collapsed'); $thisCell.removeClass('is-collapsed').addClass('is-expanded'); } else { $thisCell.removeClass('is-expanded').addClass('is-collapsed'); } }); $cell.find('.expand__close').click(function() { var $thisCell = $(this).closest('.image__cell'); $thisCell.removeClass('is-expanded').addClass('is-collapsed'); });
Конечно, вы можете легко избежать jQuery, используя `classList ()` и другие нативные методы, но вы не получите такой глубокой поддержки браузера, если не захотите полифилл.
Создание адаптивной сетки
Наличие 5 ячеек изображения в каждой строке на небольших устройствах не является идеальным, поэтому мы можем изменять количество элементов в строке, используя медиа-запросы. Например, приведенный ниже CSS уменьшает его до 2 изображений в строке.
@media only screen and (max-width: 530px) { .image__cell { width: 50%; } .image__cell:nth-of-type(2n+2) .image--expand { margin-left: -100%; } .image__cell:nth-of-type(2n+3) { clear: left; } .image--expand { width: 200%; } }
Чтобы предотвратить применение CSS, ранее относящегося к 5 элементам в строке, нам нужно будет либо сбросить эти значения, либо извлечь свойства и поместить их в собственный медиазапрос, что делается в приведенном ниже CodePen вместе с предыдущим кодом.
Обратите внимание на включение функции ячеек, которая выплевывает 50 ячеек изображения, чтобы избавить меня от беспокойства.
Для любителей Sass
Я не хотел исключать читателей, которые не используют Sass при написании этой статьи, но я также не хотел их сбрасывать со счетов. Этот проект является отличным примером использования Sass в разработке, поскольку количество элементов в строке связано с очень многими различными свойствами.
Пожалуйста, посмотрите следующую альтернативную демонстрацию CodePen . Обратите внимание, что в этой демонстрации я использую переменные Sass в верхней части CSS, что позволяет мне указать промежуток между изображениями, максимальную ширину изображения и минимальное и максимальное изображения в строке. Используя различные вычисления, Sass будет компилироваться в CSS на основе предоставленных опций. Он автоматически рассчитает оптимальные медиазапросы на основе максимального количества элементов в строке, что позволит сохранить изображения в пределах их максимальных размеров.
Эта версия Sass является экспериментальной, но, пожалуйста, дайте мне знать, если вы заметите какие-либо ошибки или потенциальные улучшения кода в обычной версии или в версии Sass.