Это был довольно интересный проект. Я пытался создать арифметический калькулятор чисто на CSS3 (а не JavaScript). Используя такие элементы, как calc(), attr(), counter() и пр. это казалось не таким уж сложным заданием, но все оказалось не так просто.
Прежде чем я начну, хотелось бы отметить, что обоснованных причин создавать калькулятор, используя только CSS, нет. Я сделал это просто ради интереса.
Стабильно работает только в Firefox 4 и IE 9
Одним из ключевых компонентов любого калькулятора является возможность преобразовывать ввод. Используя только CSS, мы имеем очень ограниченные варианты фиксации ввода. Таким образом, для регистрации всех вводов используются чекбоксы. Для применения изменений к другим элементам можно использовать состояние «:checked» и селектор «~», а так как они довольно прямолинейны, я не буду вдаваться в подробности, а вместо этого акцентирую внимание на логике вычисления значений.
Зарегистрировать первую клавишу действительно не проблема, так как если нажать на чекбокс с числом 9, вы зарегистрируете ввод числа 9. Первая проблема возникает в том случае, если пользователь хочет ввести 95 (т.е. 9 и 5). Если мы уже зарегистрировали 9, мы не можем просто прибавить 5 (так как получится 14). Мы можем умножить на 10, но тогда получится 90. Нам нужно умножить на 10 и добавить 5. Поэтому counter() вроде как пока не участвует.
Одним из способов, как этого можно достичь, является использование размера шрифта. Преимущество размера шрифта заключается в том, что дочерние элементы наследуют значения родительских элементов, и вы можете задавать значения как в процентном соотношении, так и в пикселях.
Представьте себе следующую структуру HTML:
#div1 > #div2 > #div3 > #div4 > #div5
При выборе первого числа (в этом случае 9), вы будете использовать свойство содержимого, чтобы прибавить 9 знаков к div5 с размером шрифта в 1 пиксель (другими словами, ширина div5 будет равна 9 пикселям). Теперь, когда пользователь выберет какое-либо другое число, мы будет применять размер шрифта: 1000% к фактическому значению #div5 (обратите внимание, что 9 символов все также имеют собственный размер шрифта в 1 пиксель), что приведет к тому, что #div5 теперь будет иметь ширину в 90 пикселей. Кроме того, второе число будет добавляться в качестве содержимого в #div4 таким же образом, что и в #div5, с размером шрифта равным 1 пиксель, то есть содержимое текста в #div4 будет равно 5 пикселям, но включая дочерний элемент #div5, суммарная ширина будет равна 95 пикселям!
Таким же образом мы поступим и с третьим числом, применив размер шрифта 1000% к элементу #div4 и пр. Это все хорошо звучит, но проблема заключается в том, что максимальный размер шрифта относительно невелик. Я говорю «относительно» по сравнению со следующим методом, что приведет к тому, что это метод будет пригоден только для чисел меньше 100 000 или около того (точно не помню, какой был лимит в FF/Chrome, но мне он показался достаточно низким, чтобы отказаться от этой идеи).
Для небольших чисел такой метод вполне подходит (например, как это выполнено в этом блэкджеке, сделанном чисто на CSS3 (работает только в Chrome)).
Поэтому использование размера шрифта в качестве контейнера для ввода даже не обсуждалось. Я решил попробовать элемент calc(). К сожалению, пока что его не поддерживает webkit, и в качестве примера он работает только на IE9 и Firefox 4 и выше. Как и в случае с методом размер шрифта, мы будем использовать ту же структуру HTML с элементами «div», помещенными один в один, но в этот раз мы начнем с родительского элемента.
При выборе первого числа мы указываем значение в виде ширины #div1. При выборе второго числа мы задаем ширину для #div2 в виде calc(1000% + число2). Как и в предыдущем примере, ширина #div1 будет равна 9 пикселям, а ширина #div2 — calc(1000% + 5px), что приведет к тому, что ширина будет равна 95 пикселям. Таким же образом мы можем продолжать для #div3… #div5, допуская, что пользователь вводит до 5 знаков.
Отлично, мы успешно сохранили введенное пользователем значение (при этом мы еще не выполняли вычисления, которые хочет сделать пользователь). Давайте допустим, что пользователь ввел 9146, что в результате даст следующую ширину элементов div:
#div1 9px
#div2 91px
#div3 914px
#div4 9146px
#div5 9146px (по умолчанию 100%)
В это время пользователь решил умножить (мы убрали возможность для пользователя вводить новые числа до выполнения действия, которое в данный момент представляет собой умножение).
Теперь нам нужно настроить первое значение (ширину #div5), умножив его на любое число, которое выберет пользователь. Первое, что вы можете предположить – это продолжить использовать тот же метод, что и в первом примере, т.е. изменить структуру на
#div1 > #div2 > #div3 > #div4 > #div5 > #div6 > #div7 > #div8 > #div9 и пр.
после чего изменить ширину, используя выбранное пользователем значение. К сожалению, все не так. Представьте, что в том же примере пользователь хочет умножить 9146*25.
То есть, после того, как пользователь введет первый символ числа, на которое он будет умножать, т.е. «2», ширина #div6 будет равна 200%, что в результате даст 18 292? Это выглядит довольно хорошо и правильно, и так оно и есть.
Однако когда мы переходим ко второму знаку, мы сталкиваемся, наверное, с наибольшей проблемой во всем этом приложении. Мы не можем просто умножить на 5, т.е. 500%, так как 500% от 18 292 – это 91 460, что не есть то же самое, что и 25 * 9146, которое равно 228 650. Выполнение прибавления умножения с помощью calc() тоже нам не поможет, так как настоящая проблема заключается в том, что мы не можем умножить одно и то же значение несколько раз. Нам нужно целое число (25), и его нужно использовать для умножения исходного числа (9146).
Еще одна проблема заключается в том, что #div1 не фиксирует ширину дочернего элемента, поэтому, когда дочерний элемент #div5 имеет ширину 9146px, #div1 все так же равен 9px, даже несмотря на то, что #div5 находится внутри него. В случае с размером шрифта это не имело значения.
Если #div1 будет отражать реальную ширину, тогда 5 div могут клонироваться 5 раз и располагаться друг за другом, формируя следующую структуру:
#container > {
#parent1 > #div1 > #div2 > #div3 > #div4 > #div5
#parent2 > #div1 > #div2 > #div3 > #div4 > #div5
#parent3 > #div1 > #div2 > #div3 > #div4 > #div5
#parent4 > #div1 > #div2 > #div3 > #div4 > #div5
#parent5 > #div1 > #div2 > #div3 > #div4 > #div5
}
Во-первых, только один элемент div будет отображаться (#parent1), и как только будет выбрано первое число для умножения, мы умножим ширину div на 2, что приведет к тому, что ширина контейнера будет равна 2x9146. Когда будет выбрано второе число, мы снова умножим #parent1 на 1000%, в результате чего ширина контейнера будет равна 20x9146, после чего мы отобразим #parent2 и умножим его на 500%, в результате чего ширина последнего элемента #container будет равна 20x9146 + 5x9146, то есть 25x9146, и так со всеми последующими числами. При этом, это нельзя сделать без метода размера шрифта.
Перепробовав огромное количество вариантов с таблицами и прочими вложенными элементами, я пришел к использованию селекторов для фиксации числа. Я отбросил эту идею и продолжаю это делать, так как она нарушает предназначение практически всего, что я пытался тут сделать (ничего из этого не потребуется, если вы просто сделаете тысячи различных css-селекторов для каждой отдельной комбинации цифр).
То есть, получится следующее:
— когда пользователь выбирает 2, это умножает ширину #div5 на 200%;
— если пользователь выбирает еще одну цифру (т.е. 5), у нас получается селектор, который фиксирует, что за «digit1number2:checked» идет «digit2number5:checked», что после этого регистрирует, что ширина равна 2500%.
Очевидно, что это приводит к необходимости иметь селектор для каждого отдельного числа. Поэтому, если мы хотим иметь возможность умножать на любое число от 0 до 99, нам нужно 100 разных селекторов для каждой отдельной комбинации. Таким же образом, если мы хотим 0-999, нам нужно 1000 селекторов, а для 0-9999 – 10000 селекторов, поэтому делать калькулятором с помощью такого способа – это просто глупо. Несмотря на то, что у меня закончились идеи и ввиду наличия ограничений (о которых я расскажу дальше), это не обязательно большая потеря. Все равно мы можем технически обеспечить любую длину первого числа, имея всего несколько селекторов, но второе число теперь будет ограничено до 0-99.
Отлично, мы наконец-то смогли вычислить ширину div, основывая на введенном пользователем значении. Как теперь представить это пользователю? Наверное, сейчас вы надеетесь, что элемент attr() позволит нам отобразить значения css, указанные в таблицах стилей, но, к сожалению, это не так. Как уже объяснялось ранее, использование элемента counter() не приносит реального результата, особенно в отношении умножения и деления, при этом его презентация была бы намного проще.
Так как же он показывает ширину? Помните, как использовались все предыдущие примеры для настройки ширины элемента div? Соедините эту ширину с плавающим фреймом с шириной 100% и несколькими медиа-запросами. Отлично, теперь у нас есть плавающий фрейм шириной 228 650 пикселей внутри элемента div, и нам нужно сделать 228 650 различных медиа-запросов, чтобы представить каждое отдельное число, основанное на ширине документа? Опять же, это, конечно, можно сделать, но это просто лишает все смысла.
Нет, вместо этого мы будем использовать количество плавающих фреймов, один для каждого знака числа. То есть, если мы хотим представить количество, состоящее из 6 знаков, нам понадобится 6 плавающих фреймов внутри фрейма, который мы хотим представить. Первый фрейм будет определять количество знаков, то есть получится что-то вроде этого:
0 < ширина < 100: 1-2 знака
100 < ширина < 1000: 3 знака
1000 < ширина < 10000: 4 знака
10000 < ширина < 100000: 5 знаков
100000 < ширина < 1000000: 6 знаков
и т.д.
и после этого будет отображаться нужный фрейм, который в данном случае представляет собой 6-значный фрейм.
После этого этот фрейм будет включать 10 медиа-запросов, которые будут проверять, что первый знак числа представляет собой что-то вроде этого:
@media(min-width: 100px) and (max-width: 100099px) -> 0
@media(min-width: 100100px) and (max-width: 200099px) -> 1
@media(min-width: 200100px) and (max-width: 300099px) -> 2
А каждый медиа-запрос после этого добавляет это число и сокращает ширину плавающего фрейма на нужное количество сотен тысяч, в данном случае – 200000, чтобы следующий вложенный фрейм была равен digit5.html, а его ширина была равна 28 650 (так как мы удалили 200000 из его 100%). После этого мы будем использовать тот же метод для проверки 5го знака, отображения этого знака и сокращения ширины следующего фрейма на нужное количество десятков тысяч, и так далее до последнего знака.
Возможно, вы заметили, что минимальная ширина фактически равна 100, а не 0, а максимальная ширина – на 100 больше, чем вы ожидали. Это потому, что плавающий фрейм с шириной равной 0 не будет отображать какой-либо контент (потому что он 0 пикселей в ширину!), поэтому нам нужно выделить определенное пространство, где будут отображаться цифры.
Все это приведет к появлению определенного количества фреймов, которое отображает ширину документа, а протестировать это вы можете тут. Обратите внимание, что он на 100 пикселей меньше ширины вашего окна, и работает только в браузерах, которые поддерживают calc()).
Отлично! Теперь у нас есть работающий калькулятор! Я упоминал, что существуют и другие ограничения. Неудивительно, что ширина документа / свойство css имеет максимальное значение, и такое максимальное значение, которое можно вычислить, равно:
— Firefox 17895698
— Internet Explorer 1533816
Поэтому, даже если бы мы имели возможность ввести более двух знаков во втором числе, используя этот метод для умножения мы бы очень быстро дошли до такого предельного значения свойства.
Кроме того, как вы и ожидали, тут не принимаются и не отображаются какие-либо дробные части (при делении они просто округляются), и нельзя ввести отрицательное значение (при этом я не вижу проблем в том, чтобы это исправить).
Работая над этой проблемой, я столкнулся с интересной утечкой памяти в IE при использовании плавающих фреймов и медиа-запросов, что приводит к зависанию браузера на нескольких строках CSS и HTML.
Полезные решения Paysto для читателей Хабра:
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.