Drone hologram outline

(c) 2023 All rights reserved

by PhilQ

HTML

<div id="drawing"></div>
<br>
<br>
<button onclick="document.getElementById('drawing')?.classList.toggle('spinning')">Play / Pause</button>

SCSS

* {
	margin: 0;
	padding: 0;
}

#drawing {
	background: #fafafa;
	width: 400px;
	height: 400px;
	margin: auto;
}

svg {
	--rotor-color-1: rgba(204, 204, 204, 1);
	--rotor-color-2: rgba(255, 255, 255, 0.5);
}

@keyframes spin-rotor {
	0% { rotate: 0deg; }
	100% { rotate: 360deg; }
}

.rotor-cw,
.rotor-ccw {
	animation-name: spin-rotor;
	animation-duration: 0.5s;
	animation-iteration-count: infinite;
	animation-direction: normal;
	animation-fill-mode: forwards;
	animation-timing-function: linear;
	animation-play-state: paused;
}
.rotor-cw {
	transform: rotate(45deg);
	background: conic-gradient(
		from 0turn at 50% 50%,
		var(--rotor-color-2),
		var(--rotor-color-1) 50%,
		var(--rotor-color-2) 50%,
		var(--rotor-color-1)
	);
}
.rotor-ccw {
	transform: rotate(-45deg);
	animation-direction: reverse;
	background: conic-gradient(
		from 0turn at 50% 50%,
		var(--rotor-color-1),
		var(--rotor-color-2) 50%,
		var(--rotor-color-1) 50%,
		var(--rotor-color-2)
	);
}

.guideline {
	display: none;
	fill: transparent;
	stroke-width: 1;
	stroke: #00f;
	stroke-opacity: 1;
}
#drawing:hover .guideline {
	display: initial;
}

#drawing.spinning .rotor-cw,
#drawing.spinning .rotor-ccw {
	animation-play-state: running;
}

JavaScript

function make(tag, props = {}) {
	let node = document.createElementNS('http://www.w3.org/2000/svg', tag);
	for (let p in props) {
		if (p === 'd') {
			props[p] = props[p].join(' ');
		}
		node.setAttribute(p, props[p]);
	}
	return node;
}

function clone3x(node, parent) {
	let clone;
	for (let i = 1; i <= 3; i++) {
		clone = node.cloneNode(true);
		clone.setAttribute('transform', `rotate(${(i/4)*360})`);
		clone.setAttribute('transform-origin', `50% 50%`);
		parent.append(clone);
	}
}

const c = {
	size: 100, // half width
},
body = {
	size: 0.45,
},
arm = {
	end: {
		offset: 0.9,
	},
	curve: {
		depth: 0.06,
		center: 0.5,
	},
},
rotor = {
	center: 0.5,
	r: 0.33, //25,
	ir: 0.075,
};

let tmp;
let svg = make('svg', {
	width: '100%',
	height: '100%',
	viewBox: `0 0 ${2*c.size} ${2*c.size}`,
});

let guides = make('g');

// Guides - Diagonals
guides.append(make('line', {
	'class': 'guideline',
	'x1': 0,
	'y1': 0,
	'x2': 2*c.size,
	'y2': 2*c.size,
}));
guides.append(make('line', {
	'class': 'guideline',
	'x1': 2*c.size,
	'y1': 0,
	'x2': 0,
	'y2': 2*c.size,
}));

// Guides - Verticals
for (let i = 1; i <= 3; i++) {
	guides.append(make('line', {
		'class': 'guideline',
		'x1': (i/4) * 2 * c.size,
		'y1': 0,
		'x2': (i/4) * 2 * c.size,
		'y2': 2*c.size,
	}));
}

// Guides - Horizontals
for (let i = 1; i <= 3; i++) {
	guides.append(make('line', {
		'class': 'guideline',
		'y1': (i/4) * 2 * c.size,
		'x1': 0,
		'y2': (i/4) * 2 * c.size,
		'x2': 2*c.size,
	}));
}

// Guides - Inner square
guides.append(make('rect', {
	'class': 'guideline',
	'x': (1-body.size/2) * c.size,
	'y': (1-body.size/2) * c.size,
	'width': body.size * c.size,
	'height': body.size * c.size,
}));

// Arms
let arm_coords = {
	xs: (rotor.center + arm.end.offset * rotor.ir) * c.size,
	xe: c.size,
	ys: 0.5 * c.size,
	ye: (1 - 0.5*body.size) * c.size,
};
let dir = 0.5 * Math.PI +...