<div class="cursor">
	<div class="box">
		<div id="fairyDust" class="effect"></div>
	</div>
</div>
* {
	box-sizing: border-box;
}

.cursor {
	padding: 30px 30px 0;
	width: 100%;
	height: 400px;
	position: relative;
	font-family: monospace;
}

.box {
	width: 100%;
	height: 100%;
	border: 3px solid black;
	background: white;
	display: flex;
	flex-direction: column;
}

.effect {
	height: 100%;
	position: relative;
}
function fairyDustCursor(options) {
	let possibleColors = (options && options.colors) || [
		"#D61C59",
		"#E7D84B",
		"#1B8798"
	];
	let hasWrapperEl = options && options.element;
	let element = hasWrapperEl || document.body;

	let width = window.innerWidth;
	let height = window.innerHeight;
	const cursor = { x: width / 2, y: width / 2 };
	const lastPos = { x: width / 2, y: width / 2 };
	const particles = [];
	const canvImages = [];
	let canvas, context;

	const char = "*";

	function init() {
		canvas = document.createElement("canvas");
		context = canvas.getContext("2d");
		canvas.style.top = "0px";
		canvas.style.left = "0px";
		canvas.style.pointerEvents = "none";

		if (hasWrapperEl) {
			canvas.style.position = "absolute";
			element.appendChild(canvas);
			canvas.width = element.clientWidth;
			canvas.height = element.clientHeight;
		} else {
			canvas.style.position = "fixed";
			element.appendChild(canvas);
			canvas.width = width;
			canvas.height = height;
		}

		context.font = "21px serif";
		context.textBaseline = "middle";
		context.textAlign = "center";

		possibleColors.forEach((color) => {
			let measurements = context.measureText(char);
			let bgCanvas = document.createElement("canvas");
			let bgContext = bgCanvas.getContext("2d");

			bgCanvas.width = measurements.width;
			bgCanvas.height =
				measurements.actualBoundingBoxAscent +
				measurements.actualBoundingBoxDescent;

			bgContext.fillStyle = color;
			bgContext.textAlign = "center";
			bgContext.font = "21px serif";
			bgContext.textBaseline = "middle";
			bgContext.fillText(
				char,
				bgCanvas.width / 2,
				measurements.actualBoundingBoxAscent
			);

			canvImages.push(bgCanvas);
		});

		bindEvents();
		loop();
	}

	// Bind events that are needed
	function bindEvents() {
		element.addEventListener("mousemove", onMouseMove);
		element.addEventListener("touchmove", onTouchMove, { passive: true });
		element.addEventListener("touchstart", onTouchMove, { passive: true });
		window.addEventListener("resize", onWindowResize);
	}

	function onWindowResize(e) {
		width = window.innerWidth;
		height = window.innerHeight;

		if (hasWrapperEl) {
			canvas.width = element.clientWidth;
			canvas.height = element.clientHeight;
		} else {
			canvas.width = width;
			canvas.height = height;
		}
	}

	function onTouchMove(e) {
		if (e.touches.length > 0) {
			for (let i = 0; i < e.touches.length; i++) {
				addParticle(
					e.touches[i].clientX,
					e.touches[i].clientY,
					canvImages[Math.floor(Math.random() * canvImages.length)]
				);
			}
		}
	}

	function onMouseMove(e) {
		window.requestAnimationFrame(() => {
			if (hasWrapperEl) {
				const boundingRect = element.getBoundingClientRect();
				cursor.x = e.clientX - boundingRect.left;
				cursor.y = e.clientY - boundingRect.top;
			} else {
				cursor.x = e.clientX;
				cursor.y = e.clientY;
			}

			const distBetweenPoints = Math.hypot(
				cursor.x - lastPos.x,
				cursor.y - lastPos.y
			);

			if (distBetweenPoints > 1.5) {
				addParticle(
					cursor.x,
					cursor.y,
					canvImages[Math.floor(Math.random() * possibleColors.length)]
				);

				lastPos.x = cursor.x;
				lastPos.y = cursor.y;
			}
		});
	}

	function addParticle(x, y, color) {
		particles.push(new Particle(x, y, color));
	}

	function updateParticles() {
		context.clearRect(0, 0, width, height);

		// Update
		for (let i = 0; i < particles.length; i++) {
			particles[i].update(context);
		}

		// Remove dead particles
		for (let i = particles.length - 1; i >= 0; i--) {
			if (particles[i].lifeSpan < 0) {
				particles.splice(i, 1);
			}
		}
	}

	function loop() {
		updateParticles();
		requestAnimationFrame(loop);
	}

	function Particle(x, y, canvasItem) {
		const lifeSpan = Math.floor(Math.random() * 30 + 60);
		this.initialLifeSpan = lifeSpan; //
		this.lifeSpan = lifeSpan; //ms
		this.velocity = {
			x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2),
			y: Math.random() * 0.7 + 0.9
		};
		this.position = { x: x, y: y };
		this.canv = canvasItem;

		this.update = function (context) {
			this.position.x += this.velocity.x;
			this.position.y += this.velocity.y;
			this.lifeSpan--;

			this.velocity.y += 0.02;

			const scale = Math.max(this.lifeSpan / this.initialLifeSpan, 0);

			context.drawImage(
				this.canv,
				this.position.x - (this.canv.width / 2) * scale,
				this.position.y - this.canv.height / 2,
				this.canv.width * scale,
				this.canv.height * scale
			);
		};
	}

	init();
}

new fairyDustCursor({ element: document.querySelector("#fairyDust") });

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/cursor-effects@1.0.4/dist/browser.js