Материалом о разработке калькулятора на 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.
Заключение
Я написал этот калькулятор просто ради забавы, но многому научился, это было очень весело, поэтому если у вас есть идея пустякового проекта, реализуйте её. Почему нет?
А пока вы практикуетесь, мы поможем прокачать ваши навыки или с самого начала освоить профессию, востребованную в любое время:
Выбрать другую востребованную профессию.
Краткий каталог курсов и профессий
Data Science и Machine Learning
Python, веб-разработка
Мобильная разработка
Java и C#
От основ — в глубину
А также
Комментарии (9)
zartdinov
17.04.2022 05:13+11После нужно написать компьютер, потом язык программирования, ну и запустить DOOM на нем.
izirayd
17.04.2022 07:40+4Функция CSS calc
Эх, я так надеялся что там будет интересная реализация операций с числами (Какой-нибудь сумматор на html + css :D), а всё свелось к простой функции calc
Spaceoddity
17.04.2022 12:50Со счётчикам действительно можно было бы заморочиться посерьёзнее.
Фишка в том, что счётчики, например, работают на элементах c visibility:hidden, но не работают на элементах с display:none.
iShrimp
17.04.2022 19:23Хотел написать научный калькулятор (типа такого, но с поддержкой функций и циклов). Чтобы работал на любом устройстве, где есть браузер.
Но чем больше я изучаю HTML/CSS, тем страшнее кажутся эти адские дебри разных стандартов и спецификаций. Не понимаю, как люди это осваивают с нуля. Может, где-то есть хороший готовый оперсорсный проект?
aigoncharov
17.04.2022 19:43Слышал много хорошего про этот курс - https://css-for-js.dev/. Сам пока не покупал, но планирую
gohrytt
Зачем?
Tuxman
Потому что могу.
Aleksandr-JS-Developer
Если вы задаёте такие вопросы - значит, эта статья не для вас
Finesse
Чтобы расширить свой набор навыков, помыслить нестандартно