Skip to content

Instantly share code, notes, and snippets.

@jaymcgavren
Last active February 1, 2026 20:35
Show Gist options
  • Select an option

  • Save jaymcgavren/b781737cebb4a59bfc7c8d65eb1aaa12 to your computer and use it in GitHub Desktop.

Select an option

Save jaymcgavren/b781737cebb4a59bfc7c8d65eb1aaa12 to your computer and use it in GitHub Desktop.
Bouncing images and sounds .html file
<!--
ChatGPT prompt:
Create a simple app that resides entirely in a single .html file. It should
take three images from browser storage and animate them bouncing around the
screen. Each of the three images should have a distinct sound, also loaded
from browser storage, that plays when it bounces.
Refinement (because ChatGPT didn't use localStorage like it should have):
When the user selects an image or sound file, store it in browser storage
using "window.localStorage.setItem". When the page loads, load images using
"window.localStorage.getItem", if present. Present the menu to load
images/sounds only when images/sounds are not present in
"window.localStorage".
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Bouncing Images (Persistent)</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
background: #111;
height: 100%;
font-family: system-ui, sans-serif;
color: #eee;
}
#controls {
position: fixed;
top: 12px;
left: 12px;
background: rgba(0,0,0,0.75);
padding: 12px;
border-radius: 8px;
z-index: 10;
}
#controls label {
display: block;
margin-bottom: 6px;
font-size: 14px;
}
.sprite {
position: absolute;
width: 120px;
pointer-events: none;
user-select: none;
}
</style>
</head>
<body>
<div id="controls"></div>
<script>
const SPRITE_COUNT = 3;
const sprites = [];
const controls = document.getElementById("controls");
/* ---------------- Utilities ---------------- */
function readFileAsDataURL(file) {
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(file);
});
}
function lsKey(type, index) {
return `bounce_${type}_${index}`;
}
/* ---------------- UI Setup ---------------- */
function createFileInput(labelText, accept, onLoad) {
const label = document.createElement("label");
label.textContent = labelText;
const input = document.createElement("input");
input.type = "file";
input.accept = accept;
input.addEventListener("change", async () => {
const file = input.files[0];
if (!file) return;
const dataUrl = await readFileAsDataURL(file);
onLoad(dataUrl);
label.remove();
checkReady();
});
label.appendChild(document.createElement("br"));
label.appendChild(input);
controls.appendChild(label);
}
/* ---------------- Sprite Logic ---------------- */
function createSprite(imgData, sndData) {
const img = document.createElement("img");
img.src = imgData;
img.className = "sprite";
document.body.appendChild(img);
const audio = new Audio(sndData);
audio.preload = "auto";
sprites.push({
el: img,
audio,
x: Math.random() * (innerWidth - 120),
y: Math.random() * (innerHeight - 120),
vx: (Math.random() * 4 + 2) * (Math.random() < 0.5 ? -1 : 1),
vy: (Math.random() * 4 + 2) * (Math.random() < 0.5 ? -1 : 1)
});
}
function startAnimation() {
controls.style.display = "none";
requestAnimationFrame(update);
}
function update() {
const w = innerWidth;
const h = innerHeight;
for (const s of sprites) {
s.x += s.vx;
s.y += s.vy;
let bounced = false;
if (s.x <= 0 || s.x + s.el.width >= w) {
s.vx *= -1;
bounced = true;
}
if (s.y <= 0 || s.y + s.el.height >= h) {
s.vy *= -1;
bounced = true;
}
if (bounced) {
s.audio.currentTime = 0;
s.audio.play().catch(() => {});
}
s.el.style.transform = `translate(${s.x}px, ${s.y}px)`;
}
requestAnimationFrame(update);
}
/* ---------------- Load / Restore ---------------- */
function checkReady() {
if (sprites.length === SPRITE_COUNT) {
startAnimation();
}
}
for (let i = 0; i < SPRITE_COUNT; i++) {
const imgKey = lsKey("img", i);
const sndKey = lsKey("snd", i);
const imgData = localStorage.getItem(imgKey);
const sndData = localStorage.getItem(sndKey);
if (imgData && sndData) {
createSprite(imgData, sndData);
checkReady();
} else {
if (!imgData) {
createFileInput(`Image ${i + 1}`, "image/*", data => {
localStorage.setItem(imgKey, data);
});
}
if (!sndData) {
createFileInput(`Sound ${i + 1}`, "audio/*", data => {
localStorage.setItem(sndKey, data);
});
}
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment