Требования к верстке каталогов интернет-магазинов имеют свойство повторяться из проекта в проект. Поэтому у меня выработался набор стандартных приемов, которыми я хочу поделиться в этой статье.


Некоторые приемы уже были рассмотрены в различных статьях. Однако у меня возникло желание объединить их и проиллюстрировать отдельными демо. Надеюсь, в таком виде наработки окажутся полезны верстальщикам, которым часто приходится работать над интернет-магазинами.


В качестве примера мы будем верстать список товаров для интернет-магазина комнатных растений. Демо готового каталога можно посмотреть по ссылке. В результате должен получиться список растений с фото, описаниями и всплывающими кнопками. Кроме того, вид списка можно будет переключать: товары будут выглядеть либо как плитка, либо как таблица.


Адаптивная сетка


Итак, начнем с создания адаптивных плиток — будущих карточек товаров. У нас будет 8 комнатных растений:


<ul class="products clearfix">
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
	<li class="product-wrapper">
		<a href="" class="product"></a>
	</li>
</ul>

Оберткой для товаров послужат блоки, занимающие 100% ширины родителя на мобильных устройствах.


.product-wrapper {
	display: block;
	width: 100%;
	float: left;
	transition: width .2s;
}

Теперь используем медиа-запросы, чтобы разместить по две, три и четыре плитки в ряд при больших разрешениях монитора.


@media only screen and (min-width: 450px) {
	.product-wrapper {
		width: 50%;
	}
}

@media only screen and (min-width: 768px) {
	.product-wrapper {
		width: 33.333%;
	}
}

@media only screen and (min-width: 1000px) {
	.product-wrapper {
		width: 25%;
	}
}

И зададим стили карточек товаров.


.product {
	display: block;
	border: 1px solid #b5e9a7;
	border-radius: 3px;
	position: relative;
	background: #fff;
	margin: 0 20px 20px 0;
	text-decoration: none;
	color: #474747;
	z-index: 0;
	height: 300px;
}

Из-за того, что карточки имеют margin-right равный 20px, весь список имеет нежелательный отступ справа.




Исправим это с помощью отрицательного значения margin-right у родителя.


.products {
	list-style: none;
	margin: 0 -20px 0 0;
	padding: 0;
}

Теперь все в порядке. Посмотреть на получившуюся сетку можно на страничке демо. Для наглядности блокам в демо задана фиксированная высота.


Фото товаров


Следующим интересным моментом является верстка блоков с фотографиями растений. Разметка в данном случае будет такой:


<div class="product-photo">
	<img src="images/roses/1.jpg" alt="">
</div>

Сделаем родителя тега img квадратом с помощью свойства padding-bottom со значением 100%. Вот все стили для данного блока.


.product-photo {
	position: relative;
	padding-bottom: 100%;
	overflow: hidden;
}

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


.product-photo img {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	max-width: 100%;
	max-height: 100%;
	margin: auto;
	transition: transform .4s ease-out;
}

Осталось немного увеличивать фото при наведении.


.product:hover .product-photo img {
	transform: scale(1.05);
}

Как все это работает можно посмотреть на примере демо.


Описание товара


Нам нужно, во-первых, задать описанию товара высоту в четыре строки, а во-вторых, сделать конец последней строки слегка размытым.

Первую задачу можно решить, задав высоту параграфа с описанием, равную четырем line-heigth.



.product p {
	position: relative;
	margin: 0;
	font-size: 1em;
	line-height: 1.4em;
	height: 5.6em;
	overflow: hidden;
}

А эффекта размытия последних букв четвертой строки мы добьемся с помощью псевдоэлемента :after с линейным градиентом в качестве фона.


