Когда мы проходим собеседования, нам часто задают вопросы по верстке. Зачастую они сводятся к перечислению значений свойства display или способов центрирования элементов. Меня это откровенно достало, и мне захотелось придумать свои задачи, с помощью которых можно проверить знания верстальщика, а не то, как он зазубрил значения свойств.


Описание задачи


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



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



Реализацию такого блока я начну с разметки:


<div class="post">
  <img src="example.jpg" class="post__img" alt="Черный кот в шляпе">
</div>

Чтобы задать размеры для блока-заглушки, нужно узнать соотношение сторон изображения. Для примера я буду использовать изображение размером 1920x1080px.


Но если выводить изображение такого размера, то на большинстве экранов оно будет отображаться с горизонтальной прокруткой. Поэтому элементу .post я задам максимальную ширину 640px, а ширина изображения будет подстраиваться под нее.


.post {
  max-width: 640px;
}
    
.post__img {
  max-width: 100%;
}

Добавление блока-заглушки на страницу я сделаю при помощи псевдоэлемента before. Также нужно не забыть расположить изображение поверх него.


.post {
  max-width: 640px;
  position: relative;
}
  
.post::before {
  content: "";
}
  
.post__img {
  max-width: 100%;
  position: absolute;
  top: 0;
  left: 0;  
}

Теперь нужно, чтобы ширина блока-заглушки повторяла ширину изображения. Ранее я сделал так, чтобы изображение занимало 100% ширины родительского элемента .post. То же самое сделаю для псевдоэлемента before, добавив display: block к нему.


.post {
  max-width: 640px;
  position: relative;
}

.post::before {
  content: "";
  display: block;
}
  
.post__img {
  max-width: 100%;
  position: absolute;
  top: 0;
  left: 0;  
}

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


Для этого нужно использовать свойство padding-top со значением в процентах. Чтобы сделать это правильно, нужно знать особенность расчета процентов у свойства padding-top. А также помнить, что размеры изображения 1920x1080px.


.post {
  max-width: 640px;
  position: relative;
}
  
.post::before {
  content: "";
  display: block;
  /* здесь будет свойство padding-top со значением в % */
}
  
.post__img {
  max-width: 100%;
  position: absolute;
  top: 0;
  left: 0;  
}

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


Решение


У свойств padding-top и padding-bottom есть одна очень полезная особенность. Если для них задать значения в процентах, то они будут рассчитываться от ширины родительского блока.


Соответственно, зная это и помня размеры изображения (1920x1080px), можно рассчитать padding-top для блока-заглушки с помощью пропорции:


padding-top = (Ви * 100%) / Ши =  (1080 * 100% ) / 1920 = 56.25%

где Ши и Ви — ширина и высота изображения.


Осталось добавить значение для padding-top:


.post {
  max-width: 640px;
  position: relative;
}
  
.post::before {
  content: "";
  display: block;
  padding-top: 56.25%;
}
  
