Skip to content

Instantly share code, notes, and snippets.

@aldodelgado
Created January 2, 2025 15:23
Show Gist options
  • Save aldodelgado/17c453cd3b9650ddd451b951d4455530 to your computer and use it in GitHub Desktop.
Save aldodelgado/17c453cd3b9650ddd451b951d4455530 to your computer and use it in GitHub Desktop.
Bubbles
document.addEventListener('DOMContentLoaded', () => {
const loadingElement = document.getElementById('loading');
const containerElement = document.getElementById('container');
const bubbles = [];
const initializeBackground = () => {
let backgroundElement = document.querySelector('.background');
if (!backgroundElement) {
backgroundElement = document.createElement('div');
backgroundElement.className = 'background';
document.body.appendChild(backgroundElement);
}
};
const fetchAndDisplayBubbles = async () => {
try {
const response = await fetch('https://adelgado.ngrok.io/api/tweet-data', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
if (!Array.isArray(data)) {
throw new Error('Invalid data format');
}
console.log('Received data:', data);
const interactionBubbles = containerElement.querySelector('.interaction-bubbles');
data.forEach((user) => {
// Check if bubble for user already exists
const existingBubble = bubbles.find(b => b.userId === user.periscope_user_id);
if (existingBubble) return;
const bubble = document.createElement('div');
bubble.className = 'bubble';
const img = document.createElement('img');
img.src = user.avatar_url || 'https://via.placeholder.com/50';
img.alt = `${user.display_name}'s profile picture`;
// Add a small indicator for the role
const roleIndicator = document.createElement('div');
roleIndicator.className = 'role-indicator';
// Determine role and assign emoji
let roleEmoji = '';
if (user.role === 'admin') {
roleEmoji = 'πŸ‘‘'; // Admin emoji
} else if (user.role === 'speaker') {
roleEmoji = '🎀'; // Speaker emoji
} else if (user.role === 'listener') {
roleEmoji = 'πŸ‘‚'; // Listener emoji
}
roleIndicator.textContent = roleEmoji; // Assign the emoji
bubble.appendChild(img);
bubble.appendChild(roleIndicator);
interactionBubbles.appendChild(bubble);
let x = Math.random() * (window.innerWidth - 80);
let y = Math.random() * (window.innerHeight - 80);
bubble.style.left = `${x}px`;
bubble.style.top = `${y}px`;
bubbles.push({
userId: user.periscope_user_id, // Track user ID to prevent duplicates
element: bubble,
dx: (Math.random() - 0.5) * 4,
dy: (Math.random() - 0.5) * 4
});
});
loadingElement.style.display = 'none';
containerElement.style.display = 'flex';
} catch (error) {
console.error('Error fetching bubble data:', error);
loadingElement.textContent = 'Failed to load data. Please try again later.';
}
};
const animateBubbles = () => {
bubbles.forEach((bubble, index) => {
const rect = bubble.element.getBoundingClientRect();
// Update position
let newX = rect.left + bubble.dx;
let newY = rect.top + bubble.dy;
// Bounce off screen edges
if (newX <= 0 || newX + rect.width >= window.innerWidth) {
bubble.dx *= -1; // Reverse horizontal direction
newX = Math.max(0, Math.min(newX, window.innerWidth - rect.width));
}
if (newY <= 0 || newY + rect.height >= window.innerHeight) {
bubble.dy *= -1; // Reverse vertical direction
newY = Math.max(0, Math.min(newY, window.innerHeight - rect.height));
}
// Check collisions with other bubbles
bubbles.forEach((otherBubble, otherIndex) => {
if (index === otherIndex) return; // Skip self-comparison
const otherRect = otherBubble.element.getBoundingClientRect();
const dx = rect.left - otherRect.left;
const dy = rect.top - otherRect.top;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < rect.width) {
// Bubbles are colliding, resolve collision
const nx = dx / distance; // Normal vector (x)
const ny = dy / distance; // Normal vector (y)
const p = 2 * (bubble.dx * nx + bubble.dy * ny - otherBubble.dx * nx - otherBubble.dy * ny) / 2; // Momentum exchange factor
// Update velocities for both bubbles
bubble.dx -= p * nx;
bubble.dy -= p * ny;
otherBubble.dx += p * nx;
otherBubble.dy += p * ny;
// Add collision effect (optional)
bubble.element.classList.add('colliding');
otherBubble.element.classList.add('colliding');
setTimeout(() => {
bubble.element.classList.remove('colliding');
otherBubble.element.classList.remove('colliding');
}, 200);
// Adjust positions to prevent overlap
const overlap = rect.width - distance;
const correctionX = (overlap / 2) * nx;
const correctionY = (overlap / 2) * ny;
newX += correctionX;
newY += correctionY;
const otherX = otherRect.left - correctionX;
const otherY = otherRect.top - correctionY;
otherBubble.element.style.left = `${otherX}px`;
otherBubble.element.style.top = `${otherY}px`;
}
});
// Update bubble position
bubble.element.style.left = `${newX}px`;
bubble.element.style.top = `${newY}px`;
});
requestAnimationFrame(animateBubbles);
};
initializeBackground();
fetchAndDisplayBubbles();
animateBubbles();
setInterval(fetchAndDisplayBubbles, 60000);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment