2012. július 30., hétfő

A srác csinált egy számológépet JavaScript nélkül, csak CSS-ben, és itt taglalja, hogy hogy valósította meg a...

A srác csinált egy számológépet JavaScript nélkül, csak CSS-ben, és itt taglalja, hogy hogy valósította meg a számolást. Teljesen nem értem (nem is ástam bele magam annyira), de látszik, hogy ez egy állat ... :) (Már a szó jó értelmében ... Ki talál ki ilyen agyament dolgokat?! Ügyes ...)

Originally shared by Niklas von Hertzen

http://hertzen.com/experiments/css3calculator/ (Firefox 4 and IE 9 only)

edit... yikes, this got rather long...

This was a rather interesting project. I tried to create an arithmetic calculator with purely CSS3 (no JavaScript). With stuff like calc(), attr(), counter() etc. it certainly doesn't sound like a very difficult task, but it really isn't as straight forward as you would assume.

Before I go into it, I'd just like to point out there is absolutely no good reason to create a calculator with CSS only, I just did it for the kicks.

One of the key components of any calculator is the ability to pass input into it. With using CSS only, there are very limited options how you can capture user input. As such, checkboxes are used to register all inputs. You can use the :checked state and ~ selector to apply changes to other elements, as they are quite straight forward I won't really go into more detail about that and instead focus on the logic behind calculating the values.

Registering the first key certainly isn't a problem, if they click the checkbox labelled the number 9, you would register the input to being 9. The first problem rises straight after that, what if the user wants to input 95 (i.e. 9 followed by a 5). If we registered already 9, we cannot just do an addition of 5 (it would result in 14). We cant multiply by 10, that would result in 90. What we need is to multiply by 10 and do an addition of 5. So with that in mind, counter() is more or less out of the equation already.

One way you can achieve this is by using font-size. The beauty about font-size is that child elements inherit the values from parent elements and you can provide the values in both percentages and pixels.

Imagine the following HTML structure:

#div1 > #div2 > #div3 > #div4 > #div5

When the first number is selected (in this case 9), you would use content property to add 9 characters to div5, with a font-size 1px (in other words div5 would become 9px wide). Now, when the user selects any second number, we would apply a font-size:1000% on the actual #div5 (note that the 9 characters still have their own 1px font-size), which would result the #div5 to be 90px wide now. In addition, the second number would be added as content into #div4 same way as we did in #div5 , with a font-size 1px, so the contents of the text in #div4 is 5px but including the child #div5 , the total width is 95px!

We would again continue the same pattern with the 3rd number, apply 1000% font-size to #div4 etc... This all sounds nice but the problem is that there is a relatively low maximum font-size (I say relative compared to the next method), which will result in this method only being viable to numbers below 100k or something (can't remember where exactly the limit was on FF/Chrome, but it was low enough for me to discard this idea).

