Rang slider

Rang slider developed using HTML, JavaScript, and CSS. No, the title doesn’t contain a typo. Because if you dare touch this slider, it’ll unleash its fury!.

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

Created on March 11, 2020 Updated on March 12, 2020. A Pen by Jon Kantner on CodePen. License.

<form>
	<div class="rage" id="rageslider1">
		<input class="rage__input" type="range" name="rage" value="0" min="0" max="20">
		<div class="rage__track"></div>
		<canvas class="rage__flame-area"></canvas>
		<div class="rage__face">
			<div class="rage__face-eye"></div>
			<div class="rage__face-eye"></div>
			<div class="rage__face-mouth"></div>
			<div class="rage__value">0</div>
		</div>
	</div>
</form>
window.addEventListener("DOMContentLoaded",() => {
	let rageslider1 = new RageSlider({id: "rageslider1"});
});

class RageSlider {
	constructor(args) {
		let el = document.querySelector(`#${args.id}`),
			isMobile = "ontouchstart" in document.documentElement,
			clientDownEvent = isMobile ? "touchstart" : "mousedown",
			clientUpEvent = isMobile ? "touchend" : "mouseup";

		this.track = el.querySelector(".rage__input");
		this.flameArea = el.querySelector(".rage__flame-area");
		this.flameAreaContext = this.flameArea.getContext("2d");
		this.flameAreaScale = 2;
		this.face = el.querySelector(".rage__face");
		this.value = el.querySelector(".rage__value");
		this.kbdActiveClass = "rage__input--active";

		this.isBurning = false;
		this.maxParticles = 32;
		this.particle = () => ({
			x: this.randomX(),
			y: this.flameArea.height / this.flameAreaScale - this.getHandleHeight() / 2,
			rStart: this.getHandleHeight() / 3,
			speed: this.getHandleHeight() / 10
		});
		this.particles = [];

		// assign listeners
		window.addEventListener("resize",() => {
			this.adjustCanvas();
		});
		this.track.addEventListener(clientDownEvent,e => {
			if (e.which !== 3) {
				this.isBurning = true;
				this.startFlame();
			}
		});
		this.track.addEventListener(clientUpEvent,() => {
			this.isBurning = false;
		});
		this.track.addEventListener("keydown",e => {
			let kc = e.keyCode;
			if (kc >= 37 && kc <= 40) {
				this.isBurning = true;
				this.startFlame();
				this.track.classList.add(this.kbdActiveClass);
			}
		});
		this.track.addEventListener("keyup",() => {
			this.isBurning = false;
			this.track.classList.remove(this.kbdActiveClass);
		});
		this.track.addEventListener("input",() => {
			this.updateFacePos();
		});

		// initiate
		this.adjustCanvas();
		this.updateFacePos();
	}
	adjustCanvas() {
		let S = this.flameAreaScale,
			OW = this.flameArea.offsetWidth,
			OH = this.flameArea.offsetHeight;
		// use natural canvas dimensions affected by ems
		this.flameArea.width = OW * S;
		this.flameArea.height = OH * S;
		this.flameArea.style.width = OW + "px";
		this.flameArea.style.height = OH + "px";
		this.flameAreaContext.scale(S,S);
	}
	getHandleHeight() {
		let CS = window.getComputedStyle(this.face),
			height = CS.getPropertyValue("height"),
			heightNoPx = parseFloat(height.split("px")[0]);

		return heightNoPx;
	}
	getRangePercent() {
		let max = this.track.max,
			min = this.track.min,
			relativeValue = this.track.value - min,
			ticks = max - min,
			percent = relativeValue / ticks;

		return percent;
	}
	randomX() {
		let handleSize = this.getHandleHeight(),
			handleRad = handleSize/2,
			// get the current handle position
			xLimit = this.flameArea.width / this.flameAreaScale - handleSize,
			placeX = handleRad + (this.getRangePercent() * xLimit),
			// randomly adjust between the handle center and edge
			flip = Math.random() < 0.5 ? -1 : 1,
			displace = Math.random() * (handleRad - handleSize/3) * flip,
			x = placeX + displace;

		return x;
	}
	startFlame() {
		if (!this.particles.length)
			this.updateFlame();
	}
	updateFlame() {
		let c = this.flameAreaContext,
			S = this.flameAreaScale,
			W = this.flameArea.width / S,
			H = this.flameArea.height / S,
			faceCenter = H - this.getHandleHeight()/2;

		c.clearRect(0,0,W,H);

		// supply particles
		if (this.particles.length < this.maxParticles && this.isBurning)
			this.particles.push(this.particle());

		// particle ascension
		for (let p of this.particles) {
			let faceCenterToTopPct = p.y / faceCenter,
				pRadius = p.rStart * faceCenterToTopPct;

			p.y -= p.speed;

			if (p.y <= 0) {
				// particles shouldn’t regenerate if the user stops interacting
				if (this.isBurning) {
					p.x = this.randomX();
					p.y = faceCenter;

				} else {
					this.particles.shift();
				}

			} else {
				// draw the particle
				let hue = 50 * faceCenterToTopPct;
				c.fillStyle = `hsl(${hue},90%,50%)`;
				c.beginPath();
				c.arc(p.x,p.y,pRadius,0,2*Math.PI);
				c.fill();
				c.closePath();
			}
		}

		requestAnimationFrame(() => {
			if (this.particles.length)
				this.updateFlame();
		});
	}
	updateFacePos() {
		let percent = this.getRangePercent(),
			left = percent * 100,
			emAdjust = percent * 1.5;

		this.face.style.left = `calc(${left}% - ${emAdjust}em)`;
		this.value.innerHTML = this.track.value;
	}
}
* {
	border: 0;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	--bg: #d8d8d8;
	--fg: #171717;
	--fgT: #17171700;
	--rageLight: #f13d17;
	--rageDark: #962417;
	--track: #969696;
	--animDur: 0.2s;
	--transDur: 0.1s;
	font-size: calc(32px + (48 - 32)*(100vw - 320px)/(2560 - 320));
}
body, input {
	color: var(--fg);
	font: 1em/1.5 "Oswald", sans-serif;
}
body {
	background: var(--bg);
	display: flex;
	height: 100vh;
}
form {
	margin: auto;
	width: 8.5em;
}
.rage {
	position: relative;
}
.rage__input, .rage__track, .rage__flame-area {
	width: 100%;
}
.rage__input {
	background: transparent;
	display: block;
	outline: transparent;
	margin: 2.25em 0;
	height: 0.75em;
	-webkit-appearance: none;
	-moz-appearance: none;
	appearance: none;
}
.rage__input::-webkit-slider-thumb {
	background: transparent;
	border: 0;
	border-radius: 50%;
	cursor: pointer;
	width: 1.5em;
	height: 1.5em;
	-webkit-appearance: none;
	appearance: none;
}
.rage__input::-moz-range-thumb {
	background: transparent;
	border: 0;
	border-radius: 50%;
	cursor: pointer;
	width: 1.5em;
	height: 1.5em;
}
.rage__input::-moz-focus-outer {
	border: 0;
}