.product p:after {
	content: '';
	display: inline-block;
	position: absolute;
	bottom: 0;
	right: 0;
	width: 4.2em;
	height: 1.6em;
	background: linear-gradient(to left top, #fff, rgba(255, 255, 255, 0));
}

Перечеркнутые цены


Чтобы перечеркнуть цену линией отличной по цвету от текста, задаем блоку с ценой значение color равное #ff3535 и text-decorationline-through. При этом устанавливаем для вложенных элементов серый цвет текста.


.product-price-old b,
.product-price-old small {
	color: #888;
}

Всплывающие кнопки


Что касается кнопки «Быстрый просмотр», ее реализация сравнительно проста. Кнопка абсолютно позиционирована относительно блока с классом .product-photo, скрыта с помощью opacity: 0 и немного сдвинута вниз за счет transition: translateY(2em). При наведении курсора на карточку товара стили кнопки меняются следующим образом.


.product:hover .product-preview {
	transform: translateY(0);
	opacity: 1;
}

Несколько сложнее дело обстоит с кнопками «В корзину» и «Купить в 1 клик». Здесь внешний контейнер .product-buttons-wrap абсолютно позиционирован в блоке .product и равен родителю по ширине и высоте.


.product-buttons-wrap {
	position: absolute;
	top: 0;
	left: -1px;
	right: -1px;
	bottom: 0;
	visibility: hidden;
	opacity: 0;
	transform: scaleY(.8);
	transform-origin: top;
	transition: transform .2s ease-out;
	z-index: -1;
	backface-visibility: hidden;
}

Далее мы стилизуем псевдоэлемент .product-buttons-wrap:before таким образом, чтобы он вытеснял любой контент блока вниз, под нижнюю границу, блока-родителя.


.product-buttons-wrap:before {
	content: "";
	float: left;
	height: 100%;
	width: 100%;
}

Теперь можно добавить собственно контент — кнопки, размещенные в блоке .buttons.


.buttons {
	position: relative;
	top: -1px;
	padding: 20px;
	background: #fff;
	box-shadow: 0 0 20px rgba(0, 0, 0, .5);
	border: 1px solid #56bd4b;
	border-radius: 3px;
}

Благодаря правилу float: left у псевдоэлемента .product-buttons-wrap:before, кнопки расположены ниже основного контента, и блок .buttons занимает всю площадь карточки.



Чтобы лучше разобраться с кнопками, можно посмотреть это демо


Переключение вида карточек товаров


Для переключения между плиткой и табличным видом каталога мы создаем две кнопки.


<div class="layout-buttons">
	<span class="active icon icon-list"></span>
	<span class="icon icon-table"></span>
</div>

По клику на кнопки у списка товаров удаляется и добавляется класс .table-layout.


$productList.toggleClass('table-layout');

Таким образом, прописав стили для карточек-дочерних элементов блока ul.table-layout, мы можем «превратить» список в таблицу только с помощью CSS, без перезагрузки страницы. Для этого задается ширина карточек равная 100%. А вложенные блоки теперь займут только часть ширины карточки, например:


.table-layout .product-main {
	width: 50%;
	float: left;
	background: #fff;
}

Далее абсолютно позиционированные элементы занимают свое «естественное» положение. Например, так происходит с кнопками.


.table-layout .product-buttons-wrap {
	position: static;
	visibility: visible;
	opacity: 1;
	transform: scaleY(1);
}

Это был последний момент, которым хотелось с вами поделиться. С праздниками и всего доброго.

Поделиться с друзьями
-->

Комментарии (59)


  1. copyhold
    11.01.2017 11:43

    При ховере есть неприятный скачёк — видимо из-за тени.
    В версии для телефона невозможно нажать на кнопки — их просто нет.


  1. ANTPro
    11.01.2017 11:45

    При масштабе 150% и наведении мышки на плитку:

    Скриншот с Оперы
    image


    1. St_Mikhail
      12.01.2017 11:59
      +1

      А что делать, если контент-менеджер ввёл в названии 2-3 строки текста, а у соседей только по одной?

      Заголовок спойлера


      1. Erick_Flatcher
        19.01.2017 20:55

        Я вот тоже об этом подумал. Автору надо использовать Flex.


  1. Alex_T666
    11.01.2017 11:47
    +4

    Я одного не пойму, почему как только нужна группа каких-то однородных элементов, так сразу непременно используется ul и его друзья? В каждой второй верстке обязательно ul. Особенно на плагинах, которые должны выдавать иерархию, будь то меню или дерево — обязательно висит ul. Да в половине сладеров-крутилок — обязательно ul. Сидишь и наблюдаешь, как дерево с точками красиво трансформируется в то что надо.

    Почему бы не использовать просто div? И не надо ничего обнулять, ничего не надо очищать, стилизуй сразу как надо, не задумываясь о том, как списки в том или ином браузере отображаются по умолчанию.

    Загадка…


    1. Carduelis
      11.01.2017 11:55
      +2

      Что работает как список, выглядит как список или подразумевается как список — должно быть списком. Если есть еще более глубокая семантика, выбираем, например, nav или menu.


      1. Alex_T666
        11.01.2017 12:10
        +5

        Мне кажется, что понятие список настолько растяжимое, что лично я бы не стал в данном случае говорить о семантике. Анонсы статей на сайте — список? Логотипы компаний-рекламодетелей список? Аватары топовых авторов? Номера страниц? Список тегов?

        Особенно, я не стал бы говорить про то, как выглядит список. По мне выглядит как список только, собственно, список и все. То есть тот самый, который по умолчанию, нумерованный-ненумерованный с некоторой степенью стилизации. Это и есть список, для которого используются эти теги.

        Список товаров так, как он представлен на картинках, это не список.

        Мне так кажется.


        1. DrMeJI
          11.01.2017 12:40

          Мысленно сотрите css, оставив только браузерные стили. Как лучше представить товар?
          Правильно, таблицами. Но таблицы в вебе крайне неадекватны.
          На втором месте будет список. Визуально отделен для людей, логически является списком для роботов и скриптов.

          Никогда не путайте 'чем является' и 'как выглядит'. Меню — всегда список, акцентрирование внимания — strong, и т.д. А то, что визульно я сделал меню в виде бегающих анимированых человечков, а важные элементы обвожу рамочкой — это уже работа по юзабилити и дизайну, что не касается html.

          Если вы придумали более удобные универсальные теги — поделитесь. Никто не доволен li, но лучше пока что то не получается. Конкретно Div сливаются с текстом без доп стилей и не несет в себе логическое заключение 'мой сосед — такой же, как я'. Т.е. допустим плагин на браузер, позволяющий свернуть большие объемы однотипных данных, уже не сработает. А кому то может очень надо. Ну и т.д.


        1. Delphinum
          11.01.2017 12:58

          Здесь к вопросу можно подойти с другой стороны: вы просто реализуете товар в виде div, а затем всю эту пачку div'ов оборачиваете либо в список, либо в таблицу, либо еще во что то, в зависимости от того, как вы хотите их представить — списком, таблично, иерархией и т.д.


          1. Alex_T666
            11.01.2017 15:09
            +3

            Обернуть можно, да, но мне до сих пор непонятно, зачем в данном случае использовать этот списочный неудобный тег. Неудобный по причине дефолтных стилей, которые придется специально обрабатывать и все.

            Доводы DrMeJI мне не показались убедительными. Например я не считаю,, что меню это список. Это иерархия — да, и списки тоже могут быть иерархическими да, но я не считаю что всю подряд иерархию нужно оформлять списочным тегом.

            И я не считаю, что нужно мысленно стирать CSS, зачем? Если у меня должен получится список, я использую списочный тег. Если мое меню должно выглядеть как список, как раньше делали во фреймах, то я использую тег списка. Если у меня есть список категорий, которые должны выглядеть как список, то я использую тег списка. Но если у меня выпадающее меню в шапке зачем я должен использовать теги списка и мучится с обнулением их свойств? Не понимаю.

            Помогают ли эти теги роботам? Сомневаюсь. Для людей — соглашусь, по тегам сразу видно иерархию и 'мой сосед — такой же, как я'. Но я тоже самое гораздо внятней увижу по классам.

            Ну вот зачем в каждом слайдере-крутилке с плоским набором картинок понатыкан ul+li?

            Поэтому мое мнение пока неизменно: списки — для списков. Если список похож на то, что выдают эти теги по умолчанию, используем ul а если нет, то и ну их к бесу.


            1. ermolaevalexey
              12.01.2017 11:58

              Но если у меня выпадающее меню в шапке зачем я должен использовать теги списка и мучится с обнулением их свойств? Не понимаю.

              Что в правиле list-style: none; такого мучительного?


  1. Machez
    11.01.2017 11:55
    -1

    Кстати, а почему автор не воспользовался для вёрстки каким-нибудь фреймворком? Например — Bootstrap?


    1. var_bin
      11.01.2017 12:21
      +1

      Добрый день.


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


    1. DrMeJI
      11.01.2017 12:43
      +2

      А можно врспользоваться еще и верстальщиком.

      Ну а если серьезно, то в вебе большинство магазинов на 100 — 300 товаров с посещаемрстью в 100 -500 чел. в день. И если вы не поставили на него готовый шаблон, а пишите сами, то использовать bootsrtrap, это как пушкой по воробьям. Ставить фреймворк, чтобы использовать 5% от его возможностей — это лишние байты, более сложная архитектура и поддержка.


      1. Delphinum
        11.01.2017 13:02
        +1

        А разве bootstrap запрещает использовать его модульно?


        1. Aingis
          11.01.2017 13:39
          -1

          Он для этого вообще не приспособлен.


          1. indestructable
            11.01.2017 14:06

            С препроцессором можно тянуть части (модули) бутстрапа. Единственное, что нужно вытягивать и зависимости используемых модулей, которые не всегда очевидны.



      1. boldyrev_gene
        11.01.2017 22:07
        +1

        Использование всем известного фреймворка как раз упрощает поддержку — нет необходимости тратить время на то, чтобы понять что было в голове у человека, который это навоял…


        1. psFitz
          13.01.2017 12:20

          это список товаров на css, как тут можно что-то непонятное сделать? Обычно стили фреймворка не подходят под дизайн и он больше мешает чем помогает.


  1. s0nly
    11.01.2017 11:57

    В Chrome(Версия 55.0.2883.87 m) скачков не наблюдаю.
    Решить бы еще проблему с заголовком в две строки.


    1. m1skam
      11.01.2017 12:14
      +5

      Типичная ошибка, когда дизайнер и верстальщик полагают, что название всегда поместится в одну строку, а описание товара всегда будет не больше 4х строчек.

      Скриншот проблемы


      1. stardust_kid
        11.01.2017 13:13
        +2

        Надо оборачивать каждый ряд во враппер. Или использовать flexbox


        1. AlgenGold
          11.01.2017 15:09
          +1

          Можно еще сделать через display: table, table-cell


          1. sgrogov
            11.01.2017 17:41
            +1

            Почему никто не использует inline-block? Гибче же, чем врапперы на каждую строку или display: table.


            1. stardust_kid
              11.01.2017 18:53

              Потому что inline-block рендерит пробел после элемента по умолчанию. От пробела можно избавиться через колдунство, но зачем?


              1. Aingis
                11.01.2017 22:17

                Не по умолчанию, а когда после элемента есть пробельные символы в HTML-коде. Если их убрать каким-либо образом, то пробела не будет.


                1. stardust_kid
                  12.01.2017 00:16

                  А вы уверены, что их там никогда не будет?


                  1. Aingis
                    12.01.2017 11:05

                    Если у вас сколь-либо приличный шаблонизатор, который вы контролируете, то да.


              1. sgrogov
                12.01.2017 12:05

                Затем, чтобы не случалось «вёрстки лесенкой» например.
                На мой взгляд clearfix куда большее колдунство, чем «font-size: 0».


                1. kirillsunday
                  12.01.2017 20:55

                  это же всего один дополнительный класс в html и одно правило в css, а фонт сайз надо прописывать и родителю и потомкам, так сказать, дублирование кода)


                  1. sgrogov
                    13.01.2017 07:59

                    Ну если писать модульный css, то стили потомков не будут зависеть от стилей контейнера. Да и «вёрстка лесенкой» даже с clearfix никуда не денется.


  1. pudovMaxim
    11.01.2017 12:00
    +2

    Красиво, но плохо. Кнопка «В корзину» пропадает. А что это за магазин такой в котором нужно ловить самую важную кнопку для него? Вот если бы она была постоянно, а при наведении выплывала другая маловажная информация, то было бы лучше.


  1. Alex_T666
    11.01.2017 12:01
    +2

    А по поводу верстки, не пробовали через flexbox? В последнее время для такого рода задач только ее и использую.


    1. aegnis
      11.01.2017 12:14
      -1

      Пробовала, очень удобно. В последних проектах использовали Pure.css, в котором адаптивные сетки выполнены на flexbox.


  1. var_bin
    11.01.2017 12:22

    Добрый день.


    Исправим это с помощью отрицательного значения margin-right у родителя.

    А как вы с этим боритесь на разных разрешениях, мобильных девайсах?


    1. aegnis
      11.01.2017 12:32

      Добрый день. На мобильных девайсах отрицательный margin тоже хорошо работает.


  1. hardtop
    11.01.2017 12:42

    А почему бы не сделать вот так

    .product-wrapper { display: inline-block; vertical-align:top; }
    
    Тогда плитки не будет играть в лесенку.


  1. BullDER
    11.01.2017 12:51
    -1

    Спустя уже почти 8мь лет перетирается всё та же тема


    1. stardust_kid
      11.01.2017 13:12

      А в чем проблема?


  1. stardust_kid
    11.01.2017 13:11

    При создании сетки, ее свойства лучше абстрагировать в отдельные классы, а не смешивать с дизайном. Тогда в итоге получится сетка Bootstrap.


  1. dernasherbrezon
    11.01.2017 13:40

    <img src="images/roses/1.jpg" alt="">
    



    И это все? PageSpeed выдаст не больше 50 из 100 для мобильной версии.

    Можете добавить в статью про использование srcset/picture? Я обновил свой проект на использование этих тэгов и теперь на большом каталоге получаю отличный результат(https://instasell.ru). Не говоря о том, что страница с большим количеством картинок товаров теперь грузится заметно быстрее.


    1. mgremlin
      11.01.2017 19:54

      Интересная идея про srcset, спасибо за наводку.

      Но грузится все-таки медленно… почти три секунды — и это же еще через СDN, да?
      ну и верстка в стиле деревенского забора = криво-косо 8-) Все элементы разные по высоте. надо бы поправить 8-)


  1. ShadowOfCasper
    11.01.2017 14:08
    +5

    Статья по созданию карточек полезна, но то, что в 2017 году мы упорно по инерции продолжаем использовать float-сетку с костылями типа margin: 0 -20px 0 0; меня печалит.
    Не пора ли уже использовать flexbox? Поддержка устройств доросла до 97%. Пора бы.


  1. Kasheftin
    11.01.2017 14:35

    Насчет фото товаров: давно использую технику через background-image и background-size: contain. Мне кажется, она более универсальная — красивее, когда все фотки квадратные, даже если оригиналы вытянуты (предпочтительнее сцентрировать и обрезать выступающие за края части а не вписывать прямоугольники). Типа https://jsfiddle.net/kasheftin/avqdpsgs/. Тег img имеет opacity:0 и нужен только чтобы фотку можно было потащить мышкой.


    1. olenkill
      11.01.2017 16:09
      +3

      А еще есть классная штука для картинок.
      Называется object-fit быстро накиданный пример
      Для ie есть простенькие полифиллы весом в ~1кб.


  1. greendimka
    11.01.2017 14:47

    Где поддержка нескольких строк в названии? Где поддержка нескольких строк в описании?
    Где, балин, приложенная машина времени, чтобы отвезти этот макет в девяностые?? Когда же современные UX "специалисты" осознают, что мы живём в мире мобильников и планшетов с тач-экранами? Сколько можно делать интерфейсы, которые при использовании на телефоне вызывают один негатив?


  1. vantoose
    11.01.2017 15:10
    -1

    Автор просто поделилась идеями, наработками, которые помогут кому-то. Не нужно использовать приведенный пример в качестве шаблона. Допиливайте сами и главное не забывать про тестирование)
    Спасибо за статью.


  1. Sam116
    11.01.2017 15:10

    Хотелось бы еще узнать мнение по поводу скачков выпадающих кнопок. Появляются они плавно, однако исчезают резко. Если сделать плавное исчезание, то вылезет баг с z-index, при ховере на нижний элемент будет резкое перекрытие. Кто как решает подобную проблему?


  1. Pavel_White
    11.01.2017 15:59
    -1

    Может я не в тему но не надо минусить, нашел очень четкий хостинг, есть бесплатный тариф, но только для доменов 3-его уровня. В общем, кому надо заходите http://api.hostinger.ru/redir/16374708


  1. JDay
    11.01.2017 17:44

    margin -20px у родителя напрочь убивает всю красоту решения.


  1. kirillsunday
    11.01.2017 17:44

    кто-нибудь может поделиться примером реализации сего чуда на флексе?


    1. kahi4
      12.01.2017 00:38
      +1

      https://jsfiddle.net/yaLkw88v/1/ буквально за 20 секунд (правда тут без медиа квери, хотяяяя и без них вполне ок. И без рюшек в виде цветного фона и анимашек, но, как и ожидалось, с поддержкой разной высоты блоков)


      1. kirillsunday
        12.01.2017 20:34

        спасибо!)


  1. Joannes
    11.01.2017 17:45

    Не хочется придераться, но на мобилке если отображать товары списком, верстка немного плавает.
    Все же решение flex в данном случае будет рациональным


  1. mgremlin
    11.01.2017 19:43

    Простите, это ниочем.
    Тут не затронуты самые важные вопросы витрины инетмага: что делать с разнородными товарами. например, что делать, если фотографии разные по высоте? Что, если одно описание состоит из 2 строк, а другое — из 5? Что, если на экране количество объектов не кратно сетке (например, 7)?

    Высоту прибить гвоздями всякий сумеет. А вот отобразить витрину с разными товарами культурно — это уже сложнее. Но тоже можно 8-)


  1. Flex25
    11.01.2017 20:09
    +1

    .product {height: 300px}

    Жесткое задание высоты блока может вызвать ситуацию, когда из-за длинного описания товара контент выйдет за границы блока. Если переписать вашу сетку на flexbox, подобных проблем можно избежать.


  1. AnthonySoprano
    20.01.2017 13:11
    +1

    Насчёт многострочного текста. Всё решается простейшим способом:

    Для обрезки текста

    .product .cut {
    	white-space: nowrap;
    	text-overflow: ellipsis;
    	/*overflow: auto;*/
    }
    .product:hover .cut {
    	white-space: normal;
    	text-overflow: initial;
    	overflow: hidden;
    }
    


    ...
    <h2 class="produvt-name cut">Хавортия Хавортия Хавортия Хавортия Хавортия</h2>
    ...
    


    В этом случае плитки всё же разъезжаются, это уже надо переверстать саму плитку.


  1. stepan549
    20.01.2017 13:11
    -1

    Прошу прощения что задаю вопрос немного не там где нужно( в других статьях никто не отвечает): как сделать так что бы анкор ссылки школы Итальянского выглядел красиво, вот так — «курсы итальянского в минске», а не вот так — http://papaitaliano.by