For low numbers, that method works well for doing some calculations (like in this blackjack done with purely CSS3 http://hertzen.com/experiments/css3calculator/blackjack/ (only working properly in Chrome)).

So using font-size as a container for the input was out of the question, I decided to go with calc(). It unfortunately isn't supported by webkit yet, and as such the example only works with IE9 and Firefox 4 or higher (webkit bug reference https://bugs.webkit.org/show_bug.cgi?id=16662).

Like with the font-size method, we would again be using the same structure for the HTML with the divs nested within each others, but we'd start from the parent element this time.

When first number is selected, we provide the value as a width for #div1 . When the second number is selected, we set the width for #div2 as calc(1000% + number2). So with the previous example, we would have #div1 width as 9px and #div2 width as calc(1000% + 5px) which would result the width being 95px. We'd continue with the same pattern after that for #div3 .... #div5 assuming the user enters up to 5 digits.

So great, we succesfully have stored the entered value by the user (we haven't actually done any of the calculations the user wants to do yet). Let's assume the user inputted 9146, which resulted in the following div widths:

#div1 9px
#div2 91px
#div3 914px
#div4 9146px
#div5 9146px (default is 100%)

At this point the user has selected to multiply (we've removed the option for the user to put new numbers before the action which in this case is multiplication).

We now need to adjust the first value (the width of #div5 ) by multiplying it with whatever number the user selects. The first thing you would assume is to just continue with the same method as in first example, i.e. change our structure to

#div1 > #div2 > #div3 > #div4 > #div5 > #div6 > #div7 > #div8 > #div9 etc

and then modify the width using whatever the user selects. Unfortunately not. Assume that with the same example, the user wants to do 9146*25.

So after the user would enter the first digit of the number which he multiples with, i.e. "2", #div6 width would be 200%, resulting in 18 292? That looks perfectly fine and correct, and it is.

However, when we get to the second digit we face perhaps the biggest challenge in this whole application. We can't just multiply by 5, i.e. 500% because 500% of 18 292 is 91 460 which is not same as 25 * 9146 which = 228 650. Doing some addition multiplications using calc() won't help us either, since the real problem is that we can't multiply the same value multiple times. We need the full number (25) and use that to multiply the original number (9146).

Another problem is that the #div1 doesn't capture the width of the child elements, so that when the child #div5 is 9146px wide, #div1 is still just 9px even though #div5 is within it. With the font-size option, this wasn't the case.

If the #div1 would reflect the real width, then the 5 divs could have been cloned 5 times, and placed next to each other, to a structure something like this:

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

At first, only one of the div's would be displayed ( #parent1 ), and once the first number for the multiplication would be selected, we would multiply that div's width by 2 resulting in the container's width to be 2x9146. When the second number would be selected, we would multiply again the #parent1 by 1000% so the width of the container would be 20x9146 and then we would display #parent2 and multiply it by 500%, resulting in the final #container width to be 20x9146 + 5x9146 which is === 25x9146, and continue like that with all the numbers following. This however couldn't be done without the font-size method.

After trying a number of things with tables and other nested element ideas, I resolved to using selectors to capture the number. I resented the idea and still do, since it defeats the purpose of pretty much everything I attempted to do here, (none of this would be necessary if you'd just make thousands of different css selectors for every single number combination).

So this resulted into this:
- when a user selects the 2, it multiplies the #div5 width by 200%.
- if the user selects the another digit (i.e. 5), we have a selector which captures that digit1number2:checked is followed by digit2number5:checked which then registers the width to be 2500%.

Obviously, this results in the need to have a selector for every single number. So if you want to have the option to multiply by any number of 0-99, we need 100 different selectors for every single combination. Likewise, if we want 0-999, we need 1000 selectors and 0-9999 results in 10000 selectors, and making a calculator with that method is just stupid. Nevertheless, I exhausted all my ideas as far as I could come up with and because of other limits (which I'll come to in a moment), it necessary wasn't too big of a loss. We could still technically implement the first number to be as long as we want, with just a very few selectors, but the second number was now restricted to 0-99.

So great, we finally managed to calculate the width of a div based on the input a user inserted. How would we now be able to present this to the user? Right about now, you would be hoping that attr() would allow us to display css values assigned in the stylesheet as well, but unfortunately not. As previously explained, using counter() just doesn't really work, especially with multiplication and division, although the presentation of that would have been significantly easier.

So how does it show the width then? Remember how all the examples were used to adjust the width of a div? Combine that width with an iframe with 100% width and some mediaqueries! Great, so we have an iframe of width 228 650px within the div, are we gonna now do 228 650 different media queries to represent every single number based on the width of the document? Again, it certainly can be done, but just would defeat the point here.

No, instead we use a number of iframes, one for each digit of the number. So if we want to represent a number that is 6 digits, we'll need 6 iframes within each other to represent it. The first iframe would detect how many digits it has, i.e. something like this:

0 < width < 100: 1-2 digits
100 < width < 1000: 3 digits
1000 < width < 10000: 4 digits
10000 < width < 100000: 5 digits
100000 < width < 1000000: 6 digits
etc.

and then it would display the appropriate iframe, which in this case is 6 digit iframe.

That iframe then has 10 media queries which checks what the first digit of the number is through something like this:

@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

And what each media query then does is appends that number, and reduces the width of the iframe by that many hundreds of thousands, in this case 200000, so the next iframe which is embedded is digit5.html and its width is 28 650 (as we removed 200000 from its 100%). We would then use same method to check what the 5th digit, display that digit and reduce the width of the next iframe by that many tens of thousands, and follow the same way all the way to the last digit.

You might have noticed that the min width is in fact 100 and not 0, and the max width is 100 more than you would expect it to be. This is because an iframe of width 0 wouldnt end up showing any content (because its 0px wide!), so we need to have some space where to show the number as well.

So this all results in a number of iframes which shows the width of the document, you can test that right here:

http://hertzen.com/experiments/css3calculator/iframe.html (note it is 100px less than your window width, and it only works on browsers that support calc()).

Great! So we have a functioning calculator! I did mention that there were some other limits. It doesn't come a surprise that a document width / css property does have a maximum value, and as such the maximum value which can be calculated is:

- Firefox 17895698 (https://bugzilla.mozilla.org/show_bug.cgi?id=552412#c3)
- Internet Explorer 1533816

So even if we'd able to enter more than 2 digits into the second number, for multiplication we would very fast hit that property size limit with this method.

In addition, as you could have expected, it doesn't accept or display any decimals either (they are just rounded when you use division) and you can't input negative values (although I don't see why you wouldn't be able to implement it relatively easily).

While working on this, I came across an interesting memory leak in IE with iframes and media queries, which just ends up freezing up your browser with a few lines of CSS and HTML.

I'd certainly want to hear ideas of how this method could be improved. As said, it was rather disappointing having to limit the second number to only 2 digits, so if you have an idea how it could be avoided (without making a selector for every single number combination) I'd love to hear it.

If you actually read all of this ramble, feel proud that you just wasted even a fraction of the time I spent thinking about this.
http://hertzen.com/experiments/css3calculator/

4 megjegyzés:

  1. Abusing CSS XD
    Kinek jut eszébe, hogy CSS-ben számokkal operáljon? Ez nagyon durva, de nagyon tetszik.

    VálaszTörlés
  2. Ö .... ezt az "állatot" követnem kell ...

    VálaszTörlés
  3. Én is követni akartam, aztán láttam, hogy már követem ... Ő írt PHP futtató környezetet JavaScript-ben ... Állat ... :)

    VálaszTörlés
  4. Azt láttam, és ez nem volt elég, hogy kövessem ? ... :-) Fura.

    VálaszTörlés