Статьи

Воссоздание макета поиска картинок Google с помощью CSS

В проекте, в котором я недавно участвовал, меня попросили воссоздать функциональность расширения поиска картинок Google , похожую на скриншот, показанный ниже, но в формате жесткой сетки.

Пример поиска картинок 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 .

    1. Во-первых, .image--expand задается .image--expand box-sizing: content-box; чтобы исключить значение отступа из его ширины.
    2. Элемент .image--expand имеет ширину, в 5 раз превышающую его родительский элемент, на 500% . Это позволяет расширенной области занимать всю ширину .image-grid , минус отступ.
    3. Чтобы занять оставшееся пространство, .image--expand задается .image--expand влево и вправо.
    4. 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.