Skip to content

Commit

Permalink
add blob proto
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewmeeks committed Nov 29, 2023
1 parent 6520df9 commit 3d217be
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 0 deletions.
34 changes: 34 additions & 0 deletions blob/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>

<head>
<title>Blob</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="styles.css">
</head>

<body>
<div id="container" class="container">
<template id="char-tmpl">
<div class="character">
<div class="shadow"></div>
<div class="body"></div>
<div class="face">
<div class="left-eye">
<div class="iris">
<div class="pupil"></div>
</div>
</div>
<div class="right-eye">
<div class="iris">
<div class="pupil"></div>
</div>
</div>
</div>
</div>
</template>
</div>
<script type="text/javascript" src="script.js"></script>
</body>

</html>
124 changes: 124 additions & 0 deletions blob/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const states = {};

let scale = 1.0;
const viewportElem = document.getElementById("container");

function setScale(v) {
scale = v;
viewportElem.style.scale = v;
}

visualViewport.onresize = () => {
const sx = document.body.clientWidth / 1280;
const sy = document.body.clientHeight / 720;
setScale(Math.min(sx, sy));
}
visualViewport.onresize();

function spawnCharacter(id, state) {
const tmpl = document.getElementById("char-tmpl");
const elemFrag = tmpl.content.cloneNode(true);
const charElem = elemFrag.querySelector(".character");
charElem.id = `char-${id}`;
charElem.style.setProperty("--hue", state.hue);
charElem.style.setProperty("--iris-hue", state.irisHue);
viewportElem.appendChild(elemFrag);
blinkLoop(id);
states[id] = structuredClone(state);
}

function getCharElem(id) {
const elem = document.getElementById(`char-${id}`);
if (!elem) throw new Error("Could not find element for character with ID " + id);
return elem;
}

function openEyes(id) {
getCharElem(id).style.setProperty("--eye-lid-state", 0.1);
}

function closeEyes(id) {
getCharElem(id).style.setProperty("--eye-lid-state", 1);
}

function blink(id) {
closeEyes(id);
setTimeout(() => openEyes(id), 150);
}

function blinkLoop(id) {
blink(id);
setTimeout(() => blinkLoop(id), Math.random() * 10000);
}

function setPosition(id, x, y) {
const elem = getCharElem(id);
elem.style.setProperty("--pos-x", `${x}px`);
elem.style.setProperty("--pos-y", `${y - 30}px`);
elem.style.zIndex = Math.round(y);
}

function createState(pos, rot, hue, irisHue) {
return {
x: pos[0],
y: pos[1],
tx: pos[0],
ty: pos[1],
rot,
hue,
irisHue
};
}


const localId = 0;
// spawnCharacter(localId, createState([150, 150], 0, 120, Math.random() * 360));

for (let i = 0; i < 10; i++) {
spawnCharacter(i, createState([Math.random() * 1280, Math.random() * 720], Math.random() * 360, Math.random() * 360, Math.random() * 360));
}

let prevEt = 0;
const speed = 0.1;
function updateOuter() {
requestAnimationFrame((et) => {
const dt = et - prevEt;

for (const idStr in states) {
const id = Number(idStr);
// console.log(id);
const state = states[idStr];
let dx = state.tx - state.x;
let dy = state.ty - state.y;

let dl = Math.sqrt(dx * dx + dy * dy);

if (dl <= speed * dt) {
state.x = state.tx;
state.y = state.ty;

if (id !== localId && Math.random() > 0.99) {
state.tx = Math.random() * 1280;
state.ty = Math.random() * 720;
}
} else {
state.x += dx / dl * dt * speed;
state.y += dy / dl * dt * speed;
state.rot += dt * 0.3 * (dx / dl);
getCharElem(id).style.setProperty("--rotation", `${state.rot}deg`);
}
setPosition(id, state.x, state.y);
}

prevEt = et;
updateOuter();
});
}

updateOuter();

document.onclick = (ev) => {
console.log(ev);
states[localId].tx = ev.x / scale;
states[localId].ty = ev.y / scale;
}
112 changes: 112 additions & 0 deletions blob/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
html,
:root,
body {
width: 100%;
height: 100%;
}

.container {
width: 1280px;
height: 720px;
background: orange;
overflow: hidden;
transform-origin: top left;
}

.character {
position: absolute;
translate: var(--pos-x) var(--pos-y);
}

.body {
box-sizing: border-box;
width: 80px;
height: 80px;
position: relative;
translate: -50% -50%;
}

.shadow {
left: -60px;
bottom: 40px;
width: 95px;
height: 30px;
background: #0007;
position: absolute;
border-radius: 50%;
}

.body::after {
content: "";

rotate: -30deg;
position: absolute;
inset: 0;
border-left: solid 10px #0007;
border-right: solid 5px #fff7;
border-radius: 50%;

background: hsl(var(--hue) 100% 50%);
}

.left-eye,
.right-eye {
background: white;
position: absolute;
width: 20px;
height: 20px;
top: 20px;
border-radius: 50%;
}

.left-eye::after,
.right-eye::after {
--lid-color: hsl(var(--hue) 85% 45%);
content: "";
position: absolute;
inset: 0;
box-sizing: border-box;
border-top: solid calc(var(--eye-lid-state, 0) * 10px) var(--lid-color);
border-bottom: solid calc(var(--eye-lid-state, 0) * 10px) var(--lid-color);
border-radius: 50%;
transition: border 0.1s;
}

.left-eye {
left: 15px;
}

.right-eye {
left: 45px;
}

.iris {
background: hsl(var(--iris-hue) 50% 60%);
width: 12px;
height: 12px;
position: absolute;
top: 4px;
left: 4px;
border-radius: 50%;
}

.pupil {
background: black;
width: 6px;
height: 6px;
position: absolute;
top: 3px;
left: 3px;
border-radius: 50%;
}

.face {
translate: -50% -50%;
position: absolute;
inset: 0;
rotate: var(--rotation);
}

body {
margin: 0;
}

0 comments on commit 3d217be

Please sign in to comment.