NoJS — Создание калькулятора с помощью только чистого HTML и CSS. Никакого Javascript!

557
NoJS - Создание калькулятора с помощью только чистого HTML и CSS
NoJS - Создание калькулятора с помощью только чистого HTML и CSS

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

Правила

Всякий раз, когда вы ставите перед собой задачу, полезно правильно определить свои цели. В данном случае, никакого JavaScript — просто, никакого JS-файла, никакого тега script и никакого использования обработчиков событий в HTML. Очень часто в проектах «чистого CSS» используются такие языки, как HAML и SCSS, которые компилируются в обычные статические HTML и CSS соответственно, так что конечный результат все равно остается чистым HTML и CSS. В природе существует множество безумных проектов, использующих эти языки. Я решил не использовать их в этом проекте, чтобы начать с простого. Вы можете просмотреть мою полную кодовую базу здесь.

Зачем это делать?

Почему бы и нет?

Как я это сделал?

Radio buttons

Во-первых, это взаимодействие с пользователем. Как определить, что пользователь нажал на кнопку без JS? Мы можем использовать radio инпутс.

<input type="radio" name="x" id="q-1" /> 
<input type="radio" name="x" id="q-2" /> 
<label for="q-1">Quote 1</label>
<label for="q-2">Quote 2</label>

<p class="quote-1">...</p>
<p class="quote-2">...</p>

и

input, p { display: none }

#q-1:checked ~ .quote-1 { display: block; }
#q-2:checked ~ .quote-2 { display: block; }

мы получаем

 

Поясним. Ярлыки связаны с radio так, что нажатие на них равносильно нажатию на соответствующие входы. Ярлыки предпочтительнее, чем непосредственное использование входов, поскольку они упрощают стилизацию. Символ ~ является общим селектором сиблингов, так что A ~ B будет выбирать все элементы, которые соответствуют B и имеют предшествующего сиблинга, который соответствует A. Это позволяет нам скрывать ps по умолчанию и показывать их только тогда, когда их связанный вход отмечен.

Переменные CSS и счетчики

Теперь, чтобы сгенерировать число. Нам нужно использовать переменные CSS. Чтобы объявить ее, создайте имя свойства, которое начинается с двойного дефиса (--), а значением может быть любое значение CSS. Например, --colour: brown or --digit: 3. Чтобы использовать переменные, просто воспользуйтесь функцией var, как показано ниже. Мы также будем использовать счетчики CSS, которые могут хранить и отображать числа. Обычно счетчики CSS используются для автоматической нумерации разделов. Поэтому

<input type="radio" name="theFirstDigit" id="set-to-1" /> 
<input type="radio" name="theFirstDigit" id="set-to-2" /> 
<input type="radio" name="theFirstDigit" id="set-to-3" /> 
<!-- insert labels -->

<div class="number-dsplay"></div>

и

#set-to-1:checked ~ div { --digit: 1; }
#set-to-2:checked ~ div { --digit: 2; }
#set-to-3:checked ~ div { --digit: 3; }

.number-display { counter-increment: digit var(--digit);  }
.number-display::after { content: counter(digit) }

мы получаем

 

Выбор радиокнопки устанавливает переменную --digit внутри div. Это значение также будет унаследовано всеми дочерними элементами. Затем, поскольку мы не можем вывести значение переменной напрямую, мы увеличиваем digit счетчика и отображаем это значение с помощью сгенерированногоcontent. Причина, по которой нам необходимо использовать переменные CSS, заключается в том, что значения счетчика нельзя использовать в calc, о чем мы поговорим в следующем разделе. Чтобы получить больше цифр, нам нужно просто продублировать то, что мы имеем. Тщательно вложив HTML и используя промежуточные переменные, мы можем свести к минимуму дублирование CSS. Мы можем видеть это ниже.

<!-- digit inputs name="theFirstDigit -->

<div class="first-digit">
  <!-- digit inputs name="theSecondDigit" -->

  <div class="second-digit">
    <!-- ..and so on -->
    
  </div>
</div>
/* Include previous CSS */

.first-digit { --first-digit: var(--digit); }
.second-digit { --second-digit: var(--digit); }

Здесь inputs будет устанавливать --digit , как это было сделано выше, а каждый отдельный div берет это значение и присваивает его к --first-digt. Это означает, что нам не нужно повторять #set-to-1:checked CSS для каждой цифры.

CSS calc

В CSS есть функция calc. Она позволяет выполнять вычисления (я потрясен не меньше вашего). Она имеет множество применений, например, вы можете установить ширину чего-либо в calc(100% - 95px). В нашем случае мы можем использовать его для определения наших входных чисел, а также конечного результата. Давайте рассмотрим получение входного числа

[name="theFirstDigit"]:checked ~ * .set-number { --number: var(--first-digit); }
[name="theSecondDigit"]:checked ~ * .set-number {  
  --number: calc(var(--first-digit)*10 + var(--second-digit)); 
}
[name="theThirdDigit"]:checked ~ * .set-number {  
  --number: calc(var(--first-digit)*100 + var(--second-digit)*10 + var(--third-digit)); 
}
/* and so on */

CSS-селектор * соответствует всем элементам, поэтому приведенный выше CSS найдет .set-number, который является потомком любого элемента, идущего после проверяемого ввода с определенным именем. Второй селектор отменяет первый просто потому, что находится позже в документе.

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

@property and @counter-style

С тем, что мы имеем на данный момент, мы можем создать функциональный калькулятор. Однако есть один недостаток, и это отсутствие десятичных дробей. Дело в том, что счетчики могут содержать только целые числа. Поэтому нам нужно разделить число на целую и дробную части. Первое, что нам нужно для этого, — это способ округления чисел (мы не можем использовать счетчики, потому что их нельзя перевести в calc). Мы будем использовать пока еще экспериментальную функцию @property. @property позволяет вам определить переменную с такими возможностями, как проверка типа и контроль того, наследуются ли значения дочерними элементами. Если мы определим @property следующим образом

@property --integer {
    syntax: '<integer>';
    initial-value: 0;
    inherits: true;
}

то любое значение, присвоенное параметру --integer, будет округлено до ближайшего целого числа. Чтобы вывести число с точностью до 7 знаков после запятой, мы сначала выполним следующие вычисления. Здесь --number определяется снаружи

.number-display {
    --abs-number: max(var(--number), -1 * var(--number)); 
    /* By suptracting 0.5 we make sure that we round down */
    --integer: calc(var(--abs-number) - 0.5);
    --decimal: calc((var(--integer) - var(--abs-number)) * 10000000);

    --sign-number: calc(var( --abs-number) / var(--number));
}

Используя --integer и --decimal, мы можем увеличивать счетчики с похожими именами. Но мы не можем просто отобразить их напрямую. Причина в том, что если мы сделаем это для числа 1.005, то значение --integer будет равно 1, а --decimal — 5. Нам нужно вставить десятичную дробь с помощью пользовательского @counter-style. Нам также нужно использовать @counter-style для отображения отрицательного знака, потому что при таком значении, как -0.5, мы не можем сказать системе, что у нас есть «отрицательный ноль». Чтобы правильно отобразить число, нам нужно

@counter-style pad-7 {
    system: numeric;
    symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
    pad: 7 "0"
}

@counter-style sign {
    system: numeric;
    symbols: "" "";
}

.number-display::after {
    content: counter(sign-number, sign) counter(integer) "." counter(decimal, pad-7);
}

Второй аргумент в функции counter — это стиль. Стиль pad-7 определяет обычную систему счисления, за исключением того, что любое значение, содержащее менее 7 цифр, будет заполнено нулями. Стиль sign тоже использует числовую систему, но поскольку мы определили, что символы пустые, он будет показывать только отрицательный знак (когда это необходимо).

Завершение работы

Это все ключевые элементы создания калькулятора. Осталось сделать несколько вещей. Это стилизация (да, я тоже использовал CSS по прямому назначению). Вы, наверное, заметили, что текущая настройка дает нам отдельный набор входов для каждой цифры числа, мы можем использовать ~, :checked и свойство display, чтобы всегда показывать метки следующей цифры. content может быть разделено на отдельные элементы, что позволяет нам показывать только десятичную часть, когда это необходимо.

Как далеко мы можем зайти? Мы могли бы позволить пользователю производить вычисления с результатом, однако я не думаю, что это возможно — вычислять с неограниченным количеством значений. Хотя, используя что-то для генерации HTML, можно было бы продвинуться довольно далеко. Теоретически, это можно приблизить к научному калькулятору. Например, для тригонометрических функций мы можем использовать их симметрию и периодичность, а затем использовать приближения. Я думаю, что самым сложным будет использование скобок, так как я не знаю способа динамически добавлять скобки в calc, поэтому нам придется иметь отдельные селекторы и CSS для каждого сценария.

 

Демо

Заключение

Я создал этот калькулятор просто как забавное упражнение и для того, чтобы сделать что-то глупое. Абсурдность — это то, что подогрело мое желание сделать его. Тем не менее, я многому научился в процессе работы. Хотя мне по-прежнему придется использовать Google каждый раз, когда я захочу отцентрировать текст, я получил массу удовольствия. Если у вас есть идея для ерундового проекта, я настоятельно рекомендую ее реализовать. В конце концов, почему бы и нет?