Unicycle Range Slider

By | July 7, 2020

Unicycle Range Slider developed using HTML, JavaScript and CSS. A range input where a stick figure is on a unicycle whose wheel is the handle. Watch him peddle and the flag display the value as you drag the wheel left and right.

See the Pen Unicycle Range Slider by Jon Kantner (@jkantner) on CodePen.

Created on March 5, 2020 Updated on May 20, 2020. A Pen by Jon Kantner on CodePen. License.

<form>
	<div id="unicycle1" class="unicycle">
		<input type="range" class="unicycle__wheel" name="unicycle" min="0" max="100" value="0">
		<div class="unicycle__marker">
			<div class="unicycle__rider-head"></div>
			<div class="unicycle__rider-body">
				<div class="unicycle__left-arm">
					<div class="unicycle__left-lower-arm"></div>
				</div>
				<div class="unicycle__right-arm">
					<div class="unicycle__right-lower-arm">
						<div class="unicycle__flag-pole">
							<div class="unicycle__flag">0</div>
						</div>
					</div>
				</div>
				<div class="unicycle__left-leg">
					<div class="unicycle__left-lower-leg"></div>
				</div>
				<div class="unicycle__right-leg">
					<div class="unicycle__right-lower-leg"></div>
				</div>
			</div>
			<div class="unicycle__seat"></div>
			<div class="unicycle__bar"></div>
			<div class="unicycle__pedal-arms">
				<div class="unicycle__pedal"></div>
				<div class="unicycle__pedal"></div>
			</div>
		</div>
	</div>
</form>
document.addEventListener("DOMContentLoaded",() => {
	let unicycle = new UnicycleRangeSlider("#unicycle1");
});

