Начинаем работать с CSS calc()

Начинаем работать с CSS calc()

От автора: Впервые о функции css calc() я узнал более четырех лет назад. Я был очень рад, что в CSS появились базовые математические вычисления – сложение, вычитание, умножение и деление.

Многие думают, что препроцессоры только и состоят, что из логики и вычислений, но функция calc() умеет делать то, чего не может ни один препроцессор в мире: смешивать любые единицы измерения. Препроцессоры могут смешивать только единицы измерения с фиксированными связями типа угловых единиц измерения, временных, частотных, единиц разрешения и некоторые единицы измерения длины.

1turn это всегда 360deg, 100grad — 90deg, а 3.14rad это 180deg. 1s равна 1000ms, 1kHz равен 1000Hz. 1in это 2.54cm или 25.4mm или 96px, а 1dppx всегда равен 96dpi. Именно из-за таких точных связей препроцессоры и могут в вычислениях конвертировать одни единицы в другие и смешивать их между собой. Однако препроцессор не может вычислить, сколько 1em или 1% или 1vmin или 1ch будет в пикселях, так как отсутствует контекст. Рассмотрим пару базовых примеров:

div {
   font-size: calc(3em + 5px);
   padding: calc(1vmax + -1vmin);
   transform: rotate(calc(1turn - 32deg));
   background: hsl(180, calc(2*25%), 65%); 
   line-height: calc(8/3);
   width: calc(23vmin - 2*3rem);
}

В некоторых случаях в функции calc() могут понадобиться переменные. Это можно реализовать в практически любом препроцессоре. Первый пример — Sass переменная, работает с любой другой нативной функцией CSS:

$a: 4em
height: calc(#{$a} + 7px)

LESS

@a: 4em;
height: ~"calc(@{a} + 7px)";

Stylus:

a = 4em
height: "calc(%s + 7px)" % a

Можно воспользоваться и родными CSS переменными, но они пока что работают только в Firefox 31+. Остальные браузеры еще не поддерживают CSS переменные.

--a: 4em;
height: calc(var(--a) + 7px);

Чтобы функция calc() работала, как следует, необходимо помнить пару вещей. Первое – нельзя делить на ноль, это очевидно. Между словом calc и круглой скобкой не должно быть пробелов. Операторы, такие как + или -, отделяются пробелами с двух сторон. Код ниже неправильный:

calc(50% / 0)
calc (1em + 7px)
calc(2rem+2vmin)
calc(2vw-2vh)

Calc() работает как значение в тех местах, где можно подставить просто значение, не важно с единицами измерения или нет. Поддержка у данной функции замечательная, но все же можно столкнуться с рядом проблем. Все зависит от того, где мы применяем данную функцию. Рассмотрим несколько примеров, среди которых будут также и проблемные, а также наилучшие решения данных задач.

Простой способ понять вычисленные значения

Скажем, мы хотим сделать градиент в цвета радуги. В CSS это не составит труда:

background: linear-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);

Но HEX значения непонятны. С помощью hsl() и calc() код выше можно переписать в:

background: linear-gradient(hsl(calc(0*60), 100%, 50%), 
                            hsl(calc(1*60), 100%, 50%), 
                            hsl(calc(2*60), 100%, 50%), 
                            hsl(calc(3*60), 100%, 50%), 
                            hsl(calc(4*60), 100%, 50%), 
                            hsl(calc(5*60), 100%, 50%), 
                            hsl(calc(6*60), 100%, 50%));

К сожалению метод calc() внутри одной из функций hsl(), rgb(), hsla() или rgba() пока что не работает в Firefox и Internet Explorer. Данный метод работает только в WebKit браузерах. На практике лучше всего все это отдать на вычисления препроцессору. А самое крутое, что в препроцессоре можно формировать список прямо в цикле:

$n: 6;
$l: ();

@for $i from 0 through $n {
   $l: append($l, hsl($i*360/$n, 100%, 50%), comma);
}

background: linear-gradient($l);

Эффективные фоновые градиенты для гибких элементов

К примеру, мы хотим сделать фон, сверху и снизу на котором будут прямые фиксированного размера 1em. Проблема в том, что мы не знаем точной высоты элемента. В данном случае можно использовать два градиента:

