Материалом о разработке калькулятора на CSS и HTML, без файла JS, тега script и обработчиков событий в HTML делимся к старту курса по Fullstack-разработке на Python. За подробностями приглашаем под кат.

Постановка задачи

В проектах CSS часто в обычные статические HTML и CSS компилируются HAML и SCSS. Последние при этом применяются во многих сумасшедших проектах, но свой я решил не усложнять: взгляните на весь код.

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

Начнём со взаимодействия с пользователем. Как без JS понять, что кнопка нажата? Ответ: при помощи значений радиокнопок:

<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; }

дают результат:

Метки (label) соединены с input так, что нажатие на них станет нажатием на input. Метки упрощают стиль, поэтому они лучше нажатия на радиокнопку напрямую.

Символ ~ — это селектор, выбирающий элементы на том же уровне сложности: A ~ B соответствует элементам B после A. Код выше по умолчанию скрывает элементы p, отображая их, только когда подключенная радиокнопка выбрана.

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

Значения счётчиков в функции calc неприменимы, поэтому, чтобы сгенерировать число, объявим переменную. Напишем имя свойства с двумя дефисами (--) в начале и любым значением CSS: --colour: brown или --digit: 3. Для подстановки переменной вызовите функцию CSS var.

Счётчики 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) }

дают результат:

Когда пользователь отмечает кнопку, внутри div задаётся значение переменной --digit, наследуемое всеми дочерними элементами. Это значение нельзя вывести напрямую, поэтому увеличим счётчик digit и отобразим его через сгенерированный content.

Чтобы получить числа больше 9, нужно просто продублировать уже существующие цифры. За счёт тщательно продуманной структуры 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); }

Переменная --digit по-прежнему задаётся через input, каждый отдельный div принимает это значение и присваивает его --first-digit, --second-digit и так далее: повторять код #set-to-1:checked для каждой цифры не нужно.

Функция CSS calc

Функция calc в CSS выполняет вычисления и применяется, например, когда задаётся значение width (ширины): calc(100% - 95px). Определим с помощью calc число элемента input, а также результат всех вычислений:

[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 */

Селектор * выбирает все элементы, поэтому в коде выше вы найдёте .set-number — потомка любого элемента после input с флажком и определённым именем. Второй селектор переопределяет первый просто потому, что расположен после первого.

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

@property и @counter-style

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

Счётчики при этом использовать нельзя, ведь их нельзя завести в calc. Воспользуемся экспериментальной функцией @property: она определяет переменную с помощью такого функционала, как проверка типа или контроль за наследованием значений. Определим @property:

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

Так любое присваиваемое --integer значение округляется до целого числа. Чтобы отобразить число с точностью до семи знаков после запятой, сначала выполним следующие вычисления. Здесь --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, оно применяется для отображения знака «минус»: мы не можем сообщить системе, что число -0,5 — это «отрицательный нуль». Вот как правильно отобразить -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 определяется обычная система счисления, за исключением того, что любое значение с менее чем семью цифрами дополняется нулями. 

В стиле sign тоже используется числовая система, но мы определили символы пустыми, поэтому отображается в нём только знак «минус», если нужно.

Возможности

Всё это — ключевые элементы калькулятора, но осталось ещё кое-что — это стилизация. Вы могли заметить, что для каждой цифры числа в текущей конфигурации есть отдельный набор из нескольких input. 

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

Теоретически через генерацию HTML можно приблизиться к научному калькулятору. Например, можно воспользоваться симметрией и периодичностью тригонометрических функций, применив приближённые вычисления. 

Самое сложное — скобки, ведь неизвестно, как динамически добавить их в  calc, поэтому для каждого сценария пришлось бы иметь отдельные селекторы и CSS.

Заключение

Я написал этот калькулятор просто ради забавы, но многому научился, это было очень весело, поэтому если у вас есть идея пустякового проекта, реализуйте её. Почему нет?

А пока вы практикуетесь, мы поможем прокачать ваши навыки или с самого начала освоить профессию, востребованную в любое время:

Выбрать другую востребованную профессию.

Краткий каталог курсов и профессий

Комментарии (9)


  1. gohrytt
    17.04.2022 02:13
    -3

    Зачем?


    1. Tuxman
      17.04.2022 04:08
      +12

      Потому что могу.


    1. Aleksandr-JS-Developer
      17.04.2022 14:23
      +8

      Если вы задаёте такие вопросы - значит, эта статья не для вас


    1. Finesse
      18.04.2022 05:16
      +1

      Чтобы расширить свой набор навыков, помыслить нестандартно


  1. zartdinov
    17.04.2022 05:13
    +11

    После нужно написать компьютер, потом язык программирования, ну и запустить DOOM на нем.


  1. izirayd
    17.04.2022 07:40
    +4

    Функция CSS calc

    Эх, я так надеялся что там будет интересная реализация операций с числами (Какой-нибудь сумматор на html + css :D), а всё свелось к простой функции calc


    1. Spaceoddity
      17.04.2022 12:50

      Со счётчикам действительно можно было бы заморочиться посерьёзнее.

      Фишка в том, что счётчики, например, работают на элементах c visibility:hidden, но не работают на элементах с display:none.


  1. iShrimp
    17.04.2022 19:23

    Хотел написать научный калькулятор (типа такого, но с поддержкой функций и циклов). Чтобы работал на любом устройстве, где есть браузер.

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


    1. aigoncharov
      17.04.2022 19:43

      Слышал много хорошего про этот курс - https://css-for-js.dev/. Сам пока не покупал, но планирую