- var d = 5000, o = -.5*d, k = .05;
- var ax = .5*(1 - k)*d;
- var r = ~~(.75*ax), p = 5;
- var pr = Math.round(.1*k*d), pr1 = Math.round(5*pr/7);
- var na = 2*pr, fs = 3*na;

svg(viewBox=[o, o, d, d].join(' '))
	style
		| svg { --fs: #{fs}px }
	marker#e(markerWidth=na markerHeight=na 
						viewBox='0 -4 8 8' 
						orient='auto' refX='7')
		path(d='M8 0 0-4V4')
	g#axes
		path(d=`M${-ax} 0H${ax}`)
		text(x=ax - .5*fs y=-.5*fs) x
		path(d=`M0${-ax}V${ax}`)
		text(x=.4*fs y=ax) y
	circle#cc(r=r)
	g#arc
		path
		text
	line#prev(x2=r)
	line#curr(x2=r)
	path#poly
	g#points
		- for(var i = 0; i < p; i++) {
			g.point
				circle(r=1.5*pr)
				g.lbl
					text.name(y=-.1*fs) V<tspan>#{i}</tspan>
					text.adeg(y=.8*fs) #{i}·#{360/p}°
		- }
form
	input#conv(type='radio' name='poly' data-q='1')
	label(for='conv') pentagon
	input#star(type='radio' name='poly' data-q='2')
	label(for='star') pentagram
View Compiled
$demo-dark: #262626;
$demo-light: invert($demo-dark);
$demo-ll: #555;
$demo-hl: #f90;

$graph-c0: #b53;
$graph-c1: #95a;

$t: .7s;

* { vector-effect: non-scaling-stroke }

body {
	display: flex;
	overflow: hidden;
	margin: 0;
	height: 100vh;
}

svg {
	flex: 1;
	stroke-width: 2px;
	font: var(--fs) consolas, monaco, monospace;
	letter-spacing: calc(var(--fs)*-.05)
}

path { fill: none }

line { stroke: $graph-c0; stroke-width: 3px; }

tspan { font-size: .65em }

#e * { fill: $demo-dark }

#axes {
	marker-end: url(#e);
	font: italic 1em times new roman, serif;
	
	[d] { stroke: $demo-dark }
}

#cc {
	fill: none;
	stroke: $demo-light
}

#arc {
	[d] {
		fill: #ffeb3b;
		fill-opacity: .5;
		stroke: $demo-hl
	}
}

#curr { stroke: $graph-c1 }

#poly { stroke: $demo-hl; stroke-width: 4px }

#points {
	text-anchor: middle
}

.point {
	[r] {
		fill: $demo-hl;
		stroke: $demo-dark
	}
}

.name {
	font-style: italic;
	font-family: times new roman, serif;
	font-weight: 600
}

form {
	position: absolute;
	top: 1em; left: 1em;
}

[type='radio'] {
	position: absolute;
	left: -100vw;
	
	+ label {
		display: inline-block;
		margin-right: .5em;
		border: solid 2px currentcolor;
		padding: 0 .5em;
		opacity: .5;
		font: 1.25em/2 trebuchet ms, tahoma, sans-serif;
		cursor: pointer;
		transition: $t;
		
		&:hover { opacity: .999 }
	}
	
	&:focus, &:checked { + label { opacity: .999 } }
	
	&:checked + label {
		background: darken($demo-hl, 5%);
		color: #fff;
		border-color: $demo-dark;
	}
}
const DIM = +document.querySelector('svg').getAttribute('viewBox').split(' ')[2], 
			R = +document.getElementById('cc').getAttribute('r'), 
			RA = .2*R, 
			ABASE = `M0 0 ${RA} 0A${RA} ${RA} 0 0 1 `, 
			P = 5, 
			E = 0, 
			BA_DEG = 360/P, BA_RAD = 2*Math.PI/P, 
			TBB = document.querySelector('.adeg').getBBox(), 
			TR = .5*Math.hypot(TBB.width, 2*TBB.height), 
			COORD = [], 
			VX = Array.from(document.querySelectorAll('.point')).map((c, i) => {
				const _O = {
								g: c, 
								pt: c.querySelector('circle'), 
								lb: c.querySelector('.lbl'), 
								nm: c.querySelector('.name'), 
								id: c.querySelector('tspan'), 
								dg: c.querySelector('.adeg')
							},
							CA_RAD = i*BA_RAD, 
							COS = Math.cos(CA_RAD), 
							SIN = Math.sin(CA_RAD);
				
				let x = +(R*COS).toFixed(E), 
						y = +(R*SIN).toFixed(E);
				
				COORD.push([x, y]);

				c.setAttribute(...[
					'transform', 
					`translate(${COORD[i].join(' ')})`
				]);
				
				x = +(TR*COS).toFixed(E);
				y = +(TR*SIN).toFixed(E);

				_O.lb.dataset.pos = `translate(${[x, y].join(' ')})`
				_O.lb.setAttribute(...[
					'transform', 
					_O.lb.dataset.pos
				]);

				return _O;
			}), 
			T = {
				grow: 32, 
				show: 61, 
				draw: 92
			}, 
			_ARC = { g: document.getElementById('arc') }, 
			_PREV = document.getElementById('prev'), 
			_CURR = document.getElementById('curr'), 
			_POLY = document.getElementById('poly'),
			TFN = {
				'linear': function(k) {
					return k;
				},
				'ease-in': function(k, e = 1.75) {
					return Math.pow(k, e)
				},
				'ease-out': function(k, e = 1.75) {
					return 1 - Math.pow(1 - k, e)
				}, 
				'bounce-fin': function(k, e = .7*Math.PI) {
					return Math.sin(k*e)/Math.sin(e);
				}, 
				'bounce-xtra': function(k) {
					return 1 - Math.cos(k*7.5*Math.PI)/Math.pow(2, Math.round(k/.4/3));
				}
			};

let q = null, rID = null, prev_rad, prev_deg, curr_coord;

function reset() {
	prev_deg = prev_rad = 0;
	
	VX.forEach(c => {
		c.pt.setAttribute('transform', 'scale(.7)');
		c.nm.setAttribute('font-size', '0');
		c.nm.setAttribute('opacity', 0);
	});
	
	_ARC.g.setAttribute('opacity', 0);
	_ARC.g.setAttribute('transform', 'rotate(0)')
	_ARC.path.setAttribute('d', '');
	_ARC.text.setAttribute('opacity', 0);
	_PREV.setAttribute('opacity', 0);
	_PREV.setAttribute('transform', 'none');
	_CURR.setAttribute('opacity', 0);
	_POLY.setAttribute('d', '')
};

function stopAni() {
	cancelAnimationFrame(rID);
	rID = null;
};

function ani(step = -1, t = T.grow) {
	let k, vid = ((step + 1)*q)%P, j, x, y;
	
	switch(true) {
		case t <= T.grow:			
			k = t/T.grow;
			
			let ca_rad = k*q*BA_RAD;
			
			_CURR.setAttribute('transform', `rotate(${prev_deg + k*q*BA_DEG})`);
			_ARC.path.setAttribute('d', ABASE + `${RA*Math.cos(ca_rad)} ${RA*Math.sin(ca_rad)}z`)
			
			if(t === T.grow) {
				if(step < 0) _CURR.setAttribute('transform', `none`);
				
				if(step === P - 1) { t = T.show }
				else { VX[vid].id.textContent = step + 1 }
			}
			break;
		case t <= T.show:
			k = (t - T.grow)/(T.show - T.grow);
			
			if(step < P - 1) {			
				VX[vid].pt.setAttribute('transform', `scale(${.7 + TFN['bounce-xtra'](k)*.3})`);
				VX[vid].nm.setAttribute('font-size', `${TFN['bounce-fin'](k)}em`);
				VX[vid].nm.setAttribute('opacity', TFN['ease-out'](k));
			}
			
			if(step < 0) _CURR.setAttribute('opacity', k)
			
			if(t === T.show) {				
				if(step < 0) {
					_PREV.setAttribute('opacity', 1);
					_ARC.path.setAttribute('d', '');
					_ARC.g.setAttribute('opacity', 1);
					
					++step;
					t = -1
				}
			}			
			break;
		case t <= T.draw:
			k = (t - T.show)/(T.draw - T.show);
			j = 1 - k;
			
			[x, y] = curr_coord[step].map((c, i) => j*c + k*curr_coord[(step + 1)%P][i]);
			
			_PREV.setAttribute('opacity', TFN['ease-in'](j));
			_ARC.g.setAttribute('opacity', TFN['ease-in'](j));
			if(step === P - 1) _CURR.setAttribute('opacity', TFN['ease-in'](j));
			
			_POLY.setAttribute('d', `M${curr_coord.slice(0, step + 1)} ${[x, y]}`)
			
			if(t === T.draw) {				
				if(++step === P) {
					stopAni();
					return;
				}
				
				prev_deg = step*q*BA_DEG;
				_PREV.setAttribute('transform', `rotate(${prev_deg})`);
				_ARC.g.setAttribute('transform', `rotate(${prev_deg})`);
				_PREV.setAttribute('opacity', 1);
				_ARC.path.setAttribute('d', '');
				_ARC.g.setAttribute('opacity', 1);
				
				t = -1
			}
			break;
	}
	
	rID = requestAnimationFrame(ani.bind(this, step, ++t));
};

(function init() {
	_ARC.path = _ARC.g.querySelector('path');
	_ARC.text = _ARC.g.querySelector('text');
	
	reset();
	
	addEventListener('change', e => {
		const _T = e.target;

		if(_T.dataset.q) {
			if(rID) stopAni();			
			q = +_T.dataset.q;
			reset();
			
			curr_coord = Array(P).fill(1).map((c, i) => COORD[(q*i)%P]);
			
			ani();
		}
	}, false);
})();
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.