How We Made an Interactive Checkout Using Pure CSS without JavaScript
source link: https://packetpoint.ca/blog/modern-checkout-system-without-javascript/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Often privacy focused users will browse the web without JavaScript. At Packet Point we want to support these users to the fullest extent possible.
Instead of using JavaScript we use CSS to emulate same the interactive functionality.
We will discuss the techniques used thought out Packet Point to retain full functionality for all users without JavaScript. In this post, we will focus on the server customization page.
Desired Functionality
During component selection, we want to display a summary of the options selected and the total cost at the side of the page.
Without Clever CSS
One possible implementation would be to fully utilize server-side rendering, each option could be a button that when clicked could trigger a form request reloading the entire page, which would allow the server to fill in the summary and total cost server-side.
The most severe downside of this approach is the increased latency. Every time the user changes an option a full page reload is required. This latency issue is exacerbated by the fact that privacy focused users typically use a VPN or Tor which can increase latency 100-fold.
Most often sites just disable this functionally for users without JavaScript or do not support them at all, which is the other option.
Client Side Rendering With CSS
Conditional Styling with :checked
To be interactive we will need to be able to detect user input.
Using CSS pseudo-class selectors we can trigger CSS on a number of different inputs. Some selectors include
:hover
which selects elements on hover:focus
which selects elements that are focused:checked
which selects input elements which are checked
Later examples will show :checked
in action.
Spooky Action at a Distance
To do something useful with :checked
we are going to need to be able to modify the style of another element conditionally on the :checked
state. Further, we are going to want a nice looking interface that's more than just a bunch of checkboxes.
Controlling the Checkbox At a Distance
Using the for attribute on a label tag we can create an element that once clicked toggles the checkbox from anywhere on the page. The for
attribute takes the ID of an input tag.
<input type="checkbox" id="box"/>
<label for="box">Hello</label>
We can now style the label freely to create whatever interface we want. Further, the checkbox can be made display:none;
to completely hide them away.
Styling Arbitrary Elements Conditionally
Commonly, the adjacent sibling selector (+) is used in conjunction with :checked
to style the label directly preceding the input element.
input[type="checkbox"]:checked + label {
font-size: 3rem;
}
This works great and works generically without having to specify the specific input/label pair.
However, we are going to need to effect more elements than just the next one. Thankfully, we have more CSS combinators to work with:
- '+' Adjacent Sibling Selector
- '~' General Sibling Selector
- '>' Child Selector
- ' ' Descendant Selector
For a deeper examination of these combinators refer to the Mozilla developer documentation.
Below you can see an example of using these combinators together to select different elements conditionally on the state of a checkbox. By clicking on different selectors you can see which elements would be selected.
An important limitation of these selectors is we won't be able to conditionally style sibling elements that come before the input tag nor elements which are not a descendant of the parent of the input tag. In the above example, this means we can't conditionally style the h2
tag which comes before the input
tag nor the footer
or main
tag which are not descendants of the input
tag's parent main
.
However, we can work around this limitation by nesting every element which needs conditional styling inside a sibling element that comes after the input tag.
<body>
<input type="checkbox" id="box1"/>
<input type="checkbox" id="box2"/>
<main>
...
</main>
</body>
Using the above construction, every element inside the main
tag will be conditionally style-able by the input boxes.
Say you have an element with the class name 'a', then we can conditionally style it with the following CSS.
#box1:checked ~ main .a { ... } /* when checked */
#box1:not(:checked) ~ main .a { ... }/* when not checked */
A lot can be accomplished using only the techniques we have shown so far, however let's look at couple more CSS tricks that will complement these techniques.
:checked
Works on Radio Button as Well
Radio buttons work similarly to checkboxes except clicking an already checked radio buttons will remain checked and if there are multiple radio buttons with the same name attribute in the same form then only one of them can be checked at a time.
For server customization, most components have mutually exclusive options. So radio buttons are used to represent the state of the options selected.
Selecting on the State of Multiple Checkboxes
Suppose we have the following HTML
<input type="checkbox" id="box1"/>
<input type="checkbox" id="box2"/>
<main><span class='output'></span></main>
The following CSS can be used to control the text inside .output
for each of the possible combinations of inputs.
#box1:not(:checked) ~ #box2:not(:checked) ~ main .output:before{content:"neither checked"}
#box1:checked ~ #box2:not(:checked) ~ main .output:before{content:"1 checked but not 2"}
#box1:not(checked) ~ #box2:checked ~ main .output:before{content:"2 checked but not 1"}
#box1:checked ~ #box2:checked ~ main .output:before{content:"both checked"}
CSS Math with Counters
The server customization page has many components each with many options. By pre-computing the total cost of every possible combination and using the previous trick we could implement the price total display. The problem is 'every possible combination' is a lot, say we have 6 components each with 10 options each, then there would be 10^6 = 1,000,000
options to pre-compute, that would be a massive CSS file. Instead, we use CSS counters.
If you are unfamiliar with CSS counters or just need a refresher please consult the Mozilla developer documentation.
For using CSS counters here there are a couple of things to keep in mind. Firstly, elements that are 'display:none;' do not compute their counter-increment
so we can't apply the counter directly to the input tag. Secondly, an element can only have one counter-increment
value, so we are going to need multiple elements one for each counter-increment
. We work around these issues by adding a 'b' tag after each input which can hold the counter.
So with the following HTML.
<input type="checkbox" id="box1"/>
<b></b>
<input type="checkbox" id="box2"/>
<b></b>
<span class='total'></span>
We would use the following CSS.
body { counter-reset: sum; }
#box1:checked + b { counter-increment: sum 1; }
#box2:checked + b { counter-increment: sum 7; }
.total:before { content:counter(sum); }
Putting it all together
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK