<!-- Wrap the element in a container -->
<div class='resizer'>
	<div class='container'>
		<h1 data-splitting>Can’t stop looping</h1>
	</div>	
</div>

@import url('https://fonts.googleapis.com/css?family=Nova+Mono&display=swap');

* {
	box-sizing: border-box;
}

body {
	min-height: 100vh;
	background-color: #18181c;
	display: flex;
	align-items: center;
	justify-content: center;
}

h1 {
	font-family: 'Nova Mono', monospace;
	font-size: 2.5rem;
	text-transform: uppercase;
	width: 1109px;
	height: 365px;
	color: turquoise;
}

.char {
	offset-path: path(var(--path));
	offset-distance: calc(var(--char-index) * 1.5rem);
	animation: loop 1500ms cubic-bezier(.62,.01,.42,1.01) infinite alternate calc(var(--char-index) * 10ms);
}

@keyframes loop {
	50% {
		offset-distance: calc((var(--char-index) * 2.5rem) + 700px);
		color: hotpink;
	}
	100% {
		offset-distance: calc((var(--char-index) * 1.5rem) + 1690px);
	}
}

/* Bounds are 1109 x 365 */
/* Lets say a width of 75vmin */
.container {
	/* Ensure correct aspect ratio with this little trick	*/
	position: relative;
	height: 100%;
	width: 100%;
}

.resizer {
	overflow: hidden;
	padding: 2rem;
	resize: both;
	background: hsla(0, 0%, 100%, 0.15);
	height: calc((365 / 1109) * 75vmin);
	position: relative;
	width: 75vmin;
}
/* Use absolute positioning on the moving pieces and position top, left */
.char {
	top: 0;
	left: 0;
	position: absolute;
}
Splitting({
	whitespace: true
});

// Create a variable for the path
const path = 'M.2 219.6c247-107 233.3 91.4 406.4 138.4C659.2 426.6 750.2 6.6 552.2.6 337.7-5.9 426.9 415 696.1 359.4c256.1-52.9 182.1-217.9 413.1-163.9'
// Grab the container element
const container = document.querySelector(".container");

const responsivePath = new Meanderer({
	path: path,
  // 	grabbed these by inspecing the h1 size but this would be the bounds
	width: 1109,
	height: 365,
})

// Update the path on the element via an inline CSS variable
// Do this whenever the container resizes using a ResizeObserver
const update = () => {
	const newPath = responsivePath.generatePath(
		container.offsetWidth,
		container.offsetHeight
	);
	container.style.setProperty('--path', `"${newPath}"`)
};

const SizeObserver = new ResizeObserver(update);
SizeObserver.observe(container);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/splitting@1.0.6/dist/splitting.min.js
  2. https://unpkg.com/meanderer@0.0.1/dist/meanderer.min.js