100 Days of CSS: Day 8

February 12, 2025

Metaballs: At first glance impossible to implement with CSS, but filters make this possible too.

Unsplash ↗

Project Structure

The project consists of 3 main files:

HTML Structure

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam posuere erat at mi sagittis ultricies. Aenean cursus libero vitae odio congue volutpat.

<div class="frame">
	<div class="center">
		<div class="ball">
			<!-- all divs will append here -->
		</div>
	</div>
</div>

CSS Styling

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam posuere erat at mi sagittis ultricies. Aenean cursus libero vitae odio congue volutpat.

.frame {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 400px;
  height: 400px;
  margin-top: -200px;
  margin-left: -200px;
  border-radius: 2px;
  box-shadow: 1px 2px 10px 0px rgba(0,0,0,0.3);
  background: #000;
	filter: contrast(25);
}

.center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

.ball {
	position: relative;
	width: 90px;
	height: 90px;
	background: #fff;
	border-radius: 50%;
	filter: blur(15px);
}

@for $i from 1 through 8 {
	.blubb-#{$i} {
		position: absolute;
		top: 20px;
		left: 20px;
		width: 50px;
		height: 50px;
		transform: rotate( (random(300)) + deg);
		
		&:after {
			position: absolute;
			display: block;
			content: '';
			width: 50px;
			height: 50px;
			background: #fff;
			border-radius: 50%;
			transform-origin: (40 - $i * 3) + px (40 - $i * 3) + px;
			animation: rotate (2.5 + $i / 5) + s ease-in-out ($i / 5) + s infinite;
			filter: blur(5px);
		}
	}
}

@for $i from 1 through 10 {
	.sparkle-#{$i} {
		position: absolute;
		top: 38px;
		left: 38px;
		width: (7 + $i) + px;
		height: (7 + $i) + px;
		transform: rotate( (random(300)) + deg);
		
		&:after {
			position: absolute;
			display: block;
			content: '';
			width: (7 + $i) + px;
			height: (7 + $i) + px;
			background: #fff;
			border-radius: 50%;
			transform-origin: (60 - $i * 2) + px (60 - $i * 2) + px;
			animation: rotate (3.5 + $i / 5) + s ease-in-out ($i / 5) + s infinite;
			filter: blur(3px);
		}
	}
}

@keyframes rotate {
	from {
		transform: rotate(0deg) translate3d(0, 0, 0);
	}
	to {
		transform: rotate(360deg) translate3d(0, 0, 0);
	}
}

Javascript

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam posuere erat at mi sagittis ultricies. Aenean cursus libero vitae odio congue volutpat.

$(document).ready(function () {
	for (let i = 1; i <= 8; i++) {
		$("<div>", {
			class: `blubb-${i}`
		}).appendTo(".center");
	}

	for (let i = 1; i <= 10; i++) {
		$("<div>", {
			class: `sparkle-${i}`
		}).appendTo(".center");
	}
});

Reflection