Изучаем принцип работы единиц измерения em на примере задачи «Верстка гибкого прелоадера»

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



    Для его реализации мне потребовалась следующая разметка:


    <div class="preloader"></div>
    

    По моей задумке, прелоадер состоит из двух квадратов: большого с размером 60x60px и маленького — 15x15px.


    Так как большой квадрат является контуром для маленького, то для его реализации я использовал сам элемент div .prealoder. А вот для вложенного квадрата мне понадобился псевдоэлемент before.


    .preloader {
      width: 60px;
      height: 60px;
      border: 2px solid #fff;
      position: relative;
    }
        
    .preloader::before {
      content: "";
      width: 15px;
      height: 15px;
      background-color: #fff;
          
      position: absolute;
      top: calc(50% - 7.5px);
      left: calc(50% - 7.5px);
    }
    

    Для анимации я написал следующий сценарий:


    .preloader::before {
      animation: preloader 2.25s ease-out both infinite;
    }
        
    @keyframes preloader {
        
      0%, 10%, 90%, 100% {
        transform: translate3d(0, 0, 0) scale(0);
      }
        
      20%, 70% {
        transform: translate3d(0, 0, 0) scale(1);
      }
        
      30% {
        transform: translate3d(-15px, -15px, 0) scale(1);
      }
        
      40% {
        transform: translate3d(15px, -15px, 0) scale(1);
      }
        
      50% {
        transform: translate3d(15px, 15px, 0) scale(1);
      }
        
      60% {
        transform: translate3d(-15px, 15px, 0) scale(1);
      }
    }
    

    Кроме основного отображение мне захотелось добавить прелоадер с размерами контура 120x120px и внутреннего квадрата 30x30px.


    .preloader_l {
      width: 120px;
      height: 120px;
    }
        
    .preloader_l::before {
      width: 30px;
      height: 30px;
      top: calc(50% - 15px);
      left: calc(50% - 15px);
      animation-name: preloader-l;
    }
        
    @keyframes preloader-l {
        
      0%, 10%, 90%, 100% {
        transform: translate3d(0, 0, 0) scale(0);
      }
          
      20%, 70% {
        transform: translate3d(0, 0, 0) scale(1);
      }
          
      30% {
        transform: translate3d(-30px, -30px, 0) scale(1);
      }
          
      40% {
        transform: translate3d(30px, -30px, 0) scale(1);
      }
          
      50% {
        transform: translate3d(30px, 30px, 0) scale(1);
      }
          
      60% {
        transform: translate3d(-30px, 30px, 0) scale(1);
      }
    }
    


    Все получилось, как задумывалось. Я пошел пить чай, и ко мне пришла мысль, что если мне потребуется добавить еще один размер, то снова придется указывать размеры (width и height), позицию (top и left) и сценарий анимации.


    Для того чтобы это исправить, я буду использовать единицу измерения em. С помощью нее у меня получится привязать все значения свойств к одному значению font-size, с помощью которого буду переключать размеры прелоадера.


    В итоге код изменится следующим образом:


    /* Вместо Xem и Xpx будут рассчитаны значение в em и в px. */
        
    .preloader {
      width: Xem;
      height: Xem;
      font-size: Xpx;
    }
        
    .preloader::before {
      width: Xem;
      height: Xem;
      animation: preloader 2.25s ease-out both infinite;
    }
        
    @keyframes preloader {
        
      0%, 10%, 90%, 100% {
        transform: translate3d(0, 0, 0) scale(0);
      }
          
      20%, 70% {
        transform: translate3d(0, 0, 0) scale(1);
      }
          
      30% {
        transform: translate3d(-Xem, -Xem, 0) scale(1);
      }
          
      40% {
        transform: translate3d(Xem, -Xem, 0) scale(1);
      }
          
      50% {
        transform: translate3d(Xem, Xem, 0) scale(1);
      }
          
      60% {
        transform: translate3d(-Xem, Xem, 0) scale(1);
      }
    }
        
    .preloader_l {
      font-size: Xpx;
    }
    


    Решение


    Для решения задачи мне пригодится формула расчета em:


    Vem = Vpx / Fs
    

    Vpx это значение в px, которое ранее было задано. Значение Fs это подобранное число, на которое удобно делить значение Vpx.


    Теперь можно начать расчеты следующих свойств:


    .preloader {
      width: 60px;
      height: 60px;
    }
        
    .preloader::before {
      width: 15px;
      height: 15px;  
      top: calc(50% - 7.5px);
      left: calc(50% - 7.5px);
    }
    

    Для этого нужно подобрать значение font-size. По моей задумке, данное свойство будет задавать размеры контура, поэтому логично использовать тоже самое значение, что сейчас установлено для width и height.


    Width(em) = 60px / 60px = 1em
    Height(em) = 60px / 60px = 1em
    

    .preloader {
      width: 1em; 
      height: 1em; 
      font-size: 60px;
    }
    

    Далее рассчитаю значения width, height, top и left у элемента .preloader::before. Для этого уже не надо подбирать значение font-size, потому что оно будет унаследовано от font-size элемента .preloader.


    Width(em) = 15px / 60px = 0.25em
    Height(em) = 15px / 60px = 0.25em
    Top(em) = 7.5px / 60px = 0.125em;
    Left(em) = 7.5px / 60px = 0.125em;
    

    .preloader::before {
      width: 0.25em;
      height: 0.25em;
      top: calc(50% - 0.125em);
      left: calc(50% - 0.125em);
    }
    

    Осталось изменить значения в сценарии анимации.


    @keyframes preloader {
        
      0%, 10%, 90%, 100% {
        transform: translate3d(0, 0, 0) scale(0);
      }
        
      20%, 70% {
        transform: translate3d(0, 0, 0) scale(1);
      }
        
      30% {
        transform: translate3d(-15px, -15px, 0) scale(1);
      }
        
      40% {
        transform: translate3d(15px, -15px, 0) scale(1);
      }
        
      50% {
        transform: translate3d(15px, 15px, 0) scale(1);
      }
        
      60% {
        transform: translate3d(-15px, 15px, 0) scale(1);
      }
    }
    

    Если посмотреть на значения без учета знака, то требуется перевести только 15px. Ранее я уже сделал это, и они соответствуют 0.25em.


    @keyframes preloader {
        
      0%, 10%, 90%, 100% {
        transform: translate3d(0, 0, 0) scale(0);
      }
        
      20%, 70% {
        transform: translate3d(0, 0, 0) scale(1);
      }
        
      30% {
        transform: translate3d(-0.25em, -0.25em, 0) scale(1);
      }
        
      40% {
        transform: translate3d(0.25em, -0.25em, 0) scale(1);
      }
        
      50% {
        transform: translate3d(0.25em, 0.25em, 0) scale(1);
      }
        
      60% {
        transform: translate3d(-0.25em, 0.25em, 0) scale(1);
      }
    }
    

    Теперь можно задать размер 120px для элемента .preloader_l.


    .preloader_l {
      font-size: 120px;
    }
    

    Стоит заметить, что я удалил CSS-правило .preloader_l::before и сценарий анимации preloader-l, потому что они больше не нужны.


    Домашняя задача


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


    @keyframes preloader {
        
      0%, 70%, 100% {
        transform: translate3d(0, 0, 0) scale(0);
      }
          
      10%, 60% {
        transform: translate3d(0, 0, 0) scale(1);
      }   
          
      20% {
        transform: translate3d(9px, -21px, 0) scale(1);
      }
          
      30% {
        transform: translate3d(3px, 21px, 0) scale(1);
      }
          
      40% {
        transform: translate3d(-9px, -21px, 0) scale(1);
      }   
          
      50% {
        transform: translate3d(-9px, 21px, 0) scale(1);
      }
    }       
    
    Поддержать автора
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +1

      Не хватает ссылки на CodePen с живым кодом.

      +1
      Но ведь можно и без em: codepen.io/imkremen/pen/RdLbXQ
        0
        тоже не понял, зачем эта муть с em, проценты же проще
          0
          Вы решили конкретную задачу. А теперь давайте представим, что ваш прием с % нужно перенести на другую задачу. Например, для верстки padding'а у кнопки. Прием сработает? Нет. Этот прием можно использовать в более подходящих случаях. Например, когда требуется задавать пропорции habr.com/ru/post/433710. А в своей заметке я показал трюк, который часто используется для верстки типографики, ссылок, кнопок и многих других элементов.
            0

            Но как связаны предоадер и типографика? Почему размер шрифта влияет на UI элемент в дизайне которого нет ни единого символа?
            Имхо, для решения подобных этой задач идеально подходят CSS Custom Properties.

          0
          Если для кого-то это было открытием, то советую посмотреть выступление Андрея Бойко, он там рассказывал как они (в Glivera) в анимации используют em вместо px и радуют заказчика:
          youtu.be/_63sz4aHrFo
            0
            Для байт-перфекционистов: если взять за font-size значение 15px (что, наверно, лучше соответствует текущему значению размера шрифта на странице), то можно чуть сэкономить на длине чисел:
            15px — 1em
            7.5px — 0.5em
            60px — 4em
              0
              Для мест которые не влияют на отображаемый шрифт проще брать 10px — не нужно считать
              0
              Для чего делать видео вместо gif анимации?
                0
                Я не очень понял, чем это решение лучше работы с обычным svg? Я не вижу никаких преимуществ и только недостатки в силу сложности решения.
                  0
                  Я показывал не «решение», а трюк, который можем использовать в разных решениях.

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

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