Статьи

Пуленепробиваемая функция для проверки значений длины в Sass

Как только вы освоитесь с Sass, поиграете с некоторыми функциями и напишите свои собственные миксины, достаточно скоро вы захотите проверить вклад разработчика. Это означает добавление некоторых дополнительных проверок к вашим функциям и миксинам, чтобы убедиться, что заданные аргументы верны с точки зрения того, как они будут использоваться внутри функции или миксина.

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

Давайте сначала вспомним, что длина в соответствии с официальными спецификациями CSS :

Длина относится к измерениям расстояния […] Длина – это размерность. Однако для нулевой длины идентификатор единицы является необязательным (т.е. может быть синтаксически представлен как «0»). Измерение – это число, за которым сразу следует идентификатор единицы.

Если мы суммируем то, что мы только что прочитали, длина – это число, за которым следует единица, если оно не равно нулю. Это немного упрощенно, но мы начнем с этого.

Настройка функции

Если функция возвращает логическое значение, обычно рекомендуется именовать функцию с ведущим is- . По самой природе вопроса [это] [это]? , мы подразумеваем, что ответ либо да ( true ), либо нет ( false ).

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

 /** * Check whether `$value` is a valid length. * @param {*} $value - Value to validate. * @return {Bool} */ @function is-length($value) { // Do some magic and return a Boolean. } 

Возврат действительного значения

Благодаря нашему переподготовке мы знаем, что длина – это число с единицей. Давайте начнем с этого.

 @function is-length($value) { @return type-of($value) == "number" and not unitless($value); } 

Прежде чем идти дальше, позвольте мне напомнить вам пару вещей:

  • Мы возвращаем логическое значение, поэтому нет необходимости загрязнять ядро ​​функции ненужными @if , @else if и @else .
  • Мы заключаем строки в кавычки (используя двойные кавычки), потому что это более элегантно и потому что строка без кавычек в любом случае строго равна той же строке в кавычках.

Хорошо, это хорошее начало. К сожалению, большинство Sass-фреймворков, которые выполняют проверку юнитов, на этом остановятся. Мы не можем остановиться после такого хорошего старта! Кроме того, нам не удалось правильно реализовать спецификацию CSS, согласно которой, если значение равно нулю, модуль является необязательным. Наша функция вернет false если $value равно 0 потому что она не имеет единиц измерения. Давайте это исправим!

 @function is-length($value) { @return $value == 0 or type-of($value) == "number" and not unitless($value); } 

Оператор or ( || ) реализован так в большинстве языков: продолжайте анализ условия, пока токен не станет true . Это означает, что если $value равно 0 , функция прекращает синтаксический анализ и напрямую возвращает true , в противном случае она продолжает работать.

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

 @function is-length($value) { @return type-of($value) == "number" and (not unitless($value) or $value == 0); } 

Наша первая реализация выглядит так, как будто $value равно 0 или $value это число с единицей измерения . В то время как эта реализация читается так, как будто $value является числом и либо имеет единицу, либо равно 0 . Вы можете выбрать тот, который вам наиболее удобен.

Разрешение общих ценностей

Вы, вероятно, не без знания, что есть пара значений, которые могут применяться ко всем свойствам CSS: initial и inherit . Мы также можем добавить auto в список, потому что большинство основанных на длине свойств принимают auto как допустимое значение. Таким образом, у нас есть три значения, которые мы хотим разрешить, четыре, если мы считаем 0 которое мы уже добавили ранее. Давайте начнем с уродливого пути:

 @function is-length($value) { @return $value == 0 or $value == "auto" or $value == "initial" or $value == "inherit" or type-of($value) == "number" and not unitless($value); } 

Вау … Не очень элегантно, правда? К счастью, в предыдущей статье на SitePoint я показал вам, как мы можем оптимизировать такой оператор с помощью функции index .

Функция index возвращает индекс второго параметра (наше значение) в первом параметре (список). Если значение не найдено в списке, оно возвращает null в Sass 3.4 или false в Sass 3.3.

 @function is-length($value) { @return index(0 "auto" "inherit" "initial", $value) or type-of($value) == "number" and not unitless($value); } 

Дело в том, что наше значение is-length теперь может возвращать целое число. Например, если $value равно auto , то функция возвращает 2 , потому что auto – это второй элемент в нашем списке допустимых значений. Хотя это не должно иметь большого значения, если вы используете @if is-length($value) , могут быть случаи, когда вы хотите иметь логическое значение всегда.

Чтобы убедиться, что функция возвращает тип bool , вы можете использовать трюк not-not (см. Ту же статью, что и выше) для приведения значения в качестве логического значения. Хотя это не очень элегантно, но работает нормально:

 @function is-length($value) { @return not not index(0 "auto" "inherit" "initial", $value) or type-of($value) == "number" and not unitless($value); } 

Если вы хотите сделать это более явным, вы можете создать небольшую функцию функции, которая скрывает это:

 /** * Check whether `$list` contains `$value`. * @param {List} $list - List of values. * @param {*} $value - Value to check in the list. * @return {Bool} */ @function contains($list, $value) { @return not not index($list, $value); } 

…тогда:

 @function is-length($value) { @return contains(0 "auto" "inherit" "initial", $value) or type-of($value) == "number" and not unitless($value); } 

И ясно, и красноречиво, но нуждается в дополнительной функции. Я дам вам решить, что лучше для ваших нужд.

Примечание: помните, что auto является недопустимым значением для padding . Совершенно крайний случай, но вы должны иметь это в виду.

Разрешение calc()

Наша функция начинает куда-то доходить, не так ли? Однако нам все еще нужно разрешить calc() , который часто забывают или опускают в контролерах длины. Что касается проверки для calc() , то она требует Sass 3.3, потому что нам нужен метод str-slice .

Идея довольно проста. Мы выделяем первые четыре символа из значения. Если результат «calc», то это (вероятно) означает, что значение является функцией calc() , которая является действительной:

 @function is-length($value) { @return not not index(0 "auto" "initial" "inherit", $value) or type-of($value) == "number" and not unitless($value) or str-slice($value + "", 1, 4) == "calc"; } 

Здесь нужно отметить две вещи:

  1. Мы используем $value + "" для приведения значения в виде строки в случае, если это число (например, 42px ), чтобы избежать ошибки с функцией str-slice ожидающей строку.
  2. Мы ставим эту проверку в самом конце, чтобы выполнять ее только тогда, когда это необходимо, поскольку она может быть менее частой и более дорогой, чем две другие.

Функция выглядит довольно хорошо до сих пор!

Расширение до размеров

На данный момент у нас есть довольно надежная функция для проверки длины. Мы могли бы использовать его в микшере для калибровки:

 /** * Set `width` and `height` in a single statement. * @param {Number} $width - Width. * @param {Number} $height ($width) - Height. * @output `width` and `height`. * @requires {function} is-length */ @mixin size($width, $height: $width) { @if is-length($width) { width: $width; } @else { @warn "Invalid length `#{$width}` for `$width` parameter in `size` mixin."; } @if is-length($height) { height: $height; } @else { @warn "Invalid length `#{$height}` for `$height` parameter in `size` mixin."; } } 

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

Дело в том, что мы не можем обновить нашу функцию, чтобы включить внутренние значения, так как они недопустимы для большинства свойств, использующих длины ( padding , margin , background-size и т. Д.). Однако мы могли бы создать другую функцию, используя уже созданную нами. Как насчет is-size ?

 /** * Check whether `$value` is a valid size. * @param {*} $value - Value to validate. * @return {Bool} * @requires {function} is-length */ @function is-size($value) { @return is-length($value) or not not index("fill" "fit-content" "min-content" "max-content", $value); } 

… или если вы используете функцию содержимого:

 @function is-size($value) { @return is-length($value) or contains("fill" "fit-content" "min-content" "max-content", $value); } 

Теперь мы можем безопасно использовать внутренние значения размеров с нашей совершенно новой функцией is-size не опасаясь ложного возврата! Довольно аккуратно, правда?

Последние мысли

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

Мы все еще можем улучшить функцию, отфильтровывая углы ( deg , rad , grad , turn ) и длительности ( s , ms ), потому что они в настоящее время рассматриваются нашей функцией как длины, поскольку они в основном являются числом с единицей. Но поскольку длины, углы и длительности используются в очень разных контекстах, очень мало шансов, что функция получит угол, когда она ожидает длину.

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

Вы можете найти полный код для игры на этом Sassmeister .