Author: | laurasandoval |
---|---|
Views Total: | 1,820 views |
Official Page: | Go to website |
Last Update: | July 29, 2020 |
License: | MIT |
Preview:

Description:
Just another Vanilla JavaScript & CSS solution that converts a group of radio buttons into iOS style segmented controls introduced in Human Interface Guidelines.
How to use it:
1. Create a group of radio buttons.
<div class="ios-segmented-control"> <span class="selection"></span> <div class="option"> <input type="radio" id="metro" name="sample" value="metro" checked> <label for="metro"><span>Metro</span></label> </div> <div class="option"> <input type="radio" id="bus" name="sample" value="bus"> <label for="bus"><span>Bus</span></label> </div> <div class="option"> <input type="radio" id="train" name="sample" value="train"> <label for="train"><span>Train</span></label> </div> </div>
2. The necessary CSS styles for the segmented control.
.ios-segmented-control { --background: rgba(239,239,240,1); background: var(--background); border-radius: 9px; margin: 0; padding: 2px; border: none; outline: none; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .ios-segmented-control .option { position: relative; cursor: pointer; } .ios-segmented-control .option:hover input:not(:checked) + label span, .ios-segmented-control .option:active input:not(:checked) + label span, .ios-segmented-control .option:focus input:not(:checked) + label span { opacity: .2; } .ios-segmented-control .option:active input:not(:checked) + label span { transform: scale(.95); } .ios-segmented-control .option label { position: relative; display: block; text-align: center; padding: 3px 6vmin; background: rgba(255,255,255,0); font-weight: 500; color: rgba(0,0,0,1); font-size: 14px; } .ios-segmented-control .option label::before, .ios-segmented-control .option label::after { content: ''; width: 1px; background: rgba(142,142,147,.15); position: absolute; top: 14%; bottom: 14%; border-radius: 10px; will-change: background; -webkit-transition: background .2s ease; transition: background .2s ease; } .ios-segmented-control .option label::before { left: 0; transform: translateX(-.5px); } .ios-segmented-control .option label::after { right: 0; transform: translateX(.5px); } .ios-segmented-control .option:first-of-type { grid-column: 1; grid-row: 1; box-shadow: none; } .ios-segmented-control .option:first-of-type label::before { opacity: 0; } .ios-segmented-control .option:last-of-type label::after { opacity: 0; } .ios-segmented-control .option input { position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; padding: 0; margin: 0; -webkit-appearance: none; -moz-appearance: none; appearance: none; outline: none; border: none; opacity: 0; } .ios-segmented-control .selection { background: rgba(255,255,255,1); border: .5px solid rgba(0,0,0,0.04); box-shadow: 0 3px 8px 0 rgba(0,0,0,0.12), 0 3px 1px 0 rgba(0,0,0,0.04); border-radius: 7px; grid-column: 1; grid-row: 1; z-index: 2; will-change: transform; -webkit-transition: transform .2s ease; transition: transform .2s ease; } .ios-segmented-control .option label span { display: block; position: relative; z-index: 2; -webkit-transition: all .2s ease; transition: all .2s ease; will-change: transform; } .ios-segmented-control .option input:checked+label::before, .ios-segmented-control .option input:checked+label::after { background: var(--background); z-index: 1; } .ios-segmented-control .option input:checked+label { cursor: default; }
3. The core JavaScript to activate the Segmented Control.
// Constants const SEGMENTED_CONTROL_BASE_SELECTOR = ".ios-segmented-control"; const SEGMENTED_CONTROL_INDIVIDUAL_SEGMENT_SELECTOR = ".ios-segmented-control .option input"; const SEGMENTED_CONTROL_BACKGROUND_PILL_SELECTOR = ".ios-segmented-control .selection"; // Main document.addEventListener("DOMContentLoaded", setup); // Body functions function setup() { forEachElement(SEGMENTED_CONTROL_BASE_SELECTOR, elem => { elem.addEventListener("change", updatePillPosition); }) window.addEventListener("resize", updatePillPosition); // Prevent pill from detaching from element when window resized. Becuase this is rare I haven't bothered with throttling the event } function updatePillPosition() { forEachElement(SEGMENTED_CONTROL_INDIVIDUAL_SEGMENT_SELECTOR, (elem, index) => { if (elem.checked) moveBackgroundPillToElement(elem, index); }) } function moveBackgroundPillToElement(elem, index) { document.querySelector(SEGMENTED_CONTROL_BACKGROUND_PILL_SELECTOR).style.transform = "translateX(" + (elem.offsetWidth * index) + "px)"; } // Helper functions function forEachElement(className, fn) { Array.from(document.querySelectorAll(className)).forEach(fn); }