background: 
   linear-gradient(#e53b2c 1em, transparent 1em),
   linear-gradient(0deg, #e53b2c 1em, #f9f9f9 1em);

С помощью функции calc() запись выше можно сделать с помощью одного градиента:

background: 
   linear-gradient(#e53b2c 1em, #f9f9f9 1em, 
                   #f9f9f9 calc(100% - 1em), 
                   #e53b2c calc(100% - 1em));

Такая запись будет работать во всех браузерах с поддержкой calc() и градиентов. А так как тут используются смешанные единицы измерения, то препроцессор не сможет с этим разобраться. Можно еще немного улучшить код выше, добавим переменные:

$s: 1em;
$c: #e53b2c;
$bg: #f9f9f9;

background: 
   linear-gradient($c $s, 
                   $bg $s, 
                   $bg calc(100% - #{$s}), 
                   $c calc(100% - #{$s}));

Заметка: В Chrome и Opera одна из полос по непонятным причинам может быть чуть-чуть уже другой и слегка размыта.

Диагональные полосы с помощью градиента

К примеру, нам нужно создать широкую диагональную прямую. Это можно сделать с помощью процентов.

background: 
   linear-gradient(to right bottom, 
                   transparent 42%, #000 0, #000 58%, 
                   transparent 0);

В данном случае ширина полосы будет зависеть от размера элемента. Иногда именно это и нужно. К примеру, пример выше отлично подходит, если нам нужно нарисовать флаг. Добавим немного зеленого, желтого и голубого, и вот наш флаг. Любители шоколада распознают в этом флаге флаг Танзании.

background: 
   linear-gradient(to right bottom, 
                   #1eb53a 38%, #fcd116 0, 
                   #fcd116 42%, #000 0, 
                   #000 58%, #fcd116 0, 
                   #fcd116 62%, #00a3dd 0);

А что, если необходимо задать фиксированную ширину полосы, чтобы она не изменялась в зависимости от размера объекта? В функции calc() необходимо задать значения 50% минус половина от фиксированной ширины полосы и 50% плюс половина ширины полосы. Если нам нужна полоса шириной 4em:

background: 
   linear-gradient(to right bottom, 
                   transparent calc(50% - 2em), 
                   #000 0, 
                   #000 calc(50% + 2em), 
                   transparent 0);

Пример выше можно протестировать в браузере, поигравшись с размером окна браузера. Размеры прямоугольника заданы в vmin, поэтому размер объекта изменяется при изменении размера окна браузера. Но диагональная полоса всегда остается фиксированной ширины.

Позиционирование дочерних элементов с известными размерами в центре родительского элемента

Вы, скорее всего, знаете про трюк с абсолютным позиционированием дочернего элемента в центре родительского:

position: absolute;
top: 50%; 
left: 50%;
margin: -2em -2.5em;
width: 5em; 
height: 4em;

С функцией calc() можно избавиться от margin:

position: absolute;
top: calc(50% - 2em); 
left: calc(50% - 2.5em);
width: 5em; 
height: 4em;

Сделаем код более гибким, добавим переменные ширины и высоты:

$w: 5em;
$h: 4em;

position: absolute;
top: calc(50% - #{.5*$h});
left: calc(50% - #{.5*$w});
width: $w; 
height: $h;

Обратите внимание на то, что top и left можно использовать для позиционирования элемента. Однако если вам необходимо анимировать объект, используйте трансформации. Так как во время трансформации происходит рекомпозиция объекта, при смещении объекта он заново отрисовывается на странице – тем самым снижается производительность.

Система координат и сеток с началом координат в центре

Когда я узнал про четырехзначное значение свойства background-position, мне уже стало неинтересно позиционировать элемент относительно правой или нижней стороны родительского объекта с помощью функции calc(). Но с помощью этой функции, оказалось, можно превосходно размещать в центре объекта одну конкретную точку фона.

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

Нарисовать систему координат с сеткой было легко:

background-image: 
   linear-gradient(#e53b2c .5em, transparent .5em) /* горизонтальная ось */,
   linear-gradient(90deg, #e53b2c .5em, transparent .5em) /* вертикальная ось*/, 
   linear-gradient(#333 .25em, transparent .25em) /* толстая горизонтальная */, 
   linear-gradient(90deg, #333 .25em, transparent .25em) /* толстая вертикальная */, 
   linear-gradient(#777 .125em, transparent .125em) /* тонкая горизонтальная */, 
   linear-gradient(90deg, #777 .125em, transparent .125em) /* тонкая вертикальная */;

background-size: 
   100vw 100vh, 100vw 100vh, 
   10em 10em, 10em 10em, 
   1em 1em, 1em 1em;

Но как заставить фоновый рисунок приклеиться намертво к центру элемента, а не к его верхнему левому углу? background-position: 50% 50 не сработает, т.к. точка 50% 50% градиента будет совпадать с точкой 50% 50% элемента, но линии расположены сверху и слева от градиента соответственно. Решить эту проблему можно с помощью calc(). Необходимо расположить градиент так, чтобы его верхний левый угол был ровно в центре фигуры. Затем сдвинуть градиент вверх и влево на половину ширины и высоты осей соответственно:

background-position: 
    0 calc(50vh - .25em), calc(50vw - .25em), 
    0 calc(50vh - .125em), calc(50vw - .125em), 
    0 calc(50vh - .0625em), calc(50vw - .0625em);

И снова переменные сделают код более гибким. Ознакомьтесь с примером system of coordinates + grid #2 от Ana Tudor (@thebabydino) с сайта CodePen.

Сохранение соотношения сторон и видимой области экрана

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

Скажем, нам нужно соотношение сторон 4:3, а просматриваем мы его на широкоформатном экране. Это значит, что по высоте все будет нормально, а вот по ширине у нас будут оставаться небольшие зазоры слева и справа.

То, что блок занимает всю высоту области просмотра, означает height: 100vh. По высоте и соотношению сторон можно получить ширину 4/3*100vh. Чтобы расположить бокс в центре, нам необходимо сдвинуть его вправо на половину ширины окна браузера (100vw/2) минус половина ширины самого контейнера (4/3*100vh/2). У нас две разные единицы измерения, тут нам поможет calc().

.slide {
   position: absolute;
   left: calc(100vw/2 - 4/3*100vh/2);
   width: calc(4/3*100vh);
   height: 100vh;
}

С соотношением сторон меньше, чем 4:3 у нас появляются зазоры сверху и снизу, а по горизонтали блок помещается целиком.

Блок занимает всю ширину видимой области браузера, т.е. width: 100vw. По ширине и соотношению сторон можно вычислить высоту height, она будет равна 3/4*100vw. И наконец, отступ сверху будет равен половине высоты окна браузера минус половина высоты блока 100vh/2 — 3/4*100vw/2.

@media (max-aspect-ratio: 4/3) {
   .slide {
      top: calc(100vh/2 - 3/4*100vw/2);
      left: auto; /* отменяем стили вне запроса  */
      width: 100vw;	
      height: calc(3/4*100vh);
   }
}

Можно также упростить код с помощью переменных высоты и ширины. Ниже пример на Sass, который можно протестировать, изменяя размер окна браузера:

$a: 4;
$b: 3;

.slide {
   position: absolute;
   top: 0; 
   left: calc(50vw - #{$a/$b/2*100vh});
   width: $a/$b*100vh; 
   height: 100vh;
  
   @media (max-aspect-ratio: #{$a}/#{$b}) {
      top: calc(50vh - #{$b/$a/2*100vw}); 
      left: 0;
      width: 100vw; 
      height: $b/$a*100vw;
   }
}

Еще лучше, код выше можно превратить в миксин, намного лучше, чем глобальные переменные:

@mixin proportional-box($a: 1, $b: $a) {
   position: absolute;
   top: 0; 
   left: calc(50vw - #{$a/$b/2*100vh});
   width: $a/$b*100vh; 
   height: 100vh;
  
   @media (max-aspect-ratio: #{$a}/#{$b}) {
      top: calc(50vh - #{$b/$a/2*100vw}); left: 0;
      width: 100vw; height: $b/$a*100vw;
   }
}

.slide {
   @include proportional-box(4, 3);
}

Обратите внимание, для правильной работы медиа запросов, переменные $a и $b должны быть integer. Способ поддерживается во всех современных браузерах. Тем не менее, до недавнего времени WebKit браузеры не поддерживали применение vw единиц в функции calc(). Поддержка появилась в Safari 8, Chrome 34 и последней Opera.

Короткий заголовок слайда в центре

Мне необходимо было добиться еще двух вещей. Первая это, чтобы слайд не занимал всю область просмотра, так как края бокса могли обрезаться. Это было легко, просто задаем box-sizing: border-box и саму границу. Второе – я хотел, чтобы на каждом слайде в самом центре располагался короткий запоминающийся текст.

Я не хотел использовать абсолютное позиционирование и решил работать со свойством line-height. Слайд с рамкой занимает всю высоту окна браузера, так что мне необходимо было задать свойству line-height значение 100vh минус удвоенное значение border-width:

$slide-border-width: 5vmin;

.slide {
   /* Другие стили */
   box-sizing: border-box;
   border: solid $slide-border-width dimgrey;
    
   h1 {
      line-height: calc(100vh - #{2*$slide-border-width});
   }
}

Если слайд с рамкой занимает 100% ширины окна браузера (и находится вертикально посередине), его высота равна $b/$a*100vw. А значение line-height будет равно $b/$a*100vw минус удвоенное значение border-width слайда:

line-height: calc(#{$b/$a*100vw} - #{2*$slide-border-width});

В этом заключалась моя идея, в теории она работает идеально. Более того, она работает в WebKit браузерах и IE. Но в Firefox функция calc() не вычисляет значение для свойства line-height (и некоторых других); в этом случае calc() не самый удачный вариант. Существует множество других способов решения этой проблемы (flexbox, абсолютное позиционирование и т.д.).

Фиксированная точка просмотра

Мне очень нравится CSS 3D – создавать геометрические 3D фигуры в CSS. Обычно, я создаю одну фигуру и располагаю ее в центре сцены. Сцена это элемент с perspective, а также это родитель для 3D фигуры. У 3D фигуры есть свои потомки – грани. Не будем углубляться в принцип создания. Если хотите разобраться в том, как позиционировать грани элемента, можете прочитать мою статью для сайта CSS-Tricks.

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

У нас есть простая фигура – куб, к примеру. Куб расположен в центре сцены. Фигура не сильно похожа на 3D: она симметрична, и если стороны абсолютно непрозрачны, то нам видна только передняя часть фигуры.

Давайте прокрутим куб на 30° по оси У (вертикальная ось проходит через середину объекта) или вокруг оси Х. Уже лучше, но нам теперь видны только две поверхности.

Также можно сменить точку просмотра с помощью perspective-origin. Значение по умолчанию 50% 50%. Значение задается относительно сцены, мы знаем, что точка 50% 50% сцены совпадает с точкой 50% 50% куба. Теперь, скажем, нам необходимо сдвинуть фигуру вверх и вправо. Это можно сделать, задав perspective-origin: 100% 0. И тут появляется проблема: в видео ниже можно заметить, что куб изменяется с изменением размера сцены (можете сами протестировать, изменяя размер окна браузера).

perspective-origin 100% 0 задает точку отсчета верхний правый угол сцены, а фигура всегда находится в центре сцены. Из-за этого при изменении размера сцены, изменяется расстояние от точки 50% 50% (позиция куба) до точки 100% 0 (точка наблюдения perspective-origin).

Как выход можно использовать calc(). Для этого необходимо добавить или вычесть фиксированное значение от 50%:

perspective-origin: calc(50% + 15em) calc(50% - 10em);

Попробуйте сами, измените размер окна браузера. А что насчет вас? Вы уже использовали calc()? Если да, то зачем?

Автор: Ana Tudor

Источник: http://www.smashingmagazine.com/

Редакция: Команда webformyself.

Практика HTML5 и CSS3 с нуля до результата!

Получите бесплатный пошаговый видеокурс по основам адаптивной верстки с полного нуля на HTML5 и CSS3

Получить

Метки:

Комментарии Вконтакте:

Комментарии Facebook:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Я не робот.

Spam Protection by WP-SpamFree