.post__img {
  max-width: 100%;
  position: absolute;
  top: 0;
  left: 0;  
}

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


  1. pepelsbey
    20.12.2018 10:14

    Новичкам конечно будет полезно узнать про этот трюк, но причина обозначена неудачно. Беда в том, что если картинка не 16:9, то от прыжков мы всё равно никуда не денемся. Только если договоримся прописывать размеры в атрибутах на сервере.


    1. melnik909 Автор
      20.12.2018 10:24

      Можно:
      — договориться о системе классов и через них прописать пропорции
      — использовать CSS Custom Properties в атрибуте style у div'а
      — добавить к div'у атрибут style и в нем указать padding (как делает Instagram)

      Это уже детали реализации. Но многие специалисты не знают, что такое вообще возможно.


    1. Against-vegetables
      20.12.2018 11:06

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


  1. Suliman
    20.12.2018 10:25

    Если вы располагаете картинку абсолютом, то зачем вообще использовать :before? давайте паддинг сразу для .post


    1. melnik909 Автор
      20.12.2018 10:27

      Можно, но тогда надо обвернуть div.post еще одним div'ом и ему ширину задать, чтобы проценты корректно рассчитывались.


      1. Suliman
        20.12.2018 10:48

        Если рассматривать реальный вариант который бы использовался в реальном проекте, то здесь много чего добавлять еще нужно, например что если картинка должна быть ссылкой, что если .post будет иметь padding, а картинка у вас имеет top:0; и т.д. Но в общем идея не плохая.


  1. Kurienko
    20.12.2018 12:02

    почему нельзя задать сразу размер в картинке?
    браузер сразу забронирует место под картинку с указаними размерами и все ето не будет прыгать…


    1. melnik909 Автор
      20.12.2018 12:03
      +1

      Какие единицы измерения вы будете использовать для этого?


      1. Klenov_s
        20.12.2018 12:10

        Пикселы, конечно.


        1. melnik909 Автор
          20.12.2018 13:19

          А вам надо изображение, которое адаптируется на разных экранах. Что тогда?


          1. Klenov_s
            20.12.2018 14:09

            Это выходит за рамки задачи, у вас жестко забито в тексте 640px


            1. dom1n1k
              20.12.2018 18:42

              На сегодняшний день адаптивные изображения — это задача по умолчанию.


              1. Klenov_s
                20.12.2018 22:47
                +2

                max-width: 640px;
                — о какой адаптивности речь?


                1. dom1n1k
                  21.12.2018 10:04

                  Это называется докопаться до частности, забыв о смысле статьи в целом.


          1. NickyX3
            21.12.2018 08:29

            для этого есть vw/vh, если привязывать все ширины к vw, то в случае известного соотношения сторон можно тупо сделать, например min-height: calc( (100vw/16)*9 );
            calc вообще удобная штука


            1. dom1n1k
              21.12.2018 10:09

              В реальности «привязывать все ширины к vw» — сомнительная практика (очень мягко говоря).


              1. NickyX3
                21.12.2018 10:22

                Почему же?
                Вот есть у вас 100% ширина и вам нужны картинки в три колонки.
                Что сомнительного в
                width: calc( (100vw — 10px)/3 );
                min-height: calc( ((100vw — 10px)/48)*9 );
                ????


                1. dom1n1k
                  21.12.2018 10:59

                  1. Поизучайте спецификации, всегда ли равны 100% и 100vw. Или минус 10px это и есть магическая константа для борьбы с расхождениями?
                  2. Картинка чаще должна быть привязана не к ширине вьюпорта, а вписываться в родительский контейнер (потому что он часть сетки), о размере которого она ничего не знает.


                  1. NickyX3
                    21.12.2018 11:20

                    1. Я разве говорил, что 100vw == 100%? 10px это условный размер скрола, чтоб не менялась ширина, если страница будет длинная.
                    2. Картинка и так 100% ширины родителя, а вот он и привязан к ширине вьюпорта.
                    К сожалению — ширина и высота вьюпорта сейчас это единственные абсолютные величины, к которым можно привязаться для расчета соотношения сторон блока под картинку. В отличие от процентов.
                    Сомнительные «хаки» выравнивания по вертикали типа процентных padding, или line-height тоже так себе практика, ибо они явно для этого не предусмотрены


                    1. dom1n1k
                      21.12.2018 12:27

                      Вот именно, что «условный». В зависимости от операционной системы и пользовательских настроек, он везде будет разным. От нуля до нескольких десятков px.
                      Эта особенность очень сильно обесценивает единицы вьюпорта, хотя многие разрабы относятся к ним с эйфорией.
                      Они удобны в случаях, когда не нужна особая точность. Но в большинстве ситуаций это очень скользкие единицы и обращаться с ними нужно крайне внимательно и осторожно.
                      Они как рыба фугу — круто, но предательски опасно, а потому её могут готовить только очень квалифицированные повара.

                      «Хак» с паддингом — не совсем хак, хотя на первый взгляд и выглядит таковым. Не хак, потому что он основан на документированном поведении из спецификации. Это не какая-то там багофича, которая может исчезнуть в новой версии браузера. Это работает так, как и должно работать.


                      1. NickyX3
                        21.12.2018 14:22

                        это очень скользкие единицы и обращаться с ними нужно крайне внимательно и осторожно

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

                        Для тех сайтов, где контент на полную ширину, достаточно 100vw, и оно будет типа full-screen, что и для мобильных подходит тоже.

                        основан на документированном поведении из спецификации

                        Документированное поведение не означает, что, исходя из простейшей логики, padding и line-height не предназначались для выравнивания блоков по вертикали или расчета размера по минимальной высоте. Для первого достаточно flex-box, для второго min/max-height при фиксированной ширине. Для сохранения соотношения при резиновой ширине считаю calc относительно vw вполне работоспособный вариант на «чистом» css


                        1. dom1n1k
                          21.12.2018 16:01

                          > большинство сайтов все же фиксировано по ширине в каких-то пределах [...] и таки имеют поля по бокам
                          Если сайт фиксирован по ширине, то тем более не получится привязывать ширины элементов к ширине вьюпорта.

                          > где контент на полную ширину, достаточно 100vw, и оно будет типа full-screen
                          Угу, с горизонтальным скроллом. Вы же не будете всерьез предлагать overflow: hidden? Я надеюсь.

                          > для второго min/max-height при фиксированной ширине
                          Если ширина фиксирована, проблемы нет вообще — там просто указывается height и всё. Вся суть задачи обсуждаемой именно в том, чтобы сделать резину с сохранением пропорций.

                          > считаю calc относительно vw вполне работоспособный вариант
                          Ну значит у нас разные представления о качестве верстки.


                          1. NickyX3
                            21.12.2018 16:14

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

                            Легко, к примеру ширина сайта привязана к ширине вьюпорта в зависимости от его ширины. По разному.
                            К примеру
                            При ширине вьюпорта овер 1280, ширина контента 70vw, при меньшей — 90vw, что не так? Я же описал случай, фиксирована в зависимости от максимальной, привязать к vw легко, media query еще не отменили же.

                            Ну значит у нас разные представления о качестве верстки.

                            У нас разные представления о качестве дизайна, и способах решения


                            1. dom1n1k
                              21.12.2018 17:20

                              > При ширине вьюпорта овер 1280, ширина контента 70vw, при меньшей — 90vw, что не так?
                              Не так то, что при переходе через брейкпойнт в большую сторону ширина контента будет наоборот уменьшаться, что противоестественно.


  1. mobi
    20.12.2018 13:31

    А не проще тогда «по старинке» задавать размеры изображения в атрибутах width и height, а масштабировать под ширину контейнера через

    img { max-width: 100%; height: auto; }
    ?


    1. speker
      20.12.2018 14:34

      Текст продолжает прыгать


    1. melnik909 Автор
      20.12.2018 14:35

      Попробуйте и узнаете, что нет


      1. mobi
        20.12.2018 17:04

        Проверил, действительно «нет». Но я зато придумал, как можно выкрутиться, если по какой-то причине нет возможности обернуть изображение в слой: jsfiddle.net/r5bnjm87 (вдруг, кому пригодится).


        1. mobi
          20.12.2018 17:25

          Хотя, нет, эта идея пока еще сырая. Пока у img не появится shadow dom, к нему нельзя применить ::before.


  1. lifecom
    20.12.2018 16:19

    Я так понимаю, нам необходимы даже не размеры, а соотношение сторон.
    А js-ом нельзя получить все размеры изображений на странице ДО их загрузки?
    Вообще было бы полезно «дёргать» из изображений до их загрузки какие-то данные, по типу EXIF


    1. Alexufo
      20.12.2018 17:32

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


  1. Alexufo
    20.12.2018 17:31

    Подождите, если мы говорим про padding-top в процентах, то картинка автоматом идет в бекграунд. К черту img вам этот?
    Картинку на бекграунд, блоку padding-top:% в требуемых пропорциях. Вообще у AirBnB мне нравится как сделано, но меня вымораживает их бесполезная иерархическая вложенность, которая абсолютно ничем не оправдана.


    1. speker
      20.12.2018 17:49

      pepelsbey думаю скажет тебе про доступность


    1. melnik909 Автор
      20.12.2018 20:05

      контентные изображения не надо фоном делать)


      1. Alexufo
        20.12.2018 22:31

        Ну вот AirBnb это не волнует, у них правда сайт на телефоне моем тормозит дичайшим образом.

        Очень странно, что вы ни слова не написали об этом
        developer.mozilla.org/ru/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images

        Вордпресс во всю генерит тьму ресайзов для этого, но внедрение этого я вижу не у всех.


        1. melnik909 Автор
          20.12.2018 22:42

          Всему свое время)


      1. Alexufo
        22.12.2018 01:45

        допустим, к черту все минусы и правила, тогда бы вы как логику выстраивали? Этот способ бы вас успокоил?


    1. pepelsbey
      20.12.2018 22:15

      контентные изображения не надо фоном делать

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


      1. melnik909 Автор
        20.12.2018 22:41

        Не понял вас. Я же наоборот говорю, что для контентных изображений не надо использовать background


        1. Alexufo
          20.12.2018 23:24

          ну он явно мне отвечал.


      1. Alexufo
        21.12.2018 00:28

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

        Попадаются забавные вещи, как отсутствие схожих свойств у близких по духу элементов.
        В html5 добавили по srcset и тегу img и в background. И тег picture появился, а делать background-size: cover он так и не научился.


        1. Aingis
          21.12.2018 12:13

          И тег picture появился, а делать background-size: cover он так и не научился.
          А что случилось с object-fit? Перестало работать?


  1. lipton_ice_tea
    20.12.2018 18:45

    Можно лучше) Не будут прыгать изображения. Картинки будут появляться плавно, а не прогружаться вертикально. Будет ленивая загрузка (по желанию). И любое соотношение сторон.


    1. Получаем размеры картинок на сервере и прописываем их в атрибуты width и height у img.
    2. Меняем src на data-src
    3. В css пишем для img max-width:100% (ну или сколько угодно)
    4. Добавляем скрипт js, который сравнивает фактическую ширину картинки с шириной в атрибуте width, и обновляет height
    5. Дальше в js дергаем значение data-src и грузим в фоне. Когда картинка загружена — меняем data-src на src. (Это можно делать либо сразу, либо после загрузки основного контента будет ленивая загрузка)
    6. В css либо пишем img[src], либо класс с height:auto (лучше класс)


    1. melnik909 Автор
      20.12.2018 20:06

      а для чего нужно height: auto? Оно же вроде так по умолчанию


      1. lipton_ice_tea
        21.12.2018 07:07

        Тк атрибут height у img перекрывает свойство по умолчанию. Поэтому что бы перекрыть атрибут, мы и добавляем правило в css.
        Но как альтернатива — можно просто в js удалять атрибут height у img — эффект тот же


    1. Alexufo
      21.12.2018 00:04

      Как сделано у медиума

      контейнер имеет физические данные картинки max-width: 542px; max-height: 826px;
      — плейсхолдер так же заранее расчитан padding-bottom: 152.4%;
      — врапер с absolute top:0;left:0;width:100%;height:100%
      —— img c src 30px превьюшки
      —— canvas блюрит превьюшку
      —— оригинальному img вставляется путь из data-src в src, пошла реальная загрузка файла
      —— ноускрипт
      ——— оригинальный img c src.

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


      1. lipton_ice_tea
        21.12.2018 07:17

        1. В src, действительно, удобно размещать превью и блюрить на время загрузки оригинального изображения.
        2. По поводу сравнения размеров на клиенте — если такой вариант не нравится — то можно все картинки оборачивать в контейнер, padding-top которого рассчитывать на сервере, в зависимости от соотношения сторон загружаемого изображения. Саму картинку в абсолют.
          • Если размер изображения НЕ хранится на сервере, его можно получить во время обращения к странице (операция довольно быстрая)


    1. dom1n1k
      21.12.2018 10:12

      Это пример «горя от ума». Не нужно скриптом плавно, пусть прогружаются вертикально и автоматически.


      1. lipton_ice_tea
        22.12.2018 12:45

        Чем плохо плавное появление?


        1. dom1n1k
          22.12.2018 13:06

          Тем, что его приходится ждать. Иногда долго.