.rage__input:active + .rage__track,
.rage__input--active + .rage__track {
	background: var(--rageDark);
}
.rage__input:active ~ .rage__face,
.rage__input--active ~ .rage__face,
.rage__input:active ~ .rage__face:after,
.rage__input--active ~ .rage__face:after {
	background: var(--rageLight);
}
.rage__input:active ~ .rage__face:before,
.rage__input--active ~ .rage__face:before {
	animation: pulse var(--animDur) var(--transDur) linear infinite;
	transform: scale(1);
}
.rage__input:active ~ .rage__face:after,
.rage__input--active ~ .rage__face:after {
	transform: scaleY(1);
}
.rage__input:active ~ .rage__face .rage__face-mouth,
.rage__input--active ~ .rage__face .rage__face-mouth {
	transform: scaleY(-1);
}
.rage__track, .rage__flame-area, .rage__face, .rage__face:before, .rage__face:after, .rage__value {
	position: absolute;
}
.rage__track, .rage__flame-area, .rage__face {
	left: 0;
}
.rage__track, .rage__face {
	transition: background var(--transDur) linear;
}
.rage__track, .rage__face:before, .rage__face:after {
	content: "";
	display: block;
}
.rage__track {
	background: var(--track);
	border-radius: 0.75em;
	top: 0;
	height: 0.75em;
	z-index: -3;
}
.rage__flame-area {
	bottom: -0.375em;
	width: 100%;
	height: 3em;
	z-index: -2;
}
.rage__face, .rage__face:before {
	border-radius: 50%;
}
.rage__face {
	background: #fff;
	box-shadow: 0 0 0 0.1em #0003 inset;
	display: flex;
	justify-content: center;
	align-content: center;
	flex-wrap: wrap;
	top: -0.375em;
	width: 1.5em;
	height: 1.5em;
	will-change: transform;
	z-index: -1;
}
.rage__face:before, .rage__face-mouth {
	transition: transform var(--transDur) linear;
}
.rage__face:before {
	background-image:
		radial-gradient(100% 100% at 50% 0,var(--fgT) 16%,var(--fg) 18% 31%,var(--fgT) 33%),
		radial-gradient(100% 100% at 100% 50%,var(--fgT) 16%,var(--fg) 18% 31%,var(--fgT) 33%),
		radial-gradient(100% 100% at 50% 100%,var(--fgT) 16%,var(--fg) 18% 31%,var(--fgT) 33%),
		radial-gradient(100% 100% at 0 50%,var(--fgT) 16%,var(--fg) 18% 31%,var(--fgT) 33%);
	top: -0.2em;
	right: -0.2em;
	width: 0.6em;
	height: 0.6em;
	transform: scale(0);
}
.rage__face:after {
	background: #f1f1f1;
	clip-path: polygon(0 0,100% 0,50% 100%);
	-webkit-clip-path: polygon(0 0,100% 0,50% 100%);
	top: 0.3em;
	left: calc(50% - 0.4em);
	width: 0.8em;
	height: 0.4em;
	transition: background var(--transDur) linear, transform var(--transDur) linear;
	transform: scaleY(0);
	transform-origin: 50% 0;
}
.rage__face-eye {
	background: #171717;
	border-radius: 50%;
	margin: 0 0.125em 0.2em;
	width: 0.2em;
	height: 0.4em;
}
.rage__face-mouth {
	border-radius: 0 0 50% 50% / 0 0 100% 100%;
	box-shadow: 0 -0.1em 0 #171717 inset;
	width: 0.75em;
	height: 0.25em;
}
.rage__value {
	top: 100%;
}

@media (prefers-color-scheme: dark) {
	:root {
		--bg: #242424;
		--fg: #f1f1f1;
		--fgT: #f1f1f100;
		--track: #575757;
	}
}

@keyframes pulse {
	from, to { transform: scale(1); }
	50% { transform: scale(1.25); }
}
<link href="https://fonts.googleapis.com/css?family=Oswald&display=swap&text=0123456789" rel="stylesheet" />
Categories:
W3TWEAKS
Latest posts by W3TWEAKS (see all)

Comments

  1. RandyTum Avatar

    Your comment is awaiting moderation.

    The service allows you to change clothes on images.
    It uses AI to fit outfits seamlessly.
    You can test various styles instantly.
    xnudes.ai|Ultimate Clothing Changer Application
    The results look authentic and professional.
    It’s a handy option for shopping.
    Add your photo and select the clothes you like.
    Begin trying it now.

Leave a Reply

Your email address will not be published. Required fields are marked *