class UnicycleRangeSlider {
	constructor(el) {
		this.wheel = document.querySelector(`${el} input[type=range]`);
		this.marker = document.querySelector(`${el} .unicycle__marker`);
		this.flag = document.querySelector(`${el} .unicycle__flag`);

		this.updateBodyPos();
		this.wheel.addEventListener("input",() => { this.updateBodyPos(); });
	}
	updateBodyPos() {
		let max = this.wheel.max,
			min = this.wheel.min,
			realValue = this.wheel.value,
			ticks = max - min,
			relValue = realValue - min,
			percent = relValue / ticks,
			revs = 1,
			left = percent * 100,
			emAdjust = percent * 1.5,
			pedalRot = percent * (360 * revs),
			period = (1 / ((ticks / revs) / 2)) * relValue * Math.PI,
			rightLegRot = -22.5 * Math.sin(period + (1.85 * Math.PI)) - 22.5,
			rightLowerLegRot = 45 * Math.sin(period + (0 * Math.PI)) + 45,
			leftLegRot = -22.5 * Math.sin(period + (2.85 * Math.PI)) - 22.5,
			leftLowerLegRot = 45 * Math.sin(period + (1 * Math.PI)) + 45,
			cssVars = {
				"--pedalRot": `${pedalRot}deg`,
				"--rightLegRot": `${rightLegRot}deg`,
				"--rightLowerLegRot": `${rightLowerLegRot}deg`,
				"--leftLegRot": `${leftLegRot}deg`,
				"--leftLowerLegRot": `${leftLowerLegRot}deg`
			};
		// position stick figure and unicycle body
		this.marker.style.left = `calc(${left}% - ${emAdjust}em)`;
		// update the variables in CSS
		for (let v in cssVars)
			this.marker.style.setProperty(v,cssVars[v]);
		// number in the flag
		this.flag.innerHTML = realValue;
	}
}
* {
	border: 0;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	font-size: calc(24px + (48 - 24)*(100vw - 320px)/(2560 - 320));
	--bg: #f1f1f1;
	--fg: #171717;
}
body, input {
	color: var(--fg);
	font: 1em/1.5 "B612 Mono", monospace;
}
body {
	background: var(--bg);
	display: flex;
	height: 100vh;
	overflow-x: hidden;
}
form {
	margin: auto;
	width: 8.5em;
}
.unicycle,
.unicycle__wheel,
.unicycle__rider-body,
.unicycle__pedal-arms {
	position: relative;
}
.unicycle {
	margin-top: 4.25em;
}
.unicycle__wheel {
	background: transparent;
	border-radius: 0.75em;
	box-shadow: 0 0 0 0.1em inset;
	display: block;
	outline: transparent;
	width: 100%;
	height: 1.5em;
	-webkit-appearance: none;
	appearance: none;
}
.unicycle__wheel::-webkit-slider-thumb {
	background: transparent;
	border: 0;
	border-radius: 50%;
	box-shadow: 0 0 0 0.1em inset;
	cursor: pointer;
	width: 1.5em;
	height: 1.5em;
	-webkit-appearance: none;
	appearance: none;
}
.unicycle__wheel::-moz-range-thumb {
	background: transparent;
	border: 0;
	border-radius: 50%;
	box-shadow: 0 0 0 0.1em inset;
	cursor: pointer;
	width: 1.5em;
	height: 1.5em;
}
.unicycle__marker,
.unicycle__right-arm,
.unicycle__right-lower-arm,
.unicycle__left-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__right-lower-leg,
.unicycle__left-leg,
.unicycle__left-lower-leg,
.unicycle__flag-pole,
.unicycle__flag,
.unicycle__pedal {
	position: absolute;
}
.unicycle__marker {
	
	--pedalRot: 0deg;
	--rightLegRot: 0deg;
	--rightLowerLegRot: 0deg;
	--leftLegRot: 0deg;
	--leftLowerLegRot: 0deg;
	top: -2.5em;
	left: 0;
	width: 1.5em;
	height: 4em;
	z-index: -1;
}
.unicycle__marker > div {
	margin: auto;
}
.unicycle__marker .unicycle__rider-body {
	height: 1em;
	margin-bottom: 0.1em;
}
.unicycle__rider-head, .unicycle__flag {
	box-shadow: 0 0 0 0.1em inset;
}
.unicycle__rider-head {
	border-radius: 50%;
	width: 1em;
	height: 1em;
}
.unicycle__rider-head ~ div,
.unicycle__right-arm,
.unicycle__right-lower-arm,
.unicycle__left-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__right-lower-leg,
.unicycle__left-leg,
.unicycle__left-lower-leg,
.unicycle__flag-pole,
.unicycle__pedal {
	background: currentColor;
}
.unicycle__rider-body,
.unicycle__right-arm,
.unicycle__right-lower-arm,
.unicycle__left-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__right-lower-leg,
.unicycle__left-leg,
.unicycle__left-lower-leg,
.unicycle__flag-pole,
.unicycle__bar {
	width: 0.1em;
}
.unicycle__right-lower-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__right-lower-leg,
.unicycle__left-leg,
.unicycle__left-lower-leg,
.unicycle__seat,
.unicycle__pedal-arms,
.unicycle__pedal {
	border-radius: 0.05em;
}
.unicycle__right-arm,
.unicycle__right-lower-arm,
.unicycle__left-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__right-lower-leg,
.unicycle__left-leg,
.unicycle__left-lower-leg {
	transform-origin: 50% 0.05em;
}
.unicycle__right-lower-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__left-leg,
.unicycle__right-lower-leg,
.unicycle__left-lower-leg {
	top: calc(100% - 0.05em);
}
.unicycle__right-arm,
.unicycle__right-lower-arm,
.unicycle__left-arm,
.unicycle__left-lower-arm,
.unicycle__right-leg,
.unicycle__left-leg {
	height: 0.75em;
}
.unicycle__right-lower-leg,
.unicycle__left-lower-leg {
	height: 0.78em;
}
.unicycle__right-arm {
	transform: rotate(-80deg);
}
.unicycle__right-lower-arm {
	transform: rotate(-10deg);
}
.unicycle__left-arm {
	transform: rotate(25deg);
}
.unicycle__left-lower-arm {
	transform: rotate(5deg);
}
.unicycle__right-leg {
	transform: rotate(var(--rightLegRot));
}
.unicycle__right-lower-leg {
	transform: rotate(var(--rightLowerLegRot));
}
.unicycle__left-leg {
	transform: rotate(var(--leftLegRot));
}
.unicycle__left-lower-leg {
	transform: rotate(var(--leftLowerLegRot));
}
.unicycle__flag-pole {
	height: 3em;
	bottom: 0;
	transform: rotate(90deg);
	transform-origin: 50% calc(100% - 0.1em);
}
.unicycle__flag {
	right: 0;
	text-align: center;
	width: 2.25em;
	height: 1.5em;
}
.unicycle__seat,
.unicycle__pedal-arms {
	height: 0.1em;
}
.unicycle__seat {
	display: flex;
	justify-content: center;
	align-items: flex-end;
	width: 0.4em;
}
.unicycle__bar {
	border-radius: 0 0 0.05em 0.05em;
	height: 1.1em;
}
.unicycle__pedal-arms {
	top: -0.1em;
	width: 0.5em;
	transform: rotate(var(--pedalRot));
}
.unicycle__pedal {
	top: 0;
	width: 0.3em;
	height: 0.1em;
	transform: rotate(calc(var(--pedalRot) * -1));
}
.unicycle__pedal:first-child {
	left: -0.15em;
}
.unicycle__pedal:last-child {
	right: -0.15em;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg: #171717;
		--fg: #f1f1f1;
	}
}
Author: CV

I am a Front-end Developer, graduate of Information Technology, and founder of w3tweaks.com. I have 12+ years commercial experience providing front-end development, producing high quality responsive websites and exceptional user experience.