Плейсхолдер для изображения с использованием padding-top и процентов

    Когда мы проходим собеседования, нам часто задают вопросы по верстке. Зачастую они сводятся к перечислению значений свойства 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;  
    }
    
    Поддержать автора
    Поделиться публикацией

    Комментарии 52

      +1

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

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

        Это уже детали реализации. Но многие специалисты не знают, что такое вообще возможно.
          +1
          У картинок, если они не пользовательские, должны быть стандартизированы размеры. А вот для ютубовского айфрейма этим решением приходится пользоваться регулярно.
          0
          Если вы располагаете картинку абсолютом, то зачем вообще использовать :before? давайте паддинг сразу для .post
            +1
            Можно, но тогда надо обвернуть div.post еще одним div'ом и ему ширину задать, чтобы проценты корректно рассчитывались.
              0
              Если рассматривать реальный вариант который бы использовался в реальном проекте, то здесь много чего добавлять еще нужно, например что если картинка должна быть ссылкой, что если .post будет иметь padding, а картинка у вас имеет top:0; и т.д. Но в общем идея не плохая.
            0
            почему нельзя задать сразу размер в картинке?
            браузер сразу забронирует место под картинку с указаними размерами и все ето не будет прыгать…
              +1
              Какие единицы измерения вы будете использовать для этого?
                0
                Пикселы, конечно.
                  0
                  А вам надо изображение, которое адаптируется на разных экранах. Что тогда?
                    +2
                    Это выходит за рамки задачи, у вас жестко забито в тексте 640px
                      0
                      На сегодняшний день адаптивные изображения — это задача по умолчанию.
                        +2
                        max-width: 640px;
                        — о какой адаптивности речь?
                          0
                          Это называется докопаться до частности, забыв о смысле статьи в целом.
                      0
                      для этого есть vw/vh, если привязывать все ширины к vw, то в случае известного соотношения сторон можно тупо сделать, например min-height: calc( (100vw/16)*9 );
                      calc вообще удобная штука
                        0
                        В реальности «привязывать все ширины к vw» — сомнительная практика (очень мягко говоря).
                          0
                          Почему же?
                          Вот есть у вас 100% ширина и вам нужны картинки в три колонки.
                          Что сомнительного в
                          width: calc( (100vw — 10px)/3 );
                          min-height: calc( ((100vw — 10px)/48)*9 );
                          ????
                            +1
                            1. Поизучайте спецификации, всегда ли равны 100% и 100vw. Или минус 10px это и есть магическая константа для борьбы с расхождениями?
                            2. Картинка чаще должна быть привязана не к ширине вьюпорта, а вписываться в родительский контейнер (потому что он часть сетки), о размере которого она ничего не знает.
                              0
                              1. Я разве говорил, что 100vw == 100%? 10px это условный размер скрола, чтоб не менялась ширина, если страница будет длинная.
                              2. Картинка и так 100% ширины родителя, а вот он и привязан к ширине вьюпорта.
                              К сожалению — ширина и высота вьюпорта сейчас это единственные абсолютные величины, к которым можно привязаться для расчета соотношения сторон блока под картинку. В отличие от процентов.
                              Сомнительные «хаки» выравнивания по вертикали типа процентных padding, или line-height тоже так себе практика, ибо они явно для этого не предусмотрены
                                +1
                                Вот именно, что «условный». В зависимости от операционной системы и пользовательских настроек, он везде будет разным. От нуля до нескольких десятков px.
                                Эта особенность очень сильно обесценивает единицы вьюпорта, хотя многие разрабы относятся к ним с эйфорией.
                                Они удобны в случаях, когда не нужна особая точность. Но в большинстве ситуаций это очень скользкие единицы и обращаться с ними нужно крайне внимательно и осторожно.
                                Они как рыба фугу — круто, но предательски опасно, а потому её могут готовить только очень квалифицированные повара.

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

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

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

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

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

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

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

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

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

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

                                      У нас разные представления о качестве дизайна, и способах решения
                                        +1
                                        > При ширине вьюпорта овер 1280, ширина контента 70vw, при меньшей — 90vw, что не так?
                                        Не так то, что при переходе через брейкпойнт в большую сторону ширина контента будет наоборот уменьшаться, что противоестественно.
                0
                А не проще тогда «по старинке» задавать размеры изображения в атрибутах width и height, а масштабировать под ширину контейнера через
                img { max-width: 100%; height: auto; }
                ?
                  0
                  Текст продолжает прыгать
                    0
                    Попробуйте и узнаете, что нет
                      0
                      Проверил, действительно «нет». Но я зато придумал, как можно выкрутиться, если по какой-то причине нет возможности обернуть изображение в слой: jsfiddle.net/r5bnjm87 (вдруг, кому пригодится).
                        0
                        Хотя, нет, эта идея пока еще сырая. Пока у img не появится shadow dom, к нему нельзя применить ::before. В Firefox эта проблема решается добавлением пустого атрибута alt, но в Chrome это не работает. Пока что сдаюсь.
                    0
                    Я так понимаю, нам необходимы даже не размеры, а соотношение сторон.
                    А js-ом нельзя получить все размеры изображений на странице ДО их загрузки?
                    Вообще было бы полезно «дёргать» из изображений до их загрузки какие-то данные, по типу EXIF
                      0
                      если размеры изображения известны ДО загрузки на станице т.е на сервере, сервером и надо прописывать, а js приплетать для верстки это последнее дело в каком нибудь полифиле.
                      0
                      Подождите, если мы говорим про padding-top в процентах, то картинка автоматом идет в бекграунд. К черту img вам этот?
                      Картинку на бекграунд, блоку padding-top:% в требуемых пропорциях. Вообще у AirBnB мне нравится как сделано, но меня вымораживает их бесполезная иерархическая вложенность, которая абсолютно ничем не оправдана.
                        0
                        pepelsbey думаю скажет тебе про доступность
                          0
                          контентные изображения не надо фоном делать)
                            0
                            Ну вот AirBnb это не волнует, у них правда сайт на телефоне моем тормозит дичайшим образом.

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

                            Вордпресс во всю генерит тьму ресайзов для этого, но внедрение этого я вижу не у всех.
                              0
                              Всему свое время)
                              0
                              допустим, к черту все минусы и правила, тогда бы вы как логику выстраивали? Этот способ бы вас успокоил?
                              0
                              контентные изображения не надо фоном делать

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

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

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

                                      Picture — управляющая конструкция вокруг img, а img прекрасно умеет object-fit и object-position. Другой вопрос, что вы их не умеете :)

                                        0
                                        Я IE — я не умею так же популярно(
                                  0

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


                                  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 (лучше класс)
                                    0
                                    а для чего нужно height: auto? Оно же вроде так по умолчанию
                                      0

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

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

                                      контейнер имеет физические данные картинки 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.

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

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

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

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                      Самое читаемое