Coding soon

Coding soon

Text circle

circle html
-
<div id="circle-wrap">
	<canvas id="circle-cvs"></canvas>
</div>
circle css
-
#circle-wrap {

	height: 50vh;
	margin: 3em 0;
	
}

#circle-cvs {
	display: block;
}
Circular Text main
-
new CircularTextAnimation(
	"circle-cvs",
	{
		circles: 10,
		firstOffset: 4,
		spacing: 16,
		font: "Courier New",
		repeatCount: 3
		// text: "Your text here"
	}
);
Circular Text class
+
class CircularTextAnimation {

	constructor(canvasId, options = {}) {

		this.canvas = document.getElementById(canvasId);
		this.ctx = this.canvas.getContext("2d",
			{ alpha: false });

		this.ww = 0;
		this.wh = 0;

		this.bounds();

		// Configuration with defaults
		this.config = {
			circles: options.circles || 10,
			firstOffset: options.firstOffset || 6,
			spacing: options.spacing || 16,
			font: options.font || window.getComputedStyle( document.body,
				null )
			.getPropertyValue( "font-family" ),
			text:
				options.text
				|| "Le Lorem Ipsum est tout simplement du faux texte pour remplir des pixels. ",
			textColor: options.textColor || "#0D1117",
			backgroundColor: options.backgroundColor || "#FFFFFF",
			highlightColor: options.highlightColor || "rgba(0, 155, 255, 0.3)",
			repeatCount: options.repeatCount || 1
		};

		// Initialize properties
		this.width = 0;
		this.height = 0;
		this.centerX = 0;
		this.centerY = 0;
		this.half = 0;
		this.fontSize = 0;

		// Pre-calculate angles and movements
		this.angles = new Float32Array(this.config.circles);
		this.moves = new Float32Array(this.config.circles);
		this.initializeAnglesAndMoves();

		// Bind methods
		this.resize = this.resize.bind(this);
		this.animate = this.animate.bind(this);

		// Set up event listeners
		this.setupEventListeners();

		// Initial setup
		this.resize();
		this.animate();
	
	}

	initializeAnglesAndMoves() {

		for(let i = 0; i < this.config.circles; i++) {

			this.angles[i] = Math.random() * Math.PI * 2;
			this.moves[i]
				= ((Math.round(Math.random()) * 2 - 1)
					* (1 + Math.random() * 2))
				/ 3000;
		
		}
	
	}

	setupEventListeners() {

		window.addEventListener("resize",
			this.resize);

		if(window.matchMedia) {

			const darkModeQuery = window.matchMedia(
				"(prefers-color-scheme: dark)"
			);

			const handleColorSchemeChange = () => {

				[this.config.textColor, this.config.backgroundColor] = [
					this.config.backgroundColor,
					this.config.textColor
				];
			
			};

			if(darkModeQuery.matches) {

				handleColorSchemeChange();
			
			}

			darkModeQuery.addEventListener("change",
				handleColorSchemeChange);
		
		}
	
	}

	bounds() {

		const bounds = this.canvas.parentElement.getBoundingClientRect();

		this.ww = bounds.width;
		this.wh = bounds.height;

	}

	resize() {

		const dpr = window.devicePixelRatio || 1;

		this.bounds();

		this.width = this.canvas.width = this.ww * dpr;
		this.height = this.canvas.height = this.wh * dpr;

		this.canvas.style.width = `${this.ww}px`;
		this.canvas.style.height = `${this.wh}px`;

		if(dpr > 1) {

			this.ctx.scale(dpr,
				dpr);
		
		}

		this.half = Math.min(this.ww,
			this.wh) / 2;
		this.fontSize
			= this.half / (this.config.circles + this.config.firstOffset)
			- this.config.spacing / 2;
		this.centerX = this.ww / 2;
		this.centerY = this.wh / 2;
	
	}

	drawCircleWithText(idx, radius) {

		const font = `${this.fontSize + idx}px ${this.config.font}`;

		this.ctx.font = font;

		// Reverse text for outward reading
		const text = this.config.text.split("")
		.reverse()
		.join("");
		const metrics = this.ctx.measureText(text);
		const textHeight
			= metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
		const circumference = 2 * Math.PI * radius;

		// Calculate maximum repeat count that won't cause overlap
		const singleTextWidth = metrics.width;
		const maxRepeat = Math.floor(circumference / singleTextWidth);
		const actualRepeat = Math.min(this.config.repeatCount,
			maxRepeat);

		// Draw highlight circle
		if(idx === Math.round(this.config.circles / 2)) {

			this.ctx.lineWidth = textHeight * 1.5; // Add padding to fully cover text
			this.ctx.strokeStyle = this.config.highlightColor;

			this.ctx.beginPath();
			this.ctx.arc(this.centerX,
				this.centerY,
				radius,
				0,
				Math.PI * 2);
			this.ctx.stroke();
		
		}

		// Position for text drawing
		this.ctx.save();
		this.ctx.translate(this.centerX,
			this.centerY);

		this.angles[idx] += this.moves[idx];
		this.ctx.rotate(this.angles[idx]);

		// Draw repeated text segments
		const repeatAngle = (Math.PI * 2) / actualRepeat;

		for(let rep = 0; rep < actualRepeat; rep++) {

			let currentAngle = rep * repeatAngle;
			const len = text.length;

			for(let i = 0; i < len; i++) {

				const char = text[i];
				const charWidth = this.ctx.measureText(char).width;
				const halfCharWidth = charWidth / 2;

				this.ctx.save();
				this.ctx.rotate(currentAngle + halfCharWidth / radius);
				this.ctx.translate(0,
					radius);
				this.ctx.fillText(char,
					0,
					0);
				this.ctx.restore();

				currentAngle += charWidth / radius;
			
			}
		
		}

		this.ctx.restore();
	
	}

	animate() {

		// Clear and fill background
		this.ctx.clearRect(0,
			0,
			this.width,
			this.height);
		this.ctx.fillStyle = this.config.backgroundColor;
		this.ctx.fillRect(0,
			0,
			this.width,
			this.height);

		// Set text properties
		this.ctx.fillStyle = this.config.textColor;
		this.ctx.textAlign = "center";
		this.ctx.textBaseline = "middle";

		// Draw all circles
		const total = this.config.circles + this.config.firstOffset;

		for(let i = this.config.firstOffset; i < total; i++) {

			this.drawCircleWithText(
				i - this.config.firstOffset,
				(this.half * i) / total
			);
		
		}

		requestAnimationFrame(this.animate);
	